为"数据看板"的"消耗分布"添加Token和金额显示模式切换功能#3387
Conversation
- 在模型消耗分布标签页添加 $/Token 切换按钮 - 在数据看板设置中新增默认显示模式配置项 - Token 模式使用数据库 token_used 字段
- 修复 SettingsDataDashboard localStorage 写入过时值的问题 - 增强 ChartsPanel 切换按钮的无障碍属性 (ARIA) - 修复 rawQuota/rawTokenUsed 数据赋值逻辑 - 使用严格相等 (===) 替代松散相等 (==) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WalkthroughAdds a dashboard display-mode feature (QUOTA vs TOKENS): backend option registration, frontend state and persistence, chart data/spec adjustments for token vs quota rendering, settings UI and i18n entries, and small data shaping to include token usage values. Changes
Sequence DiagramsequenceDiagram
actor User
participant ChartsPanel as ChartsPanel
participant Dashboard as Dashboard Component
participant useDashboardCharts as Chart Hook
participant localStorage
User->>ChartsPanel: toggle display mode (click/keyboard)
ChartsPanel->>Dashboard: onDisplayModeChange('TOKENS' / 'QUOTA')
Dashboard->>localStorage: write data_export_default_display_mode
Dashboard->>Dashboard: set displayMode state
Dashboard->>useDashboardCharts: re-render / call updateChartData(displayMode)
useDashboardCharts->>useDashboardCharts: recalc aggregates (token vs quota), rebuild tooltipConfig
useDashboardCharts->>ChartsPanel: provide updated chart spec/data
ChartsPanel->>User: render chart with selected metric
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment Tip You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.Change the |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
web/src/hooks/dashboard/useDashboardCharts.jsx (1)
319-361: Consider extracting tooltip config generation to avoid duplication.The tooltip configuration logic is duplicated in two places:
- Lines 319-361: Inside
updateChartDatacallback- Lines 449-493: Inside the
useEffectfor displayMode changesBoth generate identical tooltip structures. If the tooltip format needs to change, both locations must be updated in sync.
Suggested approach: Extract to a shared function
+const createTooltipConfig = (displayMode, t) => { + const renderValue = displayMode === 'TOKENS' ? renderNumber : (v) => renderQuota(v, 4); + const valueField = displayMode === 'TOKENS' ? 'rawTokenUsed' : 'rawQuota'; + + return { + mark: { + content: [ + { + key: (datum) => datum['Model'], + value: (datum) => renderValue(datum[valueField] || 0), + }, + ], + }, + dimension: { + content: [ + { + key: (datum) => datum['Model'], + value: (datum) => datum[valueField] || 0, + }, + ], + updateContent: (array) => { + array.sort((a, b) => b.value - a.value); + let sum = 0; + for (let i = 0; i < array.length; i++) { + if (array[i].key === '其他') continue; + let value = parseFloat(array[i].value); + if (isNaN(value)) value = 0; + if (array[i].datum && array[i].datum.TimeSum) { + sum = array[i].datum.TimeSum; + } + array[i].value = renderValue(value); + } + array.unshift({ key: t('总计'), value: renderValue(sum) }); + return array; + }, + }, + }; +};Then use
createTooltipConfig(displayMode, t)in both places.Also applies to: 449-500
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/hooks/dashboard/useDashboardCharts.jsx` around lines 319 - 361, Extract the duplicated tooltip configuration into a shared helper function (e.g. createTooltipConfig(displayMode, t)) that encapsulates the renderValue selection (using renderNumber or renderQuota), valueField determination ('rawTokenUsed' vs 'rawQuota'), and the full tooltipConfig object including the mark/dimension content and updateContent logic; then replace the inlined tooltipConfig blocks inside updateChartData and the useEffect that watches displayMode with calls to createTooltipConfig(displayMode, t) so both places reuse the same implementation and preserve updateContent behavior and use of renderValue/renderNumber/renderQuota.web/src/components/dashboard/index.jsx (1)
153-158: MissingdashboardCharts.updateChartDatain useEffect dependency array.The effect uses
dashboardCharts.updateChartDatabut it's not listed in the dependency array. While this may work becauseupdateChartDatais a memoized callback, the ESLint exhaustive-deps rule would flag this. SinceupdateChartDataalready hasdisplayModein its own dependency array, calling it whendisplayModechanges will use the updated function.However, the current code works because
displayModechange triggers this effect, and by that timeupdateChartDatahas been recreated with the newdisplayMode. Consider adding a comment to clarify this intentional omission or addingdashboardCharts.updateChartDatato dependencies if it's truly stable.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/index.jsx` around lines 153 - 158, The effect uses dashboardCharts.updateChartData but doesn't include it in the dependency array, which ESLint exhaustive-deps will flag; either add dashboardCharts.updateChartData to the dependency array of the useEffect or, if you intentionally rely on displayMode recreating that callback, leave deps as [displayMode, dashboardData.quotaData] and add a concise inline comment above this useEffect referencing why updateChartData is omitted (e.g., "updateChartData is recreated when displayMode changes; intentionally omitted to avoid extra runs") so linters/readers understand the choice; locate the useEffect that references dashboardCharts.updateChartData, displayMode, and dashboardData.quotaData to apply this change.model/option.go (1)
464-467: Validate and normalizeDataExportDefaultDisplayModebefore storing.Line 464 currently accepts arbitrary values. Constraining this to known enums avoids persisting invalid modes that can break frontend initialization.
♻️ Proposed hardening
case "DataExportDefaultDisplayMode": - // DataExportDefaultDisplayMode 配置项,用于设置数据看板默认显示模式(金额/TOKENS) - // 这个配置存储在数据库中,由前端读取并应用到 localStorage + normalized := strings.ToUpper(strings.TrimSpace(value)) + if normalized != "QUOTA" && normalized != "TOKEN" { + normalized = "QUOTA" + } + common.OptionMap[key] = normalized🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@model/option.go` around lines 464 - 467, The DataExportDefaultDisplayMode branch currently accepts arbitrary values; update the switch handling for "DataExportDefaultDisplayMode" in model/option.go to validate and normalize input against the known enum set (e.g., "amount"/"tokens" or your canonical enum strings), map aliases/case variants to the canonical value, and only persist the option when it matches a valid enum; if invalid, set a safe default or return an error. Ensure normalization occurs before any storage/DB write and that the stored value uses the canonical representation so the frontend can reliably initialize.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/src/components/dashboard/index.jsx`:
- Around line 61-64: The initialization of displayMode uses a misleading
fallback that checks localStorage.getItem('quota_display_type') === 'TOKENS',
but 'quota_display_type' stores currency codes so that condition never matches;
update the useState initializer for displayMode (and related setter
setDisplayMode) to remove the dead fallback and simply use the saved value or a
clear default (e.g., return saved || 'QUOTA'), and remove any reference to
'quota_display_type' in this initializer so the logic is correct and no
misleading dead code remains.
In `@web/src/helpers/dashboard.jsx`:
- Around line 367-369: The aggregation code for existing (existing.quota,
existing.tokenUsed, existing.count) assumes item.token_used is always present
and numeric; guard against missing/alternate fields by normalizing token values
before adding: coerce item.token_used (or alternate keys like item.tokens_used
or item.tokenUsage) to a Number with a fallback of 0, e.g. const token =
Number(item.token_used ?? item.tokens_used ?? item.tokenUsage ?? 0) || 0, then
add token to existing.tokenUsed and similarly ensure item.quota and item.count
are coerced to numeric defaults before incrementing existing.quota and
existing.count so NaN cannot propagate.
In `@web/src/i18n/locales/en.json`:
- Around line 1518-1525: Remove the duplicated translation key "金额" (the
duplicate mapping to "Amount") from the JSON so there is only a single "金额"
entry; locate the duplicate "金额": "Amount" mapping near the block containing
"显示金额", "显示 Token", and remove that extra key-value pair, leaving the remaining
"金额" mapping intact and keeping the JSON valid (no trailing commas).
---
Nitpick comments:
In `@model/option.go`:
- Around line 464-467: The DataExportDefaultDisplayMode branch currently accepts
arbitrary values; update the switch handling for "DataExportDefaultDisplayMode"
in model/option.go to validate and normalize input against the known enum set
(e.g., "amount"/"tokens" or your canonical enum strings), map aliases/case
variants to the canonical value, and only persist the option when it matches a
valid enum; if invalid, set a safe default or return an error. Ensure
normalization occurs before any storage/DB write and that the stored value uses
the canonical representation so the frontend can reliably initialize.
In `@web/src/components/dashboard/index.jsx`:
- Around line 153-158: The effect uses dashboardCharts.updateChartData but
doesn't include it in the dependency array, which ESLint exhaustive-deps will
flag; either add dashboardCharts.updateChartData to the dependency array of the
useEffect or, if you intentionally rely on displayMode recreating that callback,
leave deps as [displayMode, dashboardData.quotaData] and add a concise inline
comment above this useEffect referencing why updateChartData is omitted (e.g.,
"updateChartData is recreated when displayMode changes; intentionally omitted to
avoid extra runs") so linters/readers understand the choice; locate the
useEffect that references dashboardCharts.updateChartData, displayMode, and
dashboardData.quotaData to apply this change.
In `@web/src/hooks/dashboard/useDashboardCharts.jsx`:
- Around line 319-361: Extract the duplicated tooltip configuration into a
shared helper function (e.g. createTooltipConfig(displayMode, t)) that
encapsulates the renderValue selection (using renderNumber or renderQuota),
valueField determination ('rawTokenUsed' vs 'rawQuota'), and the full
tooltipConfig object including the mark/dimension content and updateContent
logic; then replace the inlined tooltipConfig blocks inside updateChartData and
the useEffect that watches displayMode with calls to
createTooltipConfig(displayMode, t) so both places reuse the same implementation
and preserve updateContent behavior and use of
renderValue/renderNumber/renderQuota.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ccdf86da-f289-4522-bbbf-799ee2c0e853
📒 Files selected for processing (10)
model/option.goweb/src/components/dashboard/ChartsPanel.jsxweb/src/components/dashboard/index.jsxweb/src/components/settings/DashboardSetting.jsxweb/src/helpers/dashboard.jsxweb/src/hooks/dashboard/useDashboardCharts.jsxweb/src/hooks/dashboard/useDashboardData.jsweb/src/i18n/locales/en.jsonweb/src/i18n/locales/zh-CN.jsonweb/src/pages/Setting/Dashboard/SettingsDataDashboard.jsx
- 移除 dashboard/index.jsx 中误导性的 quota_display_type 检查 - 添加 dashboard.jsx 中 token 值的类型转换防护 - 删除 en.json 中重复的 "金额" 翻译键 - 添加 model/option.go 中 DataExportDefaultDisplayMode 值验证 - 添加 useEffect 依赖注释说明 - 提取 useDashboardCharts.jsx 中重复的 tooltip 配置为共享函数 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
web/src/components/dashboard/index.jsx (1)
153-159: Consider addingdashboardCharts.updateChartDatato the dependency array.While the comment explains why it's omitted, ESLint's
react-hooks/exhaustive-depsrule would flag this. SinceupdateChartDatais memoized withuseCallback(includingdisplayModein its dependencies), adding it to this effect's dependencies would be safe and more explicit:♻️ Suggested improvement
// 切换显示模式时重新计算图表 - // updateChartData 会在 displayMode 变化时自动重新创建,所以这里省略依赖 useEffect(() => { if (dashboardData.quotaData && dashboardData.quotaData.length > 0) { dashboardCharts.updateChartData(dashboardData.quotaData); } - }, [displayMode, dashboardData.quotaData]); + }, [displayMode, dashboardData.quotaData, dashboardCharts.updateChartData]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/index.jsx` around lines 153 - 159, The effect omits dashboardCharts.updateChartData from its dependency array which will trigger ESLint's react-hooks/exhaustive-deps; update the useEffect dependencies to include dashboardCharts.updateChartData (i.e., use [displayMode, dashboardData.quotaData, dashboardCharts.updateChartData]) and ensure the updateChartData implementation is memoized with useCallback (including displayMode in its deps) so adding it is safe and won't cause extra re-renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@web/src/components/dashboard/index.jsx`:
- Around line 153-159: The effect omits dashboardCharts.updateChartData from its
dependency array which will trigger ESLint's react-hooks/exhaustive-deps; update
the useEffect dependencies to include dashboardCharts.updateChartData (i.e., use
[displayMode, dashboardData.quotaData, dashboardCharts.updateChartData]) and
ensure the updateChartData implementation is memoized with useCallback
(including displayMode in its deps) so adding it is safe and won't cause extra
re-renders.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e0ca7bb2-8839-4347-8dc0-400b4fc7ef9b
📒 Files selected for processing (5)
model/option.goweb/src/components/dashboard/index.jsxweb/src/helpers/dashboard.jsxweb/src/hooks/dashboard/useDashboardCharts.jsxweb/src/i18n/locales/en.json
✅ Files skipped from review due to trivial changes (2)
- model/option.go
- web/src/i18n/locales/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
- web/src/helpers/dashboard.jsx
💡 沟通提示 / Pre-submission
📝 变更描述 / Description
功能概述
为"数据看板"的"消耗分布"添加Token和金额显示模式切换功能:
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
📸 运行证明 / Proof of Work
金额显示



tokens显示
设置界面
Summary by CodeRabbit
New Features
Documentation