Skip to content

为"数据看板"的"消耗分布"添加Token和金额显示模式切换功能#3387

Open
LinZX1314 wants to merge 3 commits intoQuantumNous:mainfrom
LinZX1314:feat/dashboard-consumption-mode-v2
Open

为"数据看板"的"消耗分布"添加Token和金额显示模式切换功能#3387
LinZX1314 wants to merge 3 commits intoQuantumNous:mainfrom
LinZX1314:feat/dashboard-consumption-mode-v2

Conversation

@LinZX1314
Copy link

@LinZX1314 LinZX1314 commented Mar 22, 2026

⚠️ 提交警告 / PR Warning

请注意: 请提供人工撰写的简洁摘要。包含大量 AI 灌水内容、逻辑混乱或无视模版的 PR 可能会被无视或直接关闭


💡 沟通提示 / Pre-submission

重大功能变更? 请先提交 Issue 交流,避免无效劳动。

📝 变更描述 / Description

功能概述

为"数据看板"的"消耗分布"添加Token和金额显示模式切换功能:

  1. 切换按钮:在消耗分布图表旁添加切换按钮,支持在 金额 和 Token 两种显示模式间切换
  2. 默认模式配置:在「数据看板设置」中新增「默认显示模式」配置项,可选择 金额 或 TOKEN
  3. 数据来源:使用数据库quota_data表现有的token_used字段

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix)
  • ✨ 新功能 (New feature)
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

  • Closes # (如有)

✅ 提交前检查项 / Checklist

  • 人工确认: 已人工撰写
  • 深度理解: 已理解工作原理
  • 范围聚焦: 仅包含功能代码
  • 本地验证: 请在本地测试
  • 安全合规: 无敏感凭据

📸 运行证明 / Proof of Work

金额显示
image
tokens显示
image-1
设置界面
image-2

Summary by CodeRabbit

  • New Features

    • Display mode toggle added to dashboard consumption charts (Amount ↔ Token(s)) with keyboard accessibility.
    • New configurable default display mode in dashboard settings, persisted for users and applied to charts; toggling refreshes chart data and tooltips to match the chosen mode.
    • Empty quota responses now show a default "无数据" entry so charts include a zero-value item.
  • Documentation

    • Added English and Chinese translations for the new display mode controls.

LZX7826 and others added 2 commits March 21, 2026 23:55
- 在模型消耗分布标签页添加 $/Token 切换按钮
- 在数据看板设置中新增默认显示模式配置项
- Token 模式使用数据库 token_used 字段
- 修复 SettingsDataDashboard localStorage 写入过时值的问题
- 增强 ChartsPanel 切换按钮的无障碍属性 (ARIA)
- 修复 rawQuota/rawTokenUsed 数据赋值逻辑
- 使用严格相等 (===) 替代松散相等 (==)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 22, 2026

Walkthrough

Adds 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

Cohort / File(s) Summary
Backend Option
model/option.go
Register DataExportDefaultDisplayMode with default "QUOTA" and add update handling that normalizes and bounds values to "QUOTA"/"TOKENS" in updateOptionMap.
Dashboard UI
web/src/components/dashboard/index.jsx, web/src/components/dashboard/ChartsPanel.jsx
Introduce displayMode state persisted to localStorage, wire onDisplayModeChange into ChartsPanel, add clickable/keyboard-accessible toggle in chart header, and trigger chart data refresh on mode change.
Settings UI
web/src/components/settings/DashboardSetting.jsx, web/src/pages/Setting/Dashboard/SettingsDataDashboard.jsx
Add DataExportDefaultDisplayMode input (options QUOTA/TOKENS), include it in settings state, and sync it to localStorage when options load/save.
Charting & Data Logic
web/src/hooks/dashboard/useDashboardCharts.jsx, web/src/helpers/dashboard.jsx, web/src/hooks/dashboard/useDashboardData.js
Make charts aware of displayMode: create tooltip config helper, pass displayMode into chart hook, compute totals/sorting from tokens or quota, extend aggregation to track tokenUsed, optionally include tooltip config in chart spec, and ensure empty quota data includes token_used: 0.
Localization
web/src/i18n/locales/en.json, web/src/i18n/locales/zh-CN.json
Add translation keys/values for the default display mode UI and the toggle labels in English and Simplified Chinese.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Calcium-Ion
  • creamlike1024
  • seefs001

Poem

🐰 I hop and I toggle between quota and tokens,
Charts paint the numbers where silence was broken.
From backend to frontend the setting takes flight,
LocalStorage keeps it snug through the night.
Tokens or amount — the dashboard feels right.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a toggle feature to switch between Token and quota (amount) display modes for the consumption distribution chart in the data dashboard.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can make CodeRabbit's review stricter and more nitpicky using the `assertive` profile, if that's what you prefer.

Change the reviews.profile setting to assertive to make CodeRabbit's nitpick more issues in your PRs.

@LinZX1314 LinZX1314 changed the title Feat/dashboard consumption mode v2 为"数据看板"的"消耗分布"添加Token和金额显示模式切换功能 Mar 22, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Lines 319-361: Inside updateChartData callback
  2. Lines 449-493: Inside the useEffect for displayMode changes

Both 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: Missing dashboardCharts.updateChartData in useEffect dependency array.

The effect uses dashboardCharts.updateChartData but it's not listed in the dependency array. While this may work because updateChartData is a memoized callback, the ESLint exhaustive-deps rule would flag this. Since updateChartData already has displayMode in its own dependency array, calling it when displayMode changes will use the updated function.

However, the current code works because displayMode change triggers this effect, and by that time updateChartData has been recreated with the new displayMode. Consider adding a comment to clarify this intentional omission or adding dashboardCharts.updateChartData to 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 normalize DataExportDefaultDisplayMode before 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

📥 Commits

Reviewing files that changed from the base of the PR and between deff59a and 8698712.

📒 Files selected for processing (10)
  • model/option.go
  • web/src/components/dashboard/ChartsPanel.jsx
  • web/src/components/dashboard/index.jsx
  • web/src/components/settings/DashboardSetting.jsx
  • web/src/helpers/dashboard.jsx
  • web/src/hooks/dashboard/useDashboardCharts.jsx
  • web/src/hooks/dashboard/useDashboardData.js
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/zh-CN.json
  • web/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>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
web/src/components/dashboard/index.jsx (1)

153-159: Consider adding dashboardCharts.updateChartData to the dependency array.

While the comment explains why it's omitted, ESLint's react-hooks/exhaustive-deps rule would flag this. Since updateChartData is memoized with useCallback (including displayMode in 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

📥 Commits

Reviewing files that changed from the base of the PR and between 8698712 and 8ae2de0.

📒 Files selected for processing (5)
  • model/option.go
  • web/src/components/dashboard/index.jsx
  • web/src/helpers/dashboard.jsx
  • web/src/hooks/dashboard/useDashboardCharts.jsx
  • web/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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant