- Updated all package.json name fields and dependency references - Updated all TypeScript/JavaScript imports - Updated .woodpecker/publish.yml filters and registry paths - Updated tools/install.sh scope default - Updated .npmrc registry paths (worktree + host) - Enhanced update-checker.ts with checkForAllUpdates() multi-package support - Updated CLI update command to show table of all packages - Added KNOWN_PACKAGES, formatAllPackagesTable, getInstallAllCommand - Marked checkForUpdate() with @deprecated JSDoc Closes #391
139 lines
4.3 KiB
TypeScript
139 lines
4.3 KiB
TypeScript
import React from 'react';
|
|
import { Box, Text } from 'ink';
|
|
import type { RoutingDecisionInfo } from '@mosaicstack/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 (
|
|
<Box flexDirection="column" paddingX={0} marginTop={0}>
|
|
{/* Line 0: keybinding hints */}
|
|
<Box>
|
|
<Text dimColor>^L sidebar · ^N new · ^K search · ^T thinking · PgUp/Dn scroll</Text>
|
|
</Box>
|
|
|
|
{/* Line 1: blank ····· Gateway: Status */}
|
|
<Box justifyContent="space-between">
|
|
<Box />
|
|
<Box>
|
|
<Text dimColor>Gateway: </Text>
|
|
<Text color={gatewayColor}>{gatewayStatus}</Text>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Line 2: cwd (branch) ····· Session: id */}
|
|
<Box justifyContent="space-between">
|
|
<Box>
|
|
<Text dimColor>{compactCwd(gitInfo.cwd)}</Text>
|
|
{gitInfo.branch && <Text dimColor> ({gitInfo.branch})</Text>}
|
|
</Box>
|
|
<Box>
|
|
<Text dimColor>
|
|
{conversationId ? `Session: ${conversationId.slice(0, 8)}` : 'No session'}
|
|
</Text>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Line 3: token stats ····· (provider) model */}
|
|
<Box justifyContent="space-between" minHeight={1}>
|
|
<Box>
|
|
{hasTokens ? (
|
|
<>
|
|
<Text dimColor>↑{formatTokens(tokenUsage.input)}</Text>
|
|
<Text dimColor>{' '}</Text>
|
|
<Text dimColor>↓{formatTokens(tokenUsage.output)}</Text>
|
|
{tokenUsage.cacheRead > 0 && (
|
|
<>
|
|
<Text dimColor>{' '}</Text>
|
|
<Text dimColor>R{formatTokens(tokenUsage.cacheRead)}</Text>
|
|
</>
|
|
)}
|
|
{tokenUsage.cacheWrite > 0 && (
|
|
<>
|
|
<Text dimColor>{' '}</Text>
|
|
<Text dimColor>W{formatTokens(tokenUsage.cacheWrite)}</Text>
|
|
</>
|
|
)}
|
|
{tokenUsage.cost > 0 && (
|
|
<>
|
|
<Text dimColor>{' '}</Text>
|
|
<Text dimColor>${tokenUsage.cost.toFixed(3)}</Text>
|
|
</>
|
|
)}
|
|
{tokenUsage.contextPercent > 0 && (
|
|
<>
|
|
<Text dimColor>{' '}</Text>
|
|
<Text dimColor>
|
|
{tokenUsage.contextPercent.toFixed(1)}%/{formatTokens(tokenUsage.contextWindow)}
|
|
</Text>
|
|
</>
|
|
)}
|
|
</>
|
|
) : (
|
|
<Text dimColor>↑0 ↓0 $0.000</Text>
|
|
)}
|
|
</Box>
|
|
<Box>
|
|
<Text dimColor>
|
|
{providerName ? `(${providerName}) ` : ''}
|
|
{modelName ?? 'awaiting model'}
|
|
{thinkingLevel !== 'off' ? ` • ${thinkingLevel}` : ''}
|
|
</Text>
|
|
</Box>
|
|
</Box>
|
|
|
|
{/* Line 4: routing transparency (M4-008) — only shown when a routing decision is available */}
|
|
{routingDecision && (
|
|
<Box>
|
|
<Text dimColor>
|
|
Routed: {routingDecision.model} ({routingDecision.reason})
|
|
</Text>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|