/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import React from 'react'; import { Box, Text } from 'ink'; import { Colors } from '../colors.js'; import { formatDuration } from '../utils/formatters.js'; import { calculateAverageLatency, calculateCacheHitRate, calculateErrorRate, } from '../utils/computeStats.js'; import { useSessionStats, ModelMetrics } from '../contexts/SessionContext.js'; const METRIC_COL_WIDTH = 28; const MODEL_COL_WIDTH = 22; interface StatRowProps { title: string; values: Array; isSubtle?: boolean; isSection?: boolean; } const StatRow: React.FC = ({ title, values, isSubtle = false, isSection = false, }) => ( {isSubtle ? ` ↳ ${title}` : title} {values.map((value, index) => ( {value} ))} ); export const ModelStatsDisplay: React.FC = () => { const { stats } = useSessionStats(); const { models } = stats.metrics; const activeModels = Object.entries(models).filter( ([, metrics]) => metrics.api.totalRequests > 0, ); if (activeModels.length === 0) { return ( No API calls have been made in this session. ); } const modelNames = activeModels.map(([name]) => name); const getModelValues = ( getter: (metrics: ModelMetrics) => string | React.ReactElement, ) => activeModels.map(([, metrics]) => getter(metrics)); const hasThoughts = activeModels.some( ([, metrics]) => metrics.tokens.thoughts > 0, ); const hasTool = activeModels.some(([, metrics]) => metrics.tokens.tool > 0); const hasCached = activeModels.some( ([, metrics]) => metrics.tokens.cached > 0, ); return ( Model Stats For Nerds {/* Header */} Metric {modelNames.map((name) => ( {name} ))} {/* Divider */} {/* API Section */} m.api.totalRequests.toLocaleString())} /> { const errorRate = calculateErrorRate(m); return ( 0 ? Colors.AccentRed : Colors.Foreground } > {m.api.totalErrors.toLocaleString()} ({errorRate.toFixed(1)}%) ); })} /> { const avgLatency = calculateAverageLatency(m); return formatDuration(avgLatency); })} /> {/* Tokens Section */} ( {m.tokens.total.toLocaleString()} ))} /> m.tokens.prompt.toLocaleString())} /> {hasCached && ( { const cacheHitRate = calculateCacheHitRate(m); return ( {m.tokens.cached.toLocaleString()} ({cacheHitRate.toFixed(1)}%) ); })} /> )} {hasThoughts && ( m.tokens.thoughts.toLocaleString())} /> )} {hasTool && ( m.tokens.tool.toLocaleString())} /> )} m.tokens.candidates.toLocaleString())} /> ); };