diff --git a/backend/internal/pkg/usagestats/usage_log_types.go b/backend/internal/pkg/usagestats/usage_log_types.go index 5d1f7911..93746844 100644 --- a/backend/internal/pkg/usagestats/usage_log_types.go +++ b/backend/internal/pkg/usagestats/usage_log_types.go @@ -56,8 +56,9 @@ type DashboardStats struct { TotalCacheCreationTokens int64 `json:"total_cache_creation_tokens"` TotalCacheReadTokens int64 `json:"total_cache_read_tokens"` TotalTokens int64 `json:"total_tokens"` - TotalCost float64 `json:"total_cost"` // 累计标准计费 - TotalActualCost float64 `json:"total_actual_cost"` // 累计实际扣除 + TotalCost float64 `json:"total_cost"` // 累计标准计费 + TotalActualCost float64 `json:"total_actual_cost"` // 累计实际扣除 + TotalAccountCost float64 `json:"total_account_cost"` // 累计账号成本 // 今日 Token 使用统计 TodayRequests int64 `json:"today_requests"` @@ -66,8 +67,9 @@ type DashboardStats struct { TodayCacheCreationTokens int64 `json:"today_cache_creation_tokens"` TodayCacheReadTokens int64 `json:"today_cache_read_tokens"` TodayTokens int64 `json:"today_tokens"` - TodayCost float64 `json:"today_cost"` // 今日标准计费 - TodayActualCost float64 `json:"today_actual_cost"` // 今日实际扣除 + TodayCost float64 `json:"today_cost"` // 今日标准计费 + TodayActualCost float64 `json:"today_actual_cost"` // 今日实际扣除 + TodayAccountCost float64 `json:"today_account_cost"` // 今日账号成本 // 系统运行统计 AverageDurationMs float64 `json:"average_duration_ms"` // 平均响应时间 @@ -99,8 +101,9 @@ type ModelStat struct { 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"` // 实际扣除 + Cost float64 `json:"cost"` // 标准计费 + ActualCost float64 `json:"actual_cost"` // 实际扣除 + AccountCost float64 `json:"account_cost"` // 账号成本 } // EndpointStat represents usage statistics for a single request endpoint. @@ -125,8 +128,9 @@ type GroupStat struct { GroupName string `json:"group_name"` Requests int64 `json:"requests"` TotalTokens int64 `json:"total_tokens"` - Cost float64 `json:"cost"` // 标准计费 - ActualCost float64 `json:"actual_cost"` // 实际扣除 + Cost float64 `json:"cost"` // 标准计费 + ActualCost float64 `json:"actual_cost"` // 实际扣除 + AccountCost float64 `json:"account_cost"` // 账号成本 } // UserUsageTrendPoint represents user usage trend data point diff --git a/backend/internal/repository/dashboard_aggregation_repo.go b/backend/internal/repository/dashboard_aggregation_repo.go index e82a73a3..5e09e75d 100644 --- a/backend/internal/repository/dashboard_aggregation_repo.go +++ b/backend/internal/repository/dashboard_aggregation_repo.go @@ -331,6 +331,7 @@ func (r *dashboardAggregationRepository) upsertHourlyAggregates(ctx context.Cont COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens, COALESCE(SUM(total_cost), 0) AS total_cost, COALESCE(SUM(actual_cost), 0) AS actual_cost, + COALESCE(SUM(COALESCE(account_stats_cost, total_cost) * COALESCE(account_rate_multiplier, 1)), 0) AS account_cost, COALESCE(SUM(COALESCE(duration_ms, 0)), 0) AS total_duration_ms FROM usage_logs WHERE created_at >= $1 AND created_at < $2 @@ -351,6 +352,7 @@ func (r *dashboardAggregationRepository) upsertHourlyAggregates(ctx context.Cont cache_read_tokens, total_cost, actual_cost, + account_cost, total_duration_ms, active_users, computed_at @@ -364,6 +366,7 @@ func (r *dashboardAggregationRepository) upsertHourlyAggregates(ctx context.Cont hourly.cache_read_tokens, hourly.total_cost, hourly.actual_cost, + hourly.account_cost, hourly.total_duration_ms, COALESCE(user_counts.active_users, 0) AS active_users, NOW() @@ -378,6 +381,7 @@ func (r *dashboardAggregationRepository) upsertHourlyAggregates(ctx context.Cont cache_read_tokens = EXCLUDED.cache_read_tokens, total_cost = EXCLUDED.total_cost, actual_cost = EXCLUDED.actual_cost, + account_cost = EXCLUDED.account_cost, total_duration_ms = EXCLUDED.total_duration_ms, active_users = EXCLUDED.active_users, computed_at = EXCLUDED.computed_at @@ -399,6 +403,7 @@ func (r *dashboardAggregationRepository) upsertDailyAggregates(ctx context.Conte COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens, COALESCE(SUM(total_cost), 0) AS total_cost, COALESCE(SUM(actual_cost), 0) AS actual_cost, + COALESCE(SUM(account_cost), 0) AS account_cost, COALESCE(SUM(total_duration_ms), 0) AS total_duration_ms FROM usage_dashboard_hourly WHERE bucket_start >= $1 AND bucket_start < $2 @@ -419,6 +424,7 @@ func (r *dashboardAggregationRepository) upsertDailyAggregates(ctx context.Conte cache_read_tokens, total_cost, actual_cost, + account_cost, total_duration_ms, active_users, computed_at @@ -432,6 +438,7 @@ func (r *dashboardAggregationRepository) upsertDailyAggregates(ctx context.Conte daily.cache_read_tokens, daily.total_cost, daily.actual_cost, + daily.account_cost, daily.total_duration_ms, COALESCE(user_counts.active_users, 0) AS active_users, NOW() @@ -446,6 +453,7 @@ func (r *dashboardAggregationRepository) upsertDailyAggregates(ctx context.Conte cache_read_tokens = EXCLUDED.cache_read_tokens, total_cost = EXCLUDED.total_cost, actual_cost = EXCLUDED.actual_cost, + account_cost = EXCLUDED.account_cost, total_duration_ms = EXCLUDED.total_duration_ms, active_users = EXCLUDED.active_users, computed_at = EXCLUDED.computed_at diff --git a/backend/internal/repository/usage_log_repo.go b/backend/internal/repository/usage_log_repo.go index f942a8e1..f3789910 100644 --- a/backend/internal/repository/usage_log_repo.go +++ b/backend/internal/repository/usage_log_repo.go @@ -1528,6 +1528,7 @@ func (r *usageLogRepository) fillDashboardUsageStatsAggregated(ctx context.Conte COALESCE(SUM(cache_read_tokens), 0) as total_cache_read_tokens, COALESCE(SUM(total_cost), 0) as total_cost, COALESCE(SUM(actual_cost), 0) as total_actual_cost, + COALESCE(SUM(account_cost), 0) as total_account_cost, COALESCE(SUM(total_duration_ms), 0) as total_duration_ms FROM usage_dashboard_daily ` @@ -1544,6 +1545,7 @@ func (r *usageLogRepository) fillDashboardUsageStatsAggregated(ctx context.Conte &stats.TotalCacheReadTokens, &stats.TotalCost, &stats.TotalActualCost, + &stats.TotalAccountCost, &totalDurationMs, ); err != nil { return err @@ -1562,6 +1564,7 @@ func (r *usageLogRepository) fillDashboardUsageStatsAggregated(ctx context.Conte cache_read_tokens as today_cache_read_tokens, total_cost as today_cost, actual_cost as today_actual_cost, + account_cost as today_account_cost, active_users as active_users FROM usage_dashboard_daily WHERE bucket_date = $1::date @@ -1578,6 +1581,7 @@ func (r *usageLogRepository) fillDashboardUsageStatsAggregated(ctx context.Conte &stats.TodayCacheReadTokens, &stats.TodayCost, &stats.TodayActualCost, + &stats.TodayAccountCost, &stats.ActiveUsers, ); err != nil { if err != sql.ErrNoRows { @@ -1613,6 +1617,7 @@ func (r *usageLogRepository) fillDashboardUsageStatsFromUsageLogs(ctx context.Co cache_read_tokens, total_cost, actual_cost, + COALESCE(account_stats_cost, total_cost) * COALESCE(account_rate_multiplier, 1) AS account_cost, COALESCE(duration_ms, 0) AS duration_ms FROM usage_logs WHERE created_at >= LEAST($1::timestamptz, $3::timestamptz) @@ -1626,6 +1631,7 @@ func (r *usageLogRepository) fillDashboardUsageStatsFromUsageLogs(ctx context.Co COALESCE(SUM(cache_read_tokens) FILTER (WHERE created_at >= $1::timestamptz AND created_at < $2::timestamptz), 0) AS total_cache_read_tokens, COALESCE(SUM(total_cost) FILTER (WHERE created_at >= $1::timestamptz AND created_at < $2::timestamptz), 0) AS total_cost, COALESCE(SUM(actual_cost) FILTER (WHERE created_at >= $1::timestamptz AND created_at < $2::timestamptz), 0) AS total_actual_cost, + COALESCE(SUM(account_cost) FILTER (WHERE created_at >= $1::timestamptz AND created_at < $2::timestamptz), 0) AS total_account_cost, COALESCE(SUM(duration_ms) FILTER (WHERE created_at >= $1::timestamptz AND created_at < $2::timestamptz), 0) AS total_duration_ms, COUNT(*) FILTER (WHERE created_at >= $3::timestamptz AND created_at < $4::timestamptz) AS today_requests, COALESCE(SUM(input_tokens) FILTER (WHERE created_at >= $3::timestamptz AND created_at < $4::timestamptz), 0) AS today_input_tokens, @@ -1633,7 +1639,8 @@ func (r *usageLogRepository) fillDashboardUsageStatsFromUsageLogs(ctx context.Co COALESCE(SUM(cache_creation_tokens) FILTER (WHERE created_at >= $3::timestamptz AND created_at < $4::timestamptz), 0) AS today_cache_creation_tokens, COALESCE(SUM(cache_read_tokens) FILTER (WHERE created_at >= $3::timestamptz AND created_at < $4::timestamptz), 0) AS today_cache_read_tokens, COALESCE(SUM(total_cost) FILTER (WHERE created_at >= $3::timestamptz AND created_at < $4::timestamptz), 0) AS today_cost, - COALESCE(SUM(actual_cost) FILTER (WHERE created_at >= $3::timestamptz AND created_at < $4::timestamptz), 0) AS today_actual_cost + COALESCE(SUM(actual_cost) FILTER (WHERE created_at >= $3::timestamptz AND created_at < $4::timestamptz), 0) AS today_actual_cost, + COALESCE(SUM(account_cost) FILTER (WHERE created_at >= $3::timestamptz AND created_at < $4::timestamptz), 0) AS today_account_cost FROM scoped ` var totalDurationMs int64 @@ -1649,6 +1656,7 @@ func (r *usageLogRepository) fillDashboardUsageStatsFromUsageLogs(ctx context.Co &stats.TotalCacheReadTokens, &stats.TotalCost, &stats.TotalActualCost, + &stats.TotalAccountCost, &totalDurationMs, &stats.TodayRequests, &stats.TodayInputTokens, @@ -1657,6 +1665,7 @@ func (r *usageLogRepository) fillDashboardUsageStatsFromUsageLogs(ctx context.Co &stats.TodayCacheReadTokens, &stats.TodayCost, &stats.TodayActualCost, + &stats.TodayAccountCost, ); err != nil { return err } @@ -2595,7 +2604,8 @@ func (r *usageLogRepository) GetUserModelStats(ctx context.Context, userID int64 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 + COALESCE(SUM(actual_cost), 0) as actual_cost, + COALESCE(SUM(COALESCE(account_stats_cost, total_cost) * COALESCE(account_rate_multiplier, 1)), 0) as account_cost FROM usage_logs WHERE user_id = $1 AND created_at >= $2 AND created_at < $3 GROUP BY model @@ -3002,6 +3012,7 @@ func (r *usageLogRepository) getModelStatsWithFiltersBySource(ctx context.Contex if accountID > 0 && userID == 0 && apiKeyID == 0 { actualCostExpr = "COALESCE(SUM(COALESCE(account_stats_cost, total_cost) * COALESCE(account_rate_multiplier, 1)), 0) as actual_cost" } + accountCostExpr := "COALESCE(SUM(COALESCE(account_stats_cost, total_cost) * COALESCE(account_rate_multiplier, 1)), 0) as account_cost" modelExpr := resolveModelDimensionExpression(source) query := fmt.Sprintf(` @@ -3014,10 +3025,11 @@ func (r *usageLogRepository) getModelStatsWithFiltersBySource(ctx context.Contex 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, %s FROM usage_logs WHERE created_at >= $1 AND created_at < $2 - `, modelExpr, actualCostExpr) + `, modelExpr, actualCostExpr, accountCostExpr) args := []any{startTime, endTime} if userID > 0 { @@ -3072,7 +3084,8 @@ func (r *usageLogRepository) GetGroupStatsWithFilters(ctx context.Context, start COUNT(*) as requests, COALESCE(SUM(ul.input_tokens + ul.output_tokens + ul.cache_creation_tokens + ul.cache_read_tokens), 0) as total_tokens, COALESCE(SUM(ul.total_cost), 0) as cost, - COALESCE(SUM(ul.actual_cost), 0) as actual_cost + COALESCE(SUM(ul.actual_cost), 0) as actual_cost, + COALESCE(SUM(COALESCE(ul.account_stats_cost, ul.total_cost) * COALESCE(ul.account_rate_multiplier, 1)), 0) as account_cost FROM usage_logs ul LEFT JOIN groups g ON g.id = ul.group_id WHERE ul.created_at >= $1 AND ul.created_at < $2 @@ -3123,6 +3136,7 @@ func (r *usageLogRepository) GetGroupStatsWithFilters(ctx context.Context, start &row.TotalTokens, &row.Cost, &row.ActualCost, + &row.AccountCost, ); err != nil { return nil, err } @@ -3392,9 +3406,7 @@ func (r *usageLogRepository) GetStatsWithFilters(ctx context.Context, filters Us ); err != nil { return nil, err } - if filters.AccountID > 0 { - stats.TotalAccountCost = &totalAccountCost - } + stats.TotalAccountCost = &totalAccountCost stats.TotalTokens = stats.TotalInputTokens + stats.TotalOutputTokens + stats.TotalCacheTokens start := time.Unix(0, 0).UTC() @@ -4272,6 +4284,7 @@ func scanModelStatsRows(rows *sql.Rows) ([]ModelStat, error) { &row.TotalTokens, &row.Cost, &row.ActualCost, + &row.AccountCost, ); err != nil { return nil, err } diff --git a/backend/migrations/107_add_account_cost_to_dashboard_tables.sql b/backend/migrations/107_add_account_cost_to_dashboard_tables.sql new file mode 100644 index 00000000..9f815a3f --- /dev/null +++ b/backend/migrations/107_add_account_cost_to_dashboard_tables.sql @@ -0,0 +1,5 @@ +-- Add account_cost column to dashboard aggregation tables for admin dashboard display. +-- account_cost = SUM(COALESCE(account_stats_cost, total_cost) * COALESCE(account_rate_multiplier, 1)) + +ALTER TABLE usage_dashboard_hourly ADD COLUMN IF NOT EXISTS account_cost DECIMAL(20, 10) NOT NULL DEFAULT 0; +ALTER TABLE usage_dashboard_daily ADD COLUMN IF NOT EXISTS account_cost DECIMAL(20, 10) NOT NULL DEFAULT 0; diff --git a/frontend/src/api/admin/usage.ts b/frontend/src/api/admin/usage.ts index 37df7553..7ad00742 100644 --- a/frontend/src/api/admin/usage.ts +++ b/frontend/src/api/admin/usage.ts @@ -17,7 +17,7 @@ export interface AdminUsageStatsResponse { total_tokens: number total_cost: number total_actual_cost: number - total_account_cost?: number + total_account_cost: number average_duration_ms: number endpoints?: EndpointStat[] upstream_endpoints?: EndpointStat[] diff --git a/frontend/src/components/admin/usage/UsageStatsCards.vue b/frontend/src/components/admin/usage/UsageStatsCards.vue index cd962a09..63eb6127 100644 --- a/frontend/src/components/admin/usage/UsageStatsCards.vue +++ b/frontend/src/components/admin/usage/UsageStatsCards.vue @@ -28,17 +28,14 @@
{{ t('usage.totalCost') }}
- ${{ ((stats?.total_account_cost ?? stats?.total_actual_cost) || 0).toFixed(4) }} + ${{ (stats?.total_actual_cost || 0).toFixed(4) }}
-- {{ t('usage.userBilled') }}: - ${{ (stats?.total_actual_cost || 0).toFixed(4) }} - · {{ t('usage.standardCost') }}: - ${{ (stats?.total_cost || 0).toFixed(4) }} -
-- {{ t('usage.standardCost') }}: - ${{ (stats?.total_cost || 0).toFixed(4) }} +
+ ${{ (stats?.total_account_cost || 0).toFixed(4) }} + {{ t('usage.accountCost') }} + · + ${{ (stats?.total_cost || 0).toFixed(4) }} + {{ t('usage.standardCost') }}
${{ formatCost(stats.today_actual_cost) }} + / + ${{ formatCost(stats.today_account_cost) }} + / - / ${{ formatCost(stats.today_cost) }}${{ formatCost(stats.today_cost) }}
@@ -142,15 +148,21 @@${{ formatCost(stats.total_actual_cost) }} + / + ${{ formatCost(stats.total_account_cost) }} + / - / ${{ formatCost(stats.total_cost) }}${{ formatCost(stats.total_cost) }}