import apiClient from './client' import { cachedRequest, buildCacheKey } from '@/utils/cache' const REQUEST_DETAIL_PREFETCH_TTL_MS = 5_000 export interface DashboardStat { name: string value: string subValue?: string change?: string changeType?: 'increase' | 'decrease' | 'neutral' extraBadge?: string icon: string } export interface RecentRequest { id: string // UUID user: string model: string tokens: number time: string } export interface ProviderStatus { name: string status: 'active' | 'inactive' requests: number } // 系统健康指标(管理员专用) export interface SystemHealth { avg_response_time: number error_rate: number error_requests: number fallback_count: number total_requests: number } // 成本统计(管理员专用) export interface CostStats { total_cost: number total_actual_cost: number cost_savings: number } // 缓存统计 export interface CacheStats { cache_creation_tokens: number cache_read_tokens: number cache_creation_cost?: number cache_read_cost?: number cache_hit_rate?: number total_cache_tokens: number } // 用户统计(管理员专用) export interface UserStats { total: number active: number online?: number } // Token 详细分类 export interface TokenBreakdown { input: number output: number cache_creation: number cache_read: number } export interface DashboardStatsResponse { stats: DashboardStat[] today?: { requests: number tokens: number cost: number actual_cost?: number cache_creation_tokens?: number cache_read_tokens?: number } api_keys?: { total: number active: number } tokens?: { month: number } // 管理员专用字段 system_health?: SystemHealth cost_stats?: CostStats cache_stats?: CacheStats users?: UserStats token_breakdown?: TokenBreakdown // 普通用户专用字段 monthly_cost?: number } export interface RecentRequestsResponse { requests: RecentRequest[] } export interface ProviderStatusResponse { providers: ProviderStatus[] } // 视频/图像/音频计费信息 export interface VideoBilling { task_type: 'video' | 'image' | 'audio' duration_seconds?: number // 视频时长(秒) resolution?: string // 分辨率 video_price_per_second?: number // 每秒单价 video_cost?: number // 视频费用 cost?: number // 总费用 rule_name?: string // 计费规则名称 expression?: string // 计费公式 status?: string // 计费状态 } export interface RequestErrorDomain { source?: string | null status_code?: number | null type?: string | null message?: string | null code?: string | number | null content_type?: string | null body?: unknown category?: string | null } export interface RequestErrorDomains { request_error?: RequestErrorDomain | null upstream_error?: RequestErrorDomain | null client_error?: RequestErrorDomain | null failure_summary?: RequestErrorDomain | null } export interface RequestErrorFlow { source?: string | null status_code?: number | null propagation?: string | null client_response_source?: string | null safe_to_expose_upstream?: boolean | null summary_source?: string | null } export interface RequestSchedulingFailure { source?: string | null reason?: string | null reason_label?: string | null title?: string | null message?: string | null reason_summary?: string | null status_code?: number | null no_upstream_attempt?: boolean | null } export interface RequestPricingTier { up_to?: number | null input_price_per_1m?: number | null output_price_per_1m?: number | null cache_creation_price_per_1m?: number | null cache_read_price_per_1m?: number | null cache_ttl_pricing?: Array<{ ttl_minutes?: number | null cache_creation_price_per_1m?: number | null cache_read_price_per_1m?: number | null }> | null [key: string]: unknown } export interface RequestSettlementTieredPricing { tiers?: RequestPricingTier[] | null [key: string]: unknown } export interface RequestSettlementPricingSnapshot { requested_processing_tier?: string | null actual_processing_tier?: string | null billing_processing_tier?: string | null processing_tier_price_multiplier?: number | null pricing_source?: string | null tiered_pricing_source?: string | null price_per_request_source?: string | null tiered_pricing?: RequestSettlementTieredPricing | null [key: string]: unknown } export interface RequestSettlementSnapshot { pricing_snapshot?: RequestSettlementPricingSnapshot | null [key: string]: unknown } export type RequestBodyField = 'request_body' | 'provider_request_body' | 'response_body' | 'client_response_body' export type RequestBodyLoadErrorCode = 'too_large' | 'decode_failed' | 'missing' | 'storage_unavailable' export class RequestBodyProtocolError extends Error { constructor() { super('Invalid body response') this.name = 'RequestBodyProtocolError' } } export interface RequestDetail { id: string // UUID request_id: string user: { id: string // UUID username: string email: string } api_key: { id: string // UUID name: string display: string } provider: string api_format?: string endpoint_api_format?: string has_format_conversion?: boolean | null model: string target_model?: string | null // 映射后的目标模型名 response_model?: string | null // 上游响应体实际返回的模型名 requested_reasoning_effort?: string | null reasoning_effort?: string | null service_tier?: string | null actual_service_tier?: string | null tokens: { input: number output: number total: number } cost: { input: number output: number total: number } // Additional token fields input_tokens?: number effective_input_tokens?: number output_tokens?: number total_tokens?: number cache_creation_input_tokens?: number cache_creation_input_tokens_5m?: number cache_creation_input_tokens_1h?: number cache_creation_ephemeral_5m_input_tokens?: number cache_creation_ephemeral_1h_input_tokens?: number cache_read_input_tokens?: number // Additional cost fields input_cost?: number output_cost?: number total_cost?: number actual_cost?: number cache_creation_cost?: number cache_read_cost?: number image_output_cost?: number request_cost?: number // 按次计费费用 // Historical pricing fields (per 1M tokens) input_price_per_1m?: number output_price_per_1m?: number cache_creation_price_per_1m?: number cache_read_price_per_1m?: number price_per_request?: number // 按次计费价格 request_type: string is_stream: boolean is_websocket?: boolean websocket_transport?: string | null usage_available?: boolean usage_pricing_available?: boolean input_audio_tokens?: number | null output_audio_tokens?: number | null live_session?: Record | null realtime_session?: Record | null upstream_is_stream?: boolean client_requested_stream?: boolean client_is_stream?: boolean status_code: number status?: string // pending, streaming, completed, failed, cancelled error_message?: string request_error?: RequestErrorDomain | null upstream_error?: RequestErrorDomain | null client_error?: RequestErrorDomain | null failure_summary?: RequestErrorDomain | null errors?: RequestErrorDomains | null error_flow?: RequestErrorFlow | null scheduling_failure?: RequestSchedulingFailure | null response_time_ms: number first_byte_time_ms?: number | null end_to_end_time_ms?: number | null end_to_end_first_byte_time_ms?: number | null created_at: string updated_at?: string | null request_headers?: Record | null request_body?: Record | null provider_request_headers?: Record | null provider_request_body?: Record | null response_headers?: Record | null client_response_headers?: Record | null response_body?: Record | null client_response_body?: Record | null has_request_body?: boolean has_provider_request_body?: boolean has_response_body?: boolean has_client_response_body?: boolean body_load_errors?: { request_body?: boolean provider_request_body?: boolean response_body?: boolean client_response_body?: boolean } | null body_load_error_codes?: Partial> | null metadata?: Record routing?: Record body_capture?: Record trace?: Record settlement?: { billing_snapshot?: Record billing_snapshot_schema_version?: string billing_snapshot_status?: string rate_multiplier?: number is_free_tier?: boolean input_price_per_1m?: number output_price_per_1m?: number cache_creation_price_per_1m?: number cache_read_price_per_1m?: number price_per_request?: number settlement_snapshot?: RequestSettlementSnapshot | null } | null // 阶梯计费信息 tiered_pricing?: { total_input_context: number // 总输入上下文 (input + cache_read) tier_index: number // 命中的阶梯索引 (0-based) tier_count: number // 阶梯总数 source?: 'provider' | 'global' // 定价来源: 提供商或全局 current_tier: RequestPricingTier // 当前命中的阶梯配置 tiers: RequestPricingTier[] // 完整阶梯配置列表 } | null // 视频/图像/音频计费信息 video_billing?: VideoBilling | null } export interface CurlData { url: string method: string headers: Record body: Record curl: string } export interface ReplayRequest { provider_id?: string endpoint_id?: string api_key_id?: string body_override?: Record } export interface ReplayResponse { url: string provider: string status_code: number response_headers: Record response_body: Record response_time_ms: number mapping?: { source_model: string original_target_model?: string | null resolved_model: string target_provider_id: string target_provider: string target_endpoint_id: string target_api_format: string replay_mode: string mapping_applied: boolean mapping_source: string } } export interface ModelBreakdown { model: string requests: number tokens: number cost: number } export interface ModelSummary { model: string requests: number tokens: number cost: number avg_response_time: number cost_per_request: number tokens_per_request: number } export interface ProviderSummary { provider: string requests: number tokens: number cost: number } export interface DailyStat { date: string // ISO date string requests: number tokens: number cost: number avg_response_time: number // in seconds unique_models: number unique_providers?: number // 仅管理员返回 model_breakdown: ModelBreakdown[] } export interface DailyStatsResponse { daily_stats: DailyStat[] model_summary: ModelSummary[] provider_summary?: ProviderSummary[] // 仅管理员返回 period: { start_date: string end_date: string days: number } } export interface TimeRangeParams { start_date?: string end_date?: string preset?: string granularity?: 'hour' | 'day' | 'week' | 'month' timezone?: string tz_offset_minutes?: number } export const dashboardApi = { // 获取仪表盘统计数据 async getStats(params?: TimeRangeParams): Promise { const cacheKey = buildCacheKey('dashboard:stats', params as Record | undefined) return cachedRequest( cacheKey, async () => { const response = await apiClient.get('/api/dashboard/stats', { params }) return response.data }, 30 * 1000 ) }, // 获取最近的请求记录 async getRecentRequests(limit: number = 10): Promise { const response = await apiClient.get('/api/dashboard/recent-requests', { params: { limit } }) return response.data.requests }, // 获取提供商状态 async getProviderStatus(): Promise { return cachedRequest( 'dashboard:provider-status', async () => { const response = await apiClient.get('/api/dashboard/provider-status') return response.data.providers }, 20 * 1000 ) }, // 获取请求详情 // NOTE: This method now calls the new RESTful API at /api/admin/usage/{id} async getRequestDetail( requestId: string, options: { includeBodies?: boolean, cacheTtlMs?: number, signal?: AbortSignal } = {} ): Promise { const includeBodies = options.includeBodies ?? false const cacheTtlMs = options.cacheTtlMs ?? 0 const cacheKey = buildCacheKey('dashboard:request-detail', { requestId, includeBodies }) const fetchDetail = async () => { const response = await apiClient.get(`/api/admin/usage/${requestId}`, { params: { include_bodies: includeBodies }, ...(options.signal ? { signal: options.signal } : {}), }) return response.data } return options.signal ? fetchDetail() : cachedRequest(cacheKey, fetchDetail, cacheTtlMs) }, async getRequestBody(requestId: string, field: RequestBodyField, signal?: AbortSignal, onProgress?: (loaded: number) => void) { const response = await apiClient.get(`/api/admin/usage/${requestId}`, { params: { include_bodies: true, body_field: field, body_format: 'raw' }, responseType: 'arraybuffer', signal, ...(onProgress ? { onDownloadProgress: (event: { loaded: number }) => onProgress(event.loaded) } : {}), }) const encoding = response.headers['x-aether-body-encoding'] if ((encoding !== 'gzip' && encoding !== 'json') || response.headers['x-aether-usage-id'] !== requestId || response.headers['x-aether-body-field'] !== field) { throw new RequestBodyProtocolError() } return { bytes: response.data, encoding: encoding as 'gzip' | 'json' } }, async prefetchRequestDetail(requestId: string): Promise { await dashboardApi.getRequestDetail(requestId, { includeBodies: false, cacheTtlMs: REQUEST_DETAIL_PREFETCH_TTL_MS }) }, // 获取每日统计数据 async getDailyStats(params?: TimeRangeParams & { days?: number }): Promise { const cacheKey = buildCacheKey('dashboard:daily-stats', params as Record | undefined) return cachedRequest( cacheKey, async () => { const response = await apiClient.get('/api/dashboard/daily-stats', { params }) return response.data }, 60 * 1000 ) }, // 获取 cURL 命令数据(含明文 API Key) async getCurlData(requestId: string): Promise { const response = await apiClient.get(`/api/admin/usage/${requestId}/curl`) return response.data }, // 回放请求到提供商 async replayRequest(requestId: string, params?: ReplayRequest): Promise { const response = await apiClient.post( `/api/admin/usage/${requestId}/replay`, params || {} ) return response.data } }