qwen-code 统计仪表盘重构实战:Activity / Efficiency 双 Tab 的设计、数据层扩展与源码实现
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
导读
本文以 qwen-code 开源仓库中的 Stats Dashboard Redesign 设计规格 为核心骨架,系统讲解 TUI 中/stats统计仪表盘的重构方案:如何把原有的 Overview / Models 双 Tab 重构为 Session / Activity / Efficiency 三 Tab,如何引入时间范围选择与环比 Delta 指标,以及如何在不破坏旧数据的前提下扩展usage_record.jsonl的数据模型。读完本文,你将掌握这套仪表盘从规格设计、数据结构演进到键盘交互与热力图渲染的完整实现路径,并能直接对照仓库源码进行二次开发或移植。
一、重构目标与总体设计
规格文档(2025-06-03-stats-dashboard-redesign.md)定义了本次重构的核心目标:重新设计/statsTUI 仪表盘,改进布局层级,新增效率指标、工具使用明细与趋势对比能力,同时保持 Session 标签页原样不动。
重构后的标签页结构如下:
Tab 1: Session (不变——实时当前会话指标) Tab 2: Activity (基于时间的趋势与使用模式) Tab 3: Efficiency (性能指标与工具分析)在仓库源码中,这一结构已经落地。查看 StatsDialog.tsx 可以看到三个标签页组件被分别拆分为独立的SessionTab、ActivityTab、EfficiencyTab,由StatsDialog统一调度;TAB_DEFS(来自stats-helpers.js)定义了 Tab 枚举与循环切换顺序。StatsDialog内部还通过handleTabChange实现了Tab/Shift+Tab的循环切换((idx + direction + TAB_DEFS.length) % TAB_DEFS.length),保证焦点不会在边界卡死。
值得注意的是,StatsDialog还支持嵌入模式(availableHeight属性):当它被嵌入到设置对话框的 Stats 页时,通过EFFICIENCY_CHROME_ROWS = 24、MAX_EMBEDDED_TOOL_ROWS = 5、CODE_IMPACT_ROWS = 1等常量精确预算高度,防止模型表溢出宿主视图——这是规格文档之外、源码中体现的工程细节。
二、时间范围选择器与环比 Delta 计算
2.1 范围切换机制
所有 Activity 与 Efficiency 标签页的数据都受同一个时间范围约束,通过按r键循环切换:
Today → Week → Month → All在 StatsDialog.tsx 中,r键处理逻辑为setRangeIndex((i) => (i + 1) % RANGE_CYCLE.length),即对RANGE_CYCLE数组取模循环;range状态驱动loadStatsData(range, liveRecord)重新加载数据(useEffect依赖[range, stats.sessionId],即只在范围或会话变化时重载,而不是每次指标 tick 都刷新)。对话框底部的RangeIndicator会高亮当前选中的范围(bold+underline+ accent 色),并用·分隔各选项。
2.2 Delta 环比规则
每个 KPI 卡片都会显示一个趋势箭头,将当前范围与上一个等价范围对比:
| 范围 | 对比基准 |
|---|---|
| Today | 今天 vs 昨天 |
| Week | 最近 7 天 vs 前 7 天 |
| Month | 最近 30 天 vs 前 30 天 |
| All | 不显示 Delta |
显示规则:正向变化用绿色▲ +12%,负向变化用红色▼ -3%。唯独延迟(Latency)例外——越低越好,因此颜色取反。
实现路径:从usage_record.jsonl加载两个时间片,分别聚合后计算百分比变化。规格文档建议的 delta 字段在 statsDataService.ts 的StatsData接口中已完整落地:delta对象包含sessions、duration、tokens、cacheRate、toolSuccess、avgLatency六个number | null字段——null即表示该范围下无上一周期可比数据(如all范围)。
三、Activity 标签页:时间维度下的使用趋势
Activity 页自上而下分为四块:KPI 行、GitHub 风格热力图、Token 趋势折线图、项目排行榜。
3.1 KPI 行
三个横向排列的指标卡,每个都带数值 + Delta 箭头:
| 指标 | 数据来源 | 示例 |
|---|---|---|
| Sessions | report.sessionCount | 42 ▲+8 |
| Duration | report.totalDurationMs | 18h 32m ▲+2h |
| Tokens | 求和report.models[*].totalTokens | 2.4m ▲+12% |
其中 Duration 的展示会自动格式化为人性化时长(小时/分钟),Tokens 则缩写成2.4m这样的紧凑形式。
3.2 GitHub 风格热力图
- 全宽渲染,网格布局仿 GitHub contribution graph;
- 颜色强度 = 每日总 Token 消耗(注意:不是会话数);
- 今天的格子有特殊边框或标记字符(如用
[ ]而非 ,或更亮的描边色); - 右侧对齐元信息:
streak: 12d │ best: 23d; - 图例行:
Less ░░░░░ More; - 列标签显示月份缩写 + 日期数字;
- 行标签为 Mon / Wed / Fri(紧凑 3 行模式);
- 展示周数由终端宽度自适应:
min(26, max(8, floor((bodyWidth - 4) / 2)))。
这段公式在 StatsDialog.tsx 有对应实现:safeWidth = Math.max(72, width ?? 100),bodyWidth = safeWidth - 6,宽度下限被钳制在 72 列,保证窄终端下布局不崩。
源码印证:statsDataService.ts 中的buildHeatmap以YYYY-MM-DD为 key,对每条记录内所有模型的totalTokens(缺失时回退为inputTokens + outputTokens + thoughtsTokens)求和,产出Record<string, number>——确认热力图数值语义就是“每日总 Token”。
Streak(连续使用天数)的计算在 statsDataService.ts 的calculateStreaks中实现:将日期去重排序后逐日比对,diff === 1则当前 streak 累加,diff > 1则重置;若最后一条数据距今超过 1 天,当前 streak 归零,从而同时得出currentStreak与longestStreak。
3.3 Token 趋势折线图
- 使用仓库既有的
buildLineChartData生成 Braille 子像素折线图; - 单一序列:每日总 Token;
- 高度 6 行;
- 当范围为
all时,可用←/→按月翻页导航,月份标签形如← Jun 2025 →。
月导航的逻辑在 StatsDialog.tsx:←/h把chartMonthOffset递增(最多到months.length - 1,months 由data.tokensPerDay中所有date.slice(0, 7)去重得出),→/l递减(最小为 0),并且仅当activeTab === 'activity' && range === 'all'时生效——与规格完全一致。
3.4 项目排行榜
展示 Top 5 项目,数据源为report.projects按totalTokens降序排列:
Project Sessions Tokens Duration qwen-code 28 1.8m 12h web-app 10 420k 4h infra 4 180k 2h四、Efficiency 标签页:性能与工具效率分析
Efficiency 页自上而下分为四块:性能卡片行、工具排行榜、模型对比表、代码影响。
4.1 性能卡片行
三个盒式指标卡:
| 指标 | 计算公式 | 数据来源 |
|---|---|---|
| Cache Hit Rate | cachedTokens / inputTokens * 100 | report.models[*].cachedTokens/inputTokens |
| Tool Success Rate | totalSuccess / totalCalls * 100 | report.tools.totalSuccess/totalCalls |
| Avg Latency | totalLatencyMs / totalRequests | 持久化记录中的totalLatencyMs,或按模型数据计算 |
每个卡片展示:标签、加粗数值/百分比、Delta 箭头。
关于 Avg Latency 的关键设计决策:重构前UsageSummaryRecord并不持久化延迟数据,规格文档给出了两个候选方案——
- 仅从实时
SessionMetrics计算当前会话延迟(历史数据一律显示—); - 为持久化记录新增
totalLatencyMs字段(旧记录迁移后显示—)。
最终决策:方案 2——扩展UsageSummaryRecord增加可选的totalLatencyMs字段,旧记录因缺少该字段,延迟 Delta 显示—。
源码印证:这一决策已完全落地。usageHistoryService.ts 中UsageSummaryRecord接口包含可选字段totalLatencyMs?: number,且tools.byName中每个工具的聚合也带totalDurationMs?: number(见第 61-64 行);AggregatedReport则进一步把延迟与工具耗时升级为必填:顶层totalLatencyMs: number、每个模型的totalLatencyMs: number、topTools数组元素包含totalDurationMs: number(见第 86-117 行)——说明数据层在聚合阶段已统一补齐默认值,UI 层无需再判空。规格文档中“当前topTools只有 count/success/fail,需要为聚合增加totalDurationMs”的备注,在源码中同样已经解决。
4.2 工具排行榜
按调用次数展示 Top 8 工具:
Tool Calls Time Success edit 847 42.3s ██████████ 98% read 612 8.1s ██████████ 99% bash 431 67.8s █████████░ 89% glob 298 2.4s ██████████ 99% grep 256 3.1s █████████░ 97% write 189 12.5s ██████████ 96% agent 45 89.2s ████████░░ 82%- 成功率用 10 字符条形图可视化:实心
█+ 空心░; - 颜色规则:≥95% 绿色,≥80% 橙色,<80% 红色;
- 数据源:
report.tools.topTools(聚合时补充了 duration)。
该表格的数据结构在 statsDataService.ts 的StatsData.toolLeaderboard中定义为{ name, count, totalDurationMs, successRate },与规格文档的数据契约完全一致。
4.3 模型对比表
Model Reqs In/Out Cache Latency ● qwen-max 186 1.2m/340k 91% 2.1s ● qwen-plus 124 890k/210k 84% 1.2s ● qwen-turbo 67 310k/89k 72% 0.8s- 按
totalTokens降序排列; - 系列色圆点标识(
●); - Cache 列颜色规则:≥85% 绿色,≥70% 橙色,<70% 红色;
- 数据源:
report.models。
Efficiency 页的模型表在嵌入模式下还会受到行数上限约束(maxModelRows),高度预算由 StatsDialog.tsx 动态计算:从availableHeight中减去固定的 chrome 行数(24)、工具排行榜占用的行数(含截断时的+N more提示)以及 Code Impact 占用的 1 行,结果下限钳制为 3 行。
4.4 代码影响
单行汇总,数据源为report.files.linesAdded/report.files.linesRemoved:
Code +2,847 lines / -1,203 lines net: +1,644五、键盘控制总览
| 按键 | 动作 |
|---|---|
Tab/Shift+Tab | 切换标签页 |
r | 循环切换范围:today → week → month → all |
←/h | 上一月(图表导航,仅 range=all) |
→/l | 下一月(图表导航,仅 range=all) |
Esc | 关闭对话框 |
这些按键全部在 StatsDialog.tsx 的useKeypress中注册,并通过isFocused属性控制是否消费键盘事件(嵌入模式下避免与宿主视图抢焦点)。对话框底部还会根据当前 Tab 与范围动态显示快捷键提示,例如 Activity 页且 range=all 时提示tab · r dates · ←→ month · esc。
六、数据层变更:向后兼容的 schema 扩展
6.1 UsageSummaryRecord v1 扩展
在既有 schema 上仅新增可选字段,保证旧记录可继续读取:
interface UsageSummaryRecord { // ... existing fields ... totalLatencyMs?: number; // NEW: sum of all API response latencies tools: { // ... existing fields ... byName: Record<string, { count: number; success: number; fail: number; totalDurationMs?: number; // NEW: sum of tool execution time }>; }; }对照 usageHistoryService.ts 的实际实现,totalLatencyMs与byName[].totalDurationMs均已存在,且UsageSummaryRecord还带version: 1版本标记与可选的skills字段——仓库对“旧记录缺字段”的处理模式是统一的:所有新增字段一律 optional,UI 侧按缺省值兜底。
另外,usageHistoryService.ts顶部注释(第 19-35 行)揭示了一个重要的工程权衡:LIVE_REBUILD_WINDOW_DAYS = 35——把未持久化的 daemon / Web Shell / 进行中会话合并进历史时,只回放最近 35 天的 transcript(覆盖 month=30 天范围加余量),而已持久化的usage_record.jsonl记录不受此窗口限制,始终全量并入,从而热力图保留完整历史。这是规格文档未涉及、但直接决定“热力图能看到多久历史”的实现细节。
6.2 StatsData 扩展
interface StatsData { // ... existing fields ... delta?: { sessions: number | null; // percentage change duration: number | null; tokens: number | null; cacheRate: number | null; toolSuccess: number | null; avgLatency: number | null; }; efficiency: { cacheHitRate: number; toolSuccessRate: number; avgLatencyMs: number | null; }; toolLeaderboard: Array<{ name: string; count: number; totalDurationMs: number; successRate: number; }>; }对照 statsDataService.ts 的实现,StatsData接口包含report(AggregatedReport)、heatmap、currentStreak、longestStreak、tokensPerDay、delta、efficiency、toolLeaderboard全部字段,其中delta已从规格的“可选”升级为结构化的| null联合类型,语义更明确。
6.3 热力图数据语义变更与强度标定
变更前:buildHeatmapData接收Record<string, number>,value = 当日会话数;变更后:value = 当日总 Token,0-4 级强度映射需要重新标定:
- 0:无使用
- 1:< 10k tokens
- 2:10k - 50k tokens
- 3:50k - 200k tokens
- 4:> 200k tokens
规格同时强调:阈值应基于数据分布动态计算(百分位法),而非硬编码,以适配不同的使用模式。
源码印证:asciiCharts.ts 中存在intensityLevel(count, thresholds)函数,说明强度分级已抽象为“给定阈值数组映射到 0-4 级”的通用逻辑,支持按数据分布注入阈值。
6.4 今日高亮(Today Highlight)
buildHeatmapData需要把今天的格子打上特殊标记。源码实现为 asciiCharts.ts:通过todayKey = dayKey(new Date())生成今日 key,与当前格子比对得到isToday标记,渲染时用更明亮的描边字符(如[▓]而非▓▓)突出显示——与规格文档的字符方案完全对应。
七、国际化:新增 i18n 键
所有用户可见字符串统一包裹在t()中,新增键如下:
stats.activity = "Activity" stats.efficiency = "Efficiency" stats.today = "Today" stats.sessions = "Sessions" stats.duration = "Duration" stats.tokens = "Tokens" stats.cacheHitRate = "Cache Hit Rate" stats.toolSuccessRate = "Tool Success" stats.avgLatency = "Avg Latency" stats.toolLeaderboard = "Tool Leaderboard" stats.calls = "Calls" stats.time = "Time" stats.success = "Success" stats.models = "Models" stats.reqs = "Reqs" stats.cache = "Cache" stats.latency = "Latency" stats.codeImpact = "Code Impact" stats.net = "net" stats.streak = "streak" stats.best = "best" stats.tokenTrend = "Token Trend" stats.projects = "Projects" stats.project = "Project"在 UI 层,StatsDialog通过t('(Tab to switch)')、t('Loading stats...')、t('Failed to load stats. Press r to retry.')等调用印证了该模式,且加载失败时提示“按 r 重试”恰好复用了范围切换键,交互自洽。
八、改动文件清单与源码对应
规格文档列出的改动清单,在仓库中的落点如下:
| 文件(规格) | 实际代码位置 | 变更内容 |
|---|---|---|
StatsDialog.tsx | packages/cli/src/ui/components/StatsDialog.tsx | 以SessionTab/ActivityTab/EfficiencyTab替换原 Overview / Models,承载 Tab 切换、范围循环、月导航、嵌入模式高度预算 |
usageHistoryService.ts | packages/core/src/services/usageHistoryService.ts | UsageSummaryRecord增加totalLatencyMs与byName[].totalDurationMs;AggregatedReport增加聚合后的延迟与工具耗时 |
statsDataService.ts | packages/cli/src/ui/utils/statsDataService.ts | StatsData增加delta/efficiency/toolLeaderboard;buildHeatmap改为按每日总 Token 统计;新增 streak 计算 |
asciiCharts.ts | packages/cli/src/ui/utils/asciiCharts.ts | intensityLevel强度映射、isToday今日高亮标记、Braille 折线图复用 |
uiTelemetry.ts | packages/core/src/telemetry/uiTelemetry.ts | 确保延迟数据进入持久化路径(被usageHistoryService.ts引用) |
三个标签页组件的拆分实现分别位于 StatsSessionTab.tsx、StatsActivityTab.tsx、StatsEfficiencyTab.tsx。
九、明确不在本次范围内
为避免范围蔓延,规格明确排除了以下能力:
- 成本估算——依赖用户自配的价格体系,可后续添加;
- 单文件级变更追踪——当前数据模型不支持;
- 上下文窗口用量 / 压缩指标——当前未跟踪;
- 单个会话的下钻交互——本次不实现。
这些排除项也为后续演进划清了边界:任何想在这四个方向扩展的开发者,都需要先补齐对应的数据采集层。
总结
qwen-code 的/stats仪表盘重构是一套典型的“数据模型先行、UI 分层落地”的演进案例:规格先敲定UsageSummaryRecord的可选字段扩展与StatsData的契约,再通过r键驱动的范围循环支撑 Delta 环比,最后在 TUI 层以三 Tab 结构(Session / Activity / Efficiency)承载热力图、Braille 趋势图、工具排行榜与模型对比表。对照仓库源码可以确认,规格中的每一项决策——包括延迟持久化的方案 2、热力图按 Token 而非会话计数、今日格子高亮、强度阈值百分位化——均已实现,且工程上额外处理了窄终端钳制、嵌入模式高度预算与未持久化会话的回放窗口等边界问题。对于希望深度定制统计面板或移植到其他 TUI 应用的开发者,这份规格与其源码实现构成了完整的参考闭环。
【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考