import React from 'react'; import { Box, Text } from 'ink'; import type { RoutingDecisionInfo } from '@mosaic/types'; import type { TokenUsage } from '../hooks/use-socket.js'; import type { GitInfo } from '../hooks/use-git-info.js'; export interface BottomBarProps { gitInfo: GitInfo; tokenUsage: TokenUsage; connected: boolean; connecting: boolean; modelName: string | null; providerName: string | null; thinkingLevel: string; conversationId: string | undefined; /** Routing decision info for transparency display (M4-008) */ routingDecision?: RoutingDecisionInfo | null; } function formatTokens(n: number): string { if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; if (n >= 1_000) return `${(n / 1_000).toFixed(0)}k`; return String(n); } /** Compact the cwd — replace home with ~ */ function compactCwd(cwd: string): string { const home = process.env['HOME'] ?? ''; if (home && cwd.startsWith(home)) { return '~' + cwd.slice(home.length); } return cwd; } export function BottomBar({ gitInfo, tokenUsage, connected, connecting, modelName, providerName, thinkingLevel, conversationId, routingDecision, }: BottomBarProps) { const gatewayStatus = connected ? 'Connected' : connecting ? 'Connecting…' : 'Disconnected'; const gatewayColor = connected ? 'green' : connecting ? 'yellow' : 'red'; const hasTokens = tokenUsage.total > 0; return ( {/* Line 0: keybinding hints */} ^L sidebar · ^N new · ^K search · ^T thinking · PgUp/Dn scroll {/* Line 1: blank ····· Gateway: Status */} Gateway: {gatewayStatus} {/* Line 2: cwd (branch) ····· Session: id */} {compactCwd(gitInfo.cwd)} {gitInfo.branch && ({gitInfo.branch})} {conversationId ? `Session: ${conversationId.slice(0, 8)}` : 'No session'} {/* Line 3: token stats ····· (provider) model */} {hasTokens ? ( <> ↑{formatTokens(tokenUsage.input)} {' '} ↓{formatTokens(tokenUsage.output)} {tokenUsage.cacheRead > 0 && ( <> {' '} R{formatTokens(tokenUsage.cacheRead)} )} {tokenUsage.cacheWrite > 0 && ( <> {' '} W{formatTokens(tokenUsage.cacheWrite)} )} {tokenUsage.cost > 0 && ( <> {' '} ${tokenUsage.cost.toFixed(3)} )} {tokenUsage.contextPercent > 0 && ( <> {' '} {tokenUsage.contextPercent.toFixed(1)}%/{formatTokens(tokenUsage.contextWindow)} )} ) : ( ↑0 ↓0 $0.000 )} {providerName ? `(${providerName}) ` : ''} {modelName ?? 'awaiting model'} {thinkingLevel !== 'off' ? ` • ${thinkingLevel}` : ''} {/* Line 4: routing transparency (M4-008) — only shown when a routing decision is available */} {routingDecision && ( Routed: {routingDecision.model} ({routingDecision.reason}) )} ); }