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) --- packages/cli/src/ui/components/StatsDisplay.tsx | 262 ++++++++++++++++++------ 1 file changed, 201 insertions(+), 61 deletions(-) (limited to 'packages/cli/src/ui/components/StatsDisplay.tsx') diff --git a/packages/cli/src/ui/components/StatsDisplay.tsx b/packages/cli/src/ui/components/StatsDisplay.tsx index 76d48821..249fc106 100644 --- a/packages/cli/src/ui/components/StatsDisplay.tsx +++ b/packages/cli/src/ui/components/StatsDisplay.tsx @@ -8,90 +8,230 @@ import React from 'react'; import { Box, Text } from 'ink'; import { Colors } from '../colors.js'; import { formatDuration } from '../utils/formatters.js'; -import { CumulativeStats } from '../contexts/SessionContext.js'; -import { FormattedStats, StatRow, StatsColumn } from './Stats.js'; +import { useSessionStats, ModelMetrics } from '../contexts/SessionContext.js'; +import { + getStatusColor, + TOOL_SUCCESS_RATE_HIGH, + TOOL_SUCCESS_RATE_MEDIUM, + USER_AGREEMENT_RATE_HIGH, + USER_AGREEMENT_RATE_MEDIUM, +} from '../utils/displayUtils.js'; +import { computeSessionStats } from '../utils/computeStats.js'; -// --- Constants --- +// A more flexible and powerful StatRow component +interface StatRowProps { + title: string; + children: React.ReactNode; // Use children to allow for complex, colored values +} + +const StatRow: React.FC = ({ title, children }) => ( + + {/* Fixed width for the label creates a clean "gutter" for alignment */} + + {title} + + {children} + +); + +// A SubStatRow for indented, secondary information +interface SubStatRowProps { + title: string; + children: React.ReactNode; +} + +const SubStatRow: React.FC = ({ title, children }) => ( + + {/* Adjust width for the "» " prefix */} + + » {title} + + {children} + +); + +// A Section component to group related stats +interface SectionProps { + title: string; + children: React.ReactNode; +} + +const Section: React.FC = ({ title, children }) => ( + + {title} + {children} + +); -const COLUMN_WIDTH = '48%'; +const ModelUsageTable: React.FC<{ + models: Record; + totalCachedTokens: number; + cacheEfficiency: number; +}> = ({ models, totalCachedTokens, cacheEfficiency }) => { + const nameWidth = 25; + const requestsWidth = 8; + const inputTokensWidth = 15; + const outputTokensWidth = 15; + + return ( + + {/* Header */} + + + Model Usage + + + Reqs + + + Input Tokens + + + Output Tokens + + + {/* Divider */} + -// --- Prop and Data Structures --- + {/* Rows */} + {Object.entries(models).map(([name, modelMetrics]) => ( + + + {name.replace('-001', '')} + + + {modelMetrics.api.totalRequests} + + + + {modelMetrics.tokens.prompt.toLocaleString()} + + + + + {modelMetrics.tokens.candidates.toLocaleString()} + + + + ))} + {cacheEfficiency > 0 && ( + + + Savings Highlight:{' '} + {totalCachedTokens.toLocaleString()} ({cacheEfficiency.toFixed(1)} + %) of input tokens were served from the cache, reducing costs. + + + + » Tip: For a full token breakdown, run `/stats model`. + + + )} + + ); +}; interface StatsDisplayProps { - stats: CumulativeStats; - lastTurnStats: CumulativeStats; duration: string; } -// --- Main Component --- - -export const StatsDisplay: React.FC = ({ - stats, - lastTurnStats, - duration, -}) => { - const lastTurnFormatted: FormattedStats = { - inputTokens: lastTurnStats.promptTokenCount, - outputTokens: lastTurnStats.candidatesTokenCount, - toolUseTokens: lastTurnStats.toolUsePromptTokenCount, - thoughtsTokens: lastTurnStats.thoughtsTokenCount, - cachedTokens: lastTurnStats.cachedContentTokenCount, - totalTokens: lastTurnStats.totalTokenCount, - }; +export const StatsDisplay: React.FC = ({ duration }) => { + const { stats } = useSessionStats(); + const { metrics } = stats; + const { models, tools } = metrics; + const computed = computeSessionStats(metrics); - const cumulativeFormatted: FormattedStats = { - inputTokens: stats.promptTokenCount, - outputTokens: stats.candidatesTokenCount, - toolUseTokens: stats.toolUsePromptTokenCount, - thoughtsTokens: stats.thoughtsTokenCount, - cachedTokens: stats.cachedContentTokenCount, - totalTokens: stats.totalTokenCount, + const successThresholds = { + green: TOOL_SUCCESS_RATE_HIGH, + yellow: TOOL_SUCCESS_RATE_MEDIUM, + }; + const agreementThresholds = { + green: USER_AGREEMENT_RATE_HIGH, + yellow: USER_AGREEMENT_RATE_MEDIUM, }; + const successColor = getStatusColor(computed.successRate, successThresholds); + const agreementColor = getStatusColor( + computed.agreementRate, + agreementThresholds, + ); return ( - Stats + Session Stats + - - - - + {tools.totalCalls > 0 && ( +
+ + + {tools.totalCalls} ({' '} + ✔ {tools.totalSuccess}{' '} + ✖ {tools.totalFail} ) + + + + {computed.successRate.toFixed(1)}% + + {computed.totalDecisions > 0 && ( + + + {computed.agreementRate.toFixed(1)}%{' '} + + ({computed.totalDecisions} reviewed) + + + + )} +
+ )} - - {/* Left column for "Last Turn" duration */} - - - +
+ + {duration} + + + {formatDuration(computed.agentActiveTime)} + + + + {formatDuration(computed.totalApiTime)}{' '} + + ({computed.apiTimePercent.toFixed(1)}%) + + + + + + {formatDuration(computed.totalToolTime)}{' '} + + ({computed.toolTimePercent.toFixed(1)}%) + + + +
- {/* Right column for "Cumulative" durations */} - - - - -
+ {Object.keys(models).length > 0 && ( + + )}
); }; -- cgit v1.2.3