feat(logs): overhaul stats bar with i18n, clickable filters, and auto… - #6465
feat(logs): overhaul stats bar with i18n, clickable filters, and auto…#6465Tsuki-wz wants to merge 1 commit into
Conversation
…-refresh - Fix SSE idle timeout for Claude extended-thinking requests by adding IsThinking to RelayInfo and a THINKING_STREAMING_TIMEOUT env var (default 900s) - Always record client IP on consume logs; remove per-user RecordIpLog guard - Fix log stats query failing on SQLite/PostgreSQL by replacing CAST(... AS SIGNED) with dialect-aware CAST(... AS INTEGER) - Today Req/RPM/TPM badges navigate to today's time range on click; Errors badge filters to error-type logs on click - Add auto-refresh cycle button (off → 30s → 60s → 5m) with spinner - Add stats bar and auto-refresh i18n keys for zh, zh-TW, en, ja, ru, fr, vi
WalkthroughThe change adds thinking-aware streaming timeout configuration and extends usage logs with IP capture, filtering, aggregate statistics, interactive metrics, auto-refresh, and localized labels. ChangesThinking-aware streaming timeout
Usage log filtering and analytics
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant ClaudeHelper
participant RelayInfo
participant StreamScannerHandler
ClaudeHelper->>RelayInfo: set IsThinking=true
RelayInfo->>StreamScannerHandler: provide thinking state
StreamScannerHandler->>StreamScannerHandler: select thinking timeout
sequenceDiagram
participant UsageLogsClient
participant LogController
participant LogModel
participant LogsDatabase
UsageLogsClient->>LogController: request logs or statistics with ip
LogController->>LogModel: pass IP and query filters
LogModel->>LogsDatabase: filter and aggregate logs
LogsDatabase-->>LogModel: logs and statistics
LogModel-->>LogController: return results
LogController-->>UsageLogsClient: return data
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ 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: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/src/features/usage-logs/components/common-logs-filter-bar.tsx (1)
90-104: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winIP filter isn't wired into
searchState/sourceKey/active-filter counts.
ipwas added toCommonLogFiltersand rendered in the UI (Line 413), but the state plumbing that hydrates it from the URL was missed:
buildSearchSourceKey's hash array (Lines 93-104) never includesvalues.ipdespite the type accepting it (Line 90).sourceValues(Lines 126-137) and thefiltersobject (Lines 138-150) never readsearchParams.ip.- The
useMemodeps (Lines 156-167) don't includesearchParams.ip.hasExpandedFilters/expandedFilterCount(Lines 237-254) don't checkfilters.ip.Net effect: after a fresh mount or page reload with
?ip=...in the URL, the IP input renders empty and the "active filters" badge undercounts, even though the backend request is still filtered by IP.🐛 Proposed fix
const sourceValues = { startTime: searchParams.startTime, endTime: searchParams.endTime, channel: searchParams.channel, model: searchParams.model, token: searchParams.token, group: searchParams.group, username: searchParams.username, requestId: searchParams.requestId, upstreamRequestId: searchParams.upstreamRequestId, + ip: searchParams.ip, type: searchParams.type, } const filters: CommonLogFilters = { ... upstreamRequestId: searchParams.upstreamRequestId || undefined, + ip: searchParams.ip || undefined, } ... }, [ searchParams.startTime, ... searchParams.upstreamRequestId, + searchParams.ip, searchParams.type, ]) function buildSearchSourceKey(values: { ... }) { return [ ... values.upstreamRequestId, + values.ip, Array.isArray(values.type) ? values.type.join(',') : values.type, ] .map((value) => String(value ?? '')) .join('\u001f') } const hasExpandedFilters = !!filters.token || !!filters.username || !!filters.channel || !!filters.requestId || - !!filters.upstreamRequestId + !!filters.upstreamRequestId || + !!filters.ip const expandedFilterCount = [ filters.token, isAdmin ? filters.username : undefined, isAdmin ? filters.channel : undefined, filters.requestId, filters.upstreamRequestId, + filters.ip, ].filter(Boolean).lengthAlso applies to: 126-150, 156-167, 237-254
🤖 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/src/features/usage-logs/components/common-logs-filter-bar.tsx` around lines 90 - 104, Wire the IP filter through the common log filter state: include values.ip in buildSearchSourceKey, read searchParams.ip into sourceValues and the filters object, and add searchParams.ip to the related useMemo dependencies. Update hasExpandedFilters and expandedFilterCount to treat filters.ip as an active expanded filter so URL hydration, source keys, and counts remain consistent.
🧹 Nitpick comments (1)
model/log.go (1)
646-741: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoffFour sequential round-trips per stats request.
SumUsedQuotanow issues four separate queries (quota, realtime rpm/tpm, range aggregate, today aggregate) againstLOG_DBin sequence. With the new auto-refresh feature (30s/60s/5m) hitting this endpoint repeatedly, consider combining these into fewer round trips (e.g., a single query with multiple CASE-based aggregates, orUNION ALLsubqueries) to reduce load, especially on the log database.🤖 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 `@model/log.go` around lines 646 - 741, Reduce the four sequential LOG_DB queries in SumUsedQuota to fewer round trips by combining the quota, realtime rpm/tpm, range aggregate, and today aggregate calculations into a consolidated query or equivalent UNION ALL approach. Preserve all existing filters, time-window semantics, metric values, database-specific token casting, and error behavior while continuing to populate Stat.
🤖 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 `@common/init.go`:
- Line 178: Update the initialization of constant.ThinkingStreamingTimeout in
common/init.go to validate or clamp the parsed THINKING_STREAMING_TIMEOUT value
before publishing it, using a safe upper bound that prevents overflow or
unexpected normalization when relay/helper/stream_scanner.go passes it to
time.NewTicker. Preserve the existing default behavior for valid values.
In `@web/src/i18n/locales/en.json`:
- Around line 5217-5236: Remove the later duplicate "Auto refresh" entry from
the locale object in en.json, preserving the earlier existing definition and all
neighboring translation keys.
In `@web/src/i18n/locales/fr.json`:
- Around line 5217-5236: Remove the duplicate "Auto refresh" entry from the
locale additions and reuse the existing key/value earlier in fr.json; if the
shorter wording is required, update that single existing translation instead of
declaring the key again.
In `@web/src/i18n/locales/vi.json`:
- Line 5235: Remove the duplicate "Auto refresh" entry from the locale object,
keeping the existing definition near the earlier occurrence and leaving all
other translations unchanged.
- Around line 5220-5222: Update the Vietnamese locale entries for “Range RPM”
and “Range TPM” to use the natural translations “RPM trong khoảng” and “TPM
trong khoảng”, while leaving “Today RPM” unchanged.
In `@web/src/i18n/locales/zh-TW.json`:
- Line 5235: Remove the duplicate "Auto refresh" entry near the later locale
section in zh-TW.json, and update the existing earlier "Auto refresh" entry if
its translation needs changing. Ensure the JSON contains exactly one key with
the intended translation.
---
Outside diff comments:
In `@web/src/features/usage-logs/components/common-logs-filter-bar.tsx`:
- Around line 90-104: Wire the IP filter through the common log filter state:
include values.ip in buildSearchSourceKey, read searchParams.ip into
sourceValues and the filters object, and add searchParams.ip to the related
useMemo dependencies. Update hasExpandedFilters and expandedFilterCount to treat
filters.ip as an active expanded filter so URL hydration, source keys, and
counts remain consistent.
---
Nitpick comments:
In `@model/log.go`:
- Around line 646-741: Reduce the four sequential LOG_DB queries in SumUsedQuota
to fewer round trips by combining the quota, realtime rpm/tpm, range aggregate,
and today aggregate calculations into a consolidated query or equivalent UNION
ALL approach. Preserve all existing filters, time-window semantics, metric
values, database-specific token casting, and error behavior while continuing to
populate Stat.
🪄 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 Plus
Run ID: 71022455-182d-48ca-b474-d5337a945992
📒 Files selected for processing (22)
common/init.goconstant/env.gocontroller/log.gomodel/log.gorelay/claude_handler.gorelay/common/relay_info.gorelay/helper/stream_scanner.goweb/src/features/usage-logs/components/columns/common-logs-columns.tsxweb/src/features/usage-logs/components/common-logs-filter-bar.tsxweb/src/features/usage-logs/components/common-logs-stats.tsxweb/src/features/usage-logs/constants.tsweb/src/features/usage-logs/lib/filter.tsweb/src/features/usage-logs/lib/utils.tsweb/src/features/usage-logs/types.tsweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-TW.jsonweb/src/i18n/locales/zh.jsonweb/src/routes/_authenticated/usage-logs/$section.tsx
|
|
||
| func initConstantEnv() { | ||
| constant.StreamingTimeout = GetEnvOrDefault("STREAMING_TIMEOUT", 300) | ||
| constant.ThinkingStreamingTimeout = GetEnvOrDefault("THINKING_STREAMING_TIMEOUT", 900) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n 'func GetEnvOrDefault|THINKING_STREAMING_TIMEOUT|time\.Duration\(.*time\.Second|time\.NewTicker' \
common constant relayRepository: QuantumNous/new-api
Length of output: 1985
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== common/env.go =="
cat -n common/env.go
echo
echo "== common/init.go (near init timeout settings) =="
sed -n '90,185p' common/init.go | cat -n
echo
echo "== relay/helper/stream_scanner.go (scanner timeout usage) =="
sed -n '75,105p' relay/helper/stream_scanner.go | cat -n
echo
echo "== constant package definitions/usages =="
rg -n 'var ThinkingStreamingTimeout|ThinkingStreamingTimeout|type Duration|Duration' constant relay helper common | head -n 80
echo
echo "== Go Duration overflow probe =="
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
cat > "$tmpdir/main.go" <<'EOF'
package main
import (
"fmt"
"time"
)
func main() {
values := []int{900, 1<<30, 2147483647, (1 << 31), -1 << 31}
for _, v := range values {
d := time.Duration(v) * time.Second
fmt.Printf("%d -> %d ns\n", v, d)
}
}
EOF
go run "$tmpdir/main.go"Repository: QuantumNous/new-api
Length of output: 15491
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package declarations for relay/helper and constant =="
sed -n '1,30p' relay/helper/stream_scanner.go | cat -n
fd -a '.*\.go$' relay/helper constant | while read -r f; do
echo "--- $f"
sed -n '1,60p' "$f" | cat -n
done
echo
echo "== Go Duration overflow probe =="
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
cat > "$tmpdir/main.go" <<'EOF'
package main
import (
"fmt"
"math"
"time"
)
func main() {
values := []int{900, 1 << 30, 2147483647, -1 << 31}
for _, v := range values {
d := time.Duration(v) * time.Second
fmt.Printf("%d -> %d\n", v, d)
}
fmt.Printf("MaxDurationSeconds=%d\n", time.Duration(math.MaxInt64/time.Second/2)*time.Second/time.Second)
panic(time.Duration(2147483647) * time.Second)
}
EOF
go run "$tmpdir/main.go" 2>&1 || trueRepository: QuantumNous/new-api
Length of output: 42741
Bound THINKING_STREAMING_TIMEOUT before publishing it.
common/init.go:178 stores the parsed value directly in constant.ThinkingStreamingTimeout, and relay/helper/stream_scanner.go:97 passes the converted time.Duration to time.NewTicker. Large configured values can overflow or normalize unexpectedly; normalize/reject values above a safe upper bound during initialization.
🤖 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 `@common/init.go` at line 178, Update the initialization of
constant.ThinkingStreamingTimeout in common/init.go to validate or clamp the
parsed THINKING_STREAMING_TIMEOUT value before publishing it, using a safe upper
bound that prevents overflow or unexpected normalization when
relay/helper/stream_scanner.go passes it to time.NewTicker. Preserve the
existing default behavior for valid values.
| "Zoom": "Zoom", | ||
| "Total Req": "Total Req", | ||
| "Today Req": "Today Req", | ||
| "Range RPM": "Range RPM", | ||
| "Today RPM": "Today RPM", | ||
| "Range TPM": "Range TPM", | ||
| "Today TPM": "Today TPM", | ||
| "Avg Latency": "Avg Latency", | ||
| "Errors": "Errors", | ||
| "Total consume requests in selected range": "Total consume requests in selected range", | ||
| "Consume requests since today 00:00": "Consume requests since today 00:00", | ||
| "Average requests per minute over selected range": "Average requests per minute over selected range", | ||
| "Average requests per minute since today 00:00": "Average requests per minute since today 00:00", | ||
| "Average tokens per minute over selected range": "Average tokens per minute over selected range", | ||
| "Average tokens per minute since today 00:00": "Average tokens per minute since today 00:00", | ||
| "Average response time for consume requests": "Average response time for consume requests", | ||
| "Error log count in selected range": "Error log count in selected range", | ||
| "Auto refresh: Off. Click to enable.": "Auto refresh: Off. Click to enable.", | ||
| "Auto refresh": "Auto refresh" | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Duplicate "Auto refresh" key.
The key "Auto refresh" is already defined earlier in this file (existing entry, unchanged) and is redeclared here with the same value. Harmless in English, but see the companion note on fr.json where the duplicate carries a different value.
🧹 Suggested fix
"Auto refresh: Off. Click to enable.": "Auto refresh: Off. Click to enable.",
- "Auto refresh": "Auto refresh"
+ "Auto refresh": "Auto refresh"Reuse the existing "Auto refresh" key (already present earlier in the file) instead of re-declaring it here; drop this duplicate line entirely.
🤖 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/src/i18n/locales/en.json` around lines 5217 - 5236, Remove the later
duplicate "Auto refresh" entry from the locale object in en.json, preserving the
earlier existing definition and all neighboring translation keys.
| "Zoom": "Zoom", | ||
| "Total Req": "Req. totales", | ||
| "Today Req": "Req. aujourd'hui", | ||
| "Range RPM": "RPM plage", | ||
| "Today RPM": "RPM aujourd'hui", | ||
| "Range TPM": "TPM plage", | ||
| "Today TPM": "TPM aujourd'hui", | ||
| "Avg Latency": "Latence moy.", | ||
| "Errors": "Erreurs", | ||
| "Total consume requests in selected range": "Total des requêtes de consommation sur la plage sélectionnée", | ||
| "Consume requests since today 00:00": "Requêtes de consommation depuis 00:00 aujourd'hui", | ||
| "Average requests per minute over selected range": "Moyenne des requêtes par minute sur la plage sélectionnée", | ||
| "Average requests per minute since today 00:00": "Moyenne des requêtes par minute depuis 00:00 aujourd'hui", | ||
| "Average tokens per minute over selected range": "Moyenne des tokens par minute sur la plage sélectionnée", | ||
| "Average tokens per minute since today 00:00": "Moyenne des tokens par minute depuis 00:00 aujourd'hui", | ||
| "Average response time for consume requests": "Temps de réponse moyen pour les requêtes de consommation", | ||
| "Error log count in selected range": "Nombre d'erreurs dans la plage sélectionnée", | ||
| "Auto refresh: Off. Click to enable.": "Actualisation auto : désactivée. Cliquez pour activer.", | ||
| "Auto refresh": "Actualisation auto" | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Duplicate "Auto refresh" key silently overrides the existing translation.
"Auto refresh" already exists earlier in this file as "Actualisation automatique". This diff re-declares the same key at the end of the file with a different value, "Actualisation auto". Because JSON objects can't hold two values for one key, whichever loader parses this (e.g. i18next's JSON backend) will keep only the last occurrence — silently changing the translation for any other feature that already uses the "Auto refresh" key, not just this new stats bar.
🧹 Suggested fix
"Auto refresh: Off. Click to enable.": "Actualisation auto : désactivée. Cliquez pour activer.",
- "Auto refresh": "Actualisation auto"
+ "Auto refresh": "Actualisation auto"Drop this duplicate line and reuse the pre-existing "Auto refresh" key/value (or, if the shorter phrasing is intentional, update the single existing entry instead of adding a second one).
🤖 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/src/i18n/locales/fr.json` around lines 5217 - 5236, Remove the duplicate
"Auto refresh" entry from the locale additions and reuse the existing key/value
earlier in fr.json; if the shorter wording is required, update that single
existing translation instead of declaring the key again.
| "Range RPM": "RPM khoảng", | ||
| "Today RPM": "RPM hôm nay", | ||
| "Range TPM": "TPM khoảng", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use natural Vietnamese wording for range metrics.
"RPM khoảng" and "TPM khoảng" are unclear. Translate them as "RPM trong khoảng" and "TPM trong khoảng".
🤖 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/src/i18n/locales/vi.json` around lines 5220 - 5222, Update the Vietnamese
locale entries for “Range RPM” and “Range TPM” to use the natural translations
“RPM trong khoảng” and “TPM trong khoảng”, while leaving “Today RPM” unchanged.
| "Average response time for consume requests": "Thời gian phản hồi trung bình cho yêu cầu tiêu thụ", | ||
| "Error log count in selected range": "Số lượng lỗi trong khoảng thời gian đã chọn", | ||
| "Auto refresh: Off. Click to enable.": "Tự động làm mới: Tắt. Nhấn để bật.", | ||
| "Auto refresh": "Tự động làm mới" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Remove the duplicate Auto refresh key.
"Auto refresh" is already defined at Line 506. Keeping a second object key can cause inconsistent parser/tooling behavior and is unnecessary.
🤖 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/src/i18n/locales/vi.json` at line 5235, Remove the duplicate "Auto
refresh" entry from the locale object, keeping the existing definition near the
earlier occurrence and leaving all other translations unchanged.
| "Average response time for consume requests": "消費請求的平均回應時間", | ||
| "Error log count in selected range": "所選時間段內的錯誤日誌數", | ||
| "Auto refresh: Off. Click to enable.": "自動刷新:關閉,點擊開啟", | ||
| "Auto refresh": "自動刷新" |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Remove the duplicate Auto refresh key.
"Auto refresh" already exists at Line 506. This duplicate may fail JSON validation or silently overwrite the earlier translation, while also introducing inconsistent wording. Update the existing entry instead of adding a second one.
🤖 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/src/i18n/locales/zh-TW.json` at line 5235, Remove the duplicate "Auto
refresh" entry near the later locale section in zh-TW.json, and update the
existing earlier "Auto refresh" entry if its translation needs changing. Ensure
the JSON contains exactly one key with the intended translation.
📝 Description
1. Fix Claude extended-thinking requests being cut off mid-stream
Added
IsThinkingtoRelayInfo, set when the request carries aThinkingparameter. The SSE idle timeout instream_scanner.gonowbranches on this flag: regular requests keep
STREAMING_TIMEOUT(default 300 s), thinking requests use a new
THINKING_STREAMING_TIMEOUTenv var (default 900 s). The upstream produces no SSE chunks during the
thinking phase by design, so the old fixed 300 s timeout incorrectly
treated silence as a stall and dropped the connection.
2. Always record client IP on consume logs
Removed the per-user
RecordIpLoggate. All consume logs now writec.ClientIP()unconditionally so the IP column is always visible toadmins.
3. Fix log stats returning all-zeros on SQLite / PostgreSQL
The stats query used
CAST(... AS SIGNED), which is MySQL-only syntax.SQLite and PostgreSQL reject it, causing the entire stats fetch to fail
silently and fall back to the zero default. Fixed by branching on
UsingLogDatabase: MySQL keepsSIGNED, all others useINTEGER.4. Clickable stat badges and auto-refresh on the stats bar
today (00:00 – now).
with a spinner while fetching.
5. i18n for new stats bar labels
Added translations for all new keys (stat labels, tooltips, auto-refresh
text) across seven locales: zh, zh-TW, en, ja, ru, fr, vi.
🚀 Type of change
🔗 Related Issue
✅ Checklist
go build); frontend passes type-check (bunx tsc --noEmit); all locale JSON files are valid.📸 Proof of Work
(Please attach a screenshot of the stats bar showing correct values,


clickable badge navigation, and the auto-refresh button in its active state.)
Summary by CodeRabbit