diff --git a/backend/internal/pkg/usagestats/usage_log_types.go b/backend/internal/pkg/usagestats/usage_log_types.go index 746188ea..8826c048 100644 --- a/backend/internal/pkg/usagestats/usage_log_types.go +++ b/backend/internal/pkg/usagestats/usage_log_types.go @@ -57,25 +57,28 @@ type DashboardStats struct { // TrendDataPoint represents a single point in trend data type TrendDataPoint struct { - Date string `json:"date"` - Requests int64 `json:"requests"` - InputTokens int64 `json:"input_tokens"` - OutputTokens int64 `json:"output_tokens"` - CacheTokens int64 `json:"cache_tokens"` - TotalTokens int64 `json:"total_tokens"` - Cost float64 `json:"cost"` // 标准计费 - ActualCost float64 `json:"actual_cost"` // 实际扣除 + Date string `json:"date"` + Requests int64 `json:"requests"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheCreationTokens int64 `json:"cache_creation_tokens"` + CacheReadTokens int64 `json:"cache_read_tokens"` + TotalTokens int64 `json:"total_tokens"` + Cost float64 `json:"cost"` // 标准计费 + ActualCost float64 `json:"actual_cost"` // 实际扣除 } // ModelStat represents usage statistics for a single model type ModelStat struct { - Model string `json:"model"` - Requests int64 `json:"requests"` - InputTokens int64 `json:"input_tokens"` - OutputTokens int64 `json:"output_tokens"` - TotalTokens int64 `json:"total_tokens"` - Cost float64 `json:"cost"` // 标准计费 - ActualCost float64 `json:"actual_cost"` // 实际扣除 + Model string `json:"model"` + Requests int64 `json:"requests"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CacheCreationTokens int64 `json:"cache_creation_tokens"` + CacheReadTokens int64 `json:"cache_read_tokens"` + TotalTokens int64 `json:"total_tokens"` + Cost float64 `json:"cost"` // 标准计费 + ActualCost float64 `json:"actual_cost"` // 实际扣除 } // GroupStat represents usage statistics for a single group diff --git a/backend/internal/repository/usage_log_repo.go b/backend/internal/repository/usage_log_repo.go index 44079a55..7fc11b78 100644 --- a/backend/internal/repository/usage_log_repo.go +++ b/backend/internal/repository/usage_log_repo.go @@ -1363,7 +1363,8 @@ func (r *usageLogRepository) GetUserUsageTrendByUserID(ctx context.Context, user COUNT(*) as requests, COALESCE(SUM(input_tokens), 0) as input_tokens, COALESCE(SUM(output_tokens), 0) as output_tokens, - COALESCE(SUM(cache_creation_tokens + cache_read_tokens), 0) as cache_tokens, + COALESCE(SUM(cache_creation_tokens), 0) as cache_creation_tokens, + COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens, COALESCE(SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens), 0) as total_tokens, COALESCE(SUM(total_cost), 0) as cost, COALESCE(SUM(actual_cost), 0) as actual_cost @@ -1401,6 +1402,8 @@ func (r *usageLogRepository) GetUserModelStats(ctx context.Context, userID int64 COUNT(*) as requests, COALESCE(SUM(input_tokens), 0) as input_tokens, COALESCE(SUM(output_tokens), 0) as output_tokens, + COALESCE(SUM(cache_creation_tokens), 0) as cache_creation_tokens, + COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens, COALESCE(SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens), 0) as total_tokens, COALESCE(SUM(total_cost), 0) as cost, COALESCE(SUM(actual_cost), 0) as actual_cost @@ -1664,7 +1667,8 @@ func (r *usageLogRepository) GetUsageTrendWithFilters(ctx context.Context, start COUNT(*) as requests, COALESCE(SUM(input_tokens), 0) as input_tokens, COALESCE(SUM(output_tokens), 0) as output_tokens, - COALESCE(SUM(cache_creation_tokens + cache_read_tokens), 0) as cache_tokens, + COALESCE(SUM(cache_creation_tokens), 0) as cache_creation_tokens, + COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens, COALESCE(SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens), 0) as total_tokens, COALESCE(SUM(total_cost), 0) as cost, COALESCE(SUM(actual_cost), 0) as actual_cost @@ -1747,7 +1751,8 @@ func (r *usageLogRepository) getUsageTrendFromAggregates(ctx context.Context, st total_requests as requests, input_tokens, output_tokens, - (cache_creation_tokens + cache_read_tokens) as cache_tokens, + cache_creation_tokens, + cache_read_tokens, (input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens) as total_tokens, total_cost as cost, actual_cost @@ -1762,7 +1767,8 @@ func (r *usageLogRepository) getUsageTrendFromAggregates(ctx context.Context, st total_requests as requests, input_tokens, output_tokens, - (cache_creation_tokens + cache_read_tokens) as cache_tokens, + cache_creation_tokens, + cache_read_tokens, (input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens) as total_tokens, total_cost as cost, actual_cost @@ -1806,6 +1812,8 @@ func (r *usageLogRepository) GetModelStatsWithFilters(ctx context.Context, start COUNT(*) as requests, COALESCE(SUM(input_tokens), 0) as input_tokens, COALESCE(SUM(output_tokens), 0) as output_tokens, + COALESCE(SUM(cache_creation_tokens), 0) as cache_creation_tokens, + COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens, COALESCE(SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens), 0) as total_tokens, COALESCE(SUM(total_cost), 0) as cost, %s @@ -2622,7 +2630,8 @@ func scanTrendRows(rows *sql.Rows) ([]TrendDataPoint, error) { &row.Requests, &row.InputTokens, &row.OutputTokens, - &row.CacheTokens, + &row.CacheCreationTokens, + &row.CacheReadTokens, &row.TotalTokens, &row.Cost, &row.ActualCost, @@ -2646,6 +2655,8 @@ func scanModelStatsRows(rows *sql.Rows) ([]ModelStat, error) { &row.Requests, &row.InputTokens, &row.OutputTokens, + &row.CacheCreationTokens, + &row.CacheReadTokens, &row.TotalTokens, &row.Cost, &row.ActualCost, diff --git a/backend/internal/repository/usage_log_repo_request_type_test.go b/backend/internal/repository/usage_log_repo_request_type_test.go index 54eb81e1..53fb7227 100644 --- a/backend/internal/repository/usage_log_repo_request_type_test.go +++ b/backend/internal/repository/usage_log_repo_request_type_test.go @@ -125,7 +125,7 @@ func TestUsageLogRepositoryGetUsageTrendWithFiltersRequestTypePriority(t *testin mock.ExpectQuery("AND \\(request_type = \\$3 OR \\(request_type = 0 AND stream = TRUE AND openai_ws_mode = FALSE\\)\\)"). WithArgs(start, end, requestType). - WillReturnRows(sqlmock.NewRows([]string{"date", "requests", "input_tokens", "output_tokens", "cache_tokens", "total_tokens", "cost", "actual_cost"})) + WillReturnRows(sqlmock.NewRows([]string{"date", "requests", "input_tokens", "output_tokens", "cache_creation_tokens", "cache_read_tokens", "total_tokens", "cost", "actual_cost"})) trend, err := repo.GetUsageTrendWithFilters(context.Background(), start, end, "day", 0, 0, 0, 0, "", &requestType, &stream, nil) require.NoError(t, err) @@ -144,7 +144,7 @@ func TestUsageLogRepositoryGetModelStatsWithFiltersRequestTypePriority(t *testin mock.ExpectQuery("AND \\(request_type = \\$3 OR \\(request_type = 0 AND openai_ws_mode = TRUE\\)\\)"). WithArgs(start, end, requestType). - WillReturnRows(sqlmock.NewRows([]string{"model", "requests", "input_tokens", "output_tokens", "total_tokens", "cost", "actual_cost"})) + WillReturnRows(sqlmock.NewRows([]string{"model", "requests", "input_tokens", "output_tokens", "cache_creation_tokens", "cache_read_tokens", "total_tokens", "cost", "actual_cost"})) stats, err := repo.GetModelStatsWithFilters(context.Background(), start, end, 0, 0, 0, 0, &requestType, &stream, nil) require.NoError(t, err) diff --git a/frontend/src/components/charts/TokenUsageTrend.vue b/frontend/src/components/charts/TokenUsageTrend.vue index d9ceda87..a255fb03 100644 --- a/frontend/src/components/charts/TokenUsageTrend.vue +++ b/frontend/src/components/charts/TokenUsageTrend.vue @@ -63,7 +63,8 @@ const chartColors = computed(() => ({ grid: isDarkMode.value ? '#374151' : '#e5e7eb', input: '#3b82f6', output: '#10b981', - cache: '#f59e0b' + cacheCreation: '#f59e0b', + cacheRead: '#06b6d4' })) const chartData = computed(() => { @@ -89,10 +90,18 @@ const chartData = computed(() => { tension: 0.3 }, { - label: 'Cache', - data: props.trendData.map((d) => d.cache_tokens), - borderColor: chartColors.value.cache, - backgroundColor: `${chartColors.value.cache}20`, + label: 'Cache Creation', + data: props.trendData.map((d) => d.cache_creation_tokens), + borderColor: chartColors.value.cacheCreation, + backgroundColor: `${chartColors.value.cacheCreation}20`, + fill: true, + tension: 0.3 + }, + { + label: 'Cache Read', + data: props.trendData.map((d) => d.cache_read_tokens), + borderColor: chartColors.value.cacheRead, + backgroundColor: `${chartColors.value.cacheRead}20`, fill: true, tension: 0.3 } diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index cb388600..055998a7 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -133,6 +133,8 @@ export default { requests: 'Requests', inputTokens: 'Input Tokens', outputTokens: 'Output Tokens', + cacheCreationTokens: 'Cache Creation', + cacheReadTokens: 'Cache Read', totalTokens: 'Total Tokens', cost: 'Cost', // Status @@ -155,11 +157,19 @@ export default { subscriptionExpires: 'Subscription Expires', // Usage stat cells todayRequests: 'Today Requests', + todayInputTokens: 'Today Input', + todayOutputTokens: 'Today Output', todayTokens: 'Today Tokens', + todayCacheCreation: 'Today Cache Creation', + todayCacheRead: 'Today Cache Read', todayCost: 'Today Cost', rpmTpm: 'RPM / TPM', totalRequests: 'Total Requests', + totalInputTokens: 'Total Input', + totalOutputTokens: 'Total Output', totalTokensLabel: 'Total Tokens', + totalCacheCreation: 'Total Cache Creation', + totalCacheRead: 'Total Cache Read', totalCost: 'Total Cost', avgDuration: 'Avg Duration', // Messages diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 7c208aa9..cc203adb 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -133,6 +133,8 @@ export default { requests: '请求数', inputTokens: '输入 Tokens', outputTokens: '输出 Tokens', + cacheCreationTokens: '缓存创建', + cacheReadTokens: '缓存读取', totalTokens: '总 Tokens', cost: '费用', // Status @@ -155,11 +157,19 @@ export default { subscriptionExpires: '订阅到期', // Usage stat cells todayRequests: '今日请求', + todayInputTokens: '今日输入', + todayOutputTokens: '今日输出', todayTokens: '今日 Tokens', + todayCacheCreation: '今日缓存创建', + todayCacheRead: '今日缓存读取', todayCost: '今日费用', rpmTpm: 'RPM / TPM', totalRequests: '累计请求', + totalInputTokens: '累计输入', + totalOutputTokens: '累计输出', totalTokensLabel: '累计 Tokens', + totalCacheCreation: '累计缓存创建', + totalCacheRead: '累计缓存读取', totalCost: '累计费用', avgDuration: '平均耗时', // Messages diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index cdc4953a..915822f0 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -1098,7 +1098,8 @@ export interface TrendDataPoint { requests: number input_tokens: number output_tokens: number - cache_tokens: number + cache_creation_tokens: number + cache_read_tokens: number total_tokens: number cost: number // 标准计费 actual_cost: number // 实际扣除 @@ -1109,6 +1110,8 @@ export interface ModelStat { requests: number input_tokens: number output_tokens: number + cache_creation_tokens: number + cache_read_tokens: number total_tokens: number cost: number // 标准计费 actual_cost: number // 实际扣除 diff --git a/frontend/src/views/KeyUsageView.vue b/frontend/src/views/KeyUsageView.vue index a061ad9f..755f1966 100644 --- a/frontend/src/views/KeyUsageView.vue +++ b/frontend/src/views/KeyUsageView.vue @@ -302,6 +302,8 @@ {{ t('keyUsage.requests') }} {{ t('keyUsage.inputTokens') }} {{ t('keyUsage.outputTokens') }} + {{ t('keyUsage.cacheCreationTokens') }} + {{ t('keyUsage.cacheReadTokens') }} {{ t('keyUsage.totalTokens') }} {{ t('keyUsage.cost') }} @@ -316,6 +318,8 @@ {{ fmtNum(m.requests) }} {{ fmtNum(m.input_tokens) }} {{ fmtNum(m.output_tokens) }} + {{ fmtNum(m.cache_creation_tokens) }} + {{ fmtNum(m.cache_read_tokens) }} {{ fmtNum(m.total_tokens) }} {{ usd(m.actual_cost != null ? m.actual_cost : m.cost) }} @@ -694,11 +698,19 @@ const usageStatCells = computed(() => { return [ { label: t('keyUsage.todayRequests'), value: fmtNum(today.requests) }, + { label: t('keyUsage.todayInputTokens'), value: fmtNum(today.input_tokens) }, + { label: t('keyUsage.todayOutputTokens'), value: fmtNum(today.output_tokens) }, { label: t('keyUsage.todayTokens'), value: fmtNum(today.total_tokens) }, + { label: t('keyUsage.todayCacheCreation'), value: fmtNum(today.cache_creation_tokens) }, + { label: t('keyUsage.todayCacheRead'), value: fmtNum(today.cache_read_tokens) }, { label: t('keyUsage.todayCost'), value: usd(today.actual_cost) }, { label: t('keyUsage.rpmTpm'), value: `${usage.rpm || 0} / ${usage.tpm || 0}` }, { label: t('keyUsage.totalRequests'), value: fmtNum(total.requests) }, + { label: t('keyUsage.totalInputTokens'), value: fmtNum(total.input_tokens) }, + { label: t('keyUsage.totalOutputTokens'), value: fmtNum(total.output_tokens) }, { label: t('keyUsage.totalTokensLabel'), value: fmtNum(total.total_tokens) }, + { label: t('keyUsage.totalCacheCreation'), value: fmtNum(total.cache_creation_tokens) }, + { label: t('keyUsage.totalCacheRead'), value: fmtNum(total.cache_read_tokens) }, { label: t('keyUsage.totalCost'), value: usd(total.actual_cost) }, { label: t('keyUsage.avgDuration'), value: usage.average_duration_ms ? `${Math.round(usage.average_duration_ms)} ms` : '-' }, ] diff --git a/frontend/src/views/user/UsageView.vue b/frontend/src/views/user/UsageView.vue index ff875325..4bd5f6d8 100644 --- a/frontend/src/views/user/UsageView.vue +++ b/frontend/src/views/user/UsageView.vue @@ -113,6 +113,9 @@
+