feat: 在使用日志统计 API 和 Dashboard 中添加 Token 统计数据(提示词/补全) - #4113
Conversation
WalkthroughAdds token metrics (prompt_tokens, completion_tokens, token) to log/stat aggregations, introduces optional model_name filtering in quota queries, enables admin user_id search with debounce-driven username resolution, and updates frontend UI, hooks, API controllers, model queries, and i18n translations accordingly. Changes
Sequence DiagramsequenceDiagram
participant Admin as Admin User
participant UI as SearchModal / Hook
participant API as Backend API
participant DB as Database
Admin->>UI: choose search mode (user_id) and enter id
UI->>UI: debounce 400ms
UI->>API: GET /api/users/resolve?user_id=... (abortable)
API->>DB: query user by id
DB-->>API: return username
API-->>UI: username
UI->>UI: set inputs.username
UI->>API: GET /api/data?start=...&end=...&model_name=...&user_id/username=...
API->>DB: query quota_data (optional model_name filter), aggregate tokens (prompt/completion) and rpm/tpm
DB-->>API: return aggregated stats
API-->>UI: JSON { quota, rpm, tpm, prompt_tokens, completion_tokens, token }
UI->>UI: render stats and tags (quota, rpm, token, ...)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 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 |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
controller/log.go (1)
145-151: Changes look correct; consider minor cleanup.The token statistics fields are properly exposed. Two optional improvements:
- Rename
quotaNumtostatfor consistency withGetLogsStat(line 105)- Remove the stale commented code at line 151
♻️ Optional cleanup
- quotaNum, err := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName, channel, group) + stat, err := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName, channel, group) if err != nil { common.ApiError(c, err) return } - //tokenNum := model.SumUsedToken(logType, startTimestamp, endTimestamp, modelName, username, tokenName) c.JSON(200, gin.H{ "success": true, "message": "", "data": gin.H{ - "quota": quotaNum.Quota, - "token": quotaNum.Token, - "prompt_tokens": quotaNum.PromptTokens, - "completion_tokens": quotaNum.CompletionTokens, - "rpm": quotaNum.Rpm, - "tpm": quotaNum.Tpm, - //"token": tokenNum, + "quota": stat.Quota, + "token": stat.Token, + "prompt_tokens": stat.PromptTokens, + "completion_tokens": stat.CompletionTokens, + "rpm": stat.Rpm, + "tpm": stat.Tpm, }, })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@controller/log.go` around lines 145 - 151, Rename the local variable quotaNum to stat to match the naming used in GetLogsStat and improve consistency, update all references in the same block (e.g., "quota": quotaNum.Quota → stat.Quota), and remove the stale commented line //"token": tokenNum so the block contains only active, relevant fields.web/src/hooks/dashboard/useDashboardData.js (2)
237-244: Minor: Redundant fallback in string conversion.At line 238,
message || ''is unnecessary since theelse if (message)condition at line 237 guaranteesmessageis truthy.🔧 Simplify string conversion
} else if (message) { - const msg = String(message || ''); + const msg = String(message); if (msg.toLowerCase().includes('record not found')) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/hooks/dashboard/useDashboardData.js` around lines 237 - 244, The code redundantly uses `message || ''` inside the `else if (message)` branch; replace the conversion at the `const msg = String(message || '');` line with `String(message)` (inside the same else-if block in useDashboardData.js) so the truthy check is relied upon and the fallback is removed, leaving the subsequent `msg.toLowerCase().includes('record not found')` and `showError(msg)` logic unchanged.
324-324: Consider: Dependency array could be more precise.Using the entire
inputsobject as a dependency meansloadQuotaDatais recreated whenever any input field changes, including unrelated ones liketoken_nameorchannel. This is functionally correct but could cause unnecessary re-renders in dependent effects/callbacks.🔧 Optional: Use specific input fields in dependency array
- }, [inputs, dataExportDefaultTime, isAdminUser]); + }, [ + inputs.start_timestamp, + inputs.end_timestamp, + inputs.username, + inputs.user_id, + inputs.user_search_type, + inputs.model_name, + dataExportDefaultTime, + isAdminUser, + ]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/hooks/dashboard/useDashboardData.js` at line 324, The dependency array is too broad because it uses the whole inputs object; update the hook that defines loadQuotaData (the useCallback/useEffect that currently depends on inputs, dataExportDefaultTime, isAdminUser) to depend only on the specific properties of inputs that loadQuotaData actually reads (e.g., inputs.organizationId, inputs.workspaceId, inputs.teamId — replace with the real property names used inside loadQuotaData) along with dataExportDefaultTime and isAdminUser, so the callback is not recreated for unrelated changes like token_name or channel.
🤖 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/i18n/locales/en.json`:
- Around line 3380-3382: The two new translation keys "找不到该值" and "用户ID" were
added at the JSON root instead of inside the existing "translation" object; move
these keys into the "translation" object (before its closing brace) so i18next
can find them, ensuring they follow the same key/value format used by other
entries in that object.
In `@web/src/i18n/locales/ja.json`:
- Around line 3317-3320: The new translation keys "找不到该值" and "用户ID" were
accidentally placed outside the "translation" object (the closing brace on the
"translation" object at the line above makes them siblings), so move these
entries inside the existing "translation" object, ensuring proper JSON commas
and indentation so they become children of "translation" and follow the flat
JSON structure with Chinese source strings as keys used by i18next.
In `@web/src/i18n/locales/ru.json`:
- Around line 3350-3352: The two keys "找不到该值" and "用户ID" are defined outside the
top-level "translation" object so t('...') lookups will fail; move these entries
into the existing "translation" object in the ru.json locale (i.e., place
"找不到该值": "Значение не найдено" and "用户ID": "ID пользователя" as properties
inside the "translation" object), keeping the Chinese strings as keys and
aligning indentation/commas with the surrounding JSON structure.
In `@web/src/i18n/locales/vi.json`:
- Around line 3886-3888: The two new keys "找不到该值" and "用户ID" were added at the
JSON root instead of inside the "translation" object, which breaks lookups via
useTranslation() / t('中文key'); move these keys and their Vietnamese values into
the existing "translation" object in vi.json (preserve the Chinese strings as
keys and Vietnamese strings as values) so the file remains a flat translation
map usable by t(...).
---
Nitpick comments:
In `@controller/log.go`:
- Around line 145-151: Rename the local variable quotaNum to stat to match the
naming used in GetLogsStat and improve consistency, update all references in the
same block (e.g., "quota": quotaNum.Quota → stat.Quota), and remove the stale
commented line //"token": tokenNum so the block contains only active, relevant
fields.
In `@web/src/hooks/dashboard/useDashboardData.js`:
- Around line 237-244: The code redundantly uses `message || ''` inside the
`else if (message)` branch; replace the conversion at the `const msg =
String(message || '');` line with `String(message)` (inside the same else-if
block in useDashboardData.js) so the truthy check is relied upon and the
fallback is removed, leaving the subsequent `msg.toLowerCase().includes('record
not found')` and `showError(msg)` logic unchanged.
- Line 324: The dependency array is too broad because it uses the whole inputs
object; update the hook that defines loadQuotaData (the useCallback/useEffect
that currently depends on inputs, dataExportDefaultTime, isAdminUser) to depend
only on the specific properties of inputs that loadQuotaData actually reads
(e.g., inputs.organizationId, inputs.workspaceId, inputs.teamId — replace with
the real property names used inside loadQuotaData) along with
dataExportDefaultTime and isAdminUser, so the callback is not recreated for
unrelated changes like token_name or channel.
🪄 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: 6a4f90ac-8a8e-42a7-a50f-0e8202ee8c50
📒 Files selected for processing (13)
controller/log.gocontroller/usedata.gomodel/log.gomodel/usedata.goweb/src/components/dashboard/modals/SearchModal.jsxweb/src/components/table/usage-logs/UsageLogsActions.jsxweb/src/hooks/dashboard/useDashboardData.jsweb/src/hooks/usage-logs/useUsageLogsData.jsxweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.json
👮 Files not reviewed due to content moderation or server errors (7)
- web/src/i18n/locales/fr.json
- web/src/components/table/usage-logs/UsageLogsActions.jsx
- web/src/hooks/usage-logs/useUsageLogsData.jsx
- controller/usedata.go
- web/src/components/dashboard/modals/SearchModal.jsx
- model/log.go
- model/usedata.go
| }, | ||
| "找不到该值": "Значение не найдено", | ||
| "用户ID": "ID пользователя" |
There was a problem hiding this comment.
Move new locale keys into the translation object
Line 3351 and Line 3352 are currently outside translation, so these keys likely won’t resolve via normal t('中文key') lookups.
✅ Suggested fix
- },
- "找不到该值": "Значение не найдено",
- "用户ID": "ID пользователя"
+ "找不到该值": "Значение не найдено",
+ "用户ID": "ID пользователя"
+ }
}As per coding guidelines: web/src/i18n/**/*.json translation files must follow the project i18n structure with Chinese source strings as keys.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| }, | |
| "找不到该值": "Значение не найдено", | |
| "用户ID": "ID пользователя" | |
| "找不到该值": "Значение не найдено", | |
| "用户ID": "ID пользователя" | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/i18n/locales/ru.json` around lines 3350 - 3352, The two keys "找不到该值"
and "用户ID" are defined outside the top-level "translation" object so t('...')
lookups will fail; move these entries into the existing "translation" object in
the ru.json locale (i.e., place "找不到该值": "Значение не найдено" and "用户ID": "ID
пользователя" as properties inside the "translation" object), keeping the
Chinese strings as keys and aligning indentation/commas with the surrounding
JSON structure.
|
收到 |
…dashboard UI - Add prompt_tokens, completion_tokens, total token fields to Stat struct - Use COALESCE in DB aggregation to prevent NULL values - Add model-scoped quota queries (GetQuotaDataByUsernameAndModel, GetQuotaDataByUserIdAndModel) - Support user_id and model_name query parameters in dashboard API - Display token counts in usage logs table and dashboard search modal - Add i18n translations for new UI strings
32f7a5f to
d6de049
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
web/src/components/dashboard/modals/SearchModal.jsx (1)
135-142: UseuseTranslation()inside the modal instead of passingtdown.These new filter labels keep extending the prop-drilled i18n surface; pulling
tlocally will keep the modal API smaller and matches the frontend convention.As per coding guidelines,
web/src/**/*.{ts,tsx,js,jsx}: Frontend i18n: UseuseTranslation()hook and callt('中文key')in components.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/dashboard/modals/SearchModal.jsx` around lines 135 - 142, SearchModal is prop-drilled a translation function `t`; instead import and call useTranslation() inside the modal to reduce props and follow frontend i18n conventions. In the SearchModal component remove the `t` prop usage, add import { useTranslation } from 'react-i18next', call const { t } = useTranslation() at the top of the component, and replace uses like createFormField(... label: t('模型名称') ...) with the locally obtained t; also update the component signature to stop accepting `t` so callers no longer need to pass it.
🤖 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/modals/SearchModal.jsx`:
- Around line 97-133: SearchModal currently expects a t prop for translations;
refactor it to import and call const { t } = useTranslation() within the
SearchModal component instead of using the passed-in t prop. Remove t from the
component's props (and any PropTypes/TS types) and update usages inside JSX
(e.g., labels/placeholders in Form.RadioGroup, createFormField calls for
'username' and 'user_id') to use the locally scoped t; keep existing handlers
like handleInputChange and form field symbols (userSearchType, username,
user_id, createFormField, Form.RadioGroup, Form.Input, Form.InputNumber)
unchanged. Ensure the component imports useTranslation from react-i18next.
In `@web/src/hooks/dashboard/useDashboardData.js`:
- Around line 284-286: Trim the model_name before adding it to the URL params in
the useDashboardData hook: replace the direct use of model_name with a trimmed
value (e.g., const trimmedModel = model_name.trim()) and then call
params.set('model_name', trimmedModel) (and only set it when trimmedModel is
non-empty) so the backend exact-match filter behaves like username
normalization.
---
Nitpick comments:
In `@web/src/components/dashboard/modals/SearchModal.jsx`:
- Around line 135-142: SearchModal is prop-drilled a translation function `t`;
instead import and call useTranslation() inside the modal to reduce props and
follow frontend i18n conventions. In the SearchModal component remove the `t`
prop usage, add import { useTranslation } from 'react-i18next', call const { t }
= useTranslation() at the top of the component, and replace uses like
createFormField(... label: t('模型名称') ...) with the locally obtained t; also
update the component signature to stop accepting `t` so callers no longer need
to pass it.
🪄 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: af9dbb37-6800-4c0d-86a8-6b87ba59ac16
📒 Files selected for processing (13)
controller/log.gocontroller/usedata.gomodel/log.gomodel/usedata.goweb/src/components/dashboard/modals/SearchModal.jsxweb/src/components/table/usage-logs/UsageLogsActions.jsxweb/src/hooks/dashboard/useDashboardData.jsweb/src/hooks/usage-logs/useUsageLogsData.jsxweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.json
✅ Files skipped from review due to trivial changes (3)
- web/src/i18n/locales/fr.json
- web/src/i18n/locales/vi.json
- web/src/i18n/locales/ru.json
🚧 Files skipped from review as they are similar to previous changes (8)
- web/src/hooks/usage-logs/useUsageLogsData.jsx
- controller/usedata.go
- web/src/components/table/usage-logs/UsageLogsActions.jsx
- controller/log.go
- web/src/i18n/locales/ja.json
- web/src/i18n/locales/en.json
- model/usedata.go
- model/log.go
| {isAdminUser && ( | ||
| <Form.RadioGroup | ||
| field='user_search_type' | ||
| label={t('用户')} | ||
| initValue={userSearchType} | ||
| value={userSearchType} | ||
| onChange={(value) => handleInputChange(value, 'user_search_type')} | ||
| > | ||
| <Radio value='username'>{t('用户名')}</Radio> | ||
| <Radio value='user_id'>{t('用户ID')}</Radio> | ||
| </Form.RadioGroup> | ||
| )} | ||
|
|
||
| {isAdminUser && | ||
| userSearchType === 'username' && | ||
| createFormField(Form.Input, { | ||
| field: 'username', | ||
| label: t('用户名称'), | ||
| noLabel: true, | ||
| value: username, | ||
| placeholder: t('可选值'), | ||
| name: 'username', | ||
| onChange: (value) => handleInputChange(value, 'username'), | ||
| })} | ||
|
|
||
| {isAdminUser && | ||
| userSearchType === 'user_id' && | ||
| createFormField(Form.InputNumber, { | ||
| field: 'user_id', | ||
| noLabel: true, | ||
| value: user_id, | ||
| placeholder: t('可选值'), | ||
| name: 'user_id', | ||
| min: 1, | ||
| step: 1, | ||
| precision: 0, | ||
| onChange: (value) => handleInputChange(value, 'user_id'), | ||
| })} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the SearchModal.jsx file and examine its contents
find . -name "SearchModal.jsx" -o -name "SearchModal.tsx" | head -20Repository: QuantumNous/new-api
Length of output: 116
🏁 Script executed:
# Get file stats first
wc -l web/src/components/dashboard/modals/SearchModal.jsx 2>/dev/null || echo "Checking for alternative paths..."
fd -i searchmodalRepository: QuantumNous/new-api
Length of output: 170
🏁 Script executed:
# List files in the dashboard/modals directory
git ls-files "web/src/components/dashboard/modals/" | head -20Repository: QuantumNous/new-api
Length of output: 114
🏁 Script executed:
# Read the entire SearchModal.jsx file to understand context
cat -n web/src/components/dashboard/modals/SearchModal.jsxRepository: QuantumNous/new-api
Length of output: 5397
🏁 Script executed:
# Search for where SearchModal is used and how handleInputChange is passed
rg -n "SearchModal" web/src --type jsx --type tsx -B 2 -A 5 | head -50Repository: QuantumNous/new-api
Length of output: 90
🏁 Script executed:
# Search for handleInputChange definition in the parent component
rg -n "handleInputChange" web/src/components/dashboard --type jsx --type tsx | head -30Repository: QuantumNous/new-api
Length of output: 90
🏁 Script executed:
# Search for SearchModal usage without type filter
rg -n "SearchModal" web/src -B 2 -A 5 | head -60Repository: QuantumNous/new-api
Length of output: 4259
🏁 Script executed:
# Find the parent component that uses SearchModal
fd -e jsx -e tsx web/src/components/dashboard | xargs grep -l "SearchModal" 2>/dev/nullRepository: QuantumNous/new-api
Length of output: 482
🏁 Script executed:
# Find parent component files
fd . web/src/components/dashboard -name "*.jsx" -o -name "*.tsx" | xargs grep -l "SearchModal" 2>/dev/nullRepository: QuantumNous/new-api
Length of output: 295
🏁 Script executed:
# Examine the useDashboardData hook to understand state management and async logic
cat -n web/src/hooks/dashboard/useDashboardData.js | head -220Repository: QuantumNous/new-api
Length of output: 8783
🏁 Script executed:
# Continue reading from line 220 onwards
tail -n +220 web/src/hooks/dashboard/useDashboardData.js | head -100Repository: QuantumNous/new-api
Length of output: 3088
🏁 Script executed:
# Check if other components use useTranslation() or receive t as prop
rg -n "useTranslation" web/src/components/dashboard --type js --type jsx --type ts --type tsx | head -20Repository: QuantumNous/new-api
Length of output: 90
🏁 Script executed:
# Look at other dashboard modal components for comparison
fd . web/src/components/dashboard/modals -type f | head -10Repository: QuantumNous/new-api
Length of output: 233
🏁 Script executed:
# Check the i18n usage pattern in the codebase
rg "const.*useTranslation" web/src/components --type js --type jsx --type ts --type tsx | head -30Repository: QuantumNous/new-api
Length of output: 90
🏁 Script executed:
# Check useTranslation usage without type filter
rg "useTranslation" web/src/components/dashboard -B 1 -A 1 | head -40Repository: QuantumNous/new-api
Length of output: 505
🏁 Script executed:
# List modal files
find web/src/components/dashboard/modals -type fRepository: QuantumNous/new-api
Length of output: 114
🏁 Script executed:
# Check another dashboard component to see i18n pattern
cat -n web/src/components/dashboard/index.jsx | head -100Repository: QuantumNous/new-api
Length of output: 4008
🏁 Script executed:
# Check how t is passed around - look for patterns in other modals/components
rg "const.*{.*t.*}.*useTranslation" web/src/components -C 2 | head -50Repository: QuantumNous/new-api
Length of output: 183
🏁 Script executed:
# Check if components receive t as prop or use useTranslation
rg "SearchModal|useTranslation" web/src/components/dashboard -C 3 | grep -A 5 "SearchModal ="Repository: QuantumNous/new-api
Length of output: 418
🏁 Script executed:
# Look at how other components are structured - check if they use local useTranslation
find web/src/components -name "*.jsx" -exec grep -l "useTranslation\|const.*=.*({.*t.*})" {} \; | head -20Repository: QuantumNous/new-api
Length of output: 4106
🏁 Script executed:
# Look at the dashboard index to understand handleSearchConfirm
rg -n "handleSearchConfirm" web/src/components/dashboard -B 3 -A 10 | head -60Repository: QuantumNous/new-api
Length of output: 4760
🏁 Script executed:
# Find where SearchModal is used and where handleSearchConfirm comes from
rg -n "handleSearchConfirm\|SearchModal" web/src/hooks/dashboard/useDashboardData.js | head -20Repository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
# Examine lines around the search confirm handler in the dashboard hook
sed -n '340,380p' web/src/hooks/dashboard/useDashboardData.jsRepository: QuantumNous/new-api
Length of output: 1102
🏁 Script executed:
# Check how handleInputChange is being called and what happens with the async username update
sed -n '144,189p' web/src/hooks/dashboard/useDashboardData.jsRepository: QuantumNous/new-api
Length of output: 1454
🏁 Script executed:
# Verify the exact behavior - does the form field actually get out of sync?
# Check if there are any other Form fields with the same pattern in the file
rg -n "value=\|initValue=" web/src/components/dashboard/modals/SearchModal.jsxRepository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
# Let me check the exact line numbers and structure of the form fields
sed -n '97,143p' web/src/components/dashboard/modals/SearchModal.jsxRepository: QuantumNous/new-api
Length of output: 1541
🏁 Script executed:
# Check how other components in the codebase handle i18n - are they all receiving t as prop?
rg -n "const.*Modal.*=.*{" web/src/components --max-count 5 | head -20Repository: QuantumNous/new-api
Length of output: 162
🏁 Script executed:
# Look at StatsCards to see how it uses useTranslation
cat -n web/src/components/dashboard/StatsCards.jsx | head -50Repository: QuantumNous/new-api
Length of output: 2170
🏁 Script executed:
# Check Semi Form documentation/examples in the codebase to see how form fields are typically used
rg -n "formApi\|Form.Input\|Form.DatePicker" web/src/components --max-count 10 | head -50Repository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
# Verify: are there any uses of formApi.setValue in the codebase?
rg -n "formApi.setValue\|formApi.setValues" web/srcRepository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
# Check if SearchModal is the only component receiving t as prop vs using hook
rg -n "const.*Modal.*=" web/src/components -A 5 | grep -E "const.*Modal|useTranslation|^[0-9]+-.*t," | head -40Repository: QuantumNous/new-api
Length of output: 4500
🏁 Script executed:
# Look at how Form components are used elsewhere - check if value/onChange pattern is consistent
rg -n "Form.Input\|Form.DatePicker\|Form.Select" web/src/components -A 3 | grep -E "value=|onChange=" | head -30Repository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
# Check if there's any usage of formApi in the entire codebase
rg -n "formApi\|formRef.current" web/src --max-count 10Repository: QuantumNous/new-api
Length of output: 45
🏁 Script executed:
# Verify the actual behavior: when username updates from async, does the form field receive it?
# Check the complete async user_id lookup effect
sed -n '199,263p' web/src/hooks/dashboard/useDashboardData.jsRepository: QuantumNous/new-api
Length of output: 1951
Use useTranslation() hook directly in this component instead of receiving t as a prop.
SearchModal should follow the i18n guideline by importing and calling const { t } = useTranslation() locally, consistent with other components like StatsCards and EditVendorModal. This eliminates prop drilling and aligns with the frontend i18n pattern specified in the coding guidelines.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/components/dashboard/modals/SearchModal.jsx` around lines 97 - 133,
SearchModal currently expects a t prop for translations; refactor it to import
and call const { t } = useTranslation() within the SearchModal component instead
of using the passed-in t prop. Remove t from the component's props (and any
PropTypes/TS types) and update usages inside JSX (e.g., labels/placeholders in
Form.RadioGroup, createFormField calls for 'username' and 'user_id') to use the
locally scoped t; keep existing handlers like handleInputChange and form field
symbols (userSearchType, username, user_id, createFormField, Form.RadioGroup,
Form.Input, Form.InputNumber) unchanged. Ensure the component imports
useTranslation from react-i18next.
| if (model_name) { | ||
| params.set('model_name', model_name); | ||
| } |
There was a problem hiding this comment.
Trim model_name before appending it to the query.
The backend model filter is an exact model_name = ? match, so pasted leading/trailing whitespace here turns a valid search into an empty dataset. Normalizing it the same way as username avoids surprising misses.
✂️ Suggested fix
- if (model_name) {
- params.set('model_name', model_name);
+ const normalizedModelName = model_name?.trim();
+ if (normalizedModelName) {
+ params.set('model_name', normalizedModelName);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/hooks/dashboard/useDashboardData.js` around lines 284 - 286, Trim the
model_name before adding it to the URL params in the useDashboardData hook:
replace the direct use of model_name with a trimmed value (e.g., const
trimmedModel = model_name.trim()) and then call params.set('model_name',
trimmedModel) (and only set it when trimmedModel is non-empty) so the backend
exact-match filter behaves like username normalization.

功能描述
在使用日志统计接口和数据看板前端页面中新增 Token 用量的详细统计,方便管理员和用户查看某个用户/模型的具体 Token 消耗情况。
修改内容
后端
model/log.go:Stat 结构体新增prompt_tokens、completion_tokens、token字段,SQL 聚合使用COALESCE防止 NULLmodel/usedata.go:新增GetQuotaDataByUsernameAndModel和GetQuotaDataByUserIdAndModel查询函数,支持按模型名称过滤controller/log.go:API 响应新增 token 统计字段controller/usedata.go:新增user_id和model_name查询参数支持前端
其他
使用方法
Summary by CodeRabbit
New Features
Improvements
Localization