feat(dashboard): 增加分流图,查看token流量。add traffic flow sankey chart - #5465
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (24)
WalkthroughAdds a new "Flow" dashboard with complete backend-to-frontend flow quota visualization: centralizes quota logging around structured parameters, expands quota data with token/channel/node dimensions, implements role-specific aggregation APIs with batch name resolution, builds a Sankey graph transformation library with filtering and color palettes, integrates an interactive React component with user and metric controls, and provides multilingual i18n support across six locales. ChangesFlow Dashboard Feature
Sequence DiagramsequenceDiagram
participant Browser
participant FlowCharts
participant DashboardAPI as getFlowQuotaDates
participant Backend as controller/usedata
participant GetFlowQuota as model.GetFlowQuotaData
participant LOG_DB as quota_data table
participant Lookup as token/channel resolution
Browser->>FlowCharts: user selects time range & filters
FlowCharts->>DashboardAPI: GET /api/data/flow or /flow/self
DashboardAPI->>Backend: handler forwards start/end/username/userID
Backend->>GetFlowQuota: (startTime, endTime, username, userID, role)
GetFlowQuota->>LOG_DB: aggregate with role-specific GROUP BY
LOG_DB-->>GetFlowQuota: grouped quota rows
GetFlowQuota->>Lookup: batch load token and channel names
Lookup-->>GetFlowQuota: TokenName and ChannelName populated
GetFlowQuota-->>Backend: FlowQuotaData[]
Backend-->>DashboardAPI: {success: true, data: [...]}
DashboardAPI-->>FlowCharts: FlowQuotaDataItem[]
FlowCharts->>FlowCharts: buildDashboardFlowData + buildFlowSankeySpec
FlowCharts->>Browser: render Sankey chart with interactions
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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 |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a new “Flow” dashboard section that visualizes traffic usage as a Sankey diagram and introduces backend + frontend plumbing to fetch and aggregate flow quota data.
Changes:
- Add Flow usage aggregation to the API (
/api/data/flow,/api/data/flow/self) and SQL-side token/cache computations. - Add dashboard flow processing + Sankey spec builder utilities with tests.
- Add Flow UI section (lazy-loaded) plus new i18n keys and MultiSelect “summary chip” support.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| web/default/src/i18n/static-keys.ts | Adds Flow-related static translation keys referenced by the dashboard UI. |
| web/default/src/i18n/locales/{en,zh,vi,ru,ja,fr}.json | Adds translations for Flow UI labels and empty/filter states. |
| web/default/src/features/dashboard/types.ts | Introduces Flow-specific types (raw rows, graph nodes/links, filters, summaries). |
| web/default/src/features/dashboard/section-registry.tsx | Registers the new flow dashboard section. |
| web/default/src/features/dashboard/lib/index.ts | Exports flow builders/spec helpers from the dashboard lib barrel. |
| web/default/src/features/dashboard/lib/flow.ts | Implements flow aggregation, filtering, coloring, and Sankey spec building. |
| web/default/src/features/dashboard/lib/flow.test.ts | Adds unit tests for Flow data building and Sankey spec generation. |
| web/default/src/features/dashboard/lib/flow-selection.ts | Adds selection + display-state helpers for Flow UI. |
| web/default/src/features/dashboard/lib/flow-selection.test.ts | Adds unit tests for Flow selection helpers. |
| web/default/src/features/dashboard/lib/charts.ts | Exposes a shared getDashboardChartColors palette helper for consistent chart colors. |
| web/default/src/features/dashboard/index.tsx | Adds Flow section routing, actions, and lazy-loaded Flow charts. |
| web/default/src/features/dashboard/components/models/models-filter-dialog.tsx | Makes filter dialog title/description configurable for reuse by Flow. |
| web/default/src/features/dashboard/components/flow/flow-charts.tsx | Adds the Flow charts UI (controls, Sankey render, empty/error states). |
| web/default/src/features/dashboard/api.ts | Adds a client API wrapper to fetch flow-quota rows. |
| web/default/src/components/multi-select.tsx | Adds renderSelectedSummary to show a compact selection summary chip. |
| router/api-router.go | Wires new Flow API routes into the Gin router. |
| model/usedata_flow.go | Adds DB aggregation query for flow quota data and channel-name enrichment. |
| model/usedata_flow_test.go | Adds model-level tests for aggregation, filtering, and cache token math. |
| controller/usedata.go | Adds controller handlers for flow quota endpoints. |
| controller/usedata_flow_test.go | Adds controller tests for admin username filtering and self scoping. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| <VChart | ||
| key={`flow-${chartKey}`} | ||
| spec={{ | ||
| ...flowSpec, | ||
| theme: chartTheme, | ||
| background: 'transparent', | ||
| }} | ||
| option={VCHART_OPTION} | ||
| /> |
| func GetAllFlowQuotaDates(c *gin.Context) { | ||
| startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64) | ||
| endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64) | ||
| username := c.Query("username") | ||
| dates, err := model.GetFlowQuotaData(startTimestamp, endTimestamp, username, 0) | ||
| if err != nil { | ||
| common.ApiError(c, err) | ||
| return | ||
| } | ||
| c.JSON(http.StatusOK, gin.H{ | ||
| "success": true, | ||
| "message": "", | ||
| "data": dates, | ||
| }) | ||
| return | ||
| } |
| if endTimestamp-startTimestamp > 2592000 { | ||
| c.JSON(http.StatusOK, gin.H{ | ||
| "success": false, | ||
| "message": "时间跨度不能超过 1 个月", | ||
| }) | ||
| return | ||
| } |
| func flowLogJSONValidGuard() string { | ||
| if common.UsingPostgreSQL { | ||
| return "COALESCE(logs.other, '') <> ''" | ||
| } | ||
| if common.UsingMySQL { | ||
| return "COALESCE(logs.other, '') <> '' AND JSON_VALID(logs.other)" | ||
| } | ||
| return "COALESCE(logs.other, '') <> '' AND json_valid(logs.other)" | ||
| } | ||
|
|
||
| func flowLogJSONNumberExpr(key string) string { | ||
| guard := flowLogJSONValidGuard() | ||
| if common.UsingPostgreSQL { | ||
| return fmt.Sprintf( | ||
| "(CASE WHEN %s THEN COALESCE(NULLIF(logs.other::jsonb ->> '%s', '')::integer, 0) ELSE 0 END)", | ||
| guard, | ||
| key, | ||
| ) | ||
| } |
| export async function getFlowQuotaDates( | ||
| params: { | ||
| start_timestamp: number | ||
| end_timestamp: number | ||
| default_time?: string | ||
| username?: string | ||
| }, | ||
| isAdmin = false | ||
| ) { | ||
| const endpoint = isAdmin ? '/api/data/flow' : '/api/data/flow/self' |
Add dashboard flow APIs and a Sankey-based flow view with user, optional API key, model, and channel layers.\n\nReuse the dashboard VChart palette, add precise link/node tooltips and interactions, and cover filtering, layer ordering, color stability, and error states with tests.
0cc7252 to
3619b01
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@model/usedata_flow.go`:
- Around line 78-128: flowLogJSONValidGuard currently only checks non-empty for
PostgreSQL which lets malformed JSON reach the casts in flowLogJSONNumberExpr
and flowLogJSONStringExpr; change the PostgreSQL branch in flowLogJSONValidGuard
to a safe syntactic check (e.g. trim(logs.other) LIKE '{%' OR trim(logs.other)
LIKE '[%') combined with the non-empty check so invalid payloads are excluded
before any ::jsonb casts; the rest of the code (flowLogJSONNumberExpr and
flowLogJSONStringExpr) can continue using the returned guard variable.
In `@web/default/src/features/dashboard/components/flow/flow-charts.tsx`:
- Around line 334-345: The Sankey spec always receives formatQuota (in flowSpec
creation using buildFlowSankeySpec), causing Tokens/Requests views to use quota
formatting; update the flowSpec useMemo to select the formatter based on the
current metric (e.g., use formatQuota for 'quota', formatTokens for
'tokens'/'inputTokens'/'outputTokens', formatRequests for 'requests', etc.) and
pass that selected formatter into buildFlowSankeySpec instead of always passing
formatQuota so tooltips/values match the active metric.
In `@web/default/src/features/dashboard/lib/flow.test.ts`:
- Around line 1-9: The test file imports Node’s runner and assert; replace those
with Vitest imports so the suite runs with the frontend pipeline: change the
imports that reference "node:test" and "node:assert/strict" to import {
describe, test, expect } from "vitest" and update assertions to use expect(...)
instead of assert (locate usages in this file and update them); keep existing
type imports (FlowQuotaDataItem) and other module imports
(getDashboardChartColors, buildDashboardFlowData, buildFlowFilterOptions,
buildFlowSankeySpec) unchanged.
In `@web/default/src/features/dashboard/lib/flow.ts`:
- Around line 61-69: Import { t } from 'i18next' at the top of the module and
replace hardcoded English fallbacks in DEFAULT_FLOW_SANKEY_LABELS by wrapping
each value with t(), e.g. quota: t('Quota'), tokens: t('Tokens'), inputTokens:
t('Input Tokens'), etc.; do the same for the other default/fallback labels
object in this module (the fallback labels around lines 154-173) so all
non-React, user-visible default strings use i18n via t().
- Around line 888-914: Replace the hardcoded light-theme hex colors used in the
Sankey spec (the label style.fill '`#475569`' and the node stroke values:
node.style.stroke, node.state.hover.stroke, node.state.selected.stroke) with
values sourced from the app theme or CSS variables so the chart follows
dark/light mode; locate the style blocks in flow.ts (the label "style.fill" and
the "node" object) and pull colors from the shared theme/token provider (e.g.,
useTheme() or reading CSS vars like --color-slate-600 / --color-slate-800) or a
helper (e.g., colorAt theme lookup) and substitute those theme-derived values in
place of the hardcoded hex strings. Ensure the replacement preserves
opacity/alpha where needed (use rgba or CSS variable fallback) so hover/selected
states maintain intended contrast.
- Around line 447-458: The Sankey color mapping is being computed from the
filtered subset, causing chip colors (filterOptions.users[].color) to diverge;
change the flow color computation to always derive from the full dataset:
compute the palette/colors using flowColorMap(rows, palette) (or call
flowColorMap once with the unfiltered rows) and pass that color map/palette into
buildFlowGraph/buildDashboardFlowData so buildFlowGraph uses the full-data
colors instead of recomputing from filteredRows; update the places that
currently call flowColorMap or derive palette from filteredRows (e.g., the logic
around flowColorMap, palette, colors and where buildFlowGraph is invoked) to
accept and use the shared colors map so chips and the Sankey use the same stable
mapping.
In `@web/default/src/i18n/locales/ja.json`:
- Line 253: Update the Japanese translation for occurrences of the English
source "All API tokens" and any other entries whose source text is "API tokens"
to use "API トークン" instead of "API キー" to preserve the exact meaning; search the
locale JSON for the key/value pair containing "All API tokens" and replace the
value "すべての API キー" (and any other "API キー" translations tied to "API tokens")
with "すべての API トークン".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 23af550f-b7ce-4c36-89ef-966b529f8827
📒 Files selected for processing (25)
controller/usedata.gocontroller/usedata_flow_test.gomodel/usedata_flow.gomodel/usedata_flow_test.gorouter/api-router.goweb/default/src/components/multi-select.tsxweb/default/src/features/dashboard/api.tsweb/default/src/features/dashboard/components/flow/flow-charts.tsxweb/default/src/features/dashboard/components/models/models-filter-dialog.tsxweb/default/src/features/dashboard/index.tsxweb/default/src/features/dashboard/lib/charts.tsweb/default/src/features/dashboard/lib/flow-selection.test.tsweb/default/src/features/dashboard/lib/flow-selection.tsweb/default/src/features/dashboard/lib/flow.test.tsweb/default/src/features/dashboard/lib/flow.tsweb/default/src/features/dashboard/lib/index.tsweb/default/src/features/dashboard/section-registry.tsxweb/default/src/features/dashboard/types.tsweb/default/src/i18n/locales/en.jsonweb/default/src/i18n/locales/fr.jsonweb/default/src/i18n/locales/ja.jsonweb/default/src/i18n/locales/ru.jsonweb/default/src/i18n/locales/vi.jsonweb/default/src/i18n/locales/zh.jsonweb/default/src/i18n/static-keys.ts
| import assert from 'node:assert/strict' | ||
| import { describe, test } from 'node:test' | ||
| import type { FlowQuotaDataItem } from '../types' | ||
| import { getDashboardChartColors } from './charts' | ||
| import { | ||
| buildDashboardFlowData, | ||
| buildFlowFilterOptions, | ||
| buildFlowSankeySpec, | ||
| } from './flow' |
There was a problem hiding this comment.
Use Vitest for this frontend suite instead of node:test.
This file sits under web/default/**/*.test.ts, but it imports Node’s test runner directly. That splits this suite away from the frontend’s expected runner and can leave it undiscovered or behaving differently from the rest of the dashboard tests. Please switch the imports to Vitest (describe, test, expect) so it runs under the same pipeline as the other frontend specs.
As per coding guidelines, web/default/**/*.test.ts: prioritize unit testing for utility functions and pure logic using Vitest with *.test.ts naming.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/features/dashboard/lib/flow.test.ts` around lines 1 - 9, The
test file imports Node’s runner and assert; replace those with Vitest imports so
the suite runs with the frontend pipeline: change the imports that reference
"node:test" and "node:assert/strict" to import { describe, test, expect } from
"vitest" and update assertions to use expect(...) instead of assert (locate
usages in this file and update them); keep existing type imports
(FlowQuotaDataItem) and other module imports (getDashboardChartColors,
buildDashboardFlowData, buildFlowFilterOptions, buildFlowSankeySpec) unchanged.
Source: Coding guidelines
| const DEFAULT_FLOW_SANKEY_LABELS: FlowSankeyLabels = { | ||
| quota: 'Quota', | ||
| tokens: 'Tokens', | ||
| inputTokens: 'Input Tokens', | ||
| outputTokens: 'Output Tokens', | ||
| cacheRead: 'Cache Read', | ||
| cacheWrite: 'Cache Write', | ||
| requests: 'Requests', | ||
| share: 'Share', |
There was a problem hiding this comment.
Translate the fallback labels instead of hardcoding English here.
Quota, Tokens, Unknown User, Unknown Token, Unknown Model, and Unknown are all user-visible strings emitted from a non-React utility. They will stay English when data is missing or a caller uses the default labels. Import t from i18next in this module and wrap these fallbacks.
As per coding guidelines, web/default/**/*.ts: use import { t } from 'i18next' in non-React code for translations, and web/default/**/*.{tsx,ts}: all user-facing text must support i18n using t().
Also applies to: 154-173
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/features/dashboard/lib/flow.ts` around lines 61 - 69, Import
{ t } from 'i18next' at the top of the module and replace hardcoded English
fallbacks in DEFAULT_FLOW_SANKEY_LABELS by wrapping each value with t(), e.g.
quota: t('Quota'), tokens: t('Tokens'), inputTokens: t('Input Tokens'), etc.; do
the same for the other default/fallback labels object in this module (the
fallback labels around lines 154-173) so all non-React, user-visible default
strings use i18n via t().
Source: Coding guidelines
| const colors = flowColorMap(rows, palette) | ||
|
|
||
| for (const row of rows) { | ||
| const metrics = rowMetrics(row) | ||
| const userID = userNodeId(row) | ||
| const tokenID = tokenNodeId(row) | ||
| const modelID = modelNodeId(row) | ||
| const channelID = channelNodeId(row) | ||
| const userColor = colors.get(userID) ?? colorAt(0, palette) | ||
| const tokenColor = colors.get(tokenID) ?? userColor | ||
| const modelColor = colors.get(modelID) ?? userColor | ||
| const channelColor = colors.get(channelID) ?? modelColor |
There was a problem hiding this comment.
Keep color assignment anchored to the full dataset, not the filtered subset.
buildDashboardFlowData() builds filterOptions from rows, but it builds flow from filteredRows, and buildFlowGraph() derives its palette from that smaller set. As soon as a filter removes an earlier user/token/model/channel, the chart recomputes colors while filterOptions.users[].color stays on the original mapping, so the same entity can show one color in the chips and another in the Sankey. This also breaks stable downstream colors during filtering.
Suggested fix
-function buildFlowGraph(
+function buildFlowGraph(
rows: FlowQuotaDataItem[],
metric: FlowMetric,
pathMode: FlowPathMode = DEFAULT_FLOW_PATH_MODE,
includeTokenLayer = true,
- palette?: readonly string[]
+ palette?: readonly string[],
+ colorRows: FlowQuotaDataItem[] = rows
): DashboardFlowGraph {
@@
- const colors = flowColorMap(rows, palette)
+ const colors = flowColorMap(colorRows, palette)
@@
export function buildDashboardFlowData(
rows: FlowQuotaDataItem[],
metric: FlowMetric = 'quota',
options: FlowBuildOptions = {}
): ProcessedFlowData {
@@
flow: buildFlowGraph(
filteredRows,
metric,
pathMode,
includeTokenLayer,
- palette
+ palette,
+ rows
),Also applies to: 687-702
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/features/dashboard/lib/flow.ts` around lines 447 - 458, The
Sankey color mapping is being computed from the filtered subset, causing chip
colors (filterOptions.users[].color) to diverge; change the flow color
computation to always derive from the full dataset: compute the palette/colors
using flowColorMap(rows, palette) (or call flowColorMap once with the unfiltered
rows) and pass that color map/palette into buildFlowGraph/buildDashboardFlowData
so buildFlowGraph uses the full-data colors instead of recomputing from
filteredRows; update the places that currently call flowColorMap or derive
palette from filteredRows (e.g., the logic around flowColorMap, palette, colors
and where buildFlowGraph is invoked) to accept and use the shared colors map so
chips and the Sankey use the same stable mapping.
| style: { | ||
| fill: '#475569', | ||
| fontSize: 11, | ||
| fontWeight: 600, | ||
| }, | ||
| }, | ||
| node: { | ||
| interactive: true, | ||
| style: { | ||
| fill: (datum: Record<string, unknown>) => | ||
| String(sankeyDatumValue(datum, 'color') ?? colorAt(0)), | ||
| fillOpacity: 0.92, | ||
| stroke: 'rgba(148, 163, 184, 0.45)', | ||
| lineWidth: 1, | ||
| cursor: 'pointer', | ||
| pickMode: 'accurate', | ||
| }, | ||
| state: { | ||
| hover: { | ||
| fillOpacity: 1, | ||
| stroke: 'rgba(15, 23, 42, 0.68)', | ||
| lineWidth: 1.5, | ||
| }, | ||
| selected: { | ||
| fillOpacity: 1, | ||
| stroke: 'rgba(15, 23, 42, 0.68)', | ||
| lineWidth: 1.5, |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Avoid hardcoded light-theme colors in the Sankey spec.
The label fill and node strokes are fixed to light-theme slate values here, so the chart cannot follow the active dashboard theme. In dark mode this will drift from the rest of the dashboard and can reduce contrast on labels and outlines. Pull these from the theme/CSS variables instead of baking them into the spec.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/features/dashboard/lib/flow.ts` around lines 888 - 914,
Replace the hardcoded light-theme hex colors used in the Sankey spec (the label
style.fill '`#475569`' and the node stroke values: node.style.stroke,
node.state.hover.stroke, node.state.selected.stroke) with values sourced from
the app theme or CSS variables so the chart follows dark/light mode; locate the
style blocks in flow.ts (the label "style.fill" and the "node" object) and pull
colors from the shared theme/token provider (e.g., useTheme() or reading CSS
vars like --color-slate-600 / --color-slate-800) or a helper (e.g., colorAt
theme lookup) and substitute those theme-derived values in place of the
hardcoded hex strings. Ensure the replacement preserves opacity/alpha where
needed (use rgba or CSS variable fallback) so hover/selected states maintain
intended contrast.
| "Ali": "Ali", | ||
| "Alipay": "Alipay", | ||
| "All": "すべて", | ||
| "All API tokens": "すべての API キー", |
There was a problem hiding this comment.
“API tokens” is mistranslated as “API keys”.
Line 253 and Line 2522 currently translate “API tokens” as API キー, which changes meaning and can confuse users in token-related views.
Suggested fix
- "All API tokens": "すべての API キー",
+ "All API tokens": "すべての API トークン",
...
- "No API tokens": "API キーなし",
+ "No API tokens": "API トークンなし",Also applies to: 2522-2522
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@web/default/src/i18n/locales/ja.json` at line 253, Update the Japanese
translation for occurrences of the English source "All API tokens" and any other
entries whose source text is "API tokens" to use "API トークン" instead of "API キー"
to preserve the exact meaning; search the locale JSON for the key/value pair
containing "All API tokens" and replace the value "すべての API キー" (and any other
"API キー" translations tied to "API tokens") with "すべての API トークン".
|
Thanks for your PR! This feature is very useful. I will merge it after making some modifications. |
* feat(dashboard): add traffic flow sankey chart Add dashboard flow APIs and a Sankey-based flow view with user, optional API key, model, and channel layers.\n\nReuse the dashboard VChart palette, add precise link/node tooltips and interactions, and cover filtering, layer ordering, color stability, and error states with tests. * feat: build flow chart from quota data --------- Co-authored-by: CaIon <i@caion.me>
* feat(dashboard): add traffic flow sankey chart Add dashboard flow APIs and a Sankey-based flow view with user, optional API key, model, and channel layers.\n\nReuse the dashboard VChart palette, add precise link/node tooltips and interactions, and cover filtering, layer ordering, color stability, and error states with tests. * feat: build flow chart from quota data --------- Co-authored-by: CaIon <i@caion.me>
* feat(dashboard): add traffic flow sankey chart Add dashboard flow APIs and a Sankey-based flow view with user, optional API key, model, and channel layers.\n\nReuse the dashboard VChart palette, add precise link/node tooltips and interactions, and cover filtering, layer ordering, color stability, and error states with tests. * feat: build flow chart from quota data --------- Co-authored-by: CaIon <i@caion.me>
Important
📝 变更描述 / Description
本 PR 为新版 Dashboard 增加分流图视图,用于按用户、API 密钥、模型和渠道查看调用流向与用量分布。与原先的图表风格一致,交互逻辑一致,筛选逻辑一致,统计分析方案一致。
后端新增
/api/data/flow与/api/data/flow/self数据接口,从消费日志中按用户、密钥、模型、渠道聚合额度、请求数、输入/输出 token、缓存读写 token 等指标。前端新增基于 VChart Sankey 的分流图,将这些聚合数据绘制为固定顺序的流向层级:用户 -> API 密钥 -> 模型 -> 渠道其中用户层固定显示,API 密钥层可通过
API Key按钮显示或隐藏,模型与渠道至少显示一个;当模型和渠道同时显示时,模型始终位于渠道前面。实现上,分流图复用 Dashboard 现有 VChart 配色体系,避免引入独立色板;节点颜色按完整层级稳定分配,切换 API 密钥层时不会导致模型或渠道颜色跳变。连线继承起点节点颜色,并根据同一 source 下的流量大小调整透明度,使细流线更突出、粗流线更克制。Tooltip 区分节点与连线:节点展示端点聚合统计,连线只展示当前 source-target 的聚合数据,避免 hover 时高亮或展示无关流线。
同时补充分流图相关测试,覆盖筛选、层级顺序、API 密钥层隐藏、颜色稳定性、tooltip 数据、错误态和空态等行为。
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
#5435
✅ 提交前检查项 / Checklist
Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。📸 运行证明 / Proof of Work
Summary by CodeRabbit