From 770f862832dfef477705bee69bd2a84397d105a8 Mon Sep 17 00:00:00 2001 From: Abhi <43648792+abhipatel12@users.noreply.github.com> Date: Sun, 29 Jun 2025 20:44:33 -0400 Subject: feat: Change /stats to include more detailed breakdowns (#2615) --- .../cli/src/ui/components/ModelStatsDisplay.tsx | 197 +++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 packages/cli/src/ui/components/ModelStatsDisplay.tsx (limited to 'packages/cli/src/ui/components/ModelStatsDisplay.tsx') diff --git a/packages/cli/src/ui/components/ModelStatsDisplay.tsx b/packages/cli/src/ui/components/ModelStatsDisplay.tsx new file mode 100644 index 00000000..1911e757 --- /dev/null +++ b/packages/cli/src/ui/components/ModelStatsDisplay.tsx @@ -0,0 +1,197 @@ +/** + * @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())} + /> + + ); +}; -- cgit v1.2.3