When quitting the CLI, users now see a comprehensive session summary: - Total API time spent and session duration - Total code changes (+additions/-deletions) - Per-model token usage breakdown (input/output/cached) - Resume command with session ID Implementation details: - Extended SessionStats type with apiTimeSpent, apiCallStartTime, and modelUsage - Added startApiCall(), stopApiCall(), and addTokensWithModel() tracking functions - Created session-stats.ts utility with formatters and generateSessionSummary() - Updated TUI exit handler to display formatted stats - Added mouse tracking disable to drainStdin() for cleaner exit - Added modifiedFiles to getState() for exit summary access
484 lines
12 KiB
TypeScript
484 lines
12 KiB
TypeScript
/**
|
|
* TUI Type Definitions
|
|
*
|
|
* Types for TUI components and state management
|
|
*/
|
|
|
|
import type { ProviderModel } from "@/types/providers";
|
|
import type { BrainConnectionStatus, BrainUser } from "@/types/brain";
|
|
|
|
// ============================================================================
|
|
// App Mode Types
|
|
// ============================================================================
|
|
|
|
export type AppMode =
|
|
| "idle"
|
|
| "editing"
|
|
| "thinking"
|
|
| "tool_execution"
|
|
| "permission_prompt"
|
|
| "plan_approval"
|
|
| "command_menu"
|
|
| "model_select"
|
|
| "agent_select"
|
|
| "theme_select"
|
|
| "mode_select"
|
|
| "mcp_select"
|
|
| "mcp_add"
|
|
| "mcp_browse"
|
|
| "file_picker"
|
|
| "provider_select"
|
|
| "learning_prompt"
|
|
| "help_menu"
|
|
| "help_detail"
|
|
| "brain_menu"
|
|
| "brain_login";
|
|
|
|
/** Screen mode for determining which view to show */
|
|
export type ScreenMode = "home" | "session";
|
|
|
|
/** Interaction mode for determining AI behavior */
|
|
export type InteractionMode = "agent" | "ask" | "code-review";
|
|
|
|
// ============================================================================
|
|
// Command Types
|
|
// ============================================================================
|
|
|
|
export type CommandCategory = "general" | "session" | "settings" | "account";
|
|
|
|
export interface SlashCommand {
|
|
name: string;
|
|
description: string;
|
|
category: CommandCategory;
|
|
}
|
|
|
|
export interface CommandMenuState {
|
|
isOpen: boolean;
|
|
filter: string;
|
|
selectedIndex: number;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Select Menu Types
|
|
// ============================================================================
|
|
|
|
export interface SelectOption {
|
|
key: string;
|
|
label: string;
|
|
value: string;
|
|
description?: string;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Suggestion Prompt Types
|
|
// ============================================================================
|
|
|
|
export interface SuggestionPrompt {
|
|
id: string;
|
|
label: string;
|
|
prompt: string;
|
|
icon?: string;
|
|
}
|
|
|
|
export interface SuggestionState {
|
|
suggestions: SuggestionPrompt[];
|
|
selectedIndex: number;
|
|
visible: boolean;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Diff Types
|
|
// ============================================================================
|
|
|
|
export interface DiffData {
|
|
filePath?: string;
|
|
additions: number;
|
|
deletions: number;
|
|
isDiff: boolean;
|
|
}
|
|
|
|
export type DiffLineType =
|
|
| "add"
|
|
| "remove"
|
|
| "context"
|
|
| "header"
|
|
| "hunk"
|
|
| "summary";
|
|
|
|
export interface DiffLineData {
|
|
type: DiffLineType;
|
|
content: string;
|
|
oldLineNum?: number;
|
|
newLineNum?: number;
|
|
}
|
|
|
|
export interface DiffViewProps {
|
|
lines: DiffLineData[];
|
|
filePath?: string;
|
|
additions?: number;
|
|
deletions?: number;
|
|
compact?: boolean;
|
|
language?: string;
|
|
}
|
|
|
|
export interface DiffLineProps {
|
|
line: DiffLineData;
|
|
showLineNumbers: boolean;
|
|
maxLineNumWidth: number;
|
|
language?: string;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Log Types
|
|
// ============================================================================
|
|
|
|
export type LogEntryType =
|
|
| "user"
|
|
| "assistant"
|
|
| "assistant_streaming"
|
|
| "tool"
|
|
| "error"
|
|
| "system"
|
|
| "thinking";
|
|
|
|
export type ToolStatus = "pending" | "running" | "success" | "error";
|
|
|
|
export interface LogEntryMetadata {
|
|
toolName?: string;
|
|
toolStatus?: ToolStatus;
|
|
toolDescription?: string;
|
|
diffData?: DiffData;
|
|
quiet?: boolean;
|
|
isStreaming?: boolean;
|
|
streamComplete?: boolean;
|
|
}
|
|
|
|
export interface LogEntry {
|
|
id: string;
|
|
type: LogEntryType;
|
|
content: string;
|
|
timestamp: number;
|
|
metadata?: LogEntryMetadata;
|
|
}
|
|
|
|
export interface LogEntryProps {
|
|
entry: LogEntry;
|
|
}
|
|
|
|
export interface ThinkingIndicatorProps {
|
|
message: string;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Tool Call Types
|
|
// ============================================================================
|
|
|
|
export interface ToolCall {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
status: ToolStatus;
|
|
result?: string;
|
|
error?: string;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Permission Types
|
|
// ============================================================================
|
|
|
|
export type PermissionType = "bash" | "read" | "write" | "edit";
|
|
|
|
export type PermissionScope = "once" | "session" | "local" | "global";
|
|
|
|
export interface PermissionRequest {
|
|
id: string;
|
|
type: PermissionType;
|
|
description: string;
|
|
command?: string;
|
|
path?: string;
|
|
resolve: (response: PermissionResponse) => void;
|
|
}
|
|
|
|
export interface PermissionResponse {
|
|
allowed: boolean;
|
|
scope?: PermissionScope;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Plan Approval Types
|
|
// ============================================================================
|
|
|
|
export interface PlanApprovalPrompt {
|
|
id: string;
|
|
planTitle: string;
|
|
planSummary: string;
|
|
planContent?: string;
|
|
planFilePath?: string;
|
|
resolve: (response: PlanApprovalPromptResponse) => void;
|
|
}
|
|
|
|
export interface PlanApprovalPromptResponse {
|
|
approved: boolean;
|
|
editMode: "auto_accept_clear" | "auto_accept" | "manual_approve" | "feedback";
|
|
feedback?: string;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Learning Types
|
|
// ============================================================================
|
|
|
|
export type LearningScope = "local" | "global";
|
|
|
|
export interface LearningPrompt {
|
|
id: string;
|
|
content: string;
|
|
context: string;
|
|
category: string;
|
|
resolve: (response: LearningResponse) => void;
|
|
}
|
|
|
|
export interface LearningResponse {
|
|
save: boolean;
|
|
scope?: LearningScope;
|
|
editedContent?: string;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Session Types
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Per-model token usage tracking
|
|
*/
|
|
export interface ModelUsage {
|
|
modelId: string;
|
|
inputTokens: number;
|
|
outputTokens: number;
|
|
cachedTokens?: number;
|
|
}
|
|
|
|
export interface SessionStats {
|
|
startTime: number;
|
|
inputTokens: number;
|
|
outputTokens: number;
|
|
thinkingStartTime: number | null;
|
|
lastThinkingDuration: number;
|
|
contextMaxTokens: number;
|
|
/** Total time spent in API calls (milliseconds) */
|
|
apiTimeSpent: number;
|
|
/** API call start time for tracking (null if not in a call) */
|
|
apiCallStartTime: number | null;
|
|
/** Per-model token usage breakdown */
|
|
modelUsage: ModelUsage[];
|
|
}
|
|
|
|
// ============================================================================
|
|
// Modified File Tracking
|
|
// ============================================================================
|
|
|
|
export interface ModifiedFileEntry {
|
|
/** Relative or absolute file path */
|
|
filePath: string;
|
|
/** Net lines added */
|
|
additions: number;
|
|
/** Net lines deleted */
|
|
deletions: number;
|
|
/** Timestamp of the last modification */
|
|
lastModified: number;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Streaming Types
|
|
// ============================================================================
|
|
|
|
export interface StreamingLogState {
|
|
logId: string | null;
|
|
content: string;
|
|
isStreaming: boolean;
|
|
}
|
|
|
|
// ============================================================================
|
|
// MCP Types (for UI display)
|
|
// ============================================================================
|
|
|
|
export type MCPServerStatus = "connected" | "disconnected" | "error";
|
|
|
|
export interface MCPServerDisplay {
|
|
id: string;
|
|
name: string;
|
|
status: MCPServerStatus;
|
|
description?: string;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Component Props Types
|
|
// ============================================================================
|
|
|
|
export interface HeaderProps {
|
|
version?: string;
|
|
provider?: string;
|
|
model?: string;
|
|
showBanner?: boolean;
|
|
}
|
|
|
|
export interface CommandMenuProps {
|
|
onSelect: (command: string) => void;
|
|
onCancel?: () => void;
|
|
isActive?: boolean;
|
|
}
|
|
|
|
// ============================================================================
|
|
// App State Types
|
|
// ============================================================================
|
|
|
|
export interface AppState {
|
|
// Application mode
|
|
mode: AppMode;
|
|
|
|
// Screen mode (home vs session)
|
|
screenMode: ScreenMode;
|
|
|
|
// Interaction mode (agent, ask, code-review)
|
|
interactionMode: InteractionMode;
|
|
|
|
// Input state
|
|
inputBuffer: string;
|
|
inputCursorPosition: number;
|
|
|
|
// Log entries
|
|
logs: LogEntry[];
|
|
|
|
// Current execution state
|
|
currentToolCall: ToolCall | null;
|
|
permissionRequest: PermissionRequest | null;
|
|
learningPrompt: LearningPrompt | null;
|
|
thinkingMessage: string | null;
|
|
|
|
// Session info
|
|
sessionId: string | null;
|
|
provider: string;
|
|
model: string;
|
|
version: string;
|
|
|
|
// Command menu state
|
|
commandMenu: CommandMenuState;
|
|
|
|
// Available models for selection (with cost info)
|
|
availableModels: ProviderModel[];
|
|
|
|
// Session statistics
|
|
sessionStats: SessionStats;
|
|
|
|
// UI state
|
|
todosVisible: boolean;
|
|
interruptPending: boolean;
|
|
exitPending: boolean;
|
|
isCompacting: boolean;
|
|
|
|
// Scroll state
|
|
scrollOffset: number;
|
|
autoScroll: boolean;
|
|
userScrolled: boolean;
|
|
isSettling: boolean;
|
|
totalLines: number;
|
|
visibleHeight: number;
|
|
|
|
// Streaming state
|
|
streamingLog: StreamingLogState;
|
|
|
|
// Suggestion prompts state
|
|
suggestions: SuggestionState;
|
|
|
|
// Brain state
|
|
brain: {
|
|
status: BrainConnectionStatus;
|
|
user: BrainUser | null;
|
|
knowledgeCount: number;
|
|
memoryCount: number;
|
|
showBanner: boolean;
|
|
};
|
|
|
|
// Actions
|
|
setMode: (mode: AppMode) => void;
|
|
setScreenMode: (screenMode: ScreenMode) => void;
|
|
setInteractionMode: (mode: InteractionMode) => void;
|
|
toggleInteractionMode: () => void;
|
|
setInputBuffer: (buffer: string) => void;
|
|
setInputCursorPosition: (position: number) => void;
|
|
appendToInput: (text: string) => void;
|
|
clearInput: () => void;
|
|
|
|
addLog: (entry: Omit<LogEntry, "id" | "timestamp">) => void;
|
|
updateLog: (id: string, updates: Partial<LogEntry>) => void;
|
|
clearLogs: () => void;
|
|
|
|
setCurrentToolCall: (toolCall: ToolCall | null) => void;
|
|
updateToolCall: (updates: Partial<ToolCall>) => void;
|
|
|
|
setPermissionRequest: (request: PermissionRequest | null) => void;
|
|
setLearningPrompt: (prompt: LearningPrompt | null) => void;
|
|
setThinkingMessage: (message: string | null) => void;
|
|
|
|
setSessionInfo: (sessionId: string, provider: string, model: string) => void;
|
|
setVersion: (version: string) => void;
|
|
|
|
// Command menu actions
|
|
openCommandMenu: () => void;
|
|
closeCommandMenu: () => void;
|
|
setCommandFilter: (filter: string) => void;
|
|
setCommandSelectedIndex: (index: number) => void;
|
|
|
|
// Model actions
|
|
setAvailableModels: (models: ProviderModel[]) => void;
|
|
setModel: (model: string) => void;
|
|
|
|
// Session stats actions
|
|
startThinking: () => void;
|
|
stopThinking: () => void;
|
|
addTokens: (input: number, output: number) => void;
|
|
resetSessionStats: () => void;
|
|
|
|
// UI state actions
|
|
toggleTodos: () => void;
|
|
setInterruptPending: (pending: boolean) => void;
|
|
setExitPending: (pending: boolean) => void;
|
|
setIsCompacting: (compacting: boolean) => void;
|
|
|
|
// Scroll actions
|
|
scrollUp: (lines?: number) => void;
|
|
scrollDown: (lines?: number) => void;
|
|
scrollToTop: () => void;
|
|
scrollToBottom: () => void;
|
|
setAutoScroll: (enabled: boolean) => void;
|
|
setUserScrolled: (scrolled: boolean) => void;
|
|
setScrollDimensions: (totalLines: number, visibleHeight: number) => void;
|
|
pauseAutoScroll: () => void;
|
|
resumeAutoScroll: () => void;
|
|
getEffectiveScrollOffset: () => number;
|
|
|
|
// Streaming actions
|
|
startStreaming: () => string;
|
|
appendStreamContent: (content: string) => void;
|
|
completeStreaming: () => void;
|
|
cancelStreaming: () => void;
|
|
|
|
// Suggestion actions
|
|
setSuggestions: (suggestions: SuggestionPrompt[]) => void;
|
|
clearSuggestions: () => void;
|
|
selectSuggestion: (index: number) => void;
|
|
nextSuggestion: () => void;
|
|
prevSuggestion: () => void;
|
|
hideSuggestions: () => void;
|
|
showSuggestions: () => void;
|
|
|
|
// Brain actions
|
|
setBrainStatus: (status: BrainConnectionStatus) => void;
|
|
setBrainUser: (user: BrainUser | null) => void;
|
|
setBrainCounts: (knowledge: number, memory: number) => void;
|
|
setBrainShowBanner: (show: boolean) => void;
|
|
dismissBrainBanner: () => void;
|
|
|
|
// Computed
|
|
isInputLocked: () => boolean;
|
|
}
|