2025-12-10 20:52:44 +08:00
|
|
|
|
import apiClient from './client'
|
2026-03-03 22:04:40 +08:00
|
|
|
|
import { cachedRequest, buildCacheKey } from '@/utils/cache'
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
2026-04-19 15:17:25 +08:00
|
|
|
|
const REQUEST_DETAIL_PREFETCH_TTL_MS = 5_000
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
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
|
2026-06-02 18:16:24 +08:00
|
|
|
|
online?: number
|
2025-12-10 20:52:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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
|
2025-12-29 18:28:37 +08:00
|
|
|
|
// 普通用户专用字段
|
|
|
|
|
|
monthly_cost?: number
|
2025-12-10 20:52:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface RecentRequestsResponse {
|
|
|
|
|
|
requests: RecentRequest[]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ProviderStatusResponse {
|
|
|
|
|
|
providers: ProviderStatus[]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-03 18:48:39 +08:00
|
|
|
|
// 视频/图像/音频计费信息
|
|
|
|
|
|
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 // 计费状态
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-25 16:58:17 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-16 15:42:13 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
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
|
2026-04-10 17:44:55 +08:00
|
|
|
|
endpoint_api_format?: string
|
2026-06-20 22:08:33 +08:00
|
|
|
|
has_format_conversion?: boolean | null
|
2025-12-10 20:52:44 +08:00
|
|
|
|
model: string
|
|
|
|
|
|
target_model?: string | null // 映射后的目标模型名
|
2026-06-20 22:08:33 +08:00
|
|
|
|
reasoning_effort?: string | null
|
|
|
|
|
|
service_tier?: string | null
|
2025-12-10 20:52:44 +08:00
|
|
|
|
tokens: {
|
|
|
|
|
|
input: number
|
|
|
|
|
|
output: number
|
|
|
|
|
|
total: number
|
|
|
|
|
|
}
|
|
|
|
|
|
cost: {
|
|
|
|
|
|
input: number
|
|
|
|
|
|
output: number
|
|
|
|
|
|
total: number
|
|
|
|
|
|
}
|
|
|
|
|
|
// Additional token fields
|
|
|
|
|
|
input_tokens?: number
|
2026-04-10 17:44:55 +08:00
|
|
|
|
effective_input_tokens?: number
|
2025-12-10 20:52:44 +08:00
|
|
|
|
output_tokens?: number
|
|
|
|
|
|
total_tokens?: number
|
|
|
|
|
|
cache_creation_input_tokens?: number
|
2026-03-06 13:13:01 +08:00
|
|
|
|
cache_creation_input_tokens_5m?: number
|
|
|
|
|
|
cache_creation_input_tokens_1h?: number
|
2026-06-20 22:08:33 +08:00
|
|
|
|
cache_creation_ephemeral_5m_input_tokens?: number
|
|
|
|
|
|
cache_creation_ephemeral_1h_input_tokens?: number
|
2025-12-10 20:52:44 +08:00
|
|
|
|
cache_read_input_tokens?: number
|
|
|
|
|
|
// Additional cost fields
|
|
|
|
|
|
input_cost?: number
|
|
|
|
|
|
output_cost?: number
|
|
|
|
|
|
total_cost?: number
|
2026-06-20 22:08:33 +08:00
|
|
|
|
actual_cost?: number
|
2025-12-10 20:52:44 +08:00
|
|
|
|
cache_creation_cost?: number
|
|
|
|
|
|
cache_read_cost?: number
|
2026-05-18 02:49:56 +08:00
|
|
|
|
image_output_cost?: number
|
2025-12-10 20:52:44 +08:00
|
|
|
|
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
|
2026-04-23 14:42:51 +08:00
|
|
|
|
upstream_is_stream?: boolean
|
|
|
|
|
|
client_requested_stream?: boolean
|
|
|
|
|
|
client_is_stream?: boolean
|
2025-12-10 20:52:44 +08:00
|
|
|
|
status_code: number
|
2026-02-07 02:59:02 +08:00
|
|
|
|
status?: string // pending, streaming, completed, failed, cancelled
|
2025-12-10 20:52:44 +08:00
|
|
|
|
error_message?: string
|
2026-04-25 16:58:17 +08:00
|
|
|
|
request_error?: RequestErrorDomain | null
|
|
|
|
|
|
upstream_error?: RequestErrorDomain | null
|
|
|
|
|
|
client_error?: RequestErrorDomain | null
|
|
|
|
|
|
failure_summary?: RequestErrorDomain | null
|
|
|
|
|
|
errors?: RequestErrorDomains | null
|
|
|
|
|
|
error_flow?: RequestErrorFlow | null
|
2026-05-16 15:42:13 +08:00
|
|
|
|
scheduling_failure?: RequestSchedulingFailure | null
|
2025-12-10 20:52:44 +08:00
|
|
|
|
response_time_ms: number
|
2026-05-05 17:34:12 +08:00
|
|
|
|
first_byte_time_ms?: number | null
|
2025-12-10 20:52:44 +08:00
|
|
|
|
created_at: string
|
2026-02-22 00:43:41 +08:00
|
|
|
|
request_headers?: Record<string, unknown>
|
|
|
|
|
|
request_body?: Record<string, unknown>
|
|
|
|
|
|
provider_request_headers?: Record<string, unknown>
|
|
|
|
|
|
provider_request_body?: Record<string, unknown>
|
|
|
|
|
|
response_headers?: Record<string, unknown>
|
|
|
|
|
|
client_response_headers?: Record<string, unknown>
|
|
|
|
|
|
response_body?: Record<string, unknown>
|
|
|
|
|
|
client_response_body?: Record<string, unknown>
|
2026-03-06 15:40:11 +08:00
|
|
|
|
has_request_body?: boolean
|
|
|
|
|
|
has_provider_request_body?: boolean
|
|
|
|
|
|
has_response_body?: boolean
|
|
|
|
|
|
has_client_response_body?: boolean
|
2026-06-10 09:16:15 +08:00
|
|
|
|
body_load_errors?: {
|
|
|
|
|
|
request_body?: boolean
|
|
|
|
|
|
provider_request_body?: boolean
|
|
|
|
|
|
response_body?: boolean
|
|
|
|
|
|
client_response_body?: boolean
|
|
|
|
|
|
} | null
|
2026-02-22 00:43:41 +08:00
|
|
|
|
metadata?: Record<string, unknown>
|
2026-04-14 11:50:52 +08:00
|
|
|
|
routing?: Record<string, unknown>
|
|
|
|
|
|
body_capture?: Record<string, unknown>
|
|
|
|
|
|
trace?: Record<string, unknown>
|
2026-04-13 14:53:46 +08:00
|
|
|
|
settlement?: {
|
|
|
|
|
|
billing_snapshot?: Record<string, unknown>
|
|
|
|
|
|
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
|
|
|
|
|
|
} | null
|
2025-12-10 20:52:44 +08:00
|
|
|
|
// 阶梯计费信息
|
|
|
|
|
|
tiered_pricing?: {
|
|
|
|
|
|
total_input_context: number // 总输入上下文 (input + cache_read)
|
|
|
|
|
|
tier_index: number // 命中的阶梯索引 (0-based)
|
|
|
|
|
|
tier_count: number // 阶梯总数
|
|
|
|
|
|
source?: 'provider' | 'global' // 定价来源: 提供商或全局
|
|
|
|
|
|
current_tier: { // 当前命中的阶梯配置
|
|
|
|
|
|
up_to?: number | null
|
|
|
|
|
|
input_price_per_1m: number
|
|
|
|
|
|
output_price_per_1m: number
|
|
|
|
|
|
cache_creation_price_per_1m?: number
|
|
|
|
|
|
cache_read_price_per_1m?: number
|
|
|
|
|
|
cache_ttl_pricing?: Array<{
|
|
|
|
|
|
ttl_minutes: number
|
2026-03-06 13:13:01 +08:00
|
|
|
|
cache_creation_price_per_1m?: number
|
|
|
|
|
|
cache_read_price_per_1m?: number
|
2025-12-10 20:52:44 +08:00
|
|
|
|
}>
|
|
|
|
|
|
}
|
|
|
|
|
|
tiers: Array<{ // 完整阶梯配置列表
|
|
|
|
|
|
up_to?: number | null
|
|
|
|
|
|
input_price_per_1m: number
|
|
|
|
|
|
output_price_per_1m: number
|
|
|
|
|
|
cache_creation_price_per_1m?: number
|
|
|
|
|
|
cache_read_price_per_1m?: number
|
|
|
|
|
|
cache_ttl_pricing?: Array<{
|
|
|
|
|
|
ttl_minutes: number
|
2026-03-06 13:13:01 +08:00
|
|
|
|
cache_creation_price_per_1m?: number
|
|
|
|
|
|
cache_read_price_per_1m?: number
|
2025-12-10 20:52:44 +08:00
|
|
|
|
}>
|
|
|
|
|
|
}>
|
|
|
|
|
|
} | null
|
2026-02-03 18:48:39 +08:00
|
|
|
|
// 视频/图像/音频计费信息
|
|
|
|
|
|
video_billing?: VideoBilling | null
|
2025-12-10 20:52:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-10 03:07:49 +08:00
|
|
|
|
export interface CurlData {
|
|
|
|
|
|
url: string
|
|
|
|
|
|
method: string
|
|
|
|
|
|
headers: Record<string, string>
|
2026-02-22 00:43:41 +08:00
|
|
|
|
body: Record<string, unknown>
|
2026-02-10 03:07:49 +08:00
|
|
|
|
curl: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReplayRequest {
|
|
|
|
|
|
provider_id?: string
|
|
|
|
|
|
endpoint_id?: string
|
|
|
|
|
|
api_key_id?: string
|
2026-02-22 00:43:41 +08:00
|
|
|
|
body_override?: Record<string, unknown>
|
2026-02-10 03:07:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ReplayResponse {
|
|
|
|
|
|
url: string
|
|
|
|
|
|
provider: string
|
|
|
|
|
|
status_code: number
|
|
|
|
|
|
response_headers: Record<string, string>
|
2026-02-22 00:43:41 +08:00
|
|
|
|
response_body: Record<string, unknown>
|
2026-02-10 03:07:49 +08:00
|
|
|
|
response_time_ms: number
|
2026-03-17 17:56:06 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-02-10 03:07:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-19 20:23:55 +08:00
|
|
|
|
export interface ProviderSummary {
|
|
|
|
|
|
provider: string
|
|
|
|
|
|
requests: number
|
|
|
|
|
|
tokens: number
|
|
|
|
|
|
cost: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
export interface DailyStat {
|
|
|
|
|
|
date: string // ISO date string
|
|
|
|
|
|
requests: number
|
|
|
|
|
|
tokens: number
|
|
|
|
|
|
cost: number
|
|
|
|
|
|
avg_response_time: number // in seconds
|
|
|
|
|
|
unique_models: number
|
2026-01-20 01:58:55 +08:00
|
|
|
|
unique_providers?: number // 仅管理员返回
|
2025-12-10 20:52:44 +08:00
|
|
|
|
model_breakdown: ModelBreakdown[]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DailyStatsResponse {
|
|
|
|
|
|
daily_stats: DailyStat[]
|
|
|
|
|
|
model_summary: ModelSummary[]
|
2026-01-20 01:58:55 +08:00
|
|
|
|
provider_summary?: ProviderSummary[] // 仅管理员返回
|
2025-12-10 20:52:44 +08:00
|
|
|
|
period: {
|
|
|
|
|
|
start_date: string
|
|
|
|
|
|
end_date: string
|
|
|
|
|
|
days: number
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-04 02:04:54 +08:00
|
|
|
|
export interface TimeRangeParams {
|
|
|
|
|
|
start_date?: string
|
|
|
|
|
|
end_date?: string
|
|
|
|
|
|
preset?: string
|
|
|
|
|
|
granularity?: 'hour' | 'day' | 'week' | 'month'
|
|
|
|
|
|
timezone?: string
|
|
|
|
|
|
tz_offset_minutes?: number
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 20:52:44 +08:00
|
|
|
|
export const dashboardApi = {
|
|
|
|
|
|
// 获取仪表盘统计数据
|
2026-02-04 02:04:54 +08:00
|
|
|
|
async getStats(params?: TimeRangeParams): Promise<DashboardStatsResponse> {
|
2026-05-16 15:42:13 +08:00
|
|
|
|
const cacheKey = buildCacheKey('dashboard:stats', params as Record<string, unknown> | undefined)
|
2026-03-03 22:04:40 +08:00
|
|
|
|
return cachedRequest(
|
|
|
|
|
|
cacheKey,
|
|
|
|
|
|
async () => {
|
|
|
|
|
|
const response = await apiClient.get<DashboardStatsResponse>('/api/dashboard/stats', { params })
|
|
|
|
|
|
return response.data
|
|
|
|
|
|
},
|
|
|
|
|
|
10 * 1000
|
|
|
|
|
|
)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取最近的请求记录
|
|
|
|
|
|
async getRecentRequests(limit: number = 10): Promise<RecentRequest[]> {
|
|
|
|
|
|
const response = await apiClient.get<RecentRequestsResponse>('/api/dashboard/recent-requests', {
|
|
|
|
|
|
params: { limit }
|
|
|
|
|
|
})
|
|
|
|
|
|
return response.data.requests
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取提供商状态
|
|
|
|
|
|
async getProviderStatus(): Promise<ProviderStatus[]> {
|
2026-03-03 22:04:40 +08:00
|
|
|
|
return cachedRequest(
|
|
|
|
|
|
'dashboard:provider-status',
|
|
|
|
|
|
async () => {
|
|
|
|
|
|
const response = await apiClient.get<ProviderStatusResponse>('/api/dashboard/provider-status')
|
|
|
|
|
|
return response.data.providers
|
|
|
|
|
|
},
|
|
|
|
|
|
20 * 1000
|
|
|
|
|
|
)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取请求详情
|
|
|
|
|
|
// NOTE: This method now calls the new RESTful API at /api/admin/usage/{id}
|
2026-04-19 15:17:25 +08:00
|
|
|
|
async getRequestDetail(
|
|
|
|
|
|
requestId: string,
|
|
|
|
|
|
options: { includeBodies?: boolean, cacheTtlMs?: number } = {}
|
|
|
|
|
|
): Promise<RequestDetail> {
|
|
|
|
|
|
const includeBodies = options.includeBodies ?? true
|
|
|
|
|
|
const cacheTtlMs = options.cacheTtlMs ?? 0
|
|
|
|
|
|
const cacheKey = buildCacheKey('dashboard:request-detail', { requestId, includeBodies })
|
|
|
|
|
|
return cachedRequest(
|
|
|
|
|
|
cacheKey,
|
|
|
|
|
|
async () => {
|
|
|
|
|
|
const response = await apiClient.get<RequestDetail>(`/api/admin/usage/${requestId}`, {
|
|
|
|
|
|
params: { include_bodies: includeBodies },
|
|
|
|
|
|
})
|
|
|
|
|
|
return response.data
|
|
|
|
|
|
},
|
|
|
|
|
|
cacheTtlMs
|
|
|
|
|
|
)
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
async prefetchRequestDetail(requestId: string): Promise<void> {
|
|
|
|
|
|
await dashboardApi.getRequestDetail(requestId, {
|
|
|
|
|
|
includeBodies: false,
|
|
|
|
|
|
cacheTtlMs: REQUEST_DETAIL_PREFETCH_TTL_MS
|
2026-03-06 15:40:11 +08:00
|
|
|
|
})
|
2025-12-10 20:52:44 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取每日统计数据
|
2026-02-04 02:04:54 +08:00
|
|
|
|
async getDailyStats(params?: TimeRangeParams & { days?: number }): Promise<DailyStatsResponse> {
|
2026-05-16 15:42:13 +08:00
|
|
|
|
const cacheKey = buildCacheKey('dashboard:daily-stats', params as Record<string, unknown> | undefined)
|
2026-03-03 22:04:40 +08:00
|
|
|
|
return cachedRequest(
|
|
|
|
|
|
cacheKey,
|
|
|
|
|
|
async () => {
|
|
|
|
|
|
const response = await apiClient.get<DailyStatsResponse>('/api/dashboard/daily-stats', {
|
|
|
|
|
|
params
|
|
|
|
|
|
})
|
|
|
|
|
|
return response.data
|
|
|
|
|
|
},
|
|
|
|
|
|
20 * 1000
|
|
|
|
|
|
)
|
2026-02-10 03:07:49 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取 cURL 命令数据(含明文 API Key)
|
|
|
|
|
|
async getCurlData(requestId: string): Promise<CurlData> {
|
|
|
|
|
|
const response = await apiClient.get<CurlData>(`/api/admin/usage/${requestId}/curl`)
|
|
|
|
|
|
return response.data
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 回放请求到提供商
|
|
|
|
|
|
async replayRequest(requestId: string, params?: ReplayRequest): Promise<ReplayResponse> {
|
|
|
|
|
|
const response = await apiClient.post<ReplayResponse>(
|
|
|
|
|
|
`/api/admin/usage/${requestId}/replay`,
|
|
|
|
|
|
params || {}
|
|
|
|
|
|
)
|
|
|
|
|
|
return response.data
|
2025-12-10 20:52:44 +08:00
|
|
|
|
}
|
2026-02-10 03:07:49 +08:00
|
|
|
|
}
|