Files
stack/packages/mosaic/src/tui/components/bottom-bar.tsx
Jarvis c6fc090c98
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
feat(mosaic): merge @mosaic/cli into @mosaic/mosaic
@mosaic/mosaic is now the single package providing both:
- 'mosaic' binary (CLI: yolo, coord, prdy, tui, gateway, etc.)
- 'mosaic-wizard' binary (installation wizard)

Changes:
- Move packages/cli/src/* into packages/mosaic/src/
- Convert dynamic @mosaic/mosaic imports to static relative imports
- Add CLI deps (ink, react, socket.io-client, @mosaic/config) to mosaic
- Add jsx: react-jsx to mosaic's tsconfig
- Exclude packages/cli from workspace (pnpm-workspace.yaml)
- Update install.sh to install @mosaic/mosaic instead of @mosaic/cli
- Bump version to 0.0.17

This eliminates the circular dependency between @mosaic/cli and
@mosaic/mosaic that was blocking the build graph.
2026-04-04 20:07:27 -05:00

139 lines
4.3 KiB
TypeScript

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 (
<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>
);
}