Files
Aether/frontend/src/mocks/handler.ts

2463 lines
88 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Mock API Handler
* 演示模式的 API 请求拦截和模拟响应
*/
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import { isDemoMode, DEMO_ACCOUNTS } from '@/config/demo'
import {
MOCK_ADMIN_USER,
MOCK_NORMAL_USER,
MOCK_LOGIN_RESPONSE_ADMIN,
MOCK_LOGIN_RESPONSE_USER,
MOCK_ADMIN_PROFILE,
MOCK_USER_PROFILE,
MOCK_DASHBOARD_STATS,
MOCK_RECENT_REQUESTS,
MOCK_PROVIDER_STATUS,
MOCK_DAILY_STATS,
MOCK_ALL_USERS,
MOCK_USER_API_KEYS,
MOCK_ADMIN_API_KEYS,
MOCK_PROVIDERS,
MOCK_GLOBAL_MODELS,
MOCK_SYSTEM_CONFIGS,
MOCK_API_FORMATS
} from './data'
// 当前登录用户的 token用于判断角色
let currentUserToken: string | null = null
// 模拟网络延迟
function delay(ms: number = 150): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms + Math.random() * 200))
}
// 创建模拟响应
function createMockResponse<T>(data: T, status: number = 200): AxiosResponse<T> {
return {
data,
status,
statusText: status === 200 ? 'OK' : 'Error',
headers: {},
config: {} as any
}
}
// 判断当前是否为管理员
function isCurrentUserAdmin(): boolean {
return currentUserToken === 'demo-access-token-admin'
}
// 获取当前用户
function getCurrentUser() {
return isCurrentUserAdmin() ? MOCK_ADMIN_USER : MOCK_NORMAL_USER
}
// 获取当前用户 Profile
function getCurrentProfile() {
return isCurrentUserAdmin() ? MOCK_ADMIN_PROFILE : MOCK_USER_PROFILE
}
// 检查管理员权限
function requireAdmin() {
if (!isCurrentUserAdmin()) {
throw { response: createMockResponse({ detail: '需要管理员权限' }, 403) }
}
}
// Mock 公告数据
const MOCK_ANNOUNCEMENTS = [
{
id: 'ann-001',
title: '系统升级通知',
content: '系统将于本周六凌晨 2:00-4:00 进行维护升级,届时服务将暂停访问。',
type: 'maintenance',
priority: 100,
is_pinned: true,
is_active: true,
author: { id: 'demo-admin-uuid-0001', username: 'Demo Admin' },
created_at: '2024-12-01T00:00:00Z',
updated_at: '2024-12-01T00:00:00Z',
is_read: false
},
{
id: 'ann-002',
title: '新模型上线Claude Sonnet 4',
content: 'Anthropic 最新模型 Claude Sonnet 4 已上线,支持更长上下文和更强推理能力。',
type: 'info',
priority: 50,
is_pinned: false,
is_active: true,
author: { id: 'demo-admin-uuid-0001', username: 'Demo Admin' },
created_at: '2024-11-28T00:00:00Z',
updated_at: '2024-11-28T00:00:00Z',
is_read: true
}
]
// 生成模拟健康事件
// status: success(绿), failed(红), skipped(黄)
// 无事件的时间段会显示为灰色
function generateHealthEvents(
count: number,
successRate: number,
failRate: number,
_skipRate: number,
baseLatency: number,
latencyVariance: number
) {
const events = []
const now = Date.now()
// 6小时内随机分布事件留一些空白时段灰色
const timeSpan = 6 * 60 * 60 * 1000
// skipRate 由 1 - successRate - failRate 隐含计算
for (let i = 0; i < count; i++) {
const rand = Math.random()
let status: string
let statusCode: number
if (rand < successRate) {
status = 'success'
statusCode = 200
} else if (rand < successRate + failRate) {
status = 'failed'
statusCode = [500, 502, 503, 429, 400][Math.floor(Math.random() * 5)]
} else {
status = 'skipped'
statusCode = 0
}
events.push({
timestamp: new Date(now - Math.random() * timeSpan).toISOString(),
status,
status_code: statusCode,
latency_ms: Math.round(baseLatency + Math.random() * latencyVariance),
error_type: status === 'failed' ? ['RateLimitError', 'TimeoutError', 'ServerError'][Math.floor(Math.random() * 3)] : undefined
})
}
// 按时间排序
return events.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())
}
// Mock 端点健康数据
// 注意success_rate 使用 0-1 之间的小数,前端会乘以 100 显示为百分比
// 事件的成功/失败/跳过比例必须与 success_rate 保持一致
// 覆盖所有 API 格式claude, claude_cli, openai, openai_cli, gemini, gemini_cli
const MOCK_ENDPOINT_STATUS = {
generated_at: new Date().toISOString(),
formats: [
{
api_format: 'CLAUDE',
api_path: '/v1/messages',
total_attempts: 2580,
success_count: 2540,
failed_count: 30,
skipped_count: 10,
success_rate: 0.984,
provider_count: 2,
key_count: 4,
last_event_at: new Date().toISOString(),
// 98.4% 成功率successRate=0.984, failRate=0.012, skipRate=0.004
events: generateHealthEvents(80, 0.984, 0.012, 0.004, 900, 500)
},
{
api_format: 'CLAUDE_CLI',
api_path: '/v1/messages',
total_attempts: 1890,
success_count: 1780,
failed_count: 85,
skipped_count: 25,
success_rate: 0.942,
provider_count: 5,
key_count: 9,
last_event_at: new Date().toISOString(),
// 94.2% 成功率successRate=0.942, failRate=0.045, skipRate=0.013
events: generateHealthEvents(120, 0.942, 0.045, 0.013, 1200, 800)
},
{
api_format: 'GEMINI',
api_path: '/v1beta/models',
total_attempts: 890,
success_count: 890,
failed_count: 0,
skipped_count: 0,
success_rate: 1.0,
provider_count: 3,
key_count: 3,
last_event_at: new Date().toISOString(),
// 100% 成功率:全部成功
events: generateHealthEvents(45, 1.0, 0, 0, 400, 200)
},
{
api_format: 'GEMINI_CLI',
api_path: '/v1beta/models',
total_attempts: 456,
success_count: 450,
failed_count: 4,
skipped_count: 2,
success_rate: 0.987,
provider_count: 3,
key_count: 3,
last_event_at: new Date().toISOString(),
// 98.7% 成功率successRate=0.987, failRate=0.009, skipRate=0.004
events: generateHealthEvents(25, 0.987, 0.009, 0.004, 500, 300)
},
{
api_format: 'OPENAI',
api_path: '/v1/chat/completions',
total_attempts: 1560,
success_count: 1520,
failed_count: 35,
skipped_count: 5,
success_rate: 0.974,
provider_count: 1,
key_count: 2,
last_event_at: new Date().toISOString(),
// 97.4% 成功率successRate=0.974, failRate=0.022, skipRate=0.004
events: generateHealthEvents(60, 0.974, 0.022, 0.004, 700, 400)
},
{
api_format: 'OPENAI_CLI',
api_path: '/responses',
total_attempts: 2340,
success_count: 2200,
failed_count: 100,
skipped_count: 40,
success_rate: 0.940,
provider_count: 4,
key_count: 5,
last_event_at: new Date().toISOString(),
// 94.0% 成功率successRate=0.940, failRate=0.043, skipRate=0.017
events: generateHealthEvents(100, 0.940, 0.043, 0.017, 800, 600)
}
]
}
// 生成活跃热力图数据最近365天
function generateActivityHeatmap() {
const days: Array<{
date: string
requests: number
total_tokens: number
total_cost: number
actual_total_cost?: number
}> = []
const now = new Date()
const startDate = new Date(now)
startDate.setDate(startDate.getDate() - 364) // 365天数据一年
let maxRequests = 0
// 生成每天的数据
for (let i = 0; i < 365; i++) {
const date = new Date(startDate)
date.setDate(startDate.getDate() + i)
const dateStr = date.toISOString().split('T')[0]
// 工作日请求量更高
const dayOfWeek = date.getDay()
const isWeekend = dayOfWeek === 0 || dayOfWeek === 6
// 基础请求量 + 随机波动 + 周末减少
// 加入一些趋势:越近的日期请求量可能越高
const trendFactor = 0.7 + (i / 365) * 0.5 // 从0.7到1.2的增长趋势
const baseRequests = isWeekend ? 40 : 120
const variance = Math.floor(Math.random() * 80)
// 有些天可能没有请求约5%的天数)
const noActivity = Math.random() < 0.05
const requests = noActivity ? 0 : Math.round((baseRequests + variance) * trendFactor)
if (requests > maxRequests) maxRequests = requests
// 根据请求量计算 tokens 和 cost
const avgTokensPerRequest = 3000 + Math.floor(Math.random() * 2000)
const totalTokens = requests * avgTokensPerRequest
const avgCostPerRequest = 0.02 + Math.random() * 0.03
const totalCost = Number((requests * avgCostPerRequest).toFixed(2))
const actualTotalCost = Number((totalCost * 0.8).toFixed(2)) // 实际成本约为 80%
days.push({
date: dateStr,
requests,
total_tokens: totalTokens,
total_cost: totalCost,
actual_total_cost: actualTotalCost
})
}
return {
start_date: days[0].date,
end_date: days[days.length - 1].date,
total_days: days.length,
max_requests: maxRequests,
days
}
}
// 缓存热力图数据(避免每次请求都重新生成)
let cachedHeatmap: ReturnType<typeof generateActivityHeatmap> | null = null
function getActivityHeatmap() {
if (!cachedHeatmap) {
cachedHeatmap = generateActivityHeatmap()
}
return cachedHeatmap
}
// 生成更真实的使用记录
function generateMockUsageRecords(count: number = 100) {
const records = []
const now = Date.now()
const models = [
{ name: 'claude-sonnet-4-5-20250929', provider: 'anthropic', inputPrice: 3, outputPrice: 15 },
{ name: 'claude-haiku-4-5-20251001', provider: 'anthropic', inputPrice: 1, outputPrice: 5 },
{ name: 'claude-opus-4-5-20251101', provider: 'anthropic', inputPrice: 15, outputPrice: 75 },
{ name: 'gpt-5.1', provider: 'openai', inputPrice: 2.5, outputPrice: 10 },
{ name: 'gpt-5.1-codex', provider: 'openai', inputPrice: 2.5, outputPrice: 10 },
{ name: 'gemini-3-pro-preview', provider: 'google', inputPrice: 2, outputPrice: 12 }
]
const users = [
{ id: 'demo-admin-uuid-0001', username: 'Demo Admin', email: 'admin@demo.aether.ai' },
{ id: 'demo-user-uuid-0002', username: 'Demo User', email: 'user@demo.aether.ai' },
{ id: 'demo-user-uuid-0003', username: 'Alice Chen', email: 'alice@demo.aether.ai' },
{ id: 'demo-user-uuid-0004', username: 'Bob Zhang', email: 'bob@demo.aether.ai' }
]
const apiFormats = ['CLAUDE', 'CLAUDE_CLI', 'OPENAI', 'OPENAI_CLI', 'GEMINI', 'GEMINI_CLI']
const statusOptions: Array<'completed' | 'failed' | 'streaming'> = ['completed', 'completed', 'completed', 'completed', 'failed', 'streaming']
for (let i = 0; i < count; i++) {
const model = models[Math.floor(Math.random() * models.length)]
const user = users[Math.floor(Math.random() * users.length)]
const status = statusOptions[Math.floor(Math.random() * statusOptions.length)]
// 根据模型类型选择 API 格式
let apiFormat = apiFormats[0]
if (model.provider === 'anthropic') {
apiFormat = Math.random() > 0.3 ? 'CLAUDE_CLI' : 'CLAUDE'
} else if (model.provider === 'openai') {
apiFormat = Math.random() > 0.3 ? 'OPENAI_CLI' : 'OPENAI'
} else {
apiFormat = Math.random() > 0.3 ? 'GEMINI_CLI' : 'GEMINI'
}
const inputTokens = 500 + Math.floor(Math.random() * 10000)
const outputTokens = 200 + Math.floor(Math.random() * 4000)
const cacheCreation = Math.random() > 0.7 ? Math.floor(Math.random() * 2000) : 0
const cacheRead = Math.random() > 0.5 ? Math.floor(Math.random() * 5000) : 0
const totalTokens = inputTokens + outputTokens
// 计算成本(每百万 token
const inputCost = (inputTokens / 1000000) * model.inputPrice
const outputCost = (outputTokens / 1000000) * model.outputPrice
const cost = Number((inputCost + outputCost).toFixed(6))
const actualCost = Number((cost * (0.7 + Math.random() * 0.3)).toFixed(6))
// 时间分布:最近的记录更密集
const timeOffset = Math.pow(i / count, 1.5) * 7 * 24 * 60 * 60 * 1000 // 7天内
const createdAt = new Date(now - timeOffset)
// 响应时间:根据模型和 token 数量
const baseResponseTime = model.name.includes('opus') ? 2000 : model.name.includes('haiku') ? 500 : 1000
const responseTime = status === 'failed' ? null : baseResponseTime + Math.floor(Math.random() * outputTokens * 0.5)
records.push({
id: `usage-${String(i + 1).padStart(4, '0')}`,
user_id: user.id,
username: user.username,
user_email: user.email,
provider: model.provider,
api_key_name: `${model.provider}-key-${Math.ceil(Math.random() * 3)}`,
rate_multiplier: 1.0,
model: model.name,
target_model: model.name,
api_format: apiFormat,
input_tokens: inputTokens,
output_tokens: outputTokens,
cache_creation_input_tokens: cacheCreation,
cache_read_input_tokens: cacheRead,
total_tokens: totalTokens,
cost,
actual_cost: actualCost,
response_time_ms: responseTime,
is_stream: apiFormat.includes('CLI'),
status_code: status === 'failed' ? [500, 502, 429, 400][Math.floor(Math.random() * 4)] : 200,
error_message: status === 'failed' ? ['Rate limit exceeded', 'Internal server error', 'Model overloaded'][Math.floor(Math.random() * 3)] : undefined,
status,
created_at: createdAt.toISOString(),
has_fallback: Math.random() > 0.9,
request_metadata: model.provider === 'google' ? { model_version: 'gemini-3-pro-preview-2025-01' } : undefined
})
}
return records
}
// 缓存使用记录
let cachedUsageRecords: ReturnType<typeof generateMockUsageRecords> | null = null
function getUsageRecords() {
if (!cachedUsageRecords) {
cachedUsageRecords = generateMockUsageRecords(100)
}
return cachedUsageRecords
}
// Mock 映射数据
const MOCK_ALIASES = [
{ id: 'alias-001', source_model: 'claude-4-sonnet', target_global_model_id: 'gm-001', target_global_model_name: 'claude-sonnet-4-20250514', target_global_model_display_name: 'Claude Sonnet 4', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
{ id: 'alias-002', source_model: 'claude-4-opus', target_global_model_id: 'gm-002', target_global_model_name: 'claude-opus-4-20250514', target_global_model_display_name: 'Claude Opus 4', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
{ id: 'alias-003', source_model: 'gpt4o', target_global_model_id: 'gm-004', target_global_model_name: 'gpt-4o', target_global_model_display_name: 'GPT-4o', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' },
{ id: 'alias-004', source_model: 'gemini-flash', target_global_model_id: 'gm-005', target_global_model_name: 'gemini-2.0-flash', target_global_model_display_name: 'Gemini 2.0 Flash', provider_id: null, provider_name: null, scope: 'global', mapping_type: 'alias', is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: '2024-01-01T00:00:00Z' }
]
// Mock Endpoint Keys
const MOCK_ENDPOINT_KEYS = [
{ id: 'ekey-001', endpoint_id: 'ep-001', api_key_masked: 'sk-ant...abc1', name: 'Primary Key', rate_multiplier: 1.0, internal_priority: 1, health_score: 98, consecutive_failures: 0, request_count: 5000, success_count: 4950, error_count: 50, success_rate: 99, avg_response_time_ms: 1200, is_active: true, created_at: '2024-01-01T00:00:00Z', updated_at: new Date().toISOString() },
{ id: 'ekey-002', endpoint_id: 'ep-001', api_key_masked: 'sk-ant...def2', name: 'Backup Key', rate_multiplier: 1.0, internal_priority: 2, health_score: 95, consecutive_failures: 1, request_count: 2000, success_count: 1950, error_count: 50, success_rate: 97.5, avg_response_time_ms: 1350, is_active: true, created_at: '2024-02-01T00:00:00Z', updated_at: new Date().toISOString() },
{ id: 'ekey-003', endpoint_id: 'ep-002', api_key_masked: 'sk-oai...ghi3', name: 'OpenAI Main', rate_multiplier: 1.0, internal_priority: 1, health_score: 97, consecutive_failures: 0, request_count: 3500, success_count: 3450, error_count: 50, success_rate: 98.6, avg_response_time_ms: 900, is_active: true, created_at: '2024-01-15T00:00:00Z', updated_at: new Date().toISOString() }
]
// Mock Endpoints
const MOCK_ENDPOINTS = [
{ id: 'ep-001', provider_id: 'provider-001', provider_name: 'anthropic', api_format: 'claude', base_url: 'https://api.anthropic.com', auth_type: 'bearer', timeout: 120, max_retries: 3, priority: 100, weight: 100, health_score: 98, consecutive_failures: 0, is_active: true, total_keys: 2, active_keys: 2, created_at: '2024-01-01T00:00:00Z', updated_at: new Date().toISOString() },
{ id: 'ep-002', provider_id: 'provider-002', provider_name: 'openai', api_format: 'openai', base_url: 'https://api.openai.com', auth_type: 'bearer', timeout: 60, max_retries: 3, priority: 90, weight: 100, health_score: 97, consecutive_failures: 0, is_active: true, total_keys: 1, active_keys: 1, created_at: '2024-01-01T00:00:00Z', updated_at: new Date().toISOString() },
{ id: 'ep-003', provider_id: 'provider-003', provider_name: 'google', api_format: 'gemini', base_url: 'https://generativelanguage.googleapis.com', auth_type: 'api_key', timeout: 60, max_retries: 3, priority: 80, weight: 100, health_score: 96, consecutive_failures: 0, is_active: true, total_keys: 1, active_keys: 1, created_at: '2024-01-15T00:00:00Z', updated_at: new Date().toISOString() }
]
// Mock 能力定义
const MOCK_CAPABILITIES = [
{ name: 'cache_1h', display_name: '1小时缓存', description: '支持1小时prompt缓存', match_mode: 'exclusive', short_name: '1h' },
{ name: 'context_1m', display_name: '1M上下文', description: '支持1M上下文窗口', match_mode: 'compatible', short_name: '1M' }
]
/**
* Mock API 路由处理器
*/
const mockHandlers: Record<string, (config: AxiosRequestConfig) => Promise<AxiosResponse<any>>> = {
// ========== 认证相关 ==========
'POST /api/auth/login': async (config) => {
await delay()
const body = JSON.parse(config.data || '{}')
const { email, password } = body
if (email === DEMO_ACCOUNTS.admin.email && password === DEMO_ACCOUNTS.admin.password) {
currentUserToken = 'demo-access-token-admin'
return createMockResponse(MOCK_LOGIN_RESPONSE_ADMIN)
}
if (email === DEMO_ACCOUNTS.user.email && password === DEMO_ACCOUNTS.user.password) {
currentUserToken = 'demo-access-token-user'
return createMockResponse(MOCK_LOGIN_RESPONSE_USER)
}
throw { response: createMockResponse({ detail: '邮箱或密码错误' }, 401) }
},
'POST /api/auth/logout': async () => {
await delay(100)
currentUserToken = null
return createMockResponse({ message: '已登出' })
},
'POST /api/auth/refresh': async () => {
await delay(100)
if (isCurrentUserAdmin()) {
return createMockResponse(MOCK_LOGIN_RESPONSE_ADMIN)
}
return createMockResponse(MOCK_LOGIN_RESPONSE_USER)
},
// ========== 用户信息 ==========
'GET /api/users/me': async () => {
await delay()
return createMockResponse(getCurrentUser())
},
'PUT /api/users/me': async () => {
await delay()
return createMockResponse({ message: '更新成功(演示模式)' })
},
'PATCH /api/users/me/password': async () => {
await delay()
return createMockResponse({ message: '密码修改成功(演示模式)' })
},
'GET /api/users/me/api-keys': async () => {
await delay()
return createMockResponse(MOCK_USER_API_KEYS)
},
'POST /api/users/me/api-keys': async (config) => {
await delay()
const body = JSON.parse(config.data || '{}')
const newKey = {
id: `key-demo-${Date.now()}`,
key: `sk-aether-demo-${Math.random().toString(36).substring(2, 15)}`,
key_display: 'sk-ae...demo',
name: body.name || '新密钥(演示)',
created_at: new Date().toISOString(),
is_active: true,
is_standalone: false,
total_requests: 0,
total_cost_usd: 0
}
return createMockResponse(newKey)
},
'GET /api/users/me/usage': async () => {
await delay()
const heatmap = getActivityHeatmap()
const records = getUsageRecords()
// 只返回当前用户的数据
const userRecords = records.filter(r => r.user_id === getCurrentUser().id)
const totalRequests = userRecords.length
const totalTokens = userRecords.reduce((sum, r) => sum + r.total_tokens, 0)
const totalInputTokens = userRecords.reduce((sum, r) => sum + r.input_tokens, 0)
const totalOutputTokens = userRecords.reduce((sum, r) => sum + r.output_tokens, 0)
const totalCost = userRecords.reduce((sum, r) => sum + r.cost, 0)
const totalActualCost = userRecords.reduce((sum, r) => sum + (r.actual_cost || 0), 0)
const avgResponseTime = userRecords.filter(r => r.response_time_ms).reduce((sum, r) => sum + (r.response_time_ms || 0), 0) / userRecords.filter(r => r.response_time_ms).length / 1000
// 按模型聚合
const modelStats = new Map<string, { requests: number; input_tokens: number; output_tokens: number; total_tokens: number; total_cost_usd: number; actual_total_cost_usd: number }>()
for (const r of userRecords) {
const existing = modelStats.get(r.model) || { requests: 0, input_tokens: 0, output_tokens: 0, total_tokens: 0, total_cost_usd: 0, actual_total_cost_usd: 0 }
existing.requests++
existing.input_tokens += r.input_tokens
existing.output_tokens += r.output_tokens
existing.total_tokens += r.total_tokens
existing.total_cost_usd += r.cost
existing.actual_total_cost_usd += r.actual_cost || 0
modelStats.set(r.model, existing)
}
return createMockResponse({
total_requests: totalRequests * 20,
total_input_tokens: totalInputTokens * 20,
total_output_tokens: totalOutputTokens * 20,
total_tokens: totalTokens * 20,
total_cost: Number((totalCost * 20).toFixed(2)),
total_actual_cost: Number((totalActualCost * 20).toFixed(2)),
avg_response_time: Number(avgResponseTime.toFixed(2)) || 1.23,
quota_usd: 100,
used_usd: Number((totalCost * 20).toFixed(2)),
activity_heatmap: heatmap,
summary_by_model: Array.from(modelStats.entries()).map(([model, stats]) => ({
model,
requests: stats.requests * 20,
input_tokens: stats.input_tokens * 20,
output_tokens: stats.output_tokens * 20,
total_tokens: stats.total_tokens * 20,
total_cost_usd: Number((stats.total_cost_usd * 20).toFixed(2)),
actual_total_cost_usd: Number((stats.actual_total_cost_usd * 20).toFixed(2))
})),
records: userRecords.slice(0, 10).map(r => ({
id: r.id,
provider: r.provider,
model: r.model,
input_tokens: r.input_tokens,
output_tokens: r.output_tokens,
total_tokens: r.total_tokens,
cost: r.cost,
response_time_ms: r.response_time_ms,
is_stream: r.is_stream,
created_at: r.created_at,
status_code: r.status_code,
input_price_per_1m: 3,
output_price_per_1m: 15
}))
})
},
'GET /api/users/me/providers': async () => {
await delay()
return createMockResponse(MOCK_PROVIDERS.map(p => ({
id: p.id,
name: p.name,
display_name: p.display_name,
is_active: p.is_active
})))
},
'GET /api/users/me/endpoint-status': async () => {
await delay()
return createMockResponse(MOCK_ENDPOINTS.map(e => ({
api_format: e.api_format,
health_score: e.health_score,
is_active: e.is_active
})))
},
'GET /api/users/me/preferences': async () => {
await delay()
return createMockResponse(getCurrentProfile().preferences || { theme: 'auto', language: 'zh-CN' })
},
'PUT /api/users/me/preferences': async () => {
await delay()
return createMockResponse({ message: '偏好设置已更新(演示模式)' })
},
'GET /api/users/me/model-capabilities': async () => {
await delay()
return createMockResponse({ model_capability_settings: {} })
},
'PUT /api/users/me/model-capabilities': async () => {
await delay()
return createMockResponse({ message: '已更新', model_capability_settings: {} })
},
// ========== Dashboard ==========
'GET /api/dashboard/stats': async () => {
await delay()
return createMockResponse(MOCK_DASHBOARD_STATS)
},
'GET /api/dashboard/recent-requests': async () => {
await delay()
return createMockResponse({ requests: MOCK_RECENT_REQUESTS })
},
'GET /api/dashboard/provider-status': async () => {
await delay()
return createMockResponse({ providers: MOCK_PROVIDER_STATUS })
},
'GET /api/dashboard/daily-stats': async () => {
await delay()
return createMockResponse(MOCK_DAILY_STATS)
},
// ========== 公告 ==========
'GET /api/announcements': async () => {
await delay()
return createMockResponse({ items: MOCK_ANNOUNCEMENTS, total: MOCK_ANNOUNCEMENTS.length, unread_count: 1 })
},
'GET /api/announcements/active': async () => {
await delay()
return createMockResponse({ items: MOCK_ANNOUNCEMENTS.filter(a => a.is_active), total: MOCK_ANNOUNCEMENTS.filter(a => a.is_active).length, unread_count: 1 })
},
'GET /api/announcements/users/me/unread-count': async () => {
await delay()
return createMockResponse({ unread_count: 1 })
},
'PATCH /api/announcements': async () => {
await delay()
return createMockResponse({ message: '已标记为已读' })
},
'POST /api/announcements': async (config) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
return createMockResponse({ id: `ann-demo-${Date.now()}`, title: body.title, message: '公告已创建(演示模式)' })
},
'POST /api/announcements/read-all': async () => {
await delay()
return createMockResponse({ message: '已全部标记为已读' })
},
// ========== Admin: 用户管理 ==========
'GET /api/admin/users': async () => {
await delay()
requireAdmin()
return createMockResponse(MOCK_ALL_USERS)
},
'POST /api/admin/users': async (config) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
const newUser = {
id: `user-demo-${Date.now()}`,
username: body.username,
email: body.email,
role: body.role || 'user',
is_active: true,
quota_usd: body.quota_usd || null,
used_usd: 0,
total_usd: 0,
allowed_providers: null,
allowed_endpoints: null,
allowed_models: null,
created_at: new Date().toISOString()
}
return createMockResponse(newUser)
},
// ========== Admin: API Keys ==========
'GET /api/admin/api-keys': async () => {
await delay()
requireAdmin()
return createMockResponse(MOCK_ADMIN_API_KEYS)
},
'POST /api/admin/api-keys': async (config) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
const newKey = {
id: `standalone-demo-${Date.now()}`,
key: `sk-sa-demo-${Math.random().toString(36).substring(2, 15)}`,
user_id: 'demo-user-uuid-0002',
name: body.name || '新独立 Key演示',
key_display: 'sk-sa...demo',
is_active: true,
is_standalone: true,
balance_used_usd: 0,
current_balance_usd: body.initial_balance_usd || 100,
total_requests: 0,
created_at: new Date().toISOString()
}
return createMockResponse(newKey)
},
// ========== Admin: Providers ==========
'GET /api/admin/providers/summary': async () => {
await delay()
requireAdmin()
return createMockResponse(MOCK_PROVIDERS)
},
'GET /api/admin/providers': async () => {
await delay()
requireAdmin()
return createMockResponse(MOCK_PROVIDERS)
},
'POST /api/admin/providers': async (config) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
return createMockResponse({ ...body, id: `provider-demo-${Date.now()}`, created_at: new Date().toISOString() })
},
// ========== Admin: Endpoints ==========
'GET /api/admin/endpoints/providers': async () => {
await delay()
requireAdmin()
return createMockResponse(MOCK_ENDPOINTS)
},
'GET /api/admin/endpoints': async () => {
await delay()
requireAdmin()
return createMockResponse(MOCK_ENDPOINTS)
},
'GET /api/admin/endpoints/health/summary': async () => {
await delay()
requireAdmin()
return createMockResponse({
endpoints: { total: 6, active: 5, unhealthy: 1 },
keys: { total: 15, active: 12, unhealthy: 3 }
})
},
'GET /api/admin/endpoints/health/api-formats': async () => {
await delay()
requireAdmin()
return createMockResponse(MOCK_ENDPOINT_STATUS)
},
'GET /api/admin/endpoints/keys': async () => {
await delay()
requireAdmin()
return createMockResponse(MOCK_ENDPOINT_KEYS)
},
// ========== Admin: Global Models ==========
'GET /api/admin/models/global': async () => {
await delay()
requireAdmin()
return createMockResponse({ models: MOCK_GLOBAL_MODELS, total: MOCK_GLOBAL_MODELS.length })
},
'POST /api/admin/models/global': async (config) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
return createMockResponse({ ...body, id: `gm-demo-${Date.now()}`, created_at: new Date().toISOString() })
},
// ========== Admin: Model Mappings / Aliases ==========
'GET /api/admin/models/mappings': async () => {
await delay()
requireAdmin()
return createMockResponse(MOCK_ALIASES)
},
'POST /api/admin/models/mappings': async (config) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
return createMockResponse({ ...body, id: `alias-demo-${Date.now()}`, created_at: new Date().toISOString(), updated_at: new Date().toISOString() })
},
// ========== Admin: Usage ==========
'GET /api/admin/usage/stats': async () => {
await delay()
requireAdmin()
const heatmap = getActivityHeatmap()
const records = getUsageRecords()
const totalRequests = records.length
const totalTokens = records.reduce((sum, r) => sum + r.total_tokens, 0)
const totalCost = records.reduce((sum, r) => sum + r.cost, 0)
const totalActualCost = records.reduce((sum, r) => sum + (r.actual_cost || 0), 0)
const avgResponseTime = records.filter(r => r.response_time_ms).reduce((sum, r) => sum + (r.response_time_ms || 0), 0) / records.filter(r => r.response_time_ms).length / 1000
// 今日数据
const today = new Date().toISOString().split('T')[0]
const todayRecords = records.filter(r => r.created_at.startsWith(today))
return createMockResponse({
total_requests: totalRequests * 100, // 放大显示
total_tokens: totalTokens * 100,
total_cost: Number((totalCost * 100).toFixed(2)),
total_actual_cost: Number((totalActualCost * 100).toFixed(2)),
avg_response_time: Number(avgResponseTime.toFixed(2)),
today: {
requests: todayRecords.length * 10,
tokens: todayRecords.reduce((sum, r) => sum + r.total_tokens, 0) * 10,
cost: Number((todayRecords.reduce((sum, r) => sum + r.cost, 0) * 10).toFixed(2))
},
activity_heatmap: heatmap
})
},
'GET /api/admin/usage/records': async (config) => {
await delay()
requireAdmin()
const records = getUsageRecords()
const params = config.params || {}
const limit = parseInt(params.limit) || 20
const offset = parseInt(params.offset) || 0
return createMockResponse({
records: records.slice(offset, offset + limit),
total: records.length,
limit,
offset
})
},
'GET /api/admin/usage/aggregation/stats': async (config) => {
await delay()
requireAdmin()
const params = config.params || {}
const groupBy = params.group_by || 'model'
const records = getUsageRecords()
if (groupBy === 'model') {
// 按模型聚合
const modelStats = new Map<string, { request_count: number; total_tokens: number; total_cost: number }>()
for (const r of records) {
const existing = modelStats.get(r.model) || { request_count: 0, total_tokens: 0, total_cost: 0 }
existing.request_count++
existing.total_tokens += r.total_tokens
existing.total_cost += r.cost
modelStats.set(r.model, existing)
}
return createMockResponse(
Array.from(modelStats.entries()).map(([model, stats]) => ({
model,
request_count: stats.request_count * 50,
total_tokens: stats.total_tokens * 50,
total_cost: Number((stats.total_cost * 50).toFixed(2))
}))
)
}
if (groupBy === 'provider') {
const providerStats = new Map<string, { request_count: number; total_tokens: number; total_cost: number; actual_cost: number; response_times: number[]; errors: number }>()
for (const r of records) {
const existing = providerStats.get(r.provider) || { request_count: 0, total_tokens: 0, total_cost: 0, actual_cost: 0, response_times: [], errors: 0 }
existing.request_count++
existing.total_tokens += r.total_tokens
existing.total_cost += r.cost
existing.actual_cost += r.actual_cost || 0
if (r.response_time_ms) existing.response_times.push(r.response_time_ms)
if (r.status === 'failed') existing.errors++
providerStats.set(r.provider, existing)
}
return createMockResponse(
Array.from(providerStats.entries()).map(([provider, stats]) => ({
provider_id: `provider-${provider}`,
provider,
request_count: stats.request_count * 50,
total_tokens: stats.total_tokens * 50,
total_cost: Number((stats.total_cost * 50).toFixed(2)),
actual_cost: Number((stats.actual_cost * 50).toFixed(2)),
avg_response_time_ms: Math.round(stats.response_times.reduce((a, b) => a + b, 0) / stats.response_times.length || 0),
success_rate: (stats.request_count - stats.errors) / stats.request_count,
error_count: stats.errors * 50
}))
)
}
if (groupBy === 'user') {
const userStats = new Map<string, { user_id: string; username: string; email: string; request_count: number; total_tokens: number; total_cost: number }>()
for (const r of records) {
const existing = userStats.get(r.user_id) || { user_id: r.user_id, username: r.username, email: r.user_email || '', request_count: 0, total_tokens: 0, total_cost: 0 }
existing.request_count++
existing.total_tokens += r.total_tokens
existing.total_cost += r.cost
userStats.set(r.user_id, existing)
}
return createMockResponse(
Array.from(userStats.values()).map(stats => ({
user_id: stats.user_id,
email: stats.email,
username: stats.username,
request_count: stats.request_count * 50,
total_tokens: stats.total_tokens * 50,
total_cost: Number((stats.total_cost * 50).toFixed(2))
}))
)
}
if (groupBy === 'api_format') {
const formatStats = new Map<string, { request_count: number; total_tokens: number; total_cost: number; actual_cost: number; response_times: number[] }>()
for (const r of records) {
const existing = formatStats.get(r.api_format) || { request_count: 0, total_tokens: 0, total_cost: 0, actual_cost: 0, response_times: [] }
existing.request_count++
existing.total_tokens += r.total_tokens
existing.total_cost += r.cost
existing.actual_cost += r.actual_cost || 0
if (r.response_time_ms) existing.response_times.push(r.response_time_ms)
formatStats.set(r.api_format, existing)
}
return createMockResponse(
Array.from(formatStats.entries()).map(([api_format, stats]) => ({
api_format,
request_count: stats.request_count * 50,
total_tokens: stats.total_tokens * 50,
total_cost: Number((stats.total_cost * 50).toFixed(2)),
actual_cost: Number((stats.actual_cost * 50).toFixed(2)),
avg_response_time_ms: Math.round(stats.response_times.reduce((a, b) => a + b, 0) / stats.response_times.length || 0)
}))
)
}
return createMockResponse([])
},
'GET /api/admin/usage/active': async () => {
await delay()
requireAdmin()
return createMockResponse({ requests: [] })
},
// ========== Admin: System ==========
'GET /api/admin/system/configs': async () => {
await delay()
requireAdmin()
return createMockResponse(MOCK_SYSTEM_CONFIGS)
},
'GET /api/admin/system/api-formats': async () => {
await delay()
return createMockResponse(MOCK_API_FORMATS)
},
'GET /api/admin/system/stats': async () => {
await delay()
requireAdmin()
return createMockResponse({
total_requests_today: 1234,
total_requests_month: 45678,
total_users: 156,
active_users_today: 28,
total_cost_today: 45.67,
total_cost_month: 1234.56,
uptime_hours: 720,
cache_hit_rate: 0.35
})
},
// ========== 能力接口 ==========
'GET /api/capabilities': async () => {
await delay()
return createMockResponse({ capabilities: MOCK_CAPABILITIES })
},
'GET /api/capabilities/user-configurable': async () => {
await delay()
return createMockResponse({ capabilities: MOCK_CAPABILITIES.filter(c => c.match_mode === 'exclusive') })
},
// ========== 公开接口 ==========
'GET /api/public/global-models': async () => {
await delay()
return createMockResponse({
models: MOCK_GLOBAL_MODELS.map(m => ({
id: m.id,
name: m.name,
display_name: m.display_name,
is_active: m.is_active,
default_tiered_pricing: m.default_tiered_pricing,
default_price_per_request: m.default_price_per_request,
supported_capabilities: m.supported_capabilities,
config: m.config
})),
total: MOCK_GLOBAL_MODELS.length
})
},
'GET /api/public/models': async () => {
await delay()
return createMockResponse({
models: MOCK_GLOBAL_MODELS.map(m => ({
name: m.name,
display_name: m.display_name,
description: m.description
}))
})
},
'GET /api/public/health': async () => {
await delay(50)
return createMockResponse({ status: 'healthy', demo_mode: true })
},
'GET /api/public/health/api-formats': async () => {
await delay()
return createMockResponse({
generated_at: new Date().toISOString(),
formats: MOCK_ENDPOINT_STATUS.formats.map(f => ({
api_format: f.api_format,
api_path: f.api_path,
total_attempts: f.total_attempts,
success_count: f.success_count,
failed_count: f.failed_count,
skipped_count: f.skipped_count,
success_rate: f.success_rate,
last_event_at: f.last_event_at,
events: f.events.slice(0, 10)
}))
})
}
}
// 动态路由匹配器 - 支持 :id 形式的参数
interface RouteMatch {
handler: (config: AxiosRequestConfig, params: Record<string, string>) => Promise<AxiosResponse<any>>
params: Record<string, string>
}
type DynamicHandler = (config: AxiosRequestConfig, params: Record<string, string>) => Promise<AxiosResponse<any>>
// 动态路由注册表
const dynamicRoutes: Array<{
method: string
pattern: RegExp
paramNames: string[]
handler: DynamicHandler
}> = []
/**
* 注册动态路由
*/
function registerDynamicRoute(
method: string,
path: string,
handler: DynamicHandler
) {
// 将 :param 形式转换为正则
const paramNames: string[] = []
const regexStr = path.replace(/:([^/]+)/g, (_, paramName) => {
paramNames.push(paramName)
return '([^/]+)'
})
dynamicRoutes.push({
method: method.toUpperCase(),
pattern: new RegExp(`^${regexStr}$`),
paramNames,
handler
})
}
/**
* 匹配动态路由
*/
function matchDynamicRoute(method: string, url: string): RouteMatch | null {
const cleanUrl = url.split('?')[0]
const upperMethod = method.toUpperCase()
for (const route of dynamicRoutes) {
if (route.method !== upperMethod) continue
const match = cleanUrl.match(route.pattern)
if (match) {
const params: Record<string, string> = {}
route.paramNames.forEach((name, index) => {
params[name] = match[index + 1]
})
return { handler: route.handler, params }
}
}
return null
}
/**
* 匹配请求到 handler
*/
function matchHandler(method: string, url: string): ((config: AxiosRequestConfig) => Promise<AxiosResponse<any>>) | null {
// 移除查询参数
const cleanUrl = url.split('?')[0]
const upperMethod = method.toUpperCase()
// 精确匹配
const exactKey = `${upperMethod} ${cleanUrl}`
if (mockHandlers[exactKey]) {
return mockHandlers[exactKey]
}
// 动态路由匹配
const dynamicMatch = matchDynamicRoute(method, url)
if (dynamicMatch) {
return (config) => dynamicMatch.handler(config, dynamicMatch.params)
}
// 路径前缀匹配(按优先级排序)
const sortedPatterns = Object.keys(mockHandlers).sort((a, b) => b.length - a.length)
for (const pattern of sortedPatterns) {
const [patternMethod, patternPath] = pattern.split(' ')
if (patternMethod !== upperMethod) continue
// 检查是否为前缀匹配(用于处理带 ID 的路由)
if (cleanUrl.startsWith(patternPath) || patternPath === cleanUrl) {
return mockHandlers[pattern]
}
}
return null
}
/**
* 处理 Mock 请求
*/
export async function handleMockRequest(config: AxiosRequestConfig): Promise<AxiosResponse<any> | null> {
if (!isDemoMode()) {
return null
}
const method = config.method?.toUpperCase() || 'GET'
const url = config.url || ''
// 尝试匹配 handler
const handler = matchHandler(method, url)
if (handler) {
try {
return await handler(config)
} catch (error: any) {
if (error.response) {
throw error
}
console.error('[Mock] Handler error:', error)
throw { response: createMockResponse({ detail: '模拟请求处理失败' }, 500) }
}
}
// 未匹配的请求返回默认响应
console.warn(`[Mock] Unhandled request: ${method} ${url}`)
return createMockResponse({ message: '演示模式:该接口暂未模拟', demo_mode: true })
}
/**
* 设置当前用户 token供 client 初始化使用)
*/
export function setMockUserToken(token: string | null): void {
currentUserToken = token
}
/**
* 获取当前 mock token
*/
export function getMockUserToken(): string | null {
return currentUserToken
}
// ========== Mock Provider Endpoints 数据 ==========
// 为每个 provider 生成对应的 endpoints
function generateMockEndpointsForProvider(providerId: string) {
const provider = MOCK_PROVIDERS.find(p => p.id === providerId)
if (!provider || provider.api_formats.length === 0) return []
return provider.api_formats.map((format, index) => {
const healthDetail = provider.endpoint_health_details.find(h => h.api_format === format)
return {
id: `ep-${providerId}-${index + 1}`,
provider_id: providerId,
provider_name: provider.name,
api_format: format,
base_url: format.includes('CLAUDE') ? 'https://api.anthropic.com' :
format.includes('OPENAI') ? 'https://api.openai.com' :
'https://generativelanguage.googleapis.com',
auth_type: format.includes('GEMINI') ? 'api_key' : 'bearer',
timeout: 120,
max_retries: 3,
priority: 100 - index * 10,
weight: 100,
health_score: healthDetail?.health_score ?? 1.0,
consecutive_failures: healthDetail?.health_score && healthDetail.health_score < 0.7 ? 2 : 0,
is_active: healthDetail?.is_active ?? true,
total_keys: Math.ceil(Math.random() * 3) + 1,
active_keys: Math.ceil(Math.random() * 2) + 1,
created_at: provider.created_at,
updated_at: new Date().toISOString()
}
})
}
// 为 endpoint 生成 keys
function generateMockKeysForEndpoint(endpointId: string, count: number = 2) {
return Array.from({ length: count }, (_, i) => ({
id: `key-${endpointId}-${i + 1}`,
endpoint_id: endpointId,
api_key_masked: `sk-***...${Math.random().toString(36).substring(2, 6)}`,
name: i === 0 ? 'Primary Key' : `Backup Key ${i}`,
rate_multiplier: 1.0,
internal_priority: i + 1,
health_score: 0.90 + Math.random() * 0.10, // 0.90-1.00
consecutive_failures: Math.random() > 0.8 ? 1 : 0,
request_count: 1000 + Math.floor(Math.random() * 5000),
success_count: 950 + Math.floor(Math.random() * 4800),
error_count: Math.floor(Math.random() * 100),
success_rate: 0.95 + Math.random() * 0.04, // 0.95-0.99
avg_response_time_ms: 800 + Math.floor(Math.random() * 600),
is_active: true,
created_at: '2024-01-01T00:00:00Z',
updated_at: new Date().toISOString()
}))
}
// 为 provider 生成 models
function generateMockModelsForProvider(providerId: string) {
const provider = MOCK_PROVIDERS.find(p => p.id === providerId)
if (!provider) return []
// 基于 provider 的 api_formats 选择合适的模型
const hasClaude = provider.api_formats.some(f => f.includes('CLAUDE'))
const hasOpenAI = provider.api_formats.some(f => f.includes('OPENAI'))
const hasGemini = provider.api_formats.some(f => f.includes('GEMINI'))
const models: any[] = []
const now = new Date().toISOString()
if (hasClaude) {
models.push(
{
id: `pm-${providerId}-claude-1`,
provider_id: providerId,
global_model_id: 'gm-003',
provider_model_name: 'claude-sonnet-4-5-20250929',
global_model_name: 'claude-sonnet-4-5-20250929',
global_model_display_name: 'claude-sonnet-4-5',
effective_input_price: 3.0,
effective_output_price: 15.0,
effective_supports_vision: true,
effective_supports_function_calling: true,
effective_supports_streaming: true,
effective_supports_extended_thinking: true,
is_active: true,
is_available: true,
created_at: provider.created_at,
updated_at: now
},
{
id: `pm-${providerId}-claude-2`,
provider_id: providerId,
global_model_id: 'gm-001',
provider_model_name: 'claude-haiku-4-5-20251001',
global_model_name: 'claude-haiku-4-5-20251001',
global_model_display_name: 'claude-haiku-4-5',
effective_input_price: 1.0,
effective_output_price: 5.0,
effective_supports_vision: true,
effective_supports_function_calling: true,
effective_supports_streaming: true,
effective_supports_extended_thinking: true,
is_active: true,
is_available: true,
created_at: provider.created_at,
updated_at: now
}
)
}
if (hasOpenAI) {
models.push(
{
id: `pm-${providerId}-openai-1`,
provider_id: providerId,
global_model_id: 'gm-006',
provider_model_name: 'gpt-5.1',
global_model_name: 'gpt-5.1',
global_model_display_name: 'gpt-5.1',
effective_input_price: 1.25,
effective_output_price: 10.0,
effective_supports_vision: true,
effective_supports_function_calling: true,
effective_supports_streaming: true,
effective_supports_extended_thinking: true,
is_active: true,
is_available: true,
created_at: provider.created_at,
updated_at: now
},
{
id: `pm-${providerId}-openai-2`,
provider_id: providerId,
global_model_id: 'gm-007',
provider_model_name: 'gpt-5.1-codex',
global_model_name: 'gpt-5.1-codex',
global_model_display_name: 'gpt-5.1-codex',
effective_input_price: 1.25,
effective_output_price: 10.0,
effective_supports_vision: true,
effective_supports_function_calling: true,
effective_supports_streaming: true,
effective_supports_extended_thinking: true,
is_active: true,
is_available: true,
created_at: provider.created_at,
updated_at: now
}
)
}
if (hasGemini) {
models.push(
{
id: `pm-${providerId}-gemini-1`,
provider_id: providerId,
global_model_id: 'gm-005',
provider_model_name: 'gemini-3-pro-preview',
global_model_name: 'gemini-3-pro-preview',
global_model_display_name: 'gemini-3-pro-preview',
effective_input_price: 2.0,
effective_output_price: 12.0,
effective_supports_vision: true,
effective_supports_function_calling: true,
effective_supports_streaming: true,
effective_supports_extended_thinking: true,
is_active: true,
is_available: true,
created_at: provider.created_at,
updated_at: now
}
)
}
return models
}
// ========== 注册动态路由 ==========
// Provider 详情
registerDynamicRoute('GET', '/api/admin/providers/:providerId/summary', async (_config, params) => {
await delay()
requireAdmin()
const provider = MOCK_PROVIDERS.find(p => p.id === params.providerId)
if (!provider) {
throw { response: createMockResponse({ detail: '提供商不存在' }, 404) }
}
return createMockResponse(provider)
})
// Provider 更新
registerDynamicRoute('PATCH', '/api/admin/providers/:providerId', async (config, params) => {
await delay()
requireAdmin()
const provider = MOCK_PROVIDERS.find(p => p.id === params.providerId)
if (!provider) {
throw { response: createMockResponse({ detail: '提供商不存在' }, 404) }
}
const body = JSON.parse(config.data || '{}')
return createMockResponse({ ...provider, ...body, updated_at: new Date().toISOString() })
})
// Provider 删除
registerDynamicRoute('DELETE', '/api/admin/providers/:providerId', async (_config, params) => {
await delay()
requireAdmin()
const provider = MOCK_PROVIDERS.find(p => p.id === params.providerId)
if (!provider) {
throw { response: createMockResponse({ detail: '提供商不存在' }, 404) }
}
return createMockResponse({ message: '删除成功(演示模式)' })
})
// Provider Endpoints 列表
registerDynamicRoute('GET', '/api/admin/endpoints/providers/:providerId/endpoints', async (_config, params) => {
await delay()
requireAdmin()
const endpoints = generateMockEndpointsForProvider(params.providerId)
return createMockResponse(endpoints)
})
// 创建 Endpoint
registerDynamicRoute('POST', '/api/admin/endpoints/providers/:providerId/endpoints', async (config, params) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
return createMockResponse({
id: `ep-demo-${Date.now()}`,
provider_id: params.providerId,
...body,
created_at: new Date().toISOString()
})
})
// Endpoint 详情
registerDynamicRoute('GET', '/api/admin/endpoints/:endpointId', async (_config, params) => {
await delay()
requireAdmin()
// 从所有 providers 的 endpoints 中查找
for (const provider of MOCK_PROVIDERS) {
const endpoints = generateMockEndpointsForProvider(provider.id)
const endpoint = endpoints.find(e => e.id === params.endpointId)
if (endpoint) {
return createMockResponse(endpoint)
}
}
throw { response: createMockResponse({ detail: '端点不存在' }, 404) }
})
// Endpoint 更新
registerDynamicRoute('PUT', '/api/admin/endpoints/:endpointId', async (config, params) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
return createMockResponse({ id: params.endpointId, ...body, updated_at: new Date().toISOString() })
})
// Endpoint 删除
registerDynamicRoute('DELETE', '/api/admin/endpoints/:endpointId', async (_config, _params) => {
await delay()
requireAdmin()
return createMockResponse({ message: '删除成功(演示模式)' })
})
// Endpoint Keys 列表
registerDynamicRoute('GET', '/api/admin/endpoints/:endpointId/keys', async (_config, params) => {
await delay()
requireAdmin()
const keys = generateMockKeysForEndpoint(params.endpointId, 2)
return createMockResponse(keys)
})
// 创建 Key
registerDynamicRoute('POST', '/api/admin/endpoints/:endpointId/keys', async (config, params) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
return createMockResponse({
id: `key-demo-${Date.now()}`,
endpoint_id: params.endpointId,
api_key_masked: 'sk-***...demo',
...body,
created_at: new Date().toISOString()
})
})
// Key 更新
registerDynamicRoute('PUT', '/api/admin/endpoints/keys/:keyId', async (config, params) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
return createMockResponse({ id: params.keyId, ...body, updated_at: new Date().toISOString() })
})
// Key 删除
registerDynamicRoute('DELETE', '/api/admin/endpoints/keys/:keyId', async (_config, _params) => {
await delay()
requireAdmin()
return createMockResponse({ message: '删除成功(演示模式)' })
})
// Provider Models 列表
registerDynamicRoute('GET', '/api/admin/providers/:providerId/models', async (_config, params) => {
await delay()
requireAdmin()
const models = generateMockModelsForProvider(params.providerId)
return createMockResponse(models)
})
// Provider Model 详情
registerDynamicRoute('GET', '/api/admin/providers/:providerId/models/:modelId', async (_config, params) => {
await delay()
requireAdmin()
const models = generateMockModelsForProvider(params.providerId)
const model = models.find(m => m.id === params.modelId)
if (!model) {
throw { response: createMockResponse({ detail: '模型不存在' }, 404) }
}
return createMockResponse(model)
})
// 创建 Provider Model
registerDynamicRoute('POST', '/api/admin/providers/:providerId/models', async (config, params) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
return createMockResponse({
id: `pm-demo-${Date.now()}`,
provider_id: params.providerId,
...body,
created_at: new Date().toISOString()
})
})
// 更新 Provider Model
registerDynamicRoute('PATCH', '/api/admin/providers/:providerId/models/:modelId', async (config, params) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
return createMockResponse({ id: params.modelId, provider_id: params.providerId, ...body, updated_at: new Date().toISOString() })
})
// 删除 Provider Model
registerDynamicRoute('DELETE', '/api/admin/providers/:providerId/models/:modelId', async (_config, _params) => {
await delay()
requireAdmin()
return createMockResponse({ message: '删除成功(演示模式)' })
})
// 批量创建 Provider Models
registerDynamicRoute('POST', '/api/admin/providers/:providerId/models/batch', async (config, params) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
const models = (body.models || []).map((m: any, i: number) => ({
id: `pm-demo-${Date.now()}-${i}`,
provider_id: params.providerId,
...m,
created_at: new Date().toISOString()
}))
return createMockResponse({ models, created_count: models.length })
})
// Provider 可用源模型
registerDynamicRoute('GET', '/api/admin/providers/:providerId/available-source-models', async (_config, params) => {
await delay()
requireAdmin()
const provider = MOCK_PROVIDERS.find(p => p.id === params.providerId)
if (!provider) {
throw { response: createMockResponse({ detail: '提供商不存在' }, 404) }
}
// 返回一些可用的源模型
const availableModels = [
'claude-sonnet-4-5-20250929',
'claude-haiku-4-5-20251001',
'claude-opus-4-5-20251101',
'gpt-5.1',
'gpt-5.1-codex',
'gemini-3-pro-preview'
]
return createMockResponse({ models: availableModels })
})
// 分配 GlobalModels 到 Provider
registerDynamicRoute('POST', '/api/admin/providers/:providerId/assign-global-models', async (config, _params) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
const result = {
success: (body.global_model_ids || []).map((id: string) => ({
global_model_id: id,
provider_model_id: `pm-demo-${Date.now()}-${id}`
})),
errors: []
}
return createMockResponse(result)
})
// GlobalModel 详情
registerDynamicRoute('GET', '/api/admin/models/global/:modelId', async (_config, params) => {
await delay()
requireAdmin()
const model = MOCK_GLOBAL_MODELS.find(m => m.id === params.modelId)
if (!model) {
throw { response: createMockResponse({ detail: '模型不存在' }, 404) }
}
return createMockResponse(model)
})
// GlobalModel 更新
registerDynamicRoute('PATCH', '/api/admin/models/global/:modelId', async (config, params) => {
await delay()
requireAdmin()
const model = MOCK_GLOBAL_MODELS.find(m => m.id === params.modelId)
if (!model) {
throw { response: createMockResponse({ detail: '模型不存在' }, 404) }
}
const body = JSON.parse(config.data || '{}')
return createMockResponse({ ...model, ...body, updated_at: new Date().toISOString() })
})
// GlobalModel 删除
registerDynamicRoute('DELETE', '/api/admin/models/global/:modelId', async (_config, params) => {
await delay()
requireAdmin()
const model = MOCK_GLOBAL_MODELS.find(m => m.id === params.modelId)
if (!model) {
throw { response: createMockResponse({ detail: '模型不存在' }, 404) }
}
return createMockResponse({ message: '删除成功(演示模式)' })
})
// GlobalModel 批量分配到 Providers
registerDynamicRoute('POST', '/api/admin/models/global/:modelId/assign-to-providers', async (config, _params) => {
await delay()
requireAdmin()
const body = JSON.parse(config.data || '{}')
const result = {
success: (body.provider_ids || []).map((providerId: string) => {
const provider = MOCK_PROVIDERS.find(p => p.id === providerId)
return {
provider_id: providerId,
provider_name: provider?.name || 'unknown',
model_id: `pm-demo-${Date.now()}-${providerId}`
}
}),
errors: []
}
return createMockResponse(result)
})
// Endpoint Health 详情
registerDynamicRoute('GET', '/api/admin/endpoints/health/endpoint/:endpointId', async (_config, params) => {
await delay()
requireAdmin()
return createMockResponse({
endpoint_id: params.endpointId,
health_score: 0.95,
total_requests: 5000,
success_count: 4750,
failed_count: 250,
success_rate: 0.95,
avg_response_time_ms: 1200,
last_success_at: new Date().toISOString(),
last_failure_at: new Date(Date.now() - 3600000).toISOString()
})
})
// Key Health 详情
registerDynamicRoute('GET', '/api/admin/endpoints/health/key/:keyId', async (_config, params) => {
await delay()
requireAdmin()
return createMockResponse({
key_id: params.keyId,
health_score: 0.92,
total_requests: 2000,
success_count: 1840,
failed_count: 160,
success_rate: 0.92,
avg_response_time_ms: 1100,
last_success_at: new Date().toISOString(),
last_failure_at: new Date(Date.now() - 7200000).toISOString()
})
})
// 重置 Key Health
registerDynamicRoute('PATCH', '/api/admin/endpoints/health/keys/:keyId', async (_config, params) => {
await delay()
requireAdmin()
return createMockResponse({
key_id: params.keyId,
message: '健康状态已重置(演示模式)'
})
})
// Alias/Mapping 详情
registerDynamicRoute('GET', '/api/admin/models/mappings/:mappingId', async (_config, params) => {
await delay()
requireAdmin()
const alias = MOCK_ALIASES.find(a => a.id === params.mappingId)
if (!alias) {
throw { response: createMockResponse({ detail: '映射不存在' }, 404) }
}
return createMockResponse(alias)
})
// Alias/Mapping 更新
registerDynamicRoute('PATCH', '/api/admin/models/mappings/:mappingId', async (config, params) => {
await delay()
requireAdmin()
const alias = MOCK_ALIASES.find(a => a.id === params.mappingId)
if (!alias) {
throw { response: createMockResponse({ detail: '映射不存在' }, 404) }
}
const body = JSON.parse(config.data || '{}')
return createMockResponse({ ...alias, ...body, updated_at: new Date().toISOString() })
})
// Alias/Mapping 删除
registerDynamicRoute('DELETE', '/api/admin/models/mappings/:mappingId', async (_config, params) => {
await delay()
requireAdmin()
const alias = MOCK_ALIASES.find(a => a.id === params.mappingId)
if (!alias) {
throw { response: createMockResponse({ detail: '映射不存在' }, 404) }
}
return createMockResponse({ message: '删除成功(演示模式)' })
})
// 公告详情
registerDynamicRoute('GET', '/api/announcements/:announcementId', async (_config, params) => {
await delay()
const announcement = MOCK_ANNOUNCEMENTS.find(a => a.id === params.announcementId)
if (!announcement) {
throw { response: createMockResponse({ detail: '公告不存在' }, 404) }
}
return createMockResponse(announcement)
})
// 公告更新
registerDynamicRoute('PATCH', '/api/announcements/:announcementId', async (config, params) => {
await delay()
requireAdmin()
const announcement = MOCK_ANNOUNCEMENTS.find(a => a.id === params.announcementId)
if (!announcement) {
throw { response: createMockResponse({ detail: '公告不存在' }, 404) }
}
const body = JSON.parse(config.data || '{}')
return createMockResponse({ ...announcement, ...body, updated_at: new Date().toISOString() })
})
// 公告删除
registerDynamicRoute('DELETE', '/api/announcements/:announcementId', async (_config, params) => {
await delay()
requireAdmin()
const announcement = MOCK_ANNOUNCEMENTS.find(a => a.id === params.announcementId)
if (!announcement) {
throw { response: createMockResponse({ detail: '公告不存在' }, 404) }
}
return createMockResponse({ message: '删除成功(演示模式)' })
})
// 用户详情
registerDynamicRoute('GET', '/api/admin/users/:userId', async (_config, params) => {
await delay()
requireAdmin()
const user = MOCK_ALL_USERS.find(u => u.id === params.userId)
if (!user) {
throw { response: createMockResponse({ detail: '用户不存在' }, 404) }
}
return createMockResponse(user)
})
// 用户更新
registerDynamicRoute('PATCH', '/api/admin/users/:userId', async (config, params) => {
await delay()
requireAdmin()
const user = MOCK_ALL_USERS.find(u => u.id === params.userId)
if (!user) {
throw { response: createMockResponse({ detail: '用户不存在' }, 404) }
}
const body = JSON.parse(config.data || '{}')
return createMockResponse({ ...user, ...body })
})
// 用户删除
registerDynamicRoute('DELETE', '/api/admin/users/:userId', async (_config, params) => {
await delay()
requireAdmin()
const user = MOCK_ALL_USERS.find(u => u.id === params.userId)
if (!user) {
throw { response: createMockResponse({ detail: '用户不存在' }, 404) }
}
return createMockResponse({ message: '删除成功(演示模式)' })
})
// 用户 API Keys
registerDynamicRoute('GET', '/api/admin/users/:userId/api-keys', async (_config, _params) => {
await delay()
requireAdmin()
return createMockResponse(MOCK_USER_API_KEYS)
})
// API Key 详情
registerDynamicRoute('GET', '/api/admin/api-keys/:keyId', async (_config, params) => {
await delay()
requireAdmin()
const key = MOCK_ADMIN_API_KEYS.api_keys.find(k => k.id === params.keyId)
if (!key) {
throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) }
}
return createMockResponse(key)
})
// API Key 更新
registerDynamicRoute('PATCH', '/api/admin/api-keys/:keyId', async (config, params) => {
await delay()
requireAdmin()
const key = MOCK_ADMIN_API_KEYS.api_keys.find(k => k.id === params.keyId)
if (!key) {
throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) }
}
const body = JSON.parse(config.data || '{}')
return createMockResponse({ ...key, ...body })
})
// API Key 删除
registerDynamicRoute('DELETE', '/api/admin/api-keys/:keyId', async (_config, params) => {
await delay()
requireAdmin()
const key = MOCK_ADMIN_API_KEYS.api_keys.find(k => k.id === params.keyId)
if (!key) {
throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) }
}
return createMockResponse({ message: '删除成功(演示模式)' })
})
// 用户 API Key 删除
registerDynamicRoute('DELETE', '/api/users/me/api-keys/:keyId', async (_config, params) => {
await delay()
const key = MOCK_USER_API_KEYS.find(k => k.id === params.keyId)
if (!key) {
throw { response: createMockResponse({ detail: 'API Key 不存在' }, 404) }
}
return createMockResponse({ message: '删除成功(演示模式)' })
})
// 使用记录详情 - /api/admin/usage/:requestId
registerDynamicRoute('GET', '/api/admin/usage/:requestId', async (_config, params) => {
await delay()
requireAdmin()
const records = getUsageRecords()
const record = records.find(r => r.id === params.requestId)
if (!record) {
throw { response: createMockResponse({ detail: '请求记录不存在' }, 404) }
}
// 生成详细的请求信息
const users = [
{ id: 'demo-admin-uuid-0001', username: 'Demo Admin', email: 'admin@demo.aether.ai' },
{ id: 'demo-user-uuid-0002', username: 'Demo User', email: 'user@demo.aether.ai' },
{ id: 'demo-user-uuid-0003', username: 'Alice Chen', email: 'alice@demo.aether.ai' },
{ id: 'demo-user-uuid-0004', username: 'Bob Zhang', email: 'bob@demo.aether.ai' }
]
const user = users.find(u => u.id === record.user_id) || users[0]
// 生成模拟的请求/响应数据
const mockRequestBody = {
model: record.model,
max_tokens: 4096,
messages: [
{
role: 'user',
content: 'Hello! Can you help me understand how API gateways work?'
}
],
stream: record.is_stream
}
const mockResponseBody = record.status === 'failed' ? {
error: {
type: 'api_error',
message: record.error_message || 'An error occurred'
}
} : {
id: `msg_${record.id}`,
type: 'message',
role: 'assistant',
content: [
{
type: 'text',
text: 'API gateways are middleware services that sit between clients and backend services. They handle routing, authentication, rate limiting, and more...'
}
],
model: record.model,
stop_reason: 'end_turn',
usage: {
input_tokens: record.input_tokens,
output_tokens: record.output_tokens
}
}
// 计算费用明细
const inputPricePer1M = record.model.includes('opus') ? 15 : record.model.includes('haiku') ? 1 : 3
const outputPricePer1M = record.model.includes('opus') ? 75 : record.model.includes('haiku') ? 5 : 15
const inputCost = (record.input_tokens / 1000000) * inputPricePer1M
const outputCost = (record.output_tokens / 1000000) * outputPricePer1M
const cacheCreationCost = (record.cache_creation_input_tokens / 1000000) * (inputPricePer1M * 1.25)
const cacheReadCost = (record.cache_read_input_tokens / 1000000) * (inputPricePer1M * 0.1)
const detail = {
id: record.id,
request_id: `req_${record.id}`,
user: {
id: user.id,
username: user.username,
email: user.email
},
api_key: {
id: `key-${record.api_key_name}`,
name: record.api_key_name,
display: `sk-***${record.api_key_name.slice(-4)}`
},
provider: record.provider,
api_format: record.api_format,
model: record.model,
target_model: record.target_model,
tokens: {
input: record.input_tokens,
output: record.output_tokens,
total: record.total_tokens
},
cost: {
input: inputCost,
output: outputCost,
total: record.cost
},
input_tokens: record.input_tokens,
output_tokens: record.output_tokens,
total_tokens: record.total_tokens,
cache_creation_input_tokens: record.cache_creation_input_tokens,
cache_read_input_tokens: record.cache_read_input_tokens,
input_cost: inputCost,
output_cost: outputCost,
total_cost: record.cost,
cache_creation_cost: cacheCreationCost,
cache_read_cost: cacheReadCost,
input_price_per_1m: inputPricePer1M,
output_price_per_1m: outputPricePer1M,
cache_creation_price_per_1m: inputPricePer1M * 1.25,
cache_read_price_per_1m: inputPricePer1M * 0.1,
request_type: record.is_stream ? 'stream' : 'standard',
is_stream: record.is_stream,
status_code: record.status_code,
error_message: record.error_message,
response_time_ms: record.response_time_ms,
created_at: record.created_at,
request_headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer sk-aether-***',
'X-Api-Key': 'sk-***',
'User-Agent': 'Aether-Client/1.0',
'Accept': 'application/json',
'X-Request-ID': `req_${record.id}`
},
request_body: mockRequestBody,
provider_request_headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer sk-${record.provider}-***`,
'anthropic-version': '2024-01-01',
'X-Request-ID': `req_${record.id}`
},
response_headers: {
'Content-Type': 'application/json',
'X-Request-ID': `req_${record.id}`,
'X-RateLimit-Limit': '1000',
'X-RateLimit-Remaining': '999',
'X-RateLimit-Reset': new Date(Date.now() + 60000).toISOString()
},
response_body: mockResponseBody,
metadata: {
client_ip: '192.168.1.100',
user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
request_path: `/v1/messages`,
provider_endpoint: `https://api.${record.provider}.com/v1/messages`,
gateway_version: '1.0.0',
processing_time_ms: Math.floor((record.response_time_ms || 1000) * 0.1)
},
tiered_pricing: {
total_input_context: record.input_tokens + record.cache_creation_input_tokens + record.cache_read_input_tokens,
tier_index: 0,
source: 'provider',
tiers: [
{
up_to: 200000,
input_price_per_1m: inputPricePer1M,
output_price_per_1m: outputPricePer1M,
cache_creation_price_per_1m: inputPricePer1M * 1.25,
cache_read_price_per_1m: inputPricePer1M * 0.1
},
{
up_to: null,
input_price_per_1m: inputPricePer1M * 0.5,
output_price_per_1m: outputPricePer1M * 0.5,
cache_creation_price_per_1m: inputPricePer1M * 0.625,
cache_read_price_per_1m: inputPricePer1M * 0.05
}
]
}
}
return createMockResponse(detail)
})
// 请求链路追踪 - /api/admin/monitoring/trace/:requestId
registerDynamicRoute('GET', '/api/admin/monitoring/trace/:requestId', async (_config, params) => {
await delay()
requireAdmin()
const requestId = params.requestId
// 从 usage-xxxx 格式中提取记录
const records = getUsageRecords()
const recordId = requestId.startsWith('req_') ? requestId.replace('req_', '') : requestId
const record = records.find(r => r.id === recordId)
if (!record) {
throw { response: createMockResponse({ detail: '请求记录不存在' }, 404) }
}
// 生成候选记录
const now = new Date(record.created_at)
const baseLatency = record.response_time_ms || 1000
// 根据请求状态生成不同的候选链路
const candidates = []
const providerNames = ['AlphaAI', 'BetaClaude', 'GammaCode', 'DeltaAPI']
if (record.status === 'completed') {
// 成功请求可能有1-2个跳过的候选最后一个成功
const skipCount = Math.random() > 0.5 ? 1 : 0
for (let i = 0; i < skipCount; i++) {
const skipStarted = new Date(now.getTime() + i * 50)
candidates.push({
id: `candidate-${requestId}-${i}`,
request_id: requestId,
candidate_index: i,
retry_index: 0,
provider_id: `provider-${i + 1}`,
provider_name: providerNames[i % providerNames.length],
provider_website: `https://${providerNames[i % providerNames.length].toLowerCase()}.com`,
endpoint_id: `endpoint-${i + 1}`,
endpoint_name: record.api_format,
key_id: `key-${i + 1}`,
key_name: `${record.provider}-key-${i + 1}`,
key_preview: `sk-***${Math.random().toString(36).substring(2, 6)}`,
key_capabilities: { 'cache_1h': true, 'vision': true },
required_capabilities: { 'cache_1h': record.cache_read_input_tokens > 0 },
status: 'skipped',
skip_reason: ['并发限制已满', '健康分数过低', '倍率不匹配'][i % 3],
is_cached: false,
latency_ms: 10 + Math.floor(Math.random() * 20),
created_at: skipStarted.toISOString(),
started_at: skipStarted.toISOString(),
finished_at: new Date(skipStarted.getTime() + 10).toISOString()
})
}
// 成功的候选
const successStarted = new Date(now.getTime() + skipCount * 50)
candidates.push({
id: `candidate-${requestId}-success`,
request_id: requestId,
candidate_index: skipCount,
retry_index: 0,
provider_id: `provider-${record.provider}`,
provider_name: record.provider === 'anthropic' ? 'AlphaAI' : record.provider === 'openai' ? 'BetaClaude' : 'GammaCode',
provider_website: `https://api.${record.provider}.com`,
endpoint_id: `endpoint-${record.provider}`,
endpoint_name: record.api_format,
key_id: `key-${record.api_key_name}`,
key_name: record.api_key_name,
key_preview: `sk-***${Math.random().toString(36).substring(2, 6)}`,
key_capabilities: { 'cache_1h': true, 'vision': true, 'extended_thinking': true },
required_capabilities: {
'cache_1h': record.cache_read_input_tokens > 0,
'vision': false,
'extended_thinking': false
},
status: 'success',
is_cached: record.cache_read_input_tokens > 0,
status_code: 200,
latency_ms: baseLatency,
created_at: successStarted.toISOString(),
started_at: successStarted.toISOString(),
finished_at: new Date(successStarted.getTime() + baseLatency).toISOString()
})
} else if (record.status === 'failed') {
// 失败请求:多个候选都失败
const attemptCount = 2 + Math.floor(Math.random() * 2)
for (let i = 0; i < attemptCount; i++) {
const attemptStarted = new Date(now.getTime() + i * 200)
const attemptLatency = 100 + Math.floor(Math.random() * 500)
candidates.push({
id: `candidate-${requestId}-${i}`,
request_id: requestId,
candidate_index: i,
retry_index: 0,
provider_id: `provider-${i + 1}`,
provider_name: providerNames[i % providerNames.length],
provider_website: `https://${providerNames[i % providerNames.length].toLowerCase()}.com`,
endpoint_id: `endpoint-${i + 1}`,
endpoint_name: record.api_format,
key_id: `key-${i + 1}`,
key_name: `${record.provider}-key-${i + 1}`,
key_preview: `sk-***${Math.random().toString(36).substring(2, 6)}`,
key_capabilities: { 'cache_1h': true },
required_capabilities: {},
status: 'failed',
is_cached: false,
status_code: record.status_code,
error_type: ['rate_limit_error', 'api_error', 'timeout_error'][i % 3],
error_message: record.error_message || 'Request failed',
latency_ms: attemptLatency,
created_at: attemptStarted.toISOString(),
started_at: attemptStarted.toISOString(),
finished_at: new Date(attemptStarted.getTime() + attemptLatency).toISOString()
})
}
} else {
// 进行中的请求
candidates.push({
id: `candidate-${requestId}-0`,
request_id: requestId,
candidate_index: 0,
retry_index: 0,
provider_id: `provider-${record.provider}`,
provider_name: record.provider === 'anthropic' ? 'AlphaAI' : record.provider === 'openai' ? 'BetaClaude' : 'GammaCode',
provider_website: `https://api.${record.provider}.com`,
endpoint_id: `endpoint-${record.provider}`,
endpoint_name: record.api_format,
key_id: `key-${record.api_key_name}`,
key_name: record.api_key_name,
key_preview: `sk-***${Math.random().toString(36).substring(2, 6)}`,
key_capabilities: { 'cache_1h': true, 'vision': true },
required_capabilities: {},
status: 'streaming',
is_cached: false,
latency_ms: undefined,
created_at: now.toISOString(),
started_at: now.toISOString(),
finished_at: undefined
})
}
const totalLatency = candidates.reduce((sum, c) => sum + (c.latency_ms || 0), 0)
return createMockResponse({
request_id: requestId,
total_candidates: candidates.length,
final_status: record.status === 'completed' ? 'success' : record.status === 'failed' ? 'failed' : 'streaming',
total_latency_ms: totalLatency,
candidates
})
})
// ========== 请求间隔时间线 Mock 数据 ==========
// 生成请求间隔时间线数据(用于散点图)
function generateIntervalTimelineData(
hours: number = 24,
limit: number = 5000,
includeUserInfo: boolean = false
) {
const now = Date.now()
const startTime = now - hours * 60 * 60 * 1000
const points: Array<{ x: string; y: number; user_id?: string; model?: string }> = []
// 用户列表(用于管理员视图)
const users = [
{ id: 'demo-admin-uuid-0001', username: 'Demo Admin' },
{ id: 'demo-user-uuid-0002', username: 'Demo User' },
{ id: 'demo-user-uuid-0003', username: 'Alice Chen' },
{ id: 'demo-user-uuid-0004', username: 'Bob Zhang' }
]
// 模型列表(用于按模型区分颜色)
const models = [
'claude-sonnet-4-20250514',
'claude-3-5-sonnet-20241022',
'claude-3-5-haiku-20241022',
'claude-opus-4-20250514'
]
// 生成模拟的请求间隔数据
// 间隔时间分布:大部分在 0-10 分钟,少量在 10-60 分钟,极少数在 60-120 分钟
const pointCount = Math.min(limit, Math.floor(hours * 80)) // 每小时约 80 个数据点
let currentTime = startTime + Math.random() * 60 * 1000 // 从起始时间后随机开始
for (let i = 0; i < pointCount && currentTime < now; i++) {
// 生成间隔时间(分钟),使用指数分布模拟真实场景
let interval: number
const rand = Math.random()
if (rand < 0.7) {
// 70% 的请求间隔在 0-5 分钟
interval = Math.random() * 5
} else if (rand < 0.9) {
// 20% 的请求间隔在 5-30 分钟
interval = 5 + Math.random() * 25
} else if (rand < 0.98) {
// 8% 的请求间隔在 30-90 分钟
interval = 30 + Math.random() * 60
} else {
// 2% 的请求间隔在 90-120 分钟
interval = 90 + Math.random() * 30
}
// 添加一些工作时间的模式(工作时间间隔更短)
const hour = new Date(currentTime).getHours()
if (hour >= 9 && hour <= 18) {
interval *= 0.6 // 工作时间间隔更短
} else if (hour >= 22 || hour <= 6) {
interval *= 1.5 // 夜间间隔更长
}
// 确保间隔不超过 120 分钟
interval = Math.min(interval, 120)
const point: { x: string; y: number; user_id?: string; model?: string } = {
x: new Date(currentTime).toISOString(),
y: Math.round(interval * 100) / 100,
model: models[Math.floor(Math.random() * models.length)]
}
if (includeUserInfo) {
// 管理员视图:添加用户信息
const user = users[Math.floor(Math.random() * users.length)]
point.user_id = user.id
}
points.push(point)
// 下一个请求时间 = 当前时间 + 间隔 + 一些随机抖动
currentTime += interval * 60 * 1000 + Math.random() * 30 * 1000
}
// 按时间排序
points.sort((a, b) => new Date(a.x).getTime() - new Date(b.x).getTime())
// 收集出现的模型
const usedModels = [...new Set(points.map(p => p.model).filter(Boolean))] as string[]
const response: {
analysis_period_hours: number
total_points: number
points: typeof points
users?: Record<string, string>
models?: string[]
} = {
analysis_period_hours: hours,
total_points: points.length,
points,
models: usedModels
}
if (includeUserInfo) {
response.users = Object.fromEntries(users.map(u => [u.id, u.username]))
}
return response
}
// 用户 interval-timeline 接口
mockHandlers['GET /api/users/me/usage/interval-timeline'] = async (config) => {
await delay()
const params = config.params || {}
const hours = parseInt(params.hours) || 24
const limit = parseInt(params.limit) || 5000
const data = generateIntervalTimelineData(hours, limit, false)
return createMockResponse(data)
}
// 管理员 interval-timeline 接口
mockHandlers['GET /api/admin/usage/cache-affinity/interval-timeline'] = async (config) => {
await delay()
requireAdmin()
const params = config.params || {}
const hours = parseInt(params.hours) || 24
const limit = parseInt(params.limit) || 10000
const userId = params.user_id
const includeUserInfo = params.include_user_info === 'true' || params.include_user_info === true
// 如果指定了 user_id则不包含用户信息
const data = generateIntervalTimelineData(hours, limit, includeUserInfo && !userId)
return createMockResponse(data)
}
// ========== TTL 分析 Mock 数据 ==========
// 生成 TTL 分析数据
function generateTTLAnalysisData(hours: number = 168) {
const users = [
{ id: 'demo-admin-uuid-0001', username: 'Demo Admin', email: 'admin@demo.aether.io' },
{ id: 'demo-user-uuid-0002', username: 'Demo User', email: 'user@demo.aether.io' },
{ id: 'demo-user-uuid-0003', username: 'Alice Chen', email: 'alice@demo.aether.io' },
{ id: 'demo-user-uuid-0004', username: 'Bob Zhang', email: 'bob@demo.aether.io' }
]
const usersAnalysis = users.map(user => {
// 为每个用户生成不同的使用模式
const requestCount = 50 + Math.floor(Math.random() * 500)
// 根据用户特性生成不同的间隔分布
let within5min, within15min, within30min, within60min, over60min
let p50, p75, p90
let recommendedTtl: number
let recommendationReason: string
const userType = Math.random()
if (userType < 0.3) {
// 高频用户 (30%)
within5min = Math.floor(requestCount * (0.6 + Math.random() * 0.2))
within15min = Math.floor(requestCount * (0.1 + Math.random() * 0.1))
within30min = Math.floor(requestCount * (0.05 + Math.random() * 0.05))
within60min = Math.floor(requestCount * (0.02 + Math.random() * 0.03))
over60min = requestCount - within5min - within15min - within30min - within60min
p50 = 1.5 + Math.random() * 2
p75 = 3 + Math.random() * 3
p90 = 4 + Math.random() * 2
recommendedTtl = 5
recommendationReason = `高频用户90% 的请求间隔在 ${p90.toFixed(1)} 分钟内`
} else if (userType < 0.6) {
// 中频用户 (30%)
within5min = Math.floor(requestCount * (0.3 + Math.random() * 0.15))
within15min = Math.floor(requestCount * (0.25 + Math.random() * 0.15))
within30min = Math.floor(requestCount * (0.15 + Math.random() * 0.1))
within60min = Math.floor(requestCount * (0.1 + Math.random() * 0.05))
over60min = requestCount - within5min - within15min - within30min - within60min
p50 = 5 + Math.random() * 5
p75 = 10 + Math.random() * 8
p90 = 18 + Math.random() * 10
recommendedTtl = 15
recommendationReason = `中高频用户75% 的请求间隔在 ${p75.toFixed(1)} 分钟内`
} else if (userType < 0.85) {
// 中低频用户 (25%)
within5min = Math.floor(requestCount * (0.15 + Math.random() * 0.1))
within15min = Math.floor(requestCount * (0.2 + Math.random() * 0.1))
within30min = Math.floor(requestCount * (0.25 + Math.random() * 0.1))
within60min = Math.floor(requestCount * (0.15 + Math.random() * 0.1))
over60min = requestCount - within5min - within15min - within30min - within60min
p50 = 12 + Math.random() * 8
p75 = 22 + Math.random() * 10
p90 = 35 + Math.random() * 15
recommendedTtl = 30
recommendationReason = `中频用户75% 的请求间隔在 ${p75.toFixed(1)} 分钟内`
} else {
// 低频用户 (15%)
within5min = Math.floor(requestCount * (0.05 + Math.random() * 0.1))
within15min = Math.floor(requestCount * (0.1 + Math.random() * 0.1))
within30min = Math.floor(requestCount * (0.15 + Math.random() * 0.1))
within60min = Math.floor(requestCount * (0.25 + Math.random() * 0.1))
over60min = requestCount - within5min - within15min - within30min - within60min
p50 = 25 + Math.random() * 15
p75 = 45 + Math.random() * 20
p90 = 70 + Math.random() * 30
recommendedTtl = 60
recommendationReason = `低频用户75% 的请求间隔为 ${p75.toFixed(1)} 分钟,建议使用长 TTL`
}
// 确保没有负数
over60min = Math.max(0, over60min)
const avgInterval = (within5min * 2.5 + within15min * 10 + within30min * 22 + within60min * 45 + over60min * 80) / requestCount
return {
group_id: user.id,
username: user.username,
email: user.email,
request_count: requestCount,
interval_distribution: {
within_5min: within5min,
within_15min: within15min,
within_30min: within30min,
within_60min: within60min,
over_60min: over60min
},
interval_percentages: {
within_5min: Math.round(within5min / requestCount * 1000) / 10,
within_15min: Math.round(within15min / requestCount * 1000) / 10,
within_30min: Math.round(within30min / requestCount * 1000) / 10,
within_60min: Math.round(within60min / requestCount * 1000) / 10,
over_60min: Math.round(over60min / requestCount * 1000) / 10
},
percentiles: {
p50: Math.round(p50 * 100) / 100,
p75: Math.round(p75 * 100) / 100,
p90: Math.round(p90 * 100) / 100
},
avg_interval_minutes: Math.round(avgInterval * 100) / 100,
min_interval_minutes: Math.round((0.1 + Math.random() * 0.5) * 100) / 100,
max_interval_minutes: Math.round((80 + Math.random() * 40) * 100) / 100,
recommended_ttl_minutes: recommendedTtl,
recommendation_reason: recommendationReason
}
})
// 汇总 TTL 分布
const ttlDistribution = {
'5min': usersAnalysis.filter(u => u.recommended_ttl_minutes === 5).length,
'15min': usersAnalysis.filter(u => u.recommended_ttl_minutes === 15).length,
'30min': usersAnalysis.filter(u => u.recommended_ttl_minutes === 30).length,
'60min': usersAnalysis.filter(u => u.recommended_ttl_minutes === 60).length
}
return {
analysis_period_hours: hours,
total_users_analyzed: usersAnalysis.length,
ttl_distribution: ttlDistribution,
users: usersAnalysis
}
}
// 生成缓存命中分析数据
function generateCacheHitAnalysisData(hours: number = 168) {
const totalRequests = 5000 + Math.floor(Math.random() * 10000)
const requestsWithCacheHit = Math.floor(totalRequests * (0.25 + Math.random() * 0.35))
const totalInputTokens = totalRequests * (2000 + Math.floor(Math.random() * 3000))
const totalCacheReadTokens = Math.floor(totalInputTokens * (0.15 + Math.random() * 0.25))
const totalCacheCreationTokens = Math.floor(totalInputTokens * (0.05 + Math.random() * 0.1))
// 缓存读取成本:按每百万 token $0.30 计算
const cacheReadCostPer1M = 0.30
const cacheCreationCostPer1M = 3.75
const totalCacheReadCost = (totalCacheReadTokens / 1000000) * cacheReadCostPer1M
const totalCacheCreationCost = (totalCacheCreationTokens / 1000000) * cacheCreationCostPer1M
// 缓存读取节省了 90% 的成本
const estimatedSavings = totalCacheReadCost * 9
const tokenCacheHitRate = totalCacheReadTokens / (totalInputTokens + totalCacheReadTokens) * 100
return {
analysis_period_hours: hours,
total_requests: totalRequests,
requests_with_cache_hit: requestsWithCacheHit,
request_cache_hit_rate: Math.round(requestsWithCacheHit / totalRequests * 10000) / 100,
total_input_tokens: totalInputTokens,
total_cache_read_tokens: totalCacheReadTokens,
total_cache_creation_tokens: totalCacheCreationTokens,
token_cache_hit_rate: Math.round(tokenCacheHitRate * 100) / 100,
total_cache_read_cost_usd: Math.round(totalCacheReadCost * 10000) / 10000,
total_cache_creation_cost_usd: Math.round(totalCacheCreationCost * 10000) / 10000,
estimated_savings_usd: Math.round(estimatedSavings * 10000) / 10000
}
}
// TTL 分析接口
mockHandlers['GET /api/admin/usage/cache-affinity/ttl-analysis'] = async (config) => {
await delay()
requireAdmin()
const params = config.params || {}
const hours = parseInt(params.hours) || 168
const data = generateTTLAnalysisData(hours)
return createMockResponse(data)
}
// 缓存命中分析接口
mockHandlers['GET /api/admin/usage/cache-affinity/hit-analysis'] = async (config) => {
await delay()
requireAdmin()
const params = config.params || {}
const hours = parseInt(params.hours) || 168
const data = generateCacheHitAnalysisData(hours)
return createMockResponse(data)
}