mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-19 16:28:28 +00:00
fix(cli): keep long model stats header on one line (#4032)
Some checks failed
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
SDK Python / Classify PR (push) Has been cancelled
SDK Python / SDK Python (3.10) (push) Has been cancelled
SDK Python / SDK Python (3.11) (push) Has been cancelled
SDK Python / SDK Python (3.12) (push) Has been cancelled
Some checks failed
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
SDK Python / Classify PR (push) Has been cancelled
SDK Python / SDK Python (3.10) (push) Has been cancelled
SDK Python / SDK Python (3.11) (push) Has been cancelled
SDK Python / SDK Python (3.12) (push) Has been cancelled
* fix(cli): keep long model stats header on one line * test(cli): cover fixed model stats columns
This commit is contained in:
parent
bdd5b602de
commit
4bba75f765
2 changed files with 126 additions and 6 deletions
|
|
@ -39,6 +39,7 @@ const renderWithMockedStats = (
|
|||
string,
|
||||
{ inputPerMillionTokens?: number; outputPerMillionTokens?: number }
|
||||
>,
|
||||
width?: number,
|
||||
) => {
|
||||
useSessionStatsMock.mockReturnValue({
|
||||
stats: {
|
||||
|
|
@ -60,7 +61,7 @@ const renderWithMockedStats = (
|
|||
|
||||
return render(
|
||||
<SettingsContext.Provider value={mockSettings}>
|
||||
<ModelStatsDisplay />
|
||||
<ModelStatsDisplay width={width} />
|
||||
</SettingsContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
|
@ -275,6 +276,88 @@ describe('<ModelStatsDisplay />', () => {
|
|||
expect(output).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('keeps a long single model name on the metric header line when space is available', () => {
|
||||
const modelName = 'ggml-org/gemma-4-E4B-it-GGUF';
|
||||
const { lastFrame } = renderWithMockedStats(
|
||||
{
|
||||
models: {
|
||||
[modelName]: mainOnly({
|
||||
api: { totalRequests: 2, totalErrors: 1, totalLatencyMs: 220000 },
|
||||
tokens: {
|
||||
prompt: 17953,
|
||||
candidates: 225,
|
||||
total: 18178,
|
||||
cached: 0,
|
||||
thoughts: 0,
|
||||
},
|
||||
}),
|
||||
},
|
||||
tools: {
|
||||
totalCalls: 0,
|
||||
totalSuccess: 0,
|
||||
totalFail: 0,
|
||||
totalDurationMs: 0,
|
||||
totalDecisions: { accept: 0, reject: 0, modify: 0, auto_accept: 0 },
|
||||
byName: {},
|
||||
},
|
||||
files: { totalLinesAdded: 0, totalLinesRemoved: 0 },
|
||||
},
|
||||
undefined,
|
||||
100,
|
||||
);
|
||||
|
||||
expect(lastFrame()).toContain(`Metric ${modelName}`);
|
||||
});
|
||||
|
||||
it('keeps fixed model column widths for multiple models even when space is available', () => {
|
||||
const { lastFrame } = renderWithMockedStats(
|
||||
{
|
||||
models: {
|
||||
'model-a': mainOnly({
|
||||
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
|
||||
tokens: {
|
||||
prompt: 10,
|
||||
candidates: 20,
|
||||
total: 30,
|
||||
cached: 0,
|
||||
thoughts: 0,
|
||||
},
|
||||
}),
|
||||
'model-b': mainOnly({
|
||||
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
|
||||
tokens: {
|
||||
prompt: 10,
|
||||
candidates: 20,
|
||||
total: 30,
|
||||
cached: 0,
|
||||
thoughts: 0,
|
||||
},
|
||||
}),
|
||||
},
|
||||
tools: {
|
||||
totalCalls: 0,
|
||||
totalSuccess: 0,
|
||||
totalFail: 0,
|
||||
totalDurationMs: 0,
|
||||
totalDecisions: { accept: 0, reject: 0, modify: 0, auto_accept: 0 },
|
||||
byName: {},
|
||||
},
|
||||
files: { totalLinesAdded: 0, totalLinesRemoved: 0 },
|
||||
},
|
||||
undefined,
|
||||
120,
|
||||
);
|
||||
|
||||
const headerLine = lastFrame()
|
||||
?.split('\n')
|
||||
.find((line) => line.includes('Metric') && line.includes('model-a'));
|
||||
|
||||
expect(headerLine).toBeDefined();
|
||||
expect(
|
||||
headerLine!.indexOf('model-b') - headerLine!.indexOf('model-a'),
|
||||
).toBe(24);
|
||||
});
|
||||
|
||||
describe('Subagent source attribution', () => {
|
||||
const baseTools: SessionMetrics['tools'] = {
|
||||
totalCalls: 0,
|
||||
|
|
|
|||
|
|
@ -26,10 +26,15 @@ const METRIC_COL_WIDTH = 28;
|
|||
// Sessions with three or more sources will exceed the panel — acceptable per
|
||||
// the design doc, which accepts the crowded layout for many-subagent cases.
|
||||
const MODEL_COL_WIDTH = 24;
|
||||
// Keep this in sync with the surrounding Box borderStyle and paddingX:
|
||||
// Ink's round border consumes 2 border columns plus 2 columns of horizontal
|
||||
// padding on each side.
|
||||
const PANEL_HORIZONTAL_CHROME_WIDTH = 6;
|
||||
|
||||
interface StatRowProps {
|
||||
title: string;
|
||||
values: Array<string | React.ReactElement>;
|
||||
modelColWidth: number;
|
||||
isSubtle?: boolean;
|
||||
isSection?: boolean;
|
||||
}
|
||||
|
|
@ -37,6 +42,7 @@ interface StatRowProps {
|
|||
const StatRow: React.FC<StatRowProps> = ({
|
||||
title,
|
||||
values,
|
||||
modelColWidth,
|
||||
isSubtle = false,
|
||||
isSection = false,
|
||||
}) => (
|
||||
|
|
@ -50,7 +56,7 @@ const StatRow: React.FC<StatRowProps> = ({
|
|||
</Text>
|
||||
</Box>
|
||||
{values.map((value, index) => (
|
||||
<Box width={MODEL_COL_WIDTH} key={index}>
|
||||
<Box width={modelColWidth} key={index}>
|
||||
<Text color={theme.text.primary}>{value}</Text>
|
||||
</Box>
|
||||
))}
|
||||
|
|
@ -94,6 +100,13 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
|
|||
({ metrics }) => metrics.tokens.thoughts > 0,
|
||||
);
|
||||
const hasCached = entries.some(({ metrics }) => metrics.tokens.cached > 0);
|
||||
const modelColWidth =
|
||||
entries.length === 1 && width
|
||||
? Math.max(
|
||||
MODEL_COL_WIDTH,
|
||||
width - PANEL_HORIZONTAL_CHROME_WIDTH - METRIC_COL_WIDTH,
|
||||
)
|
||||
: MODEL_COL_WIDTH;
|
||||
|
||||
const getModelName = (key: string): string => key.split('::')[0];
|
||||
|
||||
|
|
@ -128,7 +141,7 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
|
|||
</Text>
|
||||
</Box>
|
||||
{entries.map(({ key, label }) => (
|
||||
<Box width={MODEL_COL_WIDTH} key={key}>
|
||||
<Box width={modelColWidth} key={key}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
{label}
|
||||
</Text>
|
||||
|
|
@ -147,10 +160,16 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
|
|||
/>
|
||||
|
||||
{/* API Section */}
|
||||
<StatRow title={t('API')} values={[]} isSection />
|
||||
<StatRow
|
||||
title={t('API')}
|
||||
values={[]}
|
||||
modelColWidth={modelColWidth}
|
||||
isSection
|
||||
/>
|
||||
<StatRow
|
||||
title={t('Requests')}
|
||||
values={getModelValues((m) => m.api.totalRequests.toLocaleString())}
|
||||
modelColWidth={modelColWidth}
|
||||
/>
|
||||
<StatRow
|
||||
title={t('Errors')}
|
||||
|
|
@ -166,6 +185,7 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
|
|||
</Text>
|
||||
);
|
||||
})}
|
||||
modelColWidth={modelColWidth}
|
||||
/>
|
||||
<StatRow
|
||||
title={t('Avg Latency')}
|
||||
|
|
@ -173,12 +193,18 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
|
|||
const avgLatency = calculateAverageLatency(m);
|
||||
return formatDuration(avgLatency);
|
||||
})}
|
||||
modelColWidth={modelColWidth}
|
||||
/>
|
||||
|
||||
<Box height={1} />
|
||||
|
||||
{/* Tokens Section */}
|
||||
<StatRow title={t('Tokens')} values={[]} isSection />
|
||||
<StatRow
|
||||
title={t('Tokens')}
|
||||
values={[]}
|
||||
modelColWidth={modelColWidth}
|
||||
isSection
|
||||
/>
|
||||
<StatRow
|
||||
title={t('Total')}
|
||||
values={getModelValues((m) => (
|
||||
|
|
@ -186,11 +212,13 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
|
|||
{m.tokens.total.toLocaleString()}
|
||||
</Text>
|
||||
))}
|
||||
modelColWidth={modelColWidth}
|
||||
/>
|
||||
<StatRow
|
||||
title={t('Prompt')}
|
||||
isSubtle
|
||||
values={getModelValues((m) => m.tokens.prompt.toLocaleString())}
|
||||
modelColWidth={modelColWidth}
|
||||
/>
|
||||
{hasCached && (
|
||||
<StatRow
|
||||
|
|
@ -204,6 +232,7 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
|
|||
</Text>
|
||||
);
|
||||
})}
|
||||
modelColWidth={modelColWidth}
|
||||
/>
|
||||
)}
|
||||
{hasThoughts && (
|
||||
|
|
@ -211,17 +240,24 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
|
|||
title={t('Thoughts')}
|
||||
isSubtle
|
||||
values={getModelValues((m) => m.tokens.thoughts.toLocaleString())}
|
||||
modelColWidth={modelColWidth}
|
||||
/>
|
||||
)}
|
||||
<StatRow
|
||||
title={t('Output')}
|
||||
isSubtle
|
||||
values={getModelValues((m) => m.tokens.candidates.toLocaleString())}
|
||||
modelColWidth={modelColWidth}
|
||||
/>
|
||||
{hasPricing && (
|
||||
<>
|
||||
<Box height={1} />
|
||||
<StatRow title={t('Cost')} values={[]} isSection />
|
||||
<StatRow
|
||||
title={t('Cost')}
|
||||
values={[]}
|
||||
modelColWidth={modelColWidth}
|
||||
isSection
|
||||
/>
|
||||
<StatRow
|
||||
title={t('Estimated')}
|
||||
values={entries.map(({ key, metrics }) => {
|
||||
|
|
@ -233,6 +269,7 @@ export const ModelStatsDisplay: React.FC<ModelStatsDisplayProps> = ({
|
|||
});
|
||||
return cost != null ? `$${cost.toFixed(4)}` : 'N/A';
|
||||
})}
|
||||
modelColWidth={modelColWidth}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue