/** * 开放平台API服务 * * 提供API Key管理、调用统计和日志查询功能 * 需求: 7, 11, 12 */ import { authService } from './authService'; const RAW_API_BASE = import.meta.env.VITE_API_BASE_URL || ''; const DEFAULT_API_PATH = import.meta.env.VITE_API_BASE_PATH || '/api/v1'; export const API_BASE = RAW_API_BASE; // API_BASE_FULL: 如果 RAW_API_BASE 已包含 /api/... 则直接使用,否则追加默认的路径 export const API_BASE_FULL = (() => { if (!RAW_API_BASE) return DEFAULT_API_PATH; if (/\/api(\/|$)/.test(RAW_API_BASE)) return RAW_API_BASE; return RAW_API_BASE.replace(/\/+$/g, '') + DEFAULT_API_PATH; })(); export interface ApiKey { id: number; api_key_prefix: string; name: string | null; status: 'active' | 'disabled'; key_type: 'public' | 'local'; last_used_at: string | null; created_at: string; } export interface ApiKeyCreateResponse { id: number; api_key: string; api_key_prefix: string; name: string | null; key_type: 'public' | 'local'; created_at: string; } export interface TrendItem { date: string; count: number; cost: string; } export interface ModelDistItem { model_name: string; count: number; percentage: number; } export interface Stats { today_calls: number; month_calls: number; total_calls: number; today_cost: string; month_cost: string; trend_data: TrendItem[]; model_distribution: ModelDistItem[]; } export interface CallLog { id: number; model_name: string; is_local: boolean; input_tokens: number; output_tokens: number; bill: string; status: string; api_key_prefix: string; created_at: string; input_price: string | null; output_price: string | null; } export interface CallLogQuery { start_date?: string; end_date?: string; model_id?: number; api_key_id?: number; page?: number; page_size?: number; } export interface PaginatedResponse { items: T[]; total: number; page: number; page_size: number; total_pages: number; } interface ApiResponse { code: number; message: string; data: T; } async function request(url: string, options: RequestInit = {}): Promise> { const token = authService.getToken(); const headers: HeadersInit = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}), ...options.headers, }; const response = await fetch(`${API_BASE}${url}`, { ...options, headers, }); if (!response.ok) { const error = await response.json().catch(() => ({ message: '请求失败' })); throw new Error(error.detail || error.message || '请求失败'); } return response.json(); } export const platformApi = { // API Key 管理 async getApiKeys(): Promise { const res = await request('/api/platform/api-keys'); return res.data; }, async createApiKey(name?: string, keyType: 'public' | 'local' = 'public'): Promise { const res = await request('/api/platform/api-keys', { method: 'POST', body: JSON.stringify({ name, key_type: keyType }), }); return res.data; }, async updateApiKeyStatus(id: number, status: 'active' | 'disabled'): Promise { const res = await request(`/api/platform/api-keys/${id}`, { method: 'PUT', body: JSON.stringify({ status }), }); return res.data; }, async deleteApiKey(id: number): Promise { await request<{ success: boolean }>(`/api/platform/api-keys/${id}`, { method: 'DELETE', }); }, // 统计 async getStats(trendDays: number = 7, keyType?: 'public' | 'local'): Promise { let url = `/api/platform/stats?trend_days=${trendDays}`; if (keyType) { url += `&key_type=${keyType}`; } const res = await request(url); return res.data; }, // 调用日志 async getCallLogs(query: CallLogQuery & { key_type?: 'public' | 'local' } = {}): Promise> { const params = new URLSearchParams(); if (query.start_date) params.append('start_date', query.start_date); if (query.end_date) params.append('end_date', query.end_date); if (query.model_id) params.append('model_id', String(query.model_id)); if (query.api_key_id) params.append('api_key_id', String(query.api_key_id)); if (query.key_type) params.append('key_type', query.key_type); if (query.page) params.append('page', String(query.page)); if (query.page_size) params.append('page_size', String(query.page_size)); const url = `/api/platform/call-logs${params.toString() ? '?' + params.toString() : ''}`; const res = await request>(url); return res.data; }, };