mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
feat: Add UI for /stats slash command (#883)
This commit is contained in:
@@ -255,18 +255,19 @@ describe('useSlashCommandProcessor', () => {
|
||||
describe('/stats command', () => {
|
||||
it('should show detailed session statistics', async () => {
|
||||
// Arrange
|
||||
const cumulativeStats = {
|
||||
totalTokenCount: 900,
|
||||
promptTokenCount: 200,
|
||||
candidatesTokenCount: 400,
|
||||
cachedContentTokenCount: 100,
|
||||
turnCount: 1,
|
||||
toolUsePromptTokenCount: 50,
|
||||
thoughtsTokenCount: 150,
|
||||
};
|
||||
mockUseSessionStats.mockReturnValue({
|
||||
stats: {
|
||||
sessionStartTime: new Date('2025-01-01T00:00:00.000Z'),
|
||||
cumulative: {
|
||||
totalTokenCount: 900,
|
||||
promptTokenCount: 200,
|
||||
candidatesTokenCount: 400,
|
||||
cachedContentTokenCount: 100,
|
||||
turnCount: 1,
|
||||
toolUsePromptTokenCount: 50,
|
||||
thoughtsTokenCount: 150,
|
||||
},
|
||||
cumulative: cumulativeStats,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -280,24 +281,12 @@ describe('useSlashCommandProcessor', () => {
|
||||
});
|
||||
|
||||
// Assert
|
||||
const expectedContent = [
|
||||
` ⎿ Total duration (wall): 1h 2m 3s`,
|
||||
` Total Token usage:`,
|
||||
` Turns: 1`,
|
||||
` Total: 900`,
|
||||
` ├─ Input: 200`,
|
||||
` ├─ Output: 400`,
|
||||
` ├─ Cached: 100`,
|
||||
` └─ Overhead: 200`,
|
||||
` ├─ Model thoughts: 150`,
|
||||
` └─ Tool-use prompts: 50`,
|
||||
].join('\n');
|
||||
|
||||
expect(mockAddItem).toHaveBeenNthCalledWith(
|
||||
2, // Called after the user message
|
||||
expect.objectContaining({
|
||||
type: MessageType.INFO,
|
||||
text: expectedContent,
|
||||
type: MessageType.STATS,
|
||||
stats: cumulativeStats,
|
||||
duration: '1h 2m 3s',
|
||||
}),
|
||||
expect.any(Number),
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Message, MessageType, HistoryItemWithoutId } from '../types.js';
|
||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||
import { createShowMemoryAction } from './useShowMemoryCommand.js';
|
||||
import { GIT_COMMIT_INFO } from '../../generated/git-commit.js';
|
||||
import { formatMemoryUsage } from '../utils/formatters.js';
|
||||
import { formatDuration, formatMemoryUsage } from '../utils/formatters.js';
|
||||
import { getCliVersion } from '../../utils/version.js';
|
||||
|
||||
export interface SlashCommandActionReturn {
|
||||
@@ -69,6 +69,13 @@ export const useSlashCommandProcessor = (
|
||||
sandboxEnv: message.sandboxEnv,
|
||||
modelVersion: message.modelVersion,
|
||||
};
|
||||
} else if (message.type === MessageType.STATS) {
|
||||
historyItemContent = {
|
||||
type: 'stats',
|
||||
stats: message.stats,
|
||||
lastTurnStats: message.lastTurnStats,
|
||||
duration: message.duration,
|
||||
};
|
||||
} else {
|
||||
historyItemContent = {
|
||||
type: message.type as
|
||||
@@ -152,41 +159,14 @@ export const useSlashCommandProcessor = (
|
||||
description: 'check session stats',
|
||||
action: (_mainCommand, _subCommand, _args) => {
|
||||
const now = new Date();
|
||||
const { sessionStartTime, cumulative } = session.stats;
|
||||
|
||||
const duration = now.getTime() - sessionStartTime.getTime();
|
||||
const durationInSeconds = Math.floor(duration / 1000);
|
||||
const hours = Math.floor(durationInSeconds / 3600);
|
||||
const minutes = Math.floor((durationInSeconds % 3600) / 60);
|
||||
const seconds = durationInSeconds % 60;
|
||||
|
||||
const durationString = [
|
||||
hours > 0 ? `${hours}h` : '',
|
||||
minutes > 0 ? `${minutes}m` : '',
|
||||
`${seconds}s`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
const overheadTotal =
|
||||
cumulative.thoughtsTokenCount + cumulative.toolUsePromptTokenCount;
|
||||
|
||||
const statsContent = [
|
||||
` ⎿ Total duration (wall): ${durationString}`,
|
||||
` Total Token usage:`,
|
||||
` Turns: ${cumulative.turnCount.toLocaleString()}`,
|
||||
` Total: ${cumulative.totalTokenCount.toLocaleString()}`,
|
||||
` ├─ Input: ${cumulative.promptTokenCount.toLocaleString()}`,
|
||||
` ├─ Output: ${cumulative.candidatesTokenCount.toLocaleString()}`,
|
||||
` ├─ Cached: ${cumulative.cachedContentTokenCount.toLocaleString()}`,
|
||||
` └─ Overhead: ${overheadTotal.toLocaleString()}`,
|
||||
` ├─ Model thoughts: ${cumulative.thoughtsTokenCount.toLocaleString()}`,
|
||||
` └─ Tool-use prompts: ${cumulative.toolUsePromptTokenCount.toLocaleString()}`,
|
||||
].join('\n');
|
||||
const { sessionStartTime, cumulative, currentTurn } = session.stats;
|
||||
const wallDuration = now.getTime() - sessionStartTime.getTime();
|
||||
|
||||
addMessage({
|
||||
type: MessageType.INFO,
|
||||
content: statsContent,
|
||||
type: MessageType.STATS,
|
||||
stats: cumulative,
|
||||
lastTurnStats: currentTurn,
|
||||
duration: formatDuration(wallDuration),
|
||||
timestamp: new Date(),
|
||||
});
|
||||
},
|
||||
|
||||
@@ -598,5 +598,18 @@ describe('useGeminiStream', () => {
|
||||
expect(mockStartNewTurn).toHaveBeenCalledTimes(1);
|
||||
expect(mockAddUsage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call startNewTurn for a slash command', async () => {
|
||||
mockHandleSlashCommand.mockReturnValue(true);
|
||||
|
||||
const { result } = renderTestHook();
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('/stats');
|
||||
});
|
||||
|
||||
expect(mockStartNewTurn).not.toHaveBeenCalled();
|
||||
expect(mockSendMessageStream).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -432,10 +432,6 @@ export const useGeminiStream = (
|
||||
const userMessageTimestamp = Date.now();
|
||||
setShowHelp(false);
|
||||
|
||||
if (!options?.isContinuation) {
|
||||
startNewTurn();
|
||||
}
|
||||
|
||||
abortControllerRef.current = new AbortController();
|
||||
const abortSignal = abortControllerRef.current.signal;
|
||||
|
||||
@@ -449,6 +445,10 @@ export const useGeminiStream = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options?.isContinuation) {
|
||||
startNewTurn();
|
||||
}
|
||||
|
||||
if (!geminiClient) {
|
||||
const errorMsg = 'Gemini client is not available.';
|
||||
setInitError(errorMsg);
|
||||
|
||||
Reference in New Issue
Block a user