mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
Change detailed model stats to use a new shared Table class to resolve robustness issues. (#15208)
This commit is contained in:
@@ -22,7 +22,7 @@ vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
|
||||
|
||||
const useSessionStatsMock = vi.mocked(SessionContext.useSessionStats);
|
||||
|
||||
const renderWithMockedStats = (metrics: SessionMetrics) => {
|
||||
const renderWithMockedStats = (metrics: SessionMetrics, width?: number) => {
|
||||
useSessionStatsMock.mockReturnValue({
|
||||
stats: {
|
||||
sessionId: 'test-session',
|
||||
@@ -36,7 +36,7 @@ const renderWithMockedStats = (metrics: SessionMetrics) => {
|
||||
startNewPrompt: vi.fn(),
|
||||
});
|
||||
|
||||
return render(<ModelStatsDisplay />);
|
||||
return render(<ModelStatsDisplay />, width);
|
||||
};
|
||||
|
||||
describe('<ModelStatsDisplay />', () => {
|
||||
@@ -312,4 +312,60 @@ describe('<ModelStatsDisplay />', () => {
|
||||
expect(output).not.toContain('gemini-2.5-flash');
|
||||
expect(output).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should handle models with long names (gemini-3-*-preview) without layout breaking', () => {
|
||||
const { lastFrame } = renderWithMockedStats(
|
||||
{
|
||||
models: {
|
||||
'gemini-3-pro-preview': {
|
||||
api: { totalRequests: 10, totalErrors: 0, totalLatencyMs: 2000 },
|
||||
tokens: {
|
||||
input: 1000,
|
||||
prompt: 2000,
|
||||
candidates: 4000,
|
||||
total: 6000,
|
||||
cached: 500,
|
||||
thoughts: 100,
|
||||
tool: 50,
|
||||
},
|
||||
},
|
||||
'gemini-3-flash-preview': {
|
||||
api: { totalRequests: 20, totalErrors: 0, totalLatencyMs: 1000 },
|
||||
tokens: {
|
||||
input: 2000,
|
||||
prompt: 4000,
|
||||
candidates: 8000,
|
||||
total: 12000,
|
||||
cached: 1000,
|
||||
thoughts: 200,
|
||||
tool: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
totalCalls: 0,
|
||||
totalSuccess: 0,
|
||||
totalFail: 0,
|
||||
totalDurationMs: 0,
|
||||
totalDecisions: {
|
||||
accept: 0,
|
||||
reject: 0,
|
||||
modify: 0,
|
||||
[ToolCallDecision.AUTO_ACCEPT]: 0,
|
||||
},
|
||||
byName: {},
|
||||
},
|
||||
files: {
|
||||
totalLinesAdded: 0,
|
||||
totalLinesRemoved: 0,
|
||||
},
|
||||
},
|
||||
80,
|
||||
);
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('gemini-3-pro-');
|
||||
expect(output).toContain('gemini-3-flash-');
|
||||
expect(output).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,42 +13,17 @@ import {
|
||||
calculateCacheHitRate,
|
||||
calculateErrorRate,
|
||||
} from '../utils/computeStats.js';
|
||||
import type { ModelMetrics } from '../contexts/SessionContext.js';
|
||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||
import { Table, type Column } from './Table.js';
|
||||
|
||||
const METRIC_COL_WIDTH = 28;
|
||||
const MODEL_COL_WIDTH = 22;
|
||||
|
||||
interface StatRowProps {
|
||||
title: string;
|
||||
values: Array<string | React.ReactElement>;
|
||||
isSubtle?: boolean;
|
||||
interface StatRowData {
|
||||
metric: string;
|
||||
isSection?: boolean;
|
||||
isSubtle?: boolean;
|
||||
// Dynamic keys for model values
|
||||
[key: string]: string | React.ReactNode | boolean | undefined;
|
||||
}
|
||||
|
||||
const StatRow: React.FC<StatRowProps> = ({
|
||||
title,
|
||||
values,
|
||||
isSubtle = false,
|
||||
isSection = false,
|
||||
}) => (
|
||||
<Box>
|
||||
<Box width={METRIC_COL_WIDTH}>
|
||||
<Text
|
||||
bold={isSection}
|
||||
color={isSection ? theme.text.primary : theme.text.link}
|
||||
>
|
||||
{isSubtle ? ` ↳ ${title}` : title}
|
||||
</Text>
|
||||
</Box>
|
||||
{values.map((value, index) => (
|
||||
<Box width={MODEL_COL_WIDTH} key={index}>
|
||||
<Text color={theme.text.primary}>{value}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const ModelStatsDisplay: React.FC = () => {
|
||||
const { stats } = useSessionStats();
|
||||
const { models } = stats.metrics;
|
||||
@@ -73,10 +48,6 @@ export const ModelStatsDisplay: React.FC = () => {
|
||||
|
||||
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,
|
||||
);
|
||||
@@ -85,6 +56,152 @@ export const ModelStatsDisplay: React.FC = () => {
|
||||
([, metrics]) => metrics.tokens.cached > 0,
|
||||
);
|
||||
|
||||
// Helper to create a row with values for each model
|
||||
const createRow = (
|
||||
metric: string,
|
||||
getValue: (
|
||||
metrics: (typeof activeModels)[0][1],
|
||||
) => string | React.ReactNode,
|
||||
options: { isSection?: boolean; isSubtle?: boolean } = {},
|
||||
): StatRowData => {
|
||||
const row: StatRowData = {
|
||||
metric,
|
||||
isSection: options.isSection,
|
||||
isSubtle: options.isSubtle,
|
||||
};
|
||||
activeModels.forEach(([name, metrics]) => {
|
||||
row[name] = getValue(metrics);
|
||||
});
|
||||
return row;
|
||||
};
|
||||
|
||||
const rows: StatRowData[] = [
|
||||
// API Section
|
||||
{ metric: 'API', isSection: true },
|
||||
createRow('Requests', (m) => m.api.totalRequests.toLocaleString()),
|
||||
createRow('Errors', (m) => {
|
||||
const errorRate = calculateErrorRate(m);
|
||||
return (
|
||||
<Text
|
||||
color={
|
||||
m.api.totalErrors > 0 ? theme.status.error : theme.text.primary
|
||||
}
|
||||
>
|
||||
{m.api.totalErrors.toLocaleString()} ({errorRate.toFixed(1)}%)
|
||||
</Text>
|
||||
);
|
||||
}),
|
||||
createRow('Avg Latency', (m) => formatDuration(calculateAverageLatency(m))),
|
||||
|
||||
// Spacer
|
||||
{ metric: '' },
|
||||
|
||||
// Tokens Section
|
||||
{ metric: 'Tokens', isSection: true },
|
||||
createRow('Total', (m) => (
|
||||
<Text color={theme.text.secondary}>
|
||||
{m.tokens.total.toLocaleString()}
|
||||
</Text>
|
||||
)),
|
||||
createRow(
|
||||
'Input',
|
||||
(m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.input.toLocaleString()}
|
||||
</Text>
|
||||
),
|
||||
{ isSubtle: true },
|
||||
),
|
||||
];
|
||||
|
||||
if (hasCached) {
|
||||
rows.push(
|
||||
createRow(
|
||||
'Cache Reads',
|
||||
(m) => {
|
||||
const cacheHitRate = calculateCacheHitRate(m);
|
||||
return (
|
||||
<Text color={theme.text.secondary}>
|
||||
{m.tokens.cached.toLocaleString()} ({cacheHitRate.toFixed(1)}%)
|
||||
</Text>
|
||||
);
|
||||
},
|
||||
{ isSubtle: true },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (hasThoughts) {
|
||||
rows.push(
|
||||
createRow(
|
||||
'Thoughts',
|
||||
(m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.thoughts.toLocaleString()}
|
||||
</Text>
|
||||
),
|
||||
{ isSubtle: true },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (hasTool) {
|
||||
rows.push(
|
||||
createRow(
|
||||
'Tool',
|
||||
(m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.tool.toLocaleString()}
|
||||
</Text>
|
||||
),
|
||||
{ isSubtle: true },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
rows.push(
|
||||
createRow(
|
||||
'Output',
|
||||
(m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.candidates.toLocaleString()}
|
||||
</Text>
|
||||
),
|
||||
{ isSubtle: true },
|
||||
),
|
||||
);
|
||||
|
||||
const columns: Array<Column<StatRowData>> = [
|
||||
{
|
||||
key: 'metric',
|
||||
header: 'Metric',
|
||||
width: 28,
|
||||
renderCell: (row) => (
|
||||
<Text
|
||||
bold={row.isSection}
|
||||
color={row.isSection ? theme.text.primary : theme.text.link}
|
||||
>
|
||||
{row.isSubtle ? ` ↳ ${row.metric}` : row.metric}
|
||||
</Text>
|
||||
),
|
||||
},
|
||||
...modelNames.map((name) => ({
|
||||
key: name,
|
||||
header: name,
|
||||
flexGrow: 1,
|
||||
renderCell: (row: StatRowData) => {
|
||||
// Don't render anything for section headers in model columns
|
||||
if (row.isSection) return null;
|
||||
const val = row[name];
|
||||
if (val === undefined || val === null) return null;
|
||||
if (typeof val === 'string' || typeof val === 'number') {
|
||||
return <Text color={theme.text.primary}>{val}</Text>;
|
||||
}
|
||||
return val as React.ReactNode;
|
||||
},
|
||||
})),
|
||||
];
|
||||
|
||||
return (
|
||||
<Box
|
||||
borderStyle="round"
|
||||
@@ -97,128 +214,7 @@ export const ModelStatsDisplay: React.FC = () => {
|
||||
Model Stats For Nerds
|
||||
</Text>
|
||||
<Box height={1} />
|
||||
|
||||
{/* Header */}
|
||||
<Box>
|
||||
<Box width={METRIC_COL_WIDTH}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
Metric
|
||||
</Text>
|
||||
</Box>
|
||||
{modelNames.map((name) => (
|
||||
<Box width={MODEL_COL_WIDTH} key={name}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
{name}
|
||||
</Text>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* Divider */}
|
||||
<Box
|
||||
borderStyle="single"
|
||||
borderBottom={true}
|
||||
borderTop={false}
|
||||
borderLeft={false}
|
||||
borderRight={false}
|
||||
borderColor={theme.border.default}
|
||||
/>
|
||||
|
||||
{/* API Section */}
|
||||
<StatRow title="API" values={[]} isSection />
|
||||
<StatRow
|
||||
title="Requests"
|
||||
values={getModelValues((m) => m.api.totalRequests.toLocaleString())}
|
||||
/>
|
||||
<StatRow
|
||||
title="Errors"
|
||||
values={getModelValues((m) => {
|
||||
const errorRate = calculateErrorRate(m);
|
||||
return (
|
||||
<Text
|
||||
color={
|
||||
m.api.totalErrors > 0 ? theme.status.error : theme.text.primary
|
||||
}
|
||||
>
|
||||
{m.api.totalErrors.toLocaleString()} ({errorRate.toFixed(1)}%)
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
/>
|
||||
<StatRow
|
||||
title="Avg Latency"
|
||||
values={getModelValues((m) => {
|
||||
const avgLatency = calculateAverageLatency(m);
|
||||
return formatDuration(avgLatency);
|
||||
})}
|
||||
/>
|
||||
|
||||
<Box height={1} />
|
||||
|
||||
{/* Tokens Section */}
|
||||
<StatRow title="Tokens" values={[]} isSection />
|
||||
<StatRow
|
||||
title="Total"
|
||||
values={getModelValues((m) => (
|
||||
<Text color={theme.text.secondary}>
|
||||
{m.tokens.total.toLocaleString()}
|
||||
</Text>
|
||||
))}
|
||||
/>
|
||||
<StatRow
|
||||
title="Input"
|
||||
isSubtle
|
||||
values={getModelValues((m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.input.toLocaleString()}
|
||||
</Text>
|
||||
))}
|
||||
/>
|
||||
{hasCached && (
|
||||
<StatRow
|
||||
title="Cache Reads"
|
||||
isSubtle
|
||||
values={getModelValues((m) => {
|
||||
const cacheHitRate = calculateCacheHitRate(m);
|
||||
return (
|
||||
<Text color={theme.text.secondary}>
|
||||
{m.tokens.cached.toLocaleString()} ({cacheHitRate.toFixed(1)}%)
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{hasThoughts && (
|
||||
<StatRow
|
||||
title="Thoughts"
|
||||
isSubtle
|
||||
values={getModelValues((m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.thoughts.toLocaleString()}
|
||||
</Text>
|
||||
))}
|
||||
/>
|
||||
)}
|
||||
{hasTool && (
|
||||
<StatRow
|
||||
title="Tool"
|
||||
isSubtle
|
||||
values={getModelValues((m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.tool.toLocaleString()}
|
||||
</Text>
|
||||
))}
|
||||
/>
|
||||
)}
|
||||
<StatRow
|
||||
title="Output"
|
||||
isSubtle
|
||||
values={getModelValues((m) => (
|
||||
<Text color={theme.text.primary}>
|
||||
{m.tokens.candidates.toLocaleString()}
|
||||
</Text>
|
||||
))}
|
||||
/>
|
||||
<Table data={rows} columns={columns} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
61
packages/cli/src/ui/components/Table.test.tsx
Normal file
61
packages/cli/src/ui/components/Table.test.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { Table } from './Table.js';
|
||||
import { Text } from 'ink';
|
||||
|
||||
describe('Table', () => {
|
||||
it('should render headers and data correctly', () => {
|
||||
const columns = [
|
||||
{ key: 'id', header: 'ID', width: 5 },
|
||||
{ key: 'name', header: 'Name', flexGrow: 1 },
|
||||
];
|
||||
const data = [
|
||||
{ id: 1, name: 'Alice' },
|
||||
{ id: 2, name: 'Bob' },
|
||||
];
|
||||
|
||||
const { lastFrame } = render(<Table columns={columns} data={data} />, 100);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('ID');
|
||||
expect(output).toContain('Name');
|
||||
expect(output).toContain('1');
|
||||
expect(output).toContain('Alice');
|
||||
expect(output).toContain('2');
|
||||
expect(output).toContain('Bob');
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should support custom cell rendering', () => {
|
||||
const columns = [
|
||||
{
|
||||
key: 'value',
|
||||
header: 'Value',
|
||||
flexGrow: 1,
|
||||
renderCell: (item: { value: number }) => (
|
||||
<Text color="green">{item.value * 2}</Text>
|
||||
),
|
||||
},
|
||||
];
|
||||
const data = [{ value: 10 }];
|
||||
|
||||
const { lastFrame } = render(<Table columns={columns} data={data} />, 100);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('20');
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should handle undefined values gracefully', () => {
|
||||
const columns = [{ key: 'name', header: 'Name', flexGrow: 1 }];
|
||||
const data: Array<{ name: string | undefined }> = [{ name: undefined }];
|
||||
const { lastFrame } = render(<Table columns={columns} data={data} />, 100);
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('undefined');
|
||||
});
|
||||
});
|
||||
87
packages/cli/src/ui/components/Table.tsx
Normal file
87
packages/cli/src/ui/components/Table.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
export interface Column<T> {
|
||||
key: string;
|
||||
header: React.ReactNode;
|
||||
width?: number;
|
||||
flexGrow?: number;
|
||||
flexShrink?: number;
|
||||
flexBasis?: number | string;
|
||||
renderCell?: (item: T) => React.ReactNode;
|
||||
}
|
||||
|
||||
interface TableProps<T> {
|
||||
data: T[];
|
||||
columns: Array<Column<T>>;
|
||||
}
|
||||
|
||||
export function Table<T>({ data, columns }: TableProps<T>) {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{/* Header */}
|
||||
<Box flexDirection="row">
|
||||
{columns.map((col, index) => (
|
||||
<Box
|
||||
key={`header-${index}`}
|
||||
width={col.width}
|
||||
flexGrow={col.flexGrow}
|
||||
flexShrink={col.flexShrink}
|
||||
flexBasis={col.flexBasis ?? (col.width ? undefined : 0)}
|
||||
paddingRight={1}
|
||||
>
|
||||
{typeof col.header === 'string' ? (
|
||||
<Text bold color={theme.text.primary}>
|
||||
{col.header}
|
||||
</Text>
|
||||
) : (
|
||||
col.header
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
{/* Divider */}
|
||||
<Box
|
||||
borderStyle="single"
|
||||
borderBottom={true}
|
||||
borderTop={false}
|
||||
borderLeft={false}
|
||||
borderRight={false}
|
||||
borderColor={theme.border.default}
|
||||
marginBottom={0}
|
||||
/>
|
||||
|
||||
{/* Rows */}
|
||||
{data.map((item, rowIndex) => (
|
||||
<Box key={`row-${rowIndex}`} flexDirection="row">
|
||||
{columns.map((col, colIndex) => (
|
||||
<Box
|
||||
key={`cell-${rowIndex}-${colIndex}`}
|
||||
width={col.width}
|
||||
flexGrow={col.flexGrow}
|
||||
flexShrink={col.flexShrink}
|
||||
flexBasis={col.flexBasis ?? (col.width ? undefined : 0)}
|
||||
paddingRight={1}
|
||||
>
|
||||
{col.renderCell ? (
|
||||
col.renderCell(item)
|
||||
) : (
|
||||
<Text color={theme.text.primary}>
|
||||
{String((item as Record<string, unknown>)[col.key])}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,6 @@ exports[`<ModelStatsDisplay /> > should display a single model correctly 1`] = `
|
||||
│ Requests 1 │
|
||||
│ Errors 0 (0.0%) │
|
||||
│ Avg Latency 100ms │
|
||||
│ │
|
||||
│ Tokens │
|
||||
│ Total 30 │
|
||||
│ ↳ Input 5 │
|
||||
@@ -28,20 +27,19 @@ exports[`<ModelStatsDisplay /> > should display conditional rows if at least one
|
||||
│ │
|
||||
│ Model Stats For Nerds │
|
||||
│ │
|
||||
│ Metric gemini-2.5-pro gemini-2.5-flash │
|
||||
│ Metric gemini-2.5-pro gemini-2.5-flash │
|
||||
│ ────────────────────────────────────────────────────────────────────────────────────────────── │
|
||||
│ API │
|
||||
│ Requests 1 1 │
|
||||
│ Errors 0 (0.0%) 0 (0.0%) │
|
||||
│ Avg Latency 100ms 50ms │
|
||||
│ │
|
||||
│ Requests 1 1 │
|
||||
│ Errors 0 (0.0%) 0 (0.0%) │
|
||||
│ Avg Latency 100ms 50ms │
|
||||
│ Tokens │
|
||||
│ Total 30 15 │
|
||||
│ ↳ Input 5 5 │
|
||||
│ ↳ Cache Reads 5 (50.0%) 0 (0.0%) │
|
||||
│ ↳ Thoughts 2 0 │
|
||||
│ ↳ Tool 0 3 │
|
||||
│ ↳ Output 20 10 │
|
||||
│ Total 30 15 │
|
||||
│ ↳ Input 5 5 │
|
||||
│ ↳ Cache Reads 5 (50.0%) 0 (0.0%) │
|
||||
│ ↳ Thoughts 2 0 │
|
||||
│ ↳ Tool 0 3 │
|
||||
│ ↳ Output 20 10 │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
`;
|
||||
@@ -51,20 +49,19 @@ exports[`<ModelStatsDisplay /> > should display stats for multiple models correc
|
||||
│ │
|
||||
│ Model Stats For Nerds │
|
||||
│ │
|
||||
│ Metric gemini-2.5-pro gemini-2.5-flash │
|
||||
│ Metric gemini-2.5-pro gemini-2.5-flash │
|
||||
│ ────────────────────────────────────────────────────────────────────────────────────────────── │
|
||||
│ API │
|
||||
│ Requests 10 20 │
|
||||
│ Errors 1 (10.0%) 2 (10.0%) │
|
||||
│ Avg Latency 100ms 25ms │
|
||||
│ │
|
||||
│ Requests 10 20 │
|
||||
│ Errors 1 (10.0%) 2 (10.0%) │
|
||||
│ Avg Latency 100ms 25ms │
|
||||
│ Tokens │
|
||||
│ Total 300 600 │
|
||||
│ ↳ Input 50 100 │
|
||||
│ ↳ Cache Reads 50 (50.0%) 100 (50.0%) │
|
||||
│ ↳ Thoughts 10 20 │
|
||||
│ ↳ Tool 5 10 │
|
||||
│ ↳ Output 200 400 │
|
||||
│ Total 300 600 │
|
||||
│ ↳ Input 50 100 │
|
||||
│ ↳ Cache Reads 50 (50.0%) 100 (50.0%) │
|
||||
│ ↳ Thoughts 10 20 │
|
||||
│ ↳ Tool 5 10 │
|
||||
│ ↳ Output 200 400 │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
`;
|
||||
@@ -80,7 +77,6 @@ exports[`<ModelStatsDisplay /> > should handle large values without wrapping or
|
||||
│ Requests 999,999,999 │
|
||||
│ Errors 123,456,789 (12.3%) │
|
||||
│ Avg Latency 0ms │
|
||||
│ │
|
||||
│ Tokens │
|
||||
│ Total 999,999,999 │
|
||||
│ ↳ Input 864,197,532 │
|
||||
@@ -92,6 +88,28 @@ exports[`<ModelStatsDisplay /> > should handle large values without wrapping or
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯"
|
||||
`;
|
||||
|
||||
exports[`<ModelStatsDisplay /> > should handle models with long names (gemini-3-*-preview) without layout breaking 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ Model Stats For Nerds │
|
||||
│ │
|
||||
│ Metric gemini-3-pro-preview gemini-3-flash-preview │
|
||||
│ ────────────────────────────────────────────────────────────────────────── │
|
||||
│ API │
|
||||
│ Requests 10 20 │
|
||||
│ Errors 0 (0.0%) 0 (0.0%) │
|
||||
│ Avg Latency 200ms 50ms │
|
||||
│ Tokens │
|
||||
│ Total 6,000 12,000 │
|
||||
│ ↳ Input 1,000 2,000 │
|
||||
│ ↳ Cache Reads 500 (25.0%) 1,000 (25.0%) │
|
||||
│ ↳ Thoughts 100 200 │
|
||||
│ ↳ Tool 50 100 │
|
||||
│ ↳ Output 4,000 8,000 │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯"
|
||||
`;
|
||||
|
||||
exports[`<ModelStatsDisplay /> > should not display conditional rows if no model has data for them 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
@@ -103,7 +121,6 @@ exports[`<ModelStatsDisplay /> > should not display conditional rows if no model
|
||||
│ Requests 1 │
|
||||
│ Errors 0 (0.0%) │
|
||||
│ Avg Latency 100ms │
|
||||
│ │
|
||||
│ Tokens │
|
||||
│ Total 30 │
|
||||
│ ↳ Input 10 │
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Table > should render headers and data correctly 1`] = `
|
||||
"ID Name
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
1 Alice
|
||||
2 Bob"
|
||||
`;
|
||||
|
||||
exports[`Table > should support custom cell rendering 1`] = `
|
||||
"Value
|
||||
────────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
20"
|
||||
`;
|
||||
Reference in New Issue
Block a user