Add token usage tracking and UI refinements

This commit is contained in:
temp
2026-08-24 00:05:05 +08:00
parent a34cab431a
commit e218316c55
23 changed files with 1285 additions and 93 deletions
+44 -2
View File
@@ -66,10 +66,12 @@ async function refreshDashboard() {
setDashboardOverviewPlaceholder('…');
setEl('dashboard-kpi-tools-calls', '…');
setEl('dashboard-kpi-success-rate', '…');
setEl('dashboard-kpi-token-usage', '…');
setKpiSubText('dashboard-kpi-tasks-sub-text', '…');
setKpiSubText('dashboard-kpi-vuln-sub-text', '…');
setKpiSubText('dashboard-kpi-tools-sub-text', '…');
setKpiSubText('dashboard-kpi-rate-sub-text', '…');
setKpiSubText('dashboard-kpi-token-sub-text', '…');
hideEl('dashboard-kpi-vuln-critical-badge');
hideEl('dashboard-alert-banner');
setRecentVulnsLoading();
@@ -127,7 +129,7 @@ async function refreshDashboard() {
hitlPendingRes, notificationsRes, externalMcpStatsRes,
webshellRes,
c2ListenersRes, c2SessionsRes, c2TasksRes,
projectSummaryRes, severityFilteredStatsRes
projectSummaryRes, severityFilteredStatsRes, tokenUsageRes
] = await Promise.all([
fetchJson('/api/agent-loop/tasks'),
fetchJson('/api/vulnerabilities/stats'),
@@ -159,7 +161,8 @@ async function refreshDashboard() {
fetchJson(dashboardProjectScopedUrl('/api/c2/sessions?limit=500')),
fetchJson(dashboardProjectScopedUrl('/api/c2/tasks?page=1&page_size=1')),
fetchJson('/api/projects/dashboard-summary?fact_limit=10'),
selectedSeverityStatus ? fetchJson('/api/vulnerabilities/stats?status=' + encodeURIComponent(selectedSeverityStatus)) : Promise.resolve(null)
selectedSeverityStatus ? fetchJson('/api/vulnerabilities/stats?status=' + encodeURIComponent(selectedSeverityStatus)) : Promise.resolve(null),
fetchJson(dashboardProjectScopedUrl('/api/usage/tokens?days=7&limit=5'))
]);
// 如果在 await 期间 controller 已被 abort,说明又有新刷新启动了,丢弃本次结果
@@ -330,6 +333,8 @@ async function refreshDashboard() {
renderDashboardToolsBar(null);
}
renderDashboardTokenUsage(tokenUsageRes);
// 「能力总览 → MCP 工具」用配置总数(包含未被调用过的工具);专项接口失败时回落到 monitor 的 names.length
if (toolsConfigRes && typeof toolsConfigRes.total === 'number') {
setEl('dashboard-resource-tools', formatNumber(toolsConfigRes.total));
@@ -435,10 +440,12 @@ async function refreshDashboard() {
setDashboardOverviewPlaceholder('-');
setEl('dashboard-kpi-success-rate', '-');
setEl('dashboard-kpi-tools-calls', '-');
setEl('dashboard-kpi-token-usage', '-');
setKpiSubText('dashboard-kpi-tasks-sub-text', '-');
setKpiSubText('dashboard-kpi-vuln-sub-text', '-');
setKpiSubText('dashboard-kpi-tools-sub-text', '-');
setKpiSubText('dashboard-kpi-rate-sub-text', '-');
setKpiSubText('dashboard-kpi-token-sub-text', '-');
['tools', 'skills', 'knowledge', 'roles', 'agents'].forEach(function (k) {
setEl('dashboard-resource-' + k, '-');
});
@@ -700,6 +707,41 @@ function setKpiRateBadge(id, rate, failedCount) {
}
}
function renderDashboardTokenUsage(res) {
const summary = res && res.summary ? res.summary : null;
if (!summary) {
setEl('dashboard-kpi-token-usage', '-');
setKpiSubText('dashboard-kpi-token-sub-text', '-');
return;
}
const total = Number(summary.totalTokens || 0);
const calls = Number(summary.modelCalls || 0);
const today = res && res.today ? Number(res.today.totalTokens || 0) : 0;
if (!Number.isFinite(total) || total <= 0) {
setEl('dashboard-kpi-token-usage', '0');
setKpiSubText('dashboard-kpi-token-sub-text', dt('dashboard.noTokenUsageYet', null, '暂无用量'));
return;
}
setEl('dashboard-kpi-token-usage', formatTokenUsageCompact(total));
setKpiSubText('dashboard-kpi-token-sub-text',
dt('dashboard.tokenUsageSub', {
today: formatTokenUsageCompact(today),
calls: Number.isFinite(calls) ? calls : 0
}, '近 7 天 ' + (Number.isFinite(calls) ? calls : 0) + ' 次调用 · 今日 ' + formatTokenUsageCompact(today)));
}
function formatTokenUsageCompact(num) {
const n = Number(num || 0);
if (!Number.isFinite(n) || n <= 0) return '0';
if (n >= 1000000) {
return (n / 1000000).toFixed(n >= 10000000 ? 0 : 1).replace(/\.0$/, '') + 'M';
}
if (n >= 1000) {
return (n / 1000).toFixed(n >= 10000 ? 0 : 1).replace(/\.0$/, '') + 'K';
}
return String(Math.trunc(n));
}
// sessionStorage:告警条「×」忽略记录 + 最近一次**实际展示过**的 reason 片段(不含 level),
// 用于在「问题从多变少」(如审完 HITL 后只剩严重漏洞)时,避免误用更早对「仅子集」的忽略。
var DASH_SESSION_ALERT_DISMISSED = 'dashboard.dismissedAlert';