docs: add user token breakdown design
This commit is contained in:
@@ -0,0 +1,80 @@
|
|||||||
|
# User Token Breakdown Design
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Add a token-level consumption view to the user detail page so operators can see which API keys/令牌 drive a user's usage, then expand each token to inspect the models used through that token.
|
||||||
|
|
||||||
|
## Current Behavior
|
||||||
|
|
||||||
|
The ranking page links user rows to `/detail/user/{username}`. The detail page loads `/api/detail/user/{username}` and shows summary cards, a usage trend chart, and a single breakdown table. For user and channel details that table is currently model distribution; for model details it is user distribution.
|
||||||
|
|
||||||
|
The logs table already exposes `logs.token_name` and supports filtering by token name. This field is the source for the new token breakdown.
|
||||||
|
|
||||||
|
## Proposed Experience
|
||||||
|
|
||||||
|
On user detail pages only, the breakdown area gains two tabs:
|
||||||
|
|
||||||
|
- 模型分布 / Model Distribution
|
||||||
|
- 令牌分布 / Token Distribution
|
||||||
|
|
||||||
|
Model Distribution remains the default. Token Distribution shows a table where each top-level row is one token name. Columns:
|
||||||
|
|
||||||
|
- token name
|
||||||
|
- calls
|
||||||
|
- total tokens
|
||||||
|
- cost
|
||||||
|
- share of the user's total tokens
|
||||||
|
- primary models, shown as a compact comma-separated preview
|
||||||
|
|
||||||
|
Clicking a token row expands an inline model table below it. The nested rows show the models used by that token with calls, total tokens, cost, and share of that token's total tokens.
|
||||||
|
|
||||||
|
For missing or blank token names, normalize `token_name` to an empty string in the API response. The UI displays that empty API value as `未命名令牌` in Chinese and `Unnamed Token` in English.
|
||||||
|
|
||||||
|
## Data Shape
|
||||||
|
|
||||||
|
Extend user detail responses with a `tokens` field:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
tokens: Array<{
|
||||||
|
name: string;
|
||||||
|
calls: number;
|
||||||
|
total_tokens: number;
|
||||||
|
quota: number;
|
||||||
|
models: Array<{
|
||||||
|
name: string;
|
||||||
|
calls: number;
|
||||||
|
total_tokens: number;
|
||||||
|
quota: number;
|
||||||
|
}>;
|
||||||
|
}>;
|
||||||
|
```
|
||||||
|
|
||||||
|
The top-level token rows are grouped by normalized `token_name`, where blank or whitespace-only names become an empty string. Each nested model row is grouped by token name and `REAL_MODEL`, using the same total token definition as the rest of the app:
|
||||||
|
|
||||||
|
`prompt_tokens + completion_tokens + cache_creation_tokens + cache_read_tokens`
|
||||||
|
|
||||||
|
Sort top-level tokens by total tokens descending in SQL. Sort nested models by total tokens descending in SQL. Limit the top-level token list to 50 tokens and each nested model list to 10 models to keep the payload bounded.
|
||||||
|
|
||||||
|
## UI Rules
|
||||||
|
|
||||||
|
The existing breakdown table sorting remains available for the flat model distribution. For token distribution, sort the top-level token rows by calls, total tokens, or cost in the client using the existing `sortDetailBreakdown` style. Nested model rows stay sorted by total tokens descending.
|
||||||
|
|
||||||
|
The expanded state is local UI state keyed by the normalized token name. Switching time ranges or refreshing data should keep the page valid even if an expanded token disappears; missing keys simply render closed.
|
||||||
|
|
||||||
|
The token tab appears only when `type === "user"`. Model and channel details keep their existing single breakdown table.
|
||||||
|
|
||||||
|
## Error And Empty States
|
||||||
|
|
||||||
|
If there are no token rows, show the existing no-data behavior for the token tab rather than hiding the whole breakdown area. The summary cards and trend chart should continue rendering even if token breakdown data is empty.
|
||||||
|
|
||||||
|
If the detail API fails or returns malformed token data, the page should not crash; treat missing `tokens` as an empty array.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
Add focused coverage for:
|
||||||
|
|
||||||
|
- the user detail query returning token rows with nested model rows from `token_name`
|
||||||
|
- blank token names normalizing to an empty API name and rendering as the localized unnamed label
|
||||||
|
- sorting token top-level rows without mutating source data, if a new helper is introduced
|
||||||
|
|
||||||
|
Run lint and the existing Bun tests after implementation.
|
||||||
Reference in New Issue
Block a user