feat: Token 仪表盘支持全局统计,跨会话累计 Token 分析

This commit is contained in:
Metona Dev
2026-04-24 12:53:50 +08:00
parent 08193c5d8e
commit 5fafaa691f
7 changed files with 467 additions and 29 deletions
+64
View File
@@ -580,6 +580,70 @@ export function incrementSkillUsage(id: string, success: boolean, durationMs: nu
persist();
}
// ─── Token 全局统计 ───
export interface SessionTokenStat {
session_id: string;
title: string;
model: string;
created_at: number;
total_input: number;
total_output: number;
total_duration: number;
round_count: number;
}
export interface GlobalTokenTotals {
total_input: number;
total_output: number;
total_duration: number;
total_rounds: number;
session_count: number;
}
export interface AllSessionsTokenStats {
sessions: SessionTokenStat[];
totals: GlobalTokenTotals;
}
export function getAllSessionsTokenStats(): AllSessionsTokenStats {
const d = getDb();
const sessionStats = queryAll(d, `
SELECT
s.id as session_id,
s.title,
s.model,
s.created_at,
COALESCE(SUM(m.prompt_eval_count), 0) as total_input,
COALESCE(SUM(m.eval_count), 0) as total_output,
COALESCE(SUM(m.total_duration), 0) as total_duration,
COUNT(CASE WHEN m.role = 'assistant' AND (m.eval_count > 0 OR m.prompt_eval_count > 0) THEN 1 END) as round_count
FROM sessions s
LEFT JOIN messages m ON s.id = m.session_id
GROUP BY s.id
ORDER BY s.updated_at DESC
`);
let totalInput = 0, totalOutput = 0, totalDuration = 0, totalRounds = 0;
for (const s of sessionStats) {
totalInput += (s.total_input as number) || 0;
totalOutput += (s.total_output as number) || 0;
totalDuration += (s.total_duration as number) || 0;
totalRounds += (s.round_count as number) || 0;
}
return {
sessions: sessionStats as unknown as SessionTokenStat[],
totals: {
total_input: totalInput,
total_output: totalOutput,
total_duration: totalDuration,
total_rounds: totalRounds,
session_count: sessionStats.length,
}
};
}
// ─── Export/Import ───
export interface ExportData {
+8 -1
View File
@@ -15,7 +15,8 @@ import {
saveToolCall, getToolCallsBySession,
saveTrace, getTracesBySession,
saveSkill, getAllSkills, deleteSkill, clearAllSkills, searchSkills, incrementSkillUsage,
exportAllSessions, importSessions
exportAllSessions, importSessions,
getAllSessionsTokenStats
} from './db/sqlite.js';
import type { ExportData } from './db/sqlite.js';
@@ -421,6 +422,12 @@ export async function setupIPC(): Promise<void> {
catch (err) { return { success: false, error: (err as Error).message }; }
});
// ── Token 全局统计 ──
ipcMain.handle('db:getAllTokenStats', () => {
try { return getAllSessionsTokenStats(); }
catch (err) { sendLog('error', '获取全局 Token 统计失败', (err as Error).message); return null; }
});
// ── MCP IPC ──
ipcMain.handle('mcp:startServer', async (_, config) => {
return startServer(config);
+1
View File
@@ -73,6 +73,7 @@ contextBridge.exposeInMainWorld('metonaDesktop', {
clearAllSkills: () => ipcRenderer.invoke('db:clearAllSkills'),
searchSkills: (query: string, limit?: number) => ipcRenderer.invoke('db:searchSkills', query, limit),
incrementSkillUsage: (id: string, success: boolean, durationMs: number) => ipcRenderer.invoke('db:incrementSkillUsage', id, success, durationMs),
getAllTokenStats: () => ipcRenderer.invoke('db:getAllTokenStats'),
},
workspace: {
getDir: () => ipcRenderer.invoke('workspace:getDir'),