mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-05 17:22:28 +08:00
Initial commit
This commit is contained in:
177
frontend/src/api/admin.ts
Normal file
177
frontend/src/api/admin.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// API密钥管理相关接口定义
|
||||
export interface AdminApiKey {
|
||||
id: string // UUID
|
||||
user_id: string // UUID
|
||||
user_email?: string
|
||||
username?: string
|
||||
name?: string
|
||||
key_display?: string // 脱敏后的密钥显示
|
||||
is_active: boolean
|
||||
is_standalone: boolean // 是否为独立余额Key
|
||||
balance_used_usd?: number // 已使用余额(仅独立Key)
|
||||
current_balance_usd?: number | null // 当前余额(独立Key预付费模式,null表示无限制)
|
||||
total_requests?: number
|
||||
total_tokens?: number
|
||||
total_cost_usd?: number
|
||||
rate_limit?: number
|
||||
allowed_providers?: string[] | null // 允许的提供商列表
|
||||
allowed_api_formats?: string[] | null // 允许的 API 格式列表
|
||||
allowed_models?: string[] | null // 允许的模型列表
|
||||
auto_delete_on_expiry?: boolean // 过期后是否自动删除
|
||||
last_used_at?: string
|
||||
expires_at?: string
|
||||
created_at: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export interface CreateStandaloneApiKeyRequest {
|
||||
name?: string
|
||||
allowed_providers?: string[] | null
|
||||
allowed_api_formats?: string[] | null
|
||||
allowed_models?: string[] | null
|
||||
rate_limit?: number
|
||||
expire_days?: number | null // null = 永不过期
|
||||
initial_balance_usd: number // 初始余额,必须设置
|
||||
auto_delete_on_expiry?: boolean // 过期后是否自动删除
|
||||
}
|
||||
|
||||
export interface AdminApiKeysResponse {
|
||||
api_keys: AdminApiKey[]
|
||||
total: number
|
||||
limit: number
|
||||
skip: number
|
||||
}
|
||||
|
||||
export interface ApiKeyToggleResponse {
|
||||
id: string // UUID
|
||||
is_active: boolean
|
||||
message: string
|
||||
}
|
||||
|
||||
// 管理员API密钥管理相关API
|
||||
export const adminApi = {
|
||||
// 获取所有独立余额Keys列表
|
||||
async getAllApiKeys(params?: {
|
||||
skip?: number
|
||||
limit?: number
|
||||
is_active?: boolean
|
||||
}): Promise<AdminApiKeysResponse> {
|
||||
const response = await apiClient.get<AdminApiKeysResponse>('/api/admin/api-keys', {
|
||||
params: params
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 创建独立余额Key
|
||||
async createStandaloneApiKey(data: CreateStandaloneApiKeyRequest): Promise<AdminApiKey & { key: string }> {
|
||||
const response = await apiClient.post<AdminApiKey & { key: string }>(
|
||||
'/api/admin/api-keys',
|
||||
data
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 更新独立余额Key
|
||||
async updateApiKey(keyId: string, data: Partial<CreateStandaloneApiKeyRequest>): Promise<AdminApiKey & { message: string }> {
|
||||
const response = await apiClient.put<AdminApiKey & { message: string }>(
|
||||
`/api/admin/api-keys/${keyId}`,
|
||||
data
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 切换API密钥状态(启用/禁用)
|
||||
async toggleApiKey(keyId: string): Promise<ApiKeyToggleResponse> {
|
||||
const response = await apiClient.patch<ApiKeyToggleResponse>(
|
||||
`/api/admin/api-keys/${keyId}`
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 删除API密钥
|
||||
async deleteApiKey(keyId: string): Promise<{ message: string }> {
|
||||
const response = await apiClient.delete<{ message: string}>(
|
||||
`/api/admin/api-keys/${keyId}`
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 为独立余额Key调整余额
|
||||
async addApiKeyBalance(keyId: string, amountUsd: number): Promise<AdminApiKey & { message: string }> {
|
||||
const response = await apiClient.patch<AdminApiKey & { message: string }>(
|
||||
`/api/admin/api-keys/${keyId}/balance`,
|
||||
{ amount_usd: amountUsd }
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取API密钥详情(可选包含完整密钥)
|
||||
async getApiKeyDetail(keyId: string, includeKey: boolean = false): Promise<AdminApiKey & { key?: string }> {
|
||||
const response = await apiClient.get<AdminApiKey & { key?: string }>(
|
||||
`/api/admin/api-keys/${keyId}`,
|
||||
{ params: { include_key: includeKey } }
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取完整的API密钥(用于复制)- 便捷方法
|
||||
async getFullApiKey(keyId: string): Promise<{ key: string }> {
|
||||
const response = await apiClient.get<{ key: string }>(
|
||||
`/api/admin/api-keys/${keyId}`,
|
||||
{ params: { include_key: true } }
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 系统配置相关
|
||||
// 获取所有系统配置
|
||||
async getAllSystemConfigs(): Promise<any[]> {
|
||||
const response = await apiClient.get<any[]>('/api/admin/system/configs')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取特定系统配置
|
||||
async getSystemConfig(key: string): Promise<{ key: string; value: any }> {
|
||||
const response = await apiClient.get<{ key: string; value: any }>(
|
||||
`/api/admin/system/configs/${key}`
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 更新系统配置
|
||||
async updateSystemConfig(
|
||||
key: string,
|
||||
value: any,
|
||||
description?: string
|
||||
): Promise<{ key: string; value: any; description?: string }> {
|
||||
const response = await apiClient.put<{ key: string; value: any; description?: string }>(
|
||||
`/api/admin/system/configs/${key}`,
|
||||
{ value, description }
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 删除系统配置
|
||||
async deleteSystemConfig(key: string): Promise<{ message: string }> {
|
||||
const response = await apiClient.delete<{ message: string }>(
|
||||
`/api/admin/system/configs/${key}`
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取系统统计
|
||||
async getSystemStats(): Promise<any> {
|
||||
const response = await apiClient.get<any>('/api/admin/system/stats')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取可用的API格式列表
|
||||
async getApiFormats(): Promise<{ formats: Array<{ value: string; label: string; default_path: string; aliases: string[] }> }> {
|
||||
const response = await apiClient.get<{ formats: Array<{ value: string; label: string; default_path: string; aliases: string[] }> }>(
|
||||
'/api/admin/system/api-formats'
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
109
frontend/src/api/announcements.ts
Normal file
109
frontend/src/api/announcements.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import apiClient from './client'
|
||||
|
||||
export interface Announcement {
|
||||
id: string // UUID
|
||||
title: string
|
||||
content: string // Markdown格式
|
||||
type: 'info' | 'warning' | 'maintenance' | 'important'
|
||||
priority: number
|
||||
is_pinned: boolean
|
||||
is_active: boolean
|
||||
author: {
|
||||
id: string // UUID
|
||||
username: string
|
||||
}
|
||||
start_time?: string
|
||||
end_time?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
is_read?: boolean
|
||||
}
|
||||
|
||||
export interface AnnouncementListResponse {
|
||||
items: Announcement[]
|
||||
total: number
|
||||
unread_count: number
|
||||
}
|
||||
|
||||
export interface CreateAnnouncementRequest {
|
||||
title: string
|
||||
content: string
|
||||
type?: 'info' | 'warning' | 'maintenance' | 'important'
|
||||
priority?: number
|
||||
is_pinned?: boolean
|
||||
start_time?: string
|
||||
end_time?: string
|
||||
}
|
||||
|
||||
export interface UpdateAnnouncementRequest {
|
||||
title?: string
|
||||
content?: string
|
||||
type?: string
|
||||
priority?: number
|
||||
is_active?: boolean
|
||||
is_pinned?: boolean
|
||||
start_time?: string
|
||||
end_time?: string
|
||||
}
|
||||
|
||||
export const announcementApi = {
|
||||
// 获取公告列表
|
||||
async getAnnouncements(params?: {
|
||||
active_only?: boolean
|
||||
unread_only?: boolean
|
||||
limit?: number
|
||||
offset?: number
|
||||
}): Promise<AnnouncementListResponse> {
|
||||
const response = await apiClient.get('/api/announcements', { params })
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取当前有效的公告
|
||||
async getActiveAnnouncements(): Promise<AnnouncementListResponse> {
|
||||
const response = await apiClient.get('/api/announcements/active')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取单个公告
|
||||
async getAnnouncement(id: string): Promise<Announcement> {
|
||||
const response = await apiClient.get(`/api/announcements/${id}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 标记公告为已读
|
||||
async markAsRead(id: string): Promise<{ message: string }> {
|
||||
const response = await apiClient.patch(`/api/announcements/${id}/read-status`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 标记所有公告为已读
|
||||
async markAllAsRead(): Promise<{ message: string }> {
|
||||
const response = await apiClient.post('/api/announcements/read-all')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取未读公告数量
|
||||
async getUnreadCount(): Promise<{ unread_count: number }> {
|
||||
const response = await apiClient.get('/api/announcements/users/me/unread-count')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 管理员方法
|
||||
// 创建公告
|
||||
async createAnnouncement(data: CreateAnnouncementRequest): Promise<{ id: string; title: string; message: string }> {
|
||||
const response = await apiClient.post('/api/announcements', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 更新公告
|
||||
async updateAnnouncement(id: string, data: UpdateAnnouncementRequest): Promise<{ message: string }> {
|
||||
const response = await apiClient.put(`/api/announcements/${id}`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 删除公告
|
||||
async deleteAnnouncement(id: string): Promise<{ message: string }> {
|
||||
const response = await apiClient.delete(`/api/announcements/${id}`)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
91
frontend/src/api/audit.ts
Normal file
91
frontend/src/api/audit.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import apiClient from './client'
|
||||
|
||||
export interface AuditLog {
|
||||
id: string
|
||||
event_type: string
|
||||
user_id?: number
|
||||
description: string
|
||||
ip_address?: string
|
||||
status_code?: number
|
||||
error_message?: string
|
||||
metadata?: any
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface PaginationMeta {
|
||||
total: number
|
||||
limit: number
|
||||
offset: number
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface AuditLogsResponse {
|
||||
items: AuditLog[]
|
||||
meta: PaginationMeta
|
||||
filters?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface AuditFilters {
|
||||
user_id?: string
|
||||
event_type?: string
|
||||
days?: number
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
function normalizeAuditResponse(data: any): AuditLogsResponse {
|
||||
const items: AuditLog[] = data.items ?? data.logs ?? []
|
||||
const meta: PaginationMeta = data.meta ?? {
|
||||
total: data.total ?? items.length,
|
||||
limit: data.limit ?? items.length,
|
||||
offset: data.offset ?? 0,
|
||||
count: data.count ?? items.length
|
||||
}
|
||||
|
||||
return {
|
||||
items,
|
||||
meta,
|
||||
filters: data.filters
|
||||
}
|
||||
}
|
||||
|
||||
export const auditApi = {
|
||||
// 获取当前用户的活动日志
|
||||
async getMyAuditLogs(filters?: {
|
||||
event_type?: string
|
||||
days?: number
|
||||
limit?: number
|
||||
offset?: number
|
||||
}): Promise<AuditLogsResponse> {
|
||||
const response = await apiClient.get('/api/monitoring/my-audit-logs', { params: filters })
|
||||
return normalizeAuditResponse(response.data)
|
||||
},
|
||||
|
||||
// 获取所有审计日志 (管理员)
|
||||
async getAuditLogs(filters?: AuditFilters): Promise<AuditLogsResponse> {
|
||||
const response = await apiClient.get('/api/admin/monitoring/audit-logs', { params: filters })
|
||||
return normalizeAuditResponse(response.data)
|
||||
},
|
||||
|
||||
// 获取可疑活动 (管理员)
|
||||
async getSuspiciousActivities(hours: number = 24, limit: number = 100): Promise<{
|
||||
activities: AuditLog[]
|
||||
count: number
|
||||
}> {
|
||||
const response = await apiClient.get('/api/admin/monitoring/suspicious-activities', {
|
||||
params: { hours, limit }
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 分析用户行为 (管理员)
|
||||
async analyzeUserBehavior(userId: number, days: number = 7): Promise<{
|
||||
analysis: any
|
||||
recommendations: string[]
|
||||
}> {
|
||||
const response = await apiClient.get(`/api/admin/monitoring/user-behavior/${userId}`, {
|
||||
params: { days }
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
90
frontend/src/api/auth.ts
Normal file
90
frontend/src/api/auth.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import apiClient from './client'
|
||||
|
||||
export interface LoginRequest {
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface LoginResponse {
|
||||
access_token: string
|
||||
refresh_token?: string
|
||||
token_type?: string
|
||||
expires_in?: number
|
||||
user_id?: string // UUID
|
||||
email?: string
|
||||
username?: string
|
||||
role?: string
|
||||
}
|
||||
|
||||
export interface UserPreferences {
|
||||
theme?: 'light' | 'dark' | 'auto'
|
||||
language?: string
|
||||
notifications_enabled?: boolean
|
||||
[key: string]: unknown // 允许扩展其他偏好设置
|
||||
}
|
||||
|
||||
export interface UserStats {
|
||||
total_requests?: number
|
||||
total_cost?: number
|
||||
last_request_at?: string
|
||||
[key: string]: unknown // 允许扩展其他统计数据
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string // UUID
|
||||
username: string
|
||||
email?: string
|
||||
role: string // 'admin' or 'user'
|
||||
is_active: boolean
|
||||
quota_usd?: number | null
|
||||
used_usd?: number
|
||||
total_usd?: number
|
||||
allowed_providers?: string[] | null // 允许使用的提供商 ID 列表
|
||||
allowed_endpoints?: string[] | null // 允许使用的端点 ID 列表
|
||||
allowed_models?: string[] | null // 允许使用的模型名称列表
|
||||
created_at: string
|
||||
last_login_at?: string
|
||||
preferences?: UserPreferences
|
||||
stats?: UserStats
|
||||
}
|
||||
|
||||
export const authApi = {
|
||||
async login(credentials: LoginRequest): Promise<LoginResponse> {
|
||||
const response = await apiClient.post<LoginResponse>('/api/auth/login', credentials)
|
||||
apiClient.setToken(response.data.access_token)
|
||||
// 后端暂时没有返回 refresh_token
|
||||
if (response.data.refresh_token) {
|
||||
localStorage.setItem('refresh_token', response.data.refresh_token)
|
||||
}
|
||||
return response.data
|
||||
},
|
||||
|
||||
async logout() {
|
||||
try {
|
||||
// 调用后端登出接口,将 Token 加入黑名单
|
||||
await apiClient.post('/api/auth/logout', {})
|
||||
} catch (error) {
|
||||
// 即使后端登出失败,也要清除本地认证信息
|
||||
console.warn('后端登出失败,仅清除本地认证信息:', error)
|
||||
} finally {
|
||||
// 清除本地认证信息
|
||||
apiClient.clearAuth()
|
||||
}
|
||||
},
|
||||
|
||||
async getCurrentUser(): Promise<User> {
|
||||
const response = await apiClient.get<User>('/api/users/me')
|
||||
return response.data
|
||||
},
|
||||
|
||||
async refreshToken(refreshToken: string): Promise<LoginResponse> {
|
||||
const response = await apiClient.post<LoginResponse>('/api/auth/refresh', {
|
||||
refresh_token: refreshToken
|
||||
})
|
||||
apiClient.setToken(response.data.access_token)
|
||||
if (response.data.refresh_token) {
|
||||
localStorage.setItem('refresh_token', response.data.refresh_token)
|
||||
}
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
158
frontend/src/api/cache.ts
Normal file
158
frontend/src/api/cache.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 缓存监控 API 客户端
|
||||
*/
|
||||
|
||||
import api from './client'
|
||||
|
||||
export interface CacheStats {
|
||||
scheduler: string
|
||||
cache_reservation_ratio: number
|
||||
affinity_stats: {
|
||||
storage_type: string
|
||||
total_affinities: number
|
||||
active_affinities: number | string
|
||||
cache_hits: number
|
||||
cache_misses: number
|
||||
cache_hit_rate: number
|
||||
cache_invalidations: number
|
||||
provider_switches: number
|
||||
key_switches: number
|
||||
config: {
|
||||
default_ttl: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface DynamicReservationConfig {
|
||||
probe_phase_requests: number
|
||||
probe_reservation: number
|
||||
stable_min_reservation: number
|
||||
stable_max_reservation: number
|
||||
low_load_threshold: number
|
||||
high_load_threshold: number
|
||||
success_count_for_full_confidence: number
|
||||
cooldown_hours_for_full_confidence: number
|
||||
}
|
||||
|
||||
export interface CacheConfig {
|
||||
cache_ttl_seconds: number
|
||||
cache_reservation_ratio: number
|
||||
dynamic_reservation?: {
|
||||
enabled: boolean
|
||||
config: DynamicReservationConfig
|
||||
description: Record<string, string>
|
||||
}
|
||||
description: {
|
||||
cache_ttl: string
|
||||
cache_reservation_ratio: string
|
||||
dynamic_reservation?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserAffinity {
|
||||
affinity_key: string
|
||||
user_api_key_name: string | null
|
||||
user_api_key_prefix: string | null // 用户 API Key 脱敏显示(前4...后4)
|
||||
is_standalone: boolean
|
||||
user_id: string | null
|
||||
username: string | null
|
||||
email: string | null
|
||||
provider_id: string
|
||||
provider_name: string | null
|
||||
endpoint_id: string
|
||||
endpoint_api_format: string | null
|
||||
endpoint_url: string | null
|
||||
key_id: string
|
||||
key_name: string | null
|
||||
key_prefix: string | null // Provider Key 脱敏显示(前4...后4)
|
||||
rate_multiplier: number
|
||||
model_name: string | null // 模型名称(如 claude-haiku-4-5-20250514)
|
||||
model_display_name: string | null // 模型显示名称(如 Claude Haiku 4.5)
|
||||
api_format: string | null // API 格式 (claude/openai)
|
||||
created_at: number
|
||||
expire_at: number
|
||||
request_count: number
|
||||
}
|
||||
|
||||
export interface AffinityListResponse {
|
||||
items: UserAffinity[]
|
||||
total: number
|
||||
matched_user_id?: string | null
|
||||
}
|
||||
|
||||
export const cacheApi = {
|
||||
/**
|
||||
* 获取缓存统计信息
|
||||
*/
|
||||
async getStats(): Promise<CacheStats> {
|
||||
const response = await api.get('/api/admin/monitoring/cache/stats')
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取缓存配置
|
||||
*/
|
||||
async getConfig(): Promise<CacheConfig> {
|
||||
const response = await api.get('/api/admin/monitoring/cache/config')
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询用户缓存亲和性(现在返回该用户所有端点的亲和性列表)
|
||||
*
|
||||
* @param userIdentifier 用户标识符,支持:用户名、邮箱、User UUID、API Key ID
|
||||
*/
|
||||
async getUserAffinity(userIdentifier: string): Promise<UserAffinity[] | null> {
|
||||
const response = await api.get(`/api/admin/monitoring/cache/affinity/${userIdentifier}`)
|
||||
if (response.data.status === 'not_found') {
|
||||
return null
|
||||
}
|
||||
return response.data.affinities
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除用户缓存
|
||||
*
|
||||
* @param userIdentifier 用户标识符,支持:用户名、邮箱、User UUID、API Key ID
|
||||
*/
|
||||
async clearUserCache(userIdentifier: string): Promise<void> {
|
||||
await api.delete(`/api/admin/monitoring/cache/users/${userIdentifier}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除所有缓存
|
||||
*/
|
||||
async clearAllCache(): Promise<{ count: number }> {
|
||||
const response = await api.delete('/api/admin/monitoring/cache')
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除指定Provider的所有缓存
|
||||
*/
|
||||
async clearProviderCache(providerId: string): Promise<{ count: number; provider_id: string }> {
|
||||
const response = await api.delete(`/api/admin/monitoring/cache/providers/${providerId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取缓存亲和性列表
|
||||
*/
|
||||
async listAffinities(keyword?: string): Promise<AffinityListResponse> {
|
||||
const response = await api.get('/api/admin/monitoring/cache/affinities', {
|
||||
params: keyword ? { keyword } : undefined
|
||||
})
|
||||
return response.data.data
|
||||
}
|
||||
}
|
||||
|
||||
// 导出便捷函数
|
||||
export const {
|
||||
getStats,
|
||||
getConfig,
|
||||
getUserAffinity,
|
||||
clearUserCache,
|
||||
clearAllCache,
|
||||
clearProviderCache,
|
||||
listAffinities
|
||||
} = cacheApi
|
||||
254
frontend/src/api/client.ts
Normal file
254
frontend/src/api/client.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
import axios from 'axios'
|
||||
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { NETWORK_CONFIG, AUTH_CONFIG } from '@/config/constants'
|
||||
import { log } from '@/utils/logger'
|
||||
|
||||
// 在开发环境下使用代理,生产环境使用环境变量
|
||||
const API_BASE_URL = import.meta.env.VITE_API_URL || ''
|
||||
|
||||
/**
|
||||
* 判断请求是否为公共端点
|
||||
*/
|
||||
function isPublicEndpoint(url?: string, method?: string): boolean {
|
||||
if (!url) return false
|
||||
|
||||
const isHealthCheck = url.includes('/health') &&
|
||||
method?.toLowerCase() === 'get' &&
|
||||
!url.includes('/api/admin')
|
||||
|
||||
return url.includes('/public') ||
|
||||
url.includes('.json') ||
|
||||
isHealthCheck
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为认证相关请求
|
||||
*/
|
||||
function isAuthRequest(url?: string): boolean {
|
||||
return url?.includes('/auth/login') || url?.includes('/auth/refresh') || url?.includes('/auth/logout') || false
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为可刷新的认证错误
|
||||
*/
|
||||
function isRefreshableAuthError(errorDetail: string): boolean {
|
||||
const nonRefreshableErrors = [
|
||||
'用户不存在或已禁用',
|
||||
'需要管理员权限',
|
||||
'权限不足',
|
||||
'用户已禁用',
|
||||
]
|
||||
|
||||
return !nonRefreshableErrors.some((msg) => errorDetail.includes(msg))
|
||||
}
|
||||
|
||||
class ApiClient {
|
||||
private client: AxiosInstance
|
||||
private token: string | null = null
|
||||
private isRefreshing = false
|
||||
private refreshPromise: Promise<AxiosResponse> | null = null
|
||||
|
||||
constructor() {
|
||||
this.client = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: NETWORK_CONFIG.API_TIMEOUT,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
this.setupInterceptors()
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置请求和响应拦截器
|
||||
*/
|
||||
private setupInterceptors(): void {
|
||||
// 请求拦截器
|
||||
this.client.interceptors.request.use(
|
||||
(config) => {
|
||||
const requiresAuth = !isPublicEndpoint(config.url, config.method) &&
|
||||
config.url?.includes('/api/')
|
||||
|
||||
if (requiresAuth) {
|
||||
const token = this.getToken()
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
this.client.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => this.handleResponseError(error)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理响应错误
|
||||
*/
|
||||
private async handleResponseError(error: any): Promise<any> {
|
||||
const originalRequest = error.config
|
||||
|
||||
// 请求被取消
|
||||
if (axios.isCancel(error)) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
// 网络错误或服务器不可达
|
||||
if (!error.response) {
|
||||
log.warn('Network error or server unreachable', error.message)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
// 认证请求错误,直接返回
|
||||
if (isAuthRequest(originalRequest?.url)) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
// 处理401错误
|
||||
if (error.response?.status === 401) {
|
||||
return this.handle401Error(error, originalRequest)
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理401认证错误
|
||||
*/
|
||||
private async handle401Error(error: any, originalRequest: any): Promise<any> {
|
||||
// 如果不需要认证,直接返回错误
|
||||
if (isPublicEndpoint(originalRequest?.url, originalRequest?.method)) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
// 如果已经重试过,不再重试
|
||||
if (originalRequest._retry) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
const errorDetail = error.response?.data?.detail || ''
|
||||
log.debug('Got 401 error, attempting token refresh', { errorDetail })
|
||||
|
||||
// 检查是否为业务相关的401错误
|
||||
if (!isRefreshableAuthError(errorDetail)) {
|
||||
log.info('401 error but not authentication issue, keeping session', { errorDetail })
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
// 获取refresh token
|
||||
const refreshToken = localStorage.getItem('refresh_token')
|
||||
if (!refreshToken) {
|
||||
log.info('No refresh token available, clearing invalid token')
|
||||
this.clearAuth()
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
// 标记为已重试
|
||||
originalRequest._retry = true
|
||||
originalRequest._retryCount = (originalRequest._retryCount || 0) + 1
|
||||
|
||||
// 超过最大重试次数
|
||||
if (originalRequest._retryCount > AUTH_CONFIG.MAX_RETRY_COUNT) {
|
||||
log.error('Max retry attempts reached')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
// 如果正在刷新,等待刷新完成
|
||||
if (this.isRefreshing) {
|
||||
try {
|
||||
await this.refreshPromise
|
||||
originalRequest.headers.Authorization = `Bearer ${this.getToken()}`
|
||||
return this.client.request(originalRequest)
|
||||
} catch (refreshError) {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 开始刷新token
|
||||
return this.refreshTokenAndRetry(refreshToken, originalRequest, error)
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token并重试原始请求
|
||||
*/
|
||||
private async refreshTokenAndRetry(
|
||||
refreshToken: string,
|
||||
originalRequest: any,
|
||||
originalError: any
|
||||
): Promise<any> {
|
||||
this.isRefreshing = true
|
||||
this.refreshPromise = this.refreshToken(refreshToken)
|
||||
|
||||
try {
|
||||
const response = await this.refreshPromise
|
||||
this.setToken(response.data.access_token)
|
||||
localStorage.setItem('refresh_token', response.data.refresh_token)
|
||||
this.isRefreshing = false
|
||||
this.refreshPromise = null
|
||||
|
||||
// 重试原始请求
|
||||
originalRequest.headers.Authorization = `Bearer ${response.data.access_token}`
|
||||
return this.client.request(originalRequest)
|
||||
} catch (refreshError: any) {
|
||||
log.error('Token refresh failed', refreshError)
|
||||
this.isRefreshing = false
|
||||
this.refreshPromise = null
|
||||
this.clearAuth()
|
||||
return Promise.reject(originalError)
|
||||
}
|
||||
}
|
||||
|
||||
setToken(token: string): void {
|
||||
this.token = token
|
||||
localStorage.setItem('access_token', token)
|
||||
}
|
||||
|
||||
getToken(): string | null {
|
||||
if (!this.token) {
|
||||
this.token = localStorage.getItem('access_token')
|
||||
}
|
||||
return this.token
|
||||
}
|
||||
|
||||
clearAuth(): void {
|
||||
this.token = null
|
||||
localStorage.removeItem('access_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string): Promise<AxiosResponse> {
|
||||
return this.client.post('/api/auth/refresh', { refresh_token: refreshToken })
|
||||
}
|
||||
|
||||
async request<T = any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
||||
return this.client.request<T>(config)
|
||||
}
|
||||
|
||||
async get<T = any>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
||||
return this.client.get<T>(url, config)
|
||||
}
|
||||
|
||||
async post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
||||
return this.client.post<T>(url, data, config)
|
||||
}
|
||||
|
||||
async put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
||||
return this.client.put<T>(url, data, config)
|
||||
}
|
||||
|
||||
async patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
||||
return this.client.patch<T>(url, data, config)
|
||||
}
|
||||
|
||||
async delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>> {
|
||||
return this.client.delete<T>(url, config)
|
||||
}
|
||||
}
|
||||
|
||||
export default new ApiClient()
|
||||
262
frontend/src/api/dashboard.ts
Normal file
262
frontend/src/api/dashboard.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
import apiClient from './client'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
export interface RecentRequestsResponse {
|
||||
requests: RecentRequest[]
|
||||
}
|
||||
|
||||
export interface ProviderStatusResponse {
|
||||
providers: ProviderStatus[]
|
||||
}
|
||||
|
||||
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
|
||||
model: string
|
||||
target_model?: string | null // 映射后的目标模型名
|
||||
tokens: {
|
||||
input: number
|
||||
output: number
|
||||
total: number
|
||||
}
|
||||
cost: {
|
||||
input: number
|
||||
output: number
|
||||
total: number
|
||||
}
|
||||
// Additional token fields
|
||||
input_tokens?: number
|
||||
output_tokens?: number
|
||||
total_tokens?: number
|
||||
cache_creation_input_tokens?: number
|
||||
cache_read_input_tokens?: number
|
||||
// Additional cost fields
|
||||
input_cost?: number
|
||||
output_cost?: number
|
||||
total_cost?: number
|
||||
cache_creation_cost?: number
|
||||
cache_read_cost?: number
|
||||
request_cost?: number // 按次计费费用
|
||||
// Historical pricing fields (per 1M tokens)
|
||||
input_price_per_1m?: number
|
||||
output_price_per_1m?: number
|
||||
cache_creation_price_per_1m?: number
|
||||
cache_read_price_per_1m?: number
|
||||
price_per_request?: number // 按次计费价格
|
||||
request_type: string
|
||||
is_stream: boolean
|
||||
status_code: number
|
||||
error_message?: string
|
||||
response_time_ms: number
|
||||
created_at: string
|
||||
request_headers?: Record<string, any>
|
||||
request_body?: Record<string, any>
|
||||
provider_request_headers?: Record<string, any>
|
||||
response_headers?: Record<string, any>
|
||||
response_body?: Record<string, any>
|
||||
metadata?: Record<string, any>
|
||||
// 阶梯计费信息
|
||||
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
|
||||
cache_read_price_per_1m: number
|
||||
}>
|
||||
}
|
||||
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
|
||||
cache_read_price_per_1m: number
|
||||
}>
|
||||
}>
|
||||
} | null
|
||||
}
|
||||
|
||||
export interface ModelBreakdown {
|
||||
model: string
|
||||
requests: number
|
||||
tokens: number
|
||||
cost: number
|
||||
}
|
||||
|
||||
export interface ModelSummary {
|
||||
model: string
|
||||
requests: number
|
||||
tokens: number
|
||||
cost: number
|
||||
avg_response_time: number
|
||||
cost_per_request: number
|
||||
tokens_per_request: number
|
||||
}
|
||||
|
||||
export interface DailyStat {
|
||||
date: string // ISO date string
|
||||
requests: number
|
||||
tokens: number
|
||||
cost: number
|
||||
avg_response_time: number // in seconds
|
||||
unique_models: number
|
||||
unique_providers: number
|
||||
model_breakdown: ModelBreakdown[]
|
||||
}
|
||||
|
||||
export interface DailyStatsResponse {
|
||||
daily_stats: DailyStat[]
|
||||
model_summary: ModelSummary[]
|
||||
period: {
|
||||
start_date: string
|
||||
end_date: string
|
||||
days: number
|
||||
}
|
||||
}
|
||||
|
||||
export const dashboardApi = {
|
||||
// 获取仪表盘统计数据
|
||||
async getStats(): Promise<DashboardStatsResponse> {
|
||||
const response = await apiClient.get<DashboardStatsResponse>('/api/dashboard/stats')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取最近的请求记录
|
||||
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[]> {
|
||||
const response = await apiClient.get<ProviderStatusResponse>('/api/dashboard/provider-status')
|
||||
return response.data.providers
|
||||
},
|
||||
|
||||
// 获取请求详情
|
||||
// NOTE: This method now calls the new RESTful API at /api/admin/usage/{id}
|
||||
async getRequestDetail(requestId: string): Promise<RequestDetail> {
|
||||
const response = await apiClient.get<RequestDetail>(`/api/admin/usage/${requestId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取每日统计数据
|
||||
async getDailyStats(days: number = 7): Promise<DailyStatsResponse> {
|
||||
const response = await apiClient.get<DailyStatsResponse>('/api/dashboard/daily-stats', {
|
||||
params: { days }
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
57
frontend/src/api/endpoints/adaptive.ts
Normal file
57
frontend/src/api/endpoints/adaptive.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import client from '../client'
|
||||
import type { AdaptiveStatsResponse } from './types'
|
||||
|
||||
/**
|
||||
* 启用/禁用 Key 的自适应模式
|
||||
*/
|
||||
export async function toggleAdaptiveMode(
|
||||
keyId: string,
|
||||
data: {
|
||||
enabled: boolean
|
||||
fixed_limit?: number
|
||||
}
|
||||
): Promise<{
|
||||
message: string
|
||||
key_id: string
|
||||
is_adaptive: boolean
|
||||
max_concurrent: number | null
|
||||
effective_limit: number | null
|
||||
}> {
|
||||
const response = await client.patch(`/api/admin/adaptive/keys/${keyId}/mode`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 Key 的固定并发限制
|
||||
*/
|
||||
export async function setConcurrentLimit(
|
||||
keyId: string,
|
||||
limit: number
|
||||
): Promise<{
|
||||
message: string
|
||||
key_id: string
|
||||
is_adaptive: boolean
|
||||
max_concurrent: number
|
||||
previous_mode: string
|
||||
}> {
|
||||
const response = await client.patch(`/api/admin/adaptive/keys/${keyId}/limit`, null, {
|
||||
params: { limit }
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Key 的自适应统计
|
||||
*/
|
||||
export async function getAdaptiveStats(keyId: string): Promise<AdaptiveStatsResponse> {
|
||||
const response = await client.get(`/api/admin/adaptive/keys/${keyId}/stats`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置 Key 的学习状态
|
||||
*/
|
||||
export async function resetAdaptiveLearning(keyId: string): Promise<{ message: string; key_id: string }> {
|
||||
const response = await client.delete(`/api/admin/adaptive/keys/${keyId}/learning`)
|
||||
return response.data
|
||||
}
|
||||
121
frontend/src/api/endpoints/aliases.ts
Normal file
121
frontend/src/api/endpoints/aliases.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 模型别名管理 API
|
||||
*/
|
||||
|
||||
import client from '../client'
|
||||
import type { ModelMapping, ModelMappingCreate, ModelMappingUpdate } from './types'
|
||||
|
||||
export interface ModelAlias {
|
||||
id: string
|
||||
alias: string
|
||||
global_model_id: string
|
||||
global_model_name: string | null
|
||||
global_model_display_name: string | null
|
||||
provider_id: string | null
|
||||
provider_name: string | null
|
||||
scope: 'global' | 'provider'
|
||||
mapping_type: 'alias' | 'mapping'
|
||||
is_active: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface CreateModelAliasRequest {
|
||||
alias: string
|
||||
global_model_id: string
|
||||
provider_id?: string | null
|
||||
mapping_type?: 'alias' | 'mapping'
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateModelAliasRequest {
|
||||
alias?: string
|
||||
global_model_id?: string
|
||||
provider_id?: string | null
|
||||
mapping_type?: 'alias' | 'mapping'
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
function transformMapping(mapping: ModelMapping): ModelAlias {
|
||||
return {
|
||||
id: mapping.id,
|
||||
alias: mapping.source_model,
|
||||
global_model_id: mapping.target_global_model_id,
|
||||
global_model_name: mapping.target_global_model_name,
|
||||
global_model_display_name: mapping.target_global_model_display_name,
|
||||
provider_id: mapping.provider_id ?? null,
|
||||
provider_name: mapping.provider_name ?? null,
|
||||
scope: mapping.scope,
|
||||
mapping_type: mapping.mapping_type || 'alias',
|
||||
is_active: mapping.is_active,
|
||||
created_at: mapping.created_at,
|
||||
updated_at: mapping.updated_at
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取别名列表
|
||||
*/
|
||||
export async function getAliases(params?: {
|
||||
provider_id?: string
|
||||
global_model_id?: string
|
||||
is_active?: boolean
|
||||
skip?: number
|
||||
limit?: number
|
||||
}): Promise<ModelAlias[]> {
|
||||
const response = await client.get('/api/admin/models/mappings', {
|
||||
params: {
|
||||
provider_id: params?.provider_id,
|
||||
target_global_model_id: params?.global_model_id,
|
||||
is_active: params?.is_active,
|
||||
skip: params?.skip,
|
||||
limit: params?.limit
|
||||
}
|
||||
})
|
||||
return (response.data as ModelMapping[]).map(transformMapping)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个别名
|
||||
*/
|
||||
export async function getAlias(id: string): Promise<ModelAlias> {
|
||||
const response = await client.get(`/api/admin/models/mappings/${id}`)
|
||||
return transformMapping(response.data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建别名
|
||||
*/
|
||||
export async function createAlias(data: CreateModelAliasRequest): Promise<ModelAlias> {
|
||||
const payload: ModelMappingCreate = {
|
||||
source_model: data.alias,
|
||||
target_global_model_id: data.global_model_id,
|
||||
provider_id: data.provider_id ?? null,
|
||||
mapping_type: data.mapping_type ?? 'alias',
|
||||
is_active: data.is_active ?? true
|
||||
}
|
||||
const response = await client.post('/api/admin/models/mappings', payload)
|
||||
return transformMapping(response.data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新别名
|
||||
*/
|
||||
export async function updateAlias(id: string, data: UpdateModelAliasRequest): Promise<ModelAlias> {
|
||||
const payload: ModelMappingUpdate = {
|
||||
source_model: data.alias,
|
||||
target_global_model_id: data.global_model_id,
|
||||
provider_id: data.provider_id ?? null,
|
||||
mapping_type: data.mapping_type,
|
||||
is_active: data.is_active
|
||||
}
|
||||
const response = await client.patch(`/api/admin/models/mappings/${id}`, payload)
|
||||
return transformMapping(response.data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除别名
|
||||
*/
|
||||
export async function deleteAlias(id: string): Promise<void> {
|
||||
await client.delete(`/api/admin/models/mappings/${id}`)
|
||||
}
|
||||
78
frontend/src/api/endpoints/endpoints.ts
Normal file
78
frontend/src/api/endpoints/endpoints.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import client from '../client'
|
||||
import type { ProviderEndpoint } from './types'
|
||||
|
||||
/**
|
||||
* 获取指定 Provider 的所有 Endpoints
|
||||
*/
|
||||
export async function getProviderEndpoints(providerId: string): Promise<ProviderEndpoint[]> {
|
||||
const response = await client.get(`/api/admin/endpoints/providers/${providerId}/endpoints`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Endpoint 详情
|
||||
*/
|
||||
export async function getEndpoint(endpointId: string): Promise<ProviderEndpoint> {
|
||||
const response = await client.get(`/api/admin/endpoints/${endpointId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 Provider 创建新的 Endpoint
|
||||
*/
|
||||
export async function createEndpoint(
|
||||
providerId: string,
|
||||
data: {
|
||||
provider_id: string
|
||||
api_format: string
|
||||
base_url: string
|
||||
custom_path?: string
|
||||
auth_type?: string
|
||||
auth_header?: string
|
||||
headers?: Record<string, string>
|
||||
timeout?: number
|
||||
max_retries?: number
|
||||
priority?: number
|
||||
weight?: number
|
||||
max_concurrent?: number
|
||||
rate_limit?: number
|
||||
is_active?: boolean
|
||||
config?: Record<string, any>
|
||||
}
|
||||
): Promise<ProviderEndpoint> {
|
||||
const response = await client.post(`/api/admin/endpoints/providers/${providerId}/endpoints`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Endpoint
|
||||
*/
|
||||
export async function updateEndpoint(
|
||||
endpointId: string,
|
||||
data: Partial<{
|
||||
base_url: string
|
||||
custom_path: string
|
||||
auth_type: string
|
||||
auth_header: string
|
||||
headers: Record<string, string>
|
||||
timeout: number
|
||||
max_retries: number
|
||||
priority: number
|
||||
weight: number
|
||||
max_concurrent: number
|
||||
rate_limit: number
|
||||
is_active: boolean
|
||||
config: Record<string, any>
|
||||
}>
|
||||
): Promise<ProviderEndpoint> {
|
||||
const response = await client.put(`/api/admin/endpoints/${endpointId}`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Endpoint
|
||||
*/
|
||||
export async function deleteEndpoint(endpointId: string): Promise<{ message: string; deleted_keys_count: number }> {
|
||||
const response = await client.delete(`/api/admin/endpoints/${endpointId}`)
|
||||
return response.data
|
||||
}
|
||||
85
frontend/src/api/endpoints/global-models.ts
Normal file
85
frontend/src/api/endpoints/global-models.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import client from '../client'
|
||||
import type {
|
||||
GlobalModelCreate,
|
||||
GlobalModelUpdate,
|
||||
GlobalModelResponse,
|
||||
GlobalModelWithStats,
|
||||
GlobalModelListResponse
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* 获取 GlobalModel 列表
|
||||
*/
|
||||
export async function getGlobalModels(params?: {
|
||||
skip?: number
|
||||
limit?: number
|
||||
is_active?: boolean
|
||||
search?: string
|
||||
}): Promise<GlobalModelListResponse> {
|
||||
const response = await client.get('/api/admin/models/global', { params })
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个 GlobalModel 详情
|
||||
*/
|
||||
export async function getGlobalModel(id: string): Promise<GlobalModelWithStats> {
|
||||
const response = await client.get(`/api/admin/models/global/${id}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 GlobalModel
|
||||
*/
|
||||
export async function createGlobalModel(data: GlobalModelCreate): Promise<GlobalModelResponse> {
|
||||
const response = await client.post('/api/admin/models/global', data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 GlobalModel
|
||||
*/
|
||||
export async function updateGlobalModel(
|
||||
id: string,
|
||||
data: GlobalModelUpdate
|
||||
): Promise<GlobalModelResponse> {
|
||||
const response = await client.patch(`/api/admin/models/global/${id}`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 GlobalModel
|
||||
*/
|
||||
export async function deleteGlobalModel(
|
||||
id: string,
|
||||
force: boolean = false
|
||||
): Promise<void> {
|
||||
await client.delete(`/api/admin/models/global/${id}`, { params: { force } })
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量为 GlobalModel 添加关联提供商
|
||||
*/
|
||||
export async function batchAssignToProviders(
|
||||
globalModelId: string,
|
||||
data: {
|
||||
provider_ids: string[]
|
||||
create_models: boolean
|
||||
}
|
||||
): Promise<{
|
||||
success: Array<{
|
||||
provider_id: string
|
||||
provider_name: string
|
||||
model_id?: string
|
||||
}>
|
||||
errors: Array<{
|
||||
provider_id: string
|
||||
error: string
|
||||
}>
|
||||
}> {
|
||||
const response = await client.post(
|
||||
`/api/admin/models/global/${globalModelId}/assign-to-providers`,
|
||||
data
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
88
frontend/src/api/endpoints/health.ts
Normal file
88
frontend/src/api/endpoints/health.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import client from '../client'
|
||||
import type {
|
||||
HealthStatus,
|
||||
HealthSummary,
|
||||
EndpointStatusMonitorResponse,
|
||||
PublicEndpointStatusMonitorResponse
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* 获取健康状态摘要
|
||||
*/
|
||||
export async function getHealthSummary(): Promise<HealthSummary> {
|
||||
const response = await client.get('/api/admin/endpoints/health/summary')
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Endpoint 健康状态
|
||||
*/
|
||||
export async function getEndpointHealth(endpointId: string): Promise<HealthStatus> {
|
||||
const response = await client.get(`/api/admin/endpoints/health/endpoint/${endpointId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Key 健康状态
|
||||
*/
|
||||
export async function getKeyHealth(keyId: string): Promise<HealthStatus> {
|
||||
const response = await client.get(`/api/admin/endpoints/health/key/${keyId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复Key健康状态(一键恢复:重置健康度 + 关闭熔断器 + 取消自动禁用)
|
||||
*/
|
||||
export async function recoverKeyHealth(keyId: string): Promise<{
|
||||
message: string
|
||||
details: {
|
||||
health_score: number
|
||||
circuit_breaker_open: boolean
|
||||
is_active: boolean
|
||||
}
|
||||
}> {
|
||||
const response = await client.patch(`/api/admin/endpoints/health/keys/${keyId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量恢复所有熔断的Key健康状态
|
||||
*/
|
||||
export async function recoverAllKeysHealth(): Promise<{
|
||||
message: string
|
||||
recovered_count: number
|
||||
recovered_keys: Array<{
|
||||
key_id: string
|
||||
key_name: string
|
||||
endpoint_id: string
|
||||
}>
|
||||
}> {
|
||||
const response = await client.patch('/api/admin/endpoints/health/keys')
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取按 API 格式聚合的健康监控时间线(管理员版,含 provider/key 数量)
|
||||
*/
|
||||
export async function getEndpointStatusMonitor(params?: {
|
||||
lookback_hours?: number
|
||||
per_format_limit?: number
|
||||
}): Promise<EndpointStatusMonitorResponse> {
|
||||
const response = await client.get('/api/admin/endpoints/health/api-formats', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取按 API 格式聚合的健康监控时间线(公开版,不含敏感信息)
|
||||
*/
|
||||
export async function getPublicEndpointStatusMonitor(params?: {
|
||||
lookback_hours?: number
|
||||
per_format_limit?: number
|
||||
}): Promise<PublicEndpointStatusMonitorResponse> {
|
||||
const response = await client.get('/api/public/health/api-formats', {
|
||||
params
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
9
frontend/src/api/endpoints/index.ts
Normal file
9
frontend/src/api/endpoints/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from './types'
|
||||
export * from './providers'
|
||||
export * from './endpoints'
|
||||
export * from './keys'
|
||||
export * from './health'
|
||||
export * from './models'
|
||||
export * from './aliases'
|
||||
export * from './adaptive'
|
||||
export * from './global-models'
|
||||
132
frontend/src/api/endpoints/keys.ts
Normal file
132
frontend/src/api/endpoints/keys.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import client from '../client'
|
||||
import type { EndpointAPIKey } from './types'
|
||||
|
||||
/**
|
||||
* 能力定义类型
|
||||
*/
|
||||
export interface CapabilityDefinition {
|
||||
name: string
|
||||
display_name: string
|
||||
description: string
|
||||
match_mode: 'exclusive' | 'compatible'
|
||||
config_mode?: 'user_configurable' | 'auto_detect' | 'request_param'
|
||||
short_name?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 模型支持的能力响应类型
|
||||
*/
|
||||
export interface ModelCapabilitiesResponse {
|
||||
model: string
|
||||
global_model_id?: string
|
||||
global_model_name?: string
|
||||
supported_capabilities: string[]
|
||||
capability_details: CapabilityDefinition[]
|
||||
error?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有能力定义
|
||||
*/
|
||||
export async function getAllCapabilities(): Promise<CapabilityDefinition[]> {
|
||||
const response = await client.get('/api/capabilities')
|
||||
return response.data.capabilities
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户可配置的能力列表
|
||||
*/
|
||||
export async function getUserConfigurableCapabilities(): Promise<CapabilityDefinition[]> {
|
||||
const response = await client.get('/api/capabilities/user-configurable')
|
||||
return response.data.capabilities
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定模型支持的能力列表
|
||||
*/
|
||||
export async function getModelCapabilities(modelName: string): Promise<ModelCapabilitiesResponse> {
|
||||
const response = await client.get(`/api/capabilities/model/${encodeURIComponent(modelName)}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Endpoint 的所有 Keys
|
||||
*/
|
||||
export async function getEndpointKeys(endpointId: string): Promise<EndpointAPIKey[]> {
|
||||
const response = await client.get(`/api/admin/endpoints/${endpointId}/keys`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 Endpoint 添加 Key
|
||||
*/
|
||||
export async function addEndpointKey(
|
||||
endpointId: string,
|
||||
data: {
|
||||
endpoint_id: string
|
||||
api_key: string
|
||||
name: string // 密钥名称(必填)
|
||||
rate_multiplier?: number // 成本倍率(默认 1.0)
|
||||
internal_priority?: number // Endpoint 内部优先级(数字越小越优先)
|
||||
max_concurrent?: number // 最大并发数(留空=自适应模式)
|
||||
rate_limit?: number
|
||||
daily_limit?: number
|
||||
monthly_limit?: number
|
||||
cache_ttl_minutes?: number // 缓存 TTL(分钟),0=禁用
|
||||
max_probe_interval_minutes?: number // 熔断探测间隔(分钟)
|
||||
allowed_models?: string[] // 允许使用的模型列表
|
||||
capabilities?: Record<string, boolean> // 能力标签配置
|
||||
note?: string // 备注说明(可选)
|
||||
}
|
||||
): Promise<EndpointAPIKey> {
|
||||
const response = await client.post(`/api/admin/endpoints/${endpointId}/keys`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Endpoint Key
|
||||
*/
|
||||
export async function updateEndpointKey(
|
||||
keyId: string,
|
||||
data: Partial<{
|
||||
api_key: string
|
||||
name: string // 密钥名称
|
||||
rate_multiplier: number // 成本倍率
|
||||
internal_priority: number // Endpoint 内部优先级(提供商优先模式,数字越小越优先)
|
||||
global_priority: number // 全局 Key 优先级(全局 Key 优先模式,数字越小越优先)
|
||||
max_concurrent: number // 最大并发数(留空=自适应模式)
|
||||
rate_limit: number
|
||||
daily_limit: number
|
||||
monthly_limit: number
|
||||
cache_ttl_minutes: number // 缓存 TTL(分钟),0=禁用
|
||||
max_probe_interval_minutes: number // 熔断探测间隔(分钟)
|
||||
allowed_models: string[] | null // 允许使用的模型列表,null 表示允许所有
|
||||
capabilities: Record<string, boolean> | null // 能力标签配置
|
||||
is_active: boolean
|
||||
note: string // 备注说明
|
||||
}>
|
||||
): Promise<EndpointAPIKey> {
|
||||
const response = await client.put(`/api/admin/endpoints/keys/${keyId}`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Endpoint Key
|
||||
*/
|
||||
export async function deleteEndpointKey(keyId: string): Promise<{ message: string }> {
|
||||
const response = await client.delete(`/api/admin/endpoints/keys/${keyId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新 Endpoint Keys 的优先级(用于拖动排序)
|
||||
*/
|
||||
export async function batchUpdateKeyPriority(
|
||||
endpointId: string,
|
||||
priorities: Array<{ key_id: string; internal_priority: number }>
|
||||
): Promise<{ message: string; updated_count: number }> {
|
||||
const response = await client.put(`/api/admin/endpoints/${endpointId}/keys/batch-priority`, {
|
||||
priorities
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
145
frontend/src/api/endpoints/models.ts
Normal file
145
frontend/src/api/endpoints/models.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import client from '../client'
|
||||
import type {
|
||||
Model,
|
||||
ModelCreate,
|
||||
ModelUpdate,
|
||||
ModelCatalogResponse,
|
||||
ProviderAvailableSourceModelsResponse,
|
||||
UpdateModelMappingRequest,
|
||||
UpdateModelMappingResponse,
|
||||
DeleteModelMappingResponse
|
||||
} from './types'
|
||||
|
||||
/**
|
||||
* 获取 Provider 的所有模型
|
||||
*/
|
||||
export async function getProviderModels(
|
||||
providerId: string,
|
||||
params?: {
|
||||
is_active?: boolean
|
||||
skip?: number
|
||||
limit?: number
|
||||
}
|
||||
): Promise<Model[]> {
|
||||
const response = await client.get(`/api/admin/providers/${providerId}/models`, { params })
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建模型
|
||||
*/
|
||||
export async function createModel(
|
||||
providerId: string,
|
||||
data: ModelCreate
|
||||
): Promise<Model> {
|
||||
const response = await client.post(`/api/admin/providers/${providerId}/models`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模型详情
|
||||
*/
|
||||
export async function getModel(
|
||||
providerId: string,
|
||||
modelId: string
|
||||
): Promise<Model> {
|
||||
const response = await client.get(`/api/admin/providers/${providerId}/models/${modelId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新模型
|
||||
*/
|
||||
export async function updateModel(
|
||||
providerId: string,
|
||||
modelId: string,
|
||||
data: ModelUpdate
|
||||
): Promise<Model> {
|
||||
const response = await client.patch(`/api/admin/providers/${providerId}/models/${modelId}`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除模型
|
||||
*/
|
||||
export async function deleteModel(
|
||||
providerId: string,
|
||||
modelId: string
|
||||
): Promise<{ message: string }> {
|
||||
const response = await client.delete(`/api/admin/providers/${providerId}/models/${modelId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建模型
|
||||
*/
|
||||
export async function batchCreateModels(
|
||||
providerId: string,
|
||||
modelsData: ModelCreate[]
|
||||
): Promise<Model[]> {
|
||||
const response = await client.post(`/api/admin/providers/${providerId}/models/batch`, modelsData)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统一模型目录
|
||||
*/
|
||||
export async function getModelCatalog(): Promise<ModelCatalogResponse> {
|
||||
const response = await client.get('/api/admin/models/catalog')
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Provider 支持的统一模型列表
|
||||
*/
|
||||
export async function getProviderAvailableSourceModels(
|
||||
providerId: string
|
||||
): Promise<ProviderAvailableSourceModelsResponse> {
|
||||
const response = await client.get(`/api/admin/providers/${providerId}/available-source-models`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新目录中的模型映射
|
||||
*/
|
||||
export async function updateCatalogMapping(
|
||||
mappingId: string,
|
||||
data: UpdateModelMappingRequest
|
||||
): Promise<UpdateModelMappingResponse> {
|
||||
const response = await client.put(`/api/admin/models/catalog/mappings/${mappingId}`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除目录中的模型映射
|
||||
*/
|
||||
export async function deleteCatalogMapping(
|
||||
mappingId: string
|
||||
): Promise<DeleteModelMappingResponse> {
|
||||
const response = await client.delete(`/api/admin/models/catalog/mappings/${mappingId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量为 Provider 关联 GlobalModels
|
||||
*/
|
||||
export async function batchAssignModelsToProvider(
|
||||
providerId: string,
|
||||
globalModelIds: string[]
|
||||
): Promise<{
|
||||
success: Array<{
|
||||
global_model_id: string
|
||||
global_model_name: string
|
||||
model_id: string
|
||||
}>
|
||||
errors: Array<{
|
||||
global_model_id: string
|
||||
error: string
|
||||
}>
|
||||
}> {
|
||||
const response = await client.post(
|
||||
`/api/admin/providers/${providerId}/assign-global-models`,
|
||||
{ global_model_ids: globalModelIds }
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
60
frontend/src/api/endpoints/providers.ts
Normal file
60
frontend/src/api/endpoints/providers.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import client from '../client'
|
||||
import type { ProviderWithEndpointsSummary } from './types'
|
||||
|
||||
/**
|
||||
* 获取 Providers 摘要(包含 Endpoints 统计)
|
||||
*/
|
||||
export async function getProvidersSummary(): Promise<ProviderWithEndpointsSummary[]> {
|
||||
const response = await client.get('/api/admin/providers/summary')
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个 Provider 的详细信息
|
||||
*/
|
||||
export async function getProvider(providerId: string): Promise<ProviderWithEndpointsSummary> {
|
||||
const response = await client.get(`/api/admin/providers/${providerId}/summary`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 Provider 基础配置
|
||||
*/
|
||||
export async function updateProvider(
|
||||
providerId: string,
|
||||
data: Partial<{
|
||||
display_name: string
|
||||
description: string
|
||||
website: string
|
||||
provider_priority: number
|
||||
billing_type: 'monthly_quota' | 'pay_as_you_go' | 'free_tier'
|
||||
monthly_quota_usd: number
|
||||
quota_reset_day: number
|
||||
quota_last_reset_at: string // 周期开始时间
|
||||
quota_expires_at: string
|
||||
rpm_limit: number | null
|
||||
cache_ttl_minutes: number // 0表示不支持缓存,>0表示支持缓存并设置TTL(分钟)
|
||||
max_probe_interval_minutes: number
|
||||
is_active: boolean
|
||||
}>
|
||||
): Promise<ProviderWithEndpointsSummary> {
|
||||
const response = await client.patch(`/api/admin/providers/${providerId}`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Provider
|
||||
*/
|
||||
export async function createProvider(data: any): Promise<any> {
|
||||
const response = await client.post('/api/admin/providers/', data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除 Provider
|
||||
*/
|
||||
export async function deleteProvider(providerId: string): Promise<{ message: string }> {
|
||||
const response = await client.delete(`/api/admin/providers/${providerId}`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
553
frontend/src/api/endpoints/types.ts
Normal file
553
frontend/src/api/endpoints/types.ts
Normal file
@@ -0,0 +1,553 @@
|
||||
export interface ProviderEndpoint {
|
||||
id: string
|
||||
provider_id: string
|
||||
provider_name: string
|
||||
api_format: string
|
||||
base_url: string
|
||||
custom_path?: string // 自定义请求路径(可选,为空则使用 API 格式默认路径)
|
||||
auth_type: string
|
||||
auth_header?: string
|
||||
headers?: Record<string, string>
|
||||
timeout: number
|
||||
max_retries: number
|
||||
priority: number
|
||||
weight: number
|
||||
max_concurrent?: number
|
||||
rate_limit?: number
|
||||
health_score: number
|
||||
consecutive_failures: number
|
||||
last_failure_at?: string
|
||||
is_active: boolean
|
||||
config?: Record<string, any>
|
||||
total_keys: number
|
||||
active_keys: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface EndpointAPIKey {
|
||||
id: string
|
||||
endpoint_id: string
|
||||
api_key_masked: string
|
||||
api_key_plain?: string | null
|
||||
name: string // 密钥名称(必填,用于识别)
|
||||
rate_multiplier: number // 成本倍率(真实成本 = 表面成本 × 倍率)
|
||||
internal_priority: number // Endpoint 内部优先级
|
||||
global_priority?: number | null // 全局 Key 优先级
|
||||
max_concurrent?: number
|
||||
rate_limit?: number
|
||||
daily_limit?: number
|
||||
monthly_limit?: number
|
||||
allowed_models?: string[] | null // 允许使用的模型列表(null = 支持所有模型)
|
||||
capabilities?: Record<string, boolean> | null // 能力标签配置(如 cache_1h, context_1m)
|
||||
// 缓存与熔断配置
|
||||
cache_ttl_minutes: number // 缓存 TTL(分钟),0=禁用
|
||||
max_probe_interval_minutes: number // 熔断探测间隔(分钟)
|
||||
health_score: number
|
||||
consecutive_failures: number
|
||||
last_failure_at?: string
|
||||
request_count: number
|
||||
success_count: number
|
||||
error_count: number
|
||||
success_rate: number
|
||||
avg_response_time_ms: number
|
||||
is_active: boolean
|
||||
note?: string // 备注说明(可选)
|
||||
last_used_at?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
// 自适应并发字段
|
||||
is_adaptive?: boolean // 是否为自适应模式(max_concurrent=NULL)
|
||||
effective_limit?: number // 当前有效限制(自适应使用学习值,固定使用配置值)
|
||||
learned_max_concurrent?: number
|
||||
// 滑动窗口利用率采样
|
||||
utilization_samples?: Array<{ ts: number; util: number }> // 利用率采样窗口
|
||||
last_probe_increase_at?: string // 上次探测性扩容时间
|
||||
concurrent_429_count?: number
|
||||
rpm_429_count?: number
|
||||
last_429_at?: string
|
||||
last_429_type?: string
|
||||
// 熔断器字段(滑动窗口 + 半开模式)
|
||||
circuit_breaker_open?: boolean
|
||||
circuit_breaker_open_at?: string
|
||||
next_probe_at?: string
|
||||
half_open_until?: string
|
||||
half_open_successes?: number
|
||||
half_open_failures?: number
|
||||
request_results_window?: Array<{ ts: number; ok: boolean }> // 请求结果滑动窗口
|
||||
}
|
||||
|
||||
export interface EndpointHealthDetail {
|
||||
api_format: string
|
||||
health_score: number
|
||||
is_active: boolean
|
||||
}
|
||||
|
||||
export interface EndpointHealthEvent {
|
||||
timestamp: string
|
||||
status: 'success' | 'failed' | 'skipped' | 'started'
|
||||
status_code?: number | null
|
||||
latency_ms?: number | null
|
||||
error_type?: string | null
|
||||
error_message?: string | null
|
||||
}
|
||||
|
||||
export interface EndpointStatusMonitor {
|
||||
api_format: string
|
||||
total_attempts: number
|
||||
success_count: number
|
||||
failed_count: number
|
||||
skipped_count: number
|
||||
success_rate: number
|
||||
provider_count: number
|
||||
key_count: number
|
||||
last_event_at?: string | null
|
||||
events: EndpointHealthEvent[]
|
||||
timeline?: string[]
|
||||
time_range_start?: string | null
|
||||
time_range_end?: string | null
|
||||
}
|
||||
|
||||
export interface EndpointStatusMonitorResponse {
|
||||
generated_at: string
|
||||
formats: EndpointStatusMonitor[]
|
||||
}
|
||||
|
||||
// 公开版事件(不含敏感信息如 provider_id, key_id)
|
||||
export interface PublicHealthEvent {
|
||||
timestamp: string
|
||||
status: string
|
||||
status_code?: number | null
|
||||
latency_ms?: number | null
|
||||
error_type?: string | null
|
||||
}
|
||||
|
||||
// 公开版端点状态监控类型(返回 events,前端复用 EndpointHealthTimeline 组件)
|
||||
export interface PublicEndpointStatusMonitor {
|
||||
api_format: string
|
||||
api_path: string // 本站入口路径
|
||||
total_attempts: number
|
||||
success_count: number
|
||||
failed_count: number
|
||||
skipped_count: number
|
||||
success_rate: number
|
||||
last_event_at?: string | null
|
||||
events: PublicHealthEvent[]
|
||||
timeline?: string[]
|
||||
time_range_start?: string | null
|
||||
time_range_end?: string | null
|
||||
}
|
||||
|
||||
export interface PublicEndpointStatusMonitorResponse {
|
||||
generated_at: string
|
||||
formats: PublicEndpointStatusMonitor[]
|
||||
}
|
||||
|
||||
export interface ProviderWithEndpointsSummary {
|
||||
id: string
|
||||
name: string
|
||||
display_name: string
|
||||
description?: string
|
||||
website?: string
|
||||
provider_priority: number
|
||||
billing_type?: 'monthly_quota' | 'pay_as_you_go' | 'free_tier'
|
||||
monthly_quota_usd?: number
|
||||
monthly_used_usd?: number
|
||||
quota_reset_day?: number
|
||||
quota_last_reset_at?: string // 当前周期开始时间
|
||||
quota_expires_at?: string
|
||||
rpm_limit?: number | null
|
||||
rpm_used?: number
|
||||
rpm_reset_at?: string
|
||||
is_active: boolean
|
||||
total_endpoints: number
|
||||
active_endpoints: number
|
||||
total_keys: number
|
||||
active_keys: number
|
||||
total_models: number
|
||||
active_models: number
|
||||
avg_health_score: number
|
||||
unhealthy_endpoints: number
|
||||
api_formats: string[]
|
||||
endpoint_health_details: EndpointHealthDetail[]
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface HealthStatus {
|
||||
endpoint_id?: string
|
||||
endpoint_health_score?: number
|
||||
endpoint_consecutive_failures?: number
|
||||
endpoint_last_failure_at?: string
|
||||
endpoint_is_active?: boolean
|
||||
key_id?: string
|
||||
key_health_score?: number
|
||||
key_consecutive_failures?: number
|
||||
key_last_failure_at?: string
|
||||
key_is_active?: boolean
|
||||
key_statistics?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface HealthSummary {
|
||||
endpoints: {
|
||||
total: number
|
||||
active: number
|
||||
unhealthy: number
|
||||
}
|
||||
keys: {
|
||||
total: number
|
||||
active: number
|
||||
unhealthy: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface ConcurrencyStatus {
|
||||
endpoint_id?: string
|
||||
endpoint_current_concurrency: number
|
||||
endpoint_max_concurrent?: number
|
||||
key_id?: string
|
||||
key_current_concurrency: number
|
||||
key_max_concurrent?: number
|
||||
}
|
||||
|
||||
export interface Model {
|
||||
id: string
|
||||
provider_id: string
|
||||
global_model_id?: string // 关联的 GlobalModel ID
|
||||
provider_model_name: string // Provider 侧的模型名称(原 name)
|
||||
// 原始配置值(可能为空,为空时使用 GlobalModel 默认值)
|
||||
price_per_request?: number | null // 按次计费价格
|
||||
tiered_pricing?: TieredPricingConfig | null // 阶梯计费配置
|
||||
supports_vision?: boolean | null
|
||||
supports_function_calling?: boolean | null
|
||||
supports_streaming?: boolean | null
|
||||
supports_extended_thinking?: boolean | null
|
||||
supports_image_generation?: boolean | null
|
||||
// 有效值(合并 Model 和 GlobalModel 默认值后的结果)
|
||||
effective_tiered_pricing?: TieredPricingConfig | null // 有效阶梯计费配置
|
||||
effective_input_price?: number | null
|
||||
effective_output_price?: number | null
|
||||
effective_price_per_request?: number | null // 有效按次计费价格
|
||||
effective_supports_vision?: boolean | null
|
||||
effective_supports_function_calling?: boolean | null
|
||||
effective_supports_streaming?: boolean | null
|
||||
effective_supports_extended_thinking?: boolean | null
|
||||
effective_supports_image_generation?: boolean | null
|
||||
is_active: boolean
|
||||
is_available: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
// GlobalModel 信息(从后端 join 获取)
|
||||
global_model_name?: string
|
||||
global_model_display_name?: string
|
||||
}
|
||||
|
||||
export interface ModelCreate {
|
||||
provider_model_name: string // Provider 侧的模型名称(原 name)
|
||||
global_model_id: string // 关联的 GlobalModel ID(必填)
|
||||
// 计费配置(可选,为空时使用 GlobalModel 默认值)
|
||||
price_per_request?: number // 按次计费价格
|
||||
tiered_pricing?: TieredPricingConfig // 阶梯计费配置
|
||||
// 能力配置(可选,为空时使用 GlobalModel 默认值)
|
||||
supports_vision?: boolean
|
||||
supports_function_calling?: boolean
|
||||
supports_streaming?: boolean
|
||||
supports_extended_thinking?: boolean
|
||||
supports_image_generation?: boolean
|
||||
is_active?: boolean
|
||||
config?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface ModelUpdate {
|
||||
provider_model_name?: string
|
||||
global_model_id?: string
|
||||
price_per_request?: number | null // 按次计费价格(null 表示清空/使用默认值)
|
||||
tiered_pricing?: TieredPricingConfig | null // 阶梯计费配置
|
||||
supports_vision?: boolean
|
||||
supports_function_calling?: boolean
|
||||
supports_streaming?: boolean
|
||||
supports_extended_thinking?: boolean
|
||||
supports_image_generation?: boolean
|
||||
is_active?: boolean
|
||||
is_available?: boolean
|
||||
}
|
||||
|
||||
export interface ModelMapping {
|
||||
id: string
|
||||
source_model: string // 别名/源模型名
|
||||
target_global_model_id: string // 目标 GlobalModel ID
|
||||
target_global_model_name: string | null
|
||||
target_global_model_display_name: string | null
|
||||
provider_id: string | null
|
||||
provider_name: string | null
|
||||
scope: 'global' | 'provider'
|
||||
mapping_type: 'alias' | 'mapping'
|
||||
is_active: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface ModelCapabilities {
|
||||
supports_vision: boolean
|
||||
supports_function_calling: boolean
|
||||
supports_streaming: boolean
|
||||
[key: string]: boolean
|
||||
}
|
||||
|
||||
export interface ProviderModelPriceInfo {
|
||||
input_price_per_1m?: number | null
|
||||
output_price_per_1m?: number | null
|
||||
cache_creation_price_per_1m?: number | null
|
||||
cache_read_price_per_1m?: number | null
|
||||
price_per_request?: number | null // 按次计费价格
|
||||
}
|
||||
|
||||
export interface ModelPriceRange {
|
||||
min_input: number | null
|
||||
max_input: number | null
|
||||
min_output: number | null
|
||||
max_output: number | null
|
||||
}
|
||||
|
||||
export interface ModelCatalogProviderDetail {
|
||||
provider_id: string
|
||||
provider_name: string
|
||||
provider_display_name?: string | null
|
||||
model_id?: string | null
|
||||
target_model: string
|
||||
input_price_per_1m?: number | null
|
||||
output_price_per_1m?: number | null
|
||||
cache_creation_price_per_1m?: number | null
|
||||
cache_read_price_per_1m?: number | null
|
||||
cache_1h_creation_price_per_1m?: number | null // 1h 缓存创建价格
|
||||
price_per_request?: number | null // 按次计费价格
|
||||
effective_tiered_pricing?: TieredPricingConfig | null // 有效阶梯计费配置(含继承)
|
||||
tier_count?: number // 阶梯数量
|
||||
supports_vision?: boolean | null
|
||||
supports_function_calling?: boolean | null
|
||||
supports_streaming?: boolean | null
|
||||
is_active: boolean
|
||||
mapping_id?: string | null
|
||||
}
|
||||
|
||||
export interface ModelCatalogItem {
|
||||
global_model_name: string // GlobalModel.name(原 source_model)
|
||||
display_name: string // GlobalModel.display_name
|
||||
description?: string | null // GlobalModel.description
|
||||
aliases: string[] // 所有指向该 GlobalModel 的别名列表
|
||||
providers: ModelCatalogProviderDetail[] // 支持该模型的 Provider 列表
|
||||
price_range: ModelPriceRange // 价格区间
|
||||
total_providers: number
|
||||
capabilities: ModelCapabilities // 能力聚合
|
||||
}
|
||||
|
||||
export interface ModelCatalogResponse {
|
||||
models: ModelCatalogItem[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface ProviderAvailableSourceModel {
|
||||
global_model_name: string // GlobalModel.name(原 source_model)
|
||||
display_name: string // GlobalModel.display_name
|
||||
provider_model_name: string // Model.provider_model_name(Provider 侧的模型名)
|
||||
has_alias: boolean // 是否有别名指向该 GlobalModel
|
||||
aliases: string[] // 别名列表
|
||||
model_id?: string | null // Model.id
|
||||
price: ProviderModelPriceInfo
|
||||
capabilities: ModelCapabilities
|
||||
is_active: boolean
|
||||
}
|
||||
|
||||
export interface ProviderAvailableSourceModelsResponse {
|
||||
models: ProviderAvailableSourceModel[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface BatchAssignProviderConfig {
|
||||
provider_id: string
|
||||
create_model?: boolean
|
||||
model_config?: ModelCreate
|
||||
model_id?: string
|
||||
}
|
||||
|
||||
export interface BatchAssignModelMappingRequest {
|
||||
global_model_id: string // 要分配的 GlobalModel ID(原 source_model)
|
||||
providers: BatchAssignProviderConfig[]
|
||||
}
|
||||
|
||||
export interface BatchAssignProviderResult {
|
||||
provider_id: string
|
||||
mapping_id?: string | null
|
||||
created_model: boolean
|
||||
model_id?: string | null
|
||||
updated: boolean
|
||||
}
|
||||
|
||||
export interface BatchAssignError {
|
||||
provider_id: string
|
||||
error: string
|
||||
}
|
||||
|
||||
export interface BatchAssignModelMappingResponse {
|
||||
success: boolean
|
||||
created_mappings: BatchAssignProviderResult[]
|
||||
errors: BatchAssignError[]
|
||||
}
|
||||
|
||||
export interface ModelMappingCreate {
|
||||
source_model: string // 源模型名或别名
|
||||
target_global_model_id: string // 目标 GlobalModel ID
|
||||
provider_id?: string | null
|
||||
mapping_type?: 'alias' | 'mapping'
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
export interface ModelMappingUpdate {
|
||||
source_model?: string // 源模型名或别名
|
||||
target_global_model_id?: string // 目标 GlobalModel ID
|
||||
provider_id?: string | null
|
||||
mapping_type?: 'alias' | 'mapping'
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateModelMappingRequest {
|
||||
source_model?: string
|
||||
target_global_model_id?: string
|
||||
provider_id?: string | null
|
||||
mapping_type?: 'alias' | 'mapping'
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateModelMappingResponse {
|
||||
success: boolean
|
||||
mapping_id: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export interface DeleteModelMappingResponse {
|
||||
success: boolean
|
||||
message?: string
|
||||
}
|
||||
|
||||
export interface AdaptiveStatsResponse {
|
||||
adaptive_mode: boolean
|
||||
current_limit: number | null
|
||||
learned_limit: number | null
|
||||
concurrent_429_count: number
|
||||
rpm_429_count: number
|
||||
last_429_at: string | null
|
||||
last_429_type: string | null
|
||||
adjustment_count: number
|
||||
recent_adjustments: Array<{
|
||||
timestamp: string
|
||||
old_limit: number
|
||||
new_limit: number
|
||||
reason: string
|
||||
[key: string]: any
|
||||
}>
|
||||
}
|
||||
|
||||
// ========== 阶梯计费类型 ==========
|
||||
|
||||
/** 缓存时长定价配置 */
|
||||
export interface CacheTTLPricing {
|
||||
ttl_minutes: number
|
||||
cache_creation_price_per_1m: number
|
||||
}
|
||||
|
||||
/** 单个价格阶梯配置 */
|
||||
export interface PricingTier {
|
||||
up_to: number | null // 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?: CacheTTLPricing[]
|
||||
}
|
||||
|
||||
/** 阶梯计费配置 */
|
||||
export interface TieredPricingConfig {
|
||||
tiers: PricingTier[]
|
||||
}
|
||||
|
||||
// ========== GlobalModel 类型 ==========
|
||||
|
||||
export interface GlobalModelCreate {
|
||||
name: string
|
||||
display_name: string
|
||||
description?: string
|
||||
official_url?: string
|
||||
icon_url?: string
|
||||
// 按次计费配置(可选,与阶梯计费叠加)
|
||||
default_price_per_request?: number
|
||||
// 阶梯计费配置(必填,固定价格用单阶梯表示)
|
||||
default_tiered_pricing: TieredPricingConfig
|
||||
// 默认能力配置
|
||||
default_supports_vision?: boolean
|
||||
default_supports_function_calling?: boolean
|
||||
default_supports_streaming?: boolean
|
||||
default_supports_extended_thinking?: boolean
|
||||
default_supports_image_generation?: boolean
|
||||
// Key 能力配置 - 模型支持的能力列表
|
||||
supported_capabilities?: string[]
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
export interface GlobalModelUpdate {
|
||||
display_name?: string
|
||||
description?: string
|
||||
official_url?: string
|
||||
icon_url?: string
|
||||
is_active?: boolean
|
||||
// 按次计费配置
|
||||
default_price_per_request?: number | null // null 表示清空
|
||||
// 阶梯计费配置
|
||||
default_tiered_pricing?: TieredPricingConfig
|
||||
// 默认能力配置
|
||||
default_supports_vision?: boolean
|
||||
default_supports_function_calling?: boolean
|
||||
default_supports_streaming?: boolean
|
||||
default_supports_extended_thinking?: boolean
|
||||
default_supports_image_generation?: boolean
|
||||
// Key 能力配置 - 模型支持的能力列表
|
||||
supported_capabilities?: string[] | null
|
||||
}
|
||||
|
||||
export interface GlobalModelResponse {
|
||||
id: string
|
||||
name: string
|
||||
display_name: string
|
||||
description?: string
|
||||
official_url?: string
|
||||
icon_url?: string
|
||||
is_active: boolean
|
||||
// 按次计费配置
|
||||
default_price_per_request?: number
|
||||
// 阶梯计费配置(必填)
|
||||
default_tiered_pricing: TieredPricingConfig
|
||||
// 默认能力配置
|
||||
default_supports_vision?: boolean
|
||||
default_supports_function_calling?: boolean
|
||||
default_supports_streaming?: boolean
|
||||
default_supports_extended_thinking?: boolean
|
||||
default_supports_image_generation?: boolean
|
||||
// Key 能力配置 - 模型支持的能力列表
|
||||
supported_capabilities?: string[] | null
|
||||
// 统计数据
|
||||
provider_count?: number
|
||||
alias_count?: number
|
||||
usage_count?: number
|
||||
created_at: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export interface GlobalModelWithStats extends GlobalModelResponse {
|
||||
total_models: number
|
||||
total_providers: number
|
||||
price_range: ModelPriceRange
|
||||
}
|
||||
|
||||
export interface GlobalModelListResponse {
|
||||
models: GlobalModelResponse[]
|
||||
total: number
|
||||
}
|
||||
23
frontend/src/api/global-models.ts
Normal file
23
frontend/src/api/global-models.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* GlobalModel API 客户端
|
||||
* 统一导出,简化导入路径
|
||||
*/
|
||||
|
||||
export * from './endpoints/global-models'
|
||||
export type {
|
||||
GlobalModelCreate,
|
||||
GlobalModelUpdate,
|
||||
GlobalModelResponse,
|
||||
GlobalModelWithStats,
|
||||
GlobalModelListResponse,
|
||||
} from './endpoints/types'
|
||||
|
||||
// 重新导出为更简洁的函数名
|
||||
export {
|
||||
getGlobalModels as listGlobalModels,
|
||||
getGlobalModel,
|
||||
createGlobalModel,
|
||||
updateGlobalModel,
|
||||
deleteGlobalModel,
|
||||
batchAssignToProviders,
|
||||
} from './endpoints/global-models'
|
||||
257
frontend/src/api/me.ts
Normal file
257
frontend/src/api/me.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
import apiClient from './client'
|
||||
import type { ActivityHeatmap } from '@/types/activity'
|
||||
|
||||
export interface Profile {
|
||||
id: string // UUID
|
||||
email: string
|
||||
username: string
|
||||
role: string
|
||||
is_active: boolean
|
||||
quota_usd: number | null
|
||||
used_usd: number
|
||||
total_usd?: number // 累积消费总额
|
||||
created_at: string
|
||||
updated_at: string
|
||||
last_login_at?: string
|
||||
preferences?: UserPreferences
|
||||
}
|
||||
|
||||
export interface UserPreferences {
|
||||
avatar_url?: string
|
||||
bio?: string
|
||||
default_provider_id?: string // UUID
|
||||
default_provider?: any
|
||||
theme: string
|
||||
language: string
|
||||
timezone?: string
|
||||
notifications?: {
|
||||
email?: boolean
|
||||
usage_alerts?: boolean
|
||||
announcements?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// 提供商配置接口
|
||||
export interface ProviderConfig {
|
||||
provider_id: string
|
||||
priority: number // 优先级(越高越优先)
|
||||
weight: number // 负载均衡权重
|
||||
enabled: boolean // 是否启用
|
||||
}
|
||||
|
||||
// 使用记录接口
|
||||
export interface UsageRecordDetail {
|
||||
id: string
|
||||
provider: string
|
||||
model: string
|
||||
input_tokens: number
|
||||
output_tokens: number
|
||||
total_tokens: number
|
||||
cost: number // 官方费率
|
||||
actual_cost?: number // 倍率消耗(仅管理员可见)
|
||||
rate_multiplier?: number // 成本倍率(仅管理员可见)
|
||||
response_time_ms?: number
|
||||
is_stream: boolean
|
||||
created_at: string
|
||||
cache_creation_input_tokens?: number
|
||||
cache_read_input_tokens?: number
|
||||
status_code: number
|
||||
error_message?: string
|
||||
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 // 按次计费价格
|
||||
}
|
||||
|
||||
// 模型统计接口
|
||||
export interface ModelSummary {
|
||||
model: string
|
||||
requests: number
|
||||
input_tokens: number
|
||||
output_tokens: number
|
||||
total_tokens: number
|
||||
total_cost_usd: number
|
||||
actual_total_cost_usd?: number // 倍率消耗(仅管理员可见)
|
||||
}
|
||||
|
||||
// 使用统计响应接口
|
||||
export interface UsageResponse {
|
||||
total_requests: number
|
||||
total_input_tokens: number
|
||||
total_output_tokens: number
|
||||
total_tokens: number
|
||||
total_cost: number // 官方费率
|
||||
total_actual_cost?: number // 倍率消耗(仅管理员可见)
|
||||
avg_response_time: number
|
||||
quota_usd: number | null
|
||||
used_usd: number
|
||||
summary_by_model: ModelSummary[]
|
||||
records: UsageRecordDetail[]
|
||||
activity_heatmap?: ActivityHeatmap | null
|
||||
}
|
||||
|
||||
export interface ApiKey {
|
||||
id: string // UUID
|
||||
name: string
|
||||
key?: string
|
||||
key_display: string
|
||||
is_active: boolean
|
||||
last_used_at?: string
|
||||
created_at: string
|
||||
total_requests?: number
|
||||
total_cost_usd?: number
|
||||
allowed_providers?: ProviderConfig[]
|
||||
force_capabilities?: Record<string, boolean> | null // 强制能力配置
|
||||
}
|
||||
|
||||
// 不再需要 ProviderBinding 接口
|
||||
|
||||
export interface ChangePasswordRequest {
|
||||
old_password: string
|
||||
new_password: string
|
||||
}
|
||||
|
||||
export const meApi = {
|
||||
// 获取个人信息
|
||||
async getProfile(): Promise<Profile> {
|
||||
const response = await apiClient.get<Profile>('/api/users/me')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 更新个人信息
|
||||
async updateProfile(data: {
|
||||
email?: string
|
||||
username?: string
|
||||
}): Promise<{ message: string }> {
|
||||
const response = await apiClient.put('/api/users/me', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 修改密码
|
||||
async changePassword(data: ChangePasswordRequest): Promise<{ message: string }> {
|
||||
const response = await apiClient.patch('/api/users/me/password', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// API密钥管理
|
||||
async getApiKeys(): Promise<ApiKey[]> {
|
||||
const response = await apiClient.get<ApiKey[]>('/api/users/me/api-keys')
|
||||
return response.data
|
||||
},
|
||||
|
||||
async createApiKey(name: string): Promise<ApiKey> {
|
||||
const response = await apiClient.post<ApiKey>('/api/users/me/api-keys', { name })
|
||||
return response.data
|
||||
},
|
||||
|
||||
async getApiKeyDetail(keyId: string, includeKey: boolean = false): Promise<ApiKey & { key?: string }> {
|
||||
const response = await apiClient.get<ApiKey & { key?: string }>(
|
||||
`/api/users/me/api-keys/${keyId}`,
|
||||
{ params: { include_key: includeKey } }
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async getFullApiKey(keyId: string): Promise<{ key: string }> {
|
||||
const response = await apiClient.get<{ key: string }>(
|
||||
`/api/users/me/api-keys/${keyId}`,
|
||||
{ params: { include_key: true } }
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async deleteApiKey(keyId: string): Promise<{ message: string }> {
|
||||
const response = await apiClient.delete(`/api/users/me/api-keys/${keyId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async toggleApiKey(keyId: string): Promise<ApiKey> {
|
||||
const response = await apiClient.patch<ApiKey>(`/api/users/me/api-keys/${keyId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 使用统计
|
||||
async getUsage(params?: {
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
}): Promise<UsageResponse> {
|
||||
const response = await apiClient.get<UsageResponse>('/api/users/me/usage', { params })
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取活跃请求状态(用于轮询更新)
|
||||
async getActiveRequests(ids?: string): Promise<{
|
||||
requests: Array<{
|
||||
id: string
|
||||
status: string
|
||||
input_tokens: number
|
||||
output_tokens: number
|
||||
cost: number
|
||||
response_time_ms: number | null
|
||||
}>
|
||||
}> {
|
||||
const params = ids ? { ids } : {}
|
||||
const response = await apiClient.get('/api/users/me/usage/active', { params })
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取可用的提供商
|
||||
async getAvailableProviders(): Promise<any[]> {
|
||||
const response = await apiClient.get('/api/users/me/providers')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 获取端点状态(不包含敏感信息)
|
||||
async getEndpointStatus(): Promise<any[]> {
|
||||
const response = await apiClient.get('/api/users/me/endpoint-status')
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 偏好设置
|
||||
async getPreferences(): Promise<UserPreferences> {
|
||||
const response = await apiClient.get('/api/users/me/preferences')
|
||||
return response.data
|
||||
},
|
||||
|
||||
async updatePreferences(data: Partial<UserPreferences>): Promise<{ message: string }> {
|
||||
const response = await apiClient.put('/api/users/me/preferences', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 提供商绑定管理相关方法已移除,改为直接从可用提供商中选择
|
||||
|
||||
// API密钥提供商关联
|
||||
async updateApiKeyProviders(keyId: string, data: {
|
||||
allowed_providers?: ProviderConfig[]
|
||||
}): Promise<{ message: string }> {
|
||||
const response = await apiClient.put(`/api/users/me/api-keys/${keyId}/providers`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// API密钥能力配置
|
||||
async updateApiKeyCapabilities(keyId: string, data: {
|
||||
force_capabilities?: Record<string, boolean> | null
|
||||
}): Promise<{ message: string; force_capabilities?: Record<string, boolean> | null }> {
|
||||
const response = await apiClient.put(`/api/users/me/api-keys/${keyId}/capabilities`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 模型能力配置
|
||||
async getModelCapabilitySettings(): Promise<{
|
||||
model_capability_settings: Record<string, Record<string, boolean>>
|
||||
}> {
|
||||
const response = await apiClient.get('/api/users/me/model-capabilities')
|
||||
return response.data
|
||||
},
|
||||
|
||||
async updateModelCapabilitySettings(data: {
|
||||
model_capability_settings: Record<string, Record<string, boolean>> | null
|
||||
}): Promise<{
|
||||
message: string
|
||||
model_capability_settings: Record<string, Record<string, boolean>> | null
|
||||
}> {
|
||||
const response = await apiClient.put('/api/users/me/model-capabilities', data)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
55
frontend/src/api/provider-strategy.ts
Normal file
55
frontend/src/api/provider-strategy.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 提供商策略管理 API 客户端
|
||||
*/
|
||||
|
||||
import apiClient from './client';
|
||||
|
||||
const API_BASE = '/api/admin/provider-strategy';
|
||||
|
||||
export interface ProviderBillingConfig {
|
||||
billing_type: 'monthly_quota' | 'pay_as_you_go' | 'free_tier';
|
||||
monthly_quota_usd?: number;
|
||||
quota_reset_day?: number;
|
||||
quota_last_reset_at?: string; // 当前周期开始时间
|
||||
quota_expires_at?: string;
|
||||
rpm_limit?: number | null;
|
||||
cache_ttl_minutes?: number; // 0表示不支持缓存,>0表示支持缓存并设置TTL(分钟)
|
||||
provider_priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新提供商计费配置
|
||||
*/
|
||||
export async function updateProviderBilling(
|
||||
providerId: string,
|
||||
config: ProviderBillingConfig
|
||||
) {
|
||||
const response = await apiClient.put(`${API_BASE}/providers/${providerId}/billing`, config);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提供商使用统计
|
||||
*/
|
||||
export async function getProviderStats(providerId: string, hours: number = 24) {
|
||||
const response = await apiClient.get(`${API_BASE}/providers/${providerId}/stats`, {
|
||||
params: { hours }
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置提供商月卡额度
|
||||
*/
|
||||
export async function resetProviderQuota(providerId: string) {
|
||||
const response = await apiClient.delete(`${API_BASE}/providers/${providerId}/quota`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用的负载均衡策略
|
||||
*/
|
||||
export async function listAvailableStrategies() {
|
||||
const response = await apiClient.get(`${API_BASE}/strategies`);
|
||||
return response.data;
|
||||
}
|
||||
44
frontend/src/api/public-models.ts
Normal file
44
frontend/src/api/public-models.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Public Models API - 普通用户可访问的模型列表
|
||||
*/
|
||||
|
||||
import client from './client'
|
||||
import type { TieredPricingConfig } from './endpoints/types'
|
||||
|
||||
export interface PublicGlobalModel {
|
||||
id: string
|
||||
name: string
|
||||
display_name: string | null
|
||||
description: string | null
|
||||
icon_url: string | null
|
||||
is_active: boolean
|
||||
// 阶梯计费配置
|
||||
default_tiered_pricing: TieredPricingConfig
|
||||
default_price_per_request: number | null // 按次计费价格
|
||||
// 能力
|
||||
default_supports_vision: boolean
|
||||
default_supports_function_calling: boolean
|
||||
default_supports_streaming: boolean
|
||||
default_supports_extended_thinking: boolean
|
||||
default_supports_image_generation: boolean
|
||||
// Key 能力支持
|
||||
supported_capabilities: string[] | null
|
||||
}
|
||||
|
||||
export interface PublicGlobalModelListResponse {
|
||||
models: PublicGlobalModel[]
|
||||
total: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开的 GlobalModel 列表(普通用户可访问)
|
||||
*/
|
||||
export async function getPublicGlobalModels(params?: {
|
||||
skip?: number
|
||||
limit?: number
|
||||
is_active?: boolean
|
||||
search?: string
|
||||
}): Promise<PublicGlobalModelListResponse> {
|
||||
const response = await client.get('/api/public/global-models', { params })
|
||||
return response.data
|
||||
}
|
||||
69
frontend/src/api/requestTrace.ts
Normal file
69
frontend/src/api/requestTrace.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import apiClient from './client'
|
||||
|
||||
export interface CandidateRecord {
|
||||
id: string
|
||||
request_id: string
|
||||
candidate_index: number
|
||||
retry_index: number
|
||||
provider_id?: string
|
||||
provider_name?: string
|
||||
provider_website?: string // Provider 官网
|
||||
endpoint_id?: string
|
||||
endpoint_name?: string // 端点显示名称(api_format)
|
||||
key_id?: string
|
||||
key_name?: string // 密钥名称
|
||||
key_preview?: string // 密钥脱敏预览(如 sk-***abc)
|
||||
key_capabilities?: Record<string, boolean> | null // Key 支持的能力
|
||||
required_capabilities?: Record<string, boolean> | null // 请求实际需要的能力标签
|
||||
status: 'pending' | 'streaming' | 'success' | 'failed' | 'skipped'
|
||||
skip_reason?: string
|
||||
is_cached: boolean
|
||||
// 执行结果字段
|
||||
status_code?: number
|
||||
error_type?: string
|
||||
error_message?: string
|
||||
latency_ms?: number
|
||||
concurrent_requests?: number
|
||||
extra_data?: Record<string, any>
|
||||
created_at: string
|
||||
started_at?: string
|
||||
finished_at?: string
|
||||
}
|
||||
|
||||
export interface RequestTrace {
|
||||
request_id: string
|
||||
total_candidates: number
|
||||
final_status: 'success' | 'failed' | 'streaming' | 'pending'
|
||||
total_latency_ms: number
|
||||
candidates: CandidateRecord[]
|
||||
}
|
||||
|
||||
export interface ProviderStats {
|
||||
total_attempts: number
|
||||
success_count: number
|
||||
failed_count: number
|
||||
skipped_count: number
|
||||
pending_count: number
|
||||
available_count: number
|
||||
failure_rate: number
|
||||
}
|
||||
|
||||
export const requestTraceApi = {
|
||||
/**
|
||||
* 获取特定请求的完整追踪信息
|
||||
*/
|
||||
async getRequestTrace(requestId: string): Promise<RequestTrace> {
|
||||
const response = await apiClient.get<RequestTrace>(`/api/admin/monitoring/trace/${requestId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取某个 Provider 的失败率统计
|
||||
*/
|
||||
async getProviderStats(providerId: string, limit: number = 100): Promise<ProviderStats> {
|
||||
const response = await apiClient.get<ProviderStats>(`/api/admin/monitoring/trace/stats/provider/${providerId}`, {
|
||||
params: { limit }
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
83
frontend/src/api/security.ts
Normal file
83
frontend/src/api/security.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* IP 安全管理 API
|
||||
*/
|
||||
import apiClient from './client'
|
||||
|
||||
export interface IPBlacklistEntry {
|
||||
ip_address: string
|
||||
reason: string
|
||||
ttl?: number
|
||||
}
|
||||
|
||||
export interface IPWhitelistEntry {
|
||||
ip_address: string
|
||||
}
|
||||
|
||||
export interface BlacklistStats {
|
||||
available: boolean
|
||||
total: number
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface WhitelistResponse {
|
||||
whitelist: string[]
|
||||
total: number
|
||||
}
|
||||
|
||||
/**
|
||||
* IP 黑名单管理
|
||||
*/
|
||||
export const blacklistApi = {
|
||||
/**
|
||||
* 添加 IP 到黑名单
|
||||
*/
|
||||
async add(data: IPBlacklistEntry) {
|
||||
const response = await apiClient.post('/api/admin/security/ip/blacklist', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 从黑名单移除 IP
|
||||
*/
|
||||
async remove(ip_address: string) {
|
||||
const response = await apiClient.delete(`/api/admin/security/ip/blacklist/${encodeURIComponent(ip_address)}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取黑名单统计
|
||||
*/
|
||||
async getStats(): Promise<BlacklistStats> {
|
||||
const response = await apiClient.get('/api/admin/security/ip/blacklist/stats')
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* IP 白名单管理
|
||||
*/
|
||||
export const whitelistApi = {
|
||||
/**
|
||||
* 添加 IP 到白名单
|
||||
*/
|
||||
async add(data: IPWhitelistEntry) {
|
||||
const response = await apiClient.post('/api/admin/security/ip/whitelist', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 从白名单移除 IP
|
||||
*/
|
||||
async remove(ip_address: string) {
|
||||
const response = await apiClient.delete(`/api/admin/security/ip/whitelist/${encodeURIComponent(ip_address)}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取白名单列表
|
||||
*/
|
||||
async getList(): Promise<WhitelistResponse> {
|
||||
const response = await apiClient.get('/api/admin/security/ip/whitelist')
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
202
frontend/src/api/usage.ts
Normal file
202
frontend/src/api/usage.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import apiClient from './client'
|
||||
import { cachedRequest } from '@/utils/cache'
|
||||
import type { ActivityHeatmap } from '@/types/activity'
|
||||
|
||||
export interface UsageRecord {
|
||||
id: string // UUID
|
||||
user_id: string // UUID
|
||||
username?: string
|
||||
provider_id?: string // UUID
|
||||
provider_name?: string
|
||||
model: string
|
||||
input_tokens: number
|
||||
output_tokens: number
|
||||
cache_creation_input_tokens?: number
|
||||
cache_read_input_tokens?: number
|
||||
total_tokens: number
|
||||
cost?: number
|
||||
response_time?: number
|
||||
created_at: string
|
||||
has_fallback?: boolean // 🆕 是否发生了 fallback
|
||||
}
|
||||
|
||||
export interface UsageStats {
|
||||
total_requests: number
|
||||
total_tokens: number
|
||||
total_cost: number
|
||||
total_actual_cost?: number
|
||||
avg_response_time: number
|
||||
today?: {
|
||||
requests: number
|
||||
tokens: number
|
||||
cost: number
|
||||
}
|
||||
activity_heatmap?: ActivityHeatmap | null
|
||||
}
|
||||
|
||||
export interface UsageByModel {
|
||||
model: string
|
||||
request_count: number
|
||||
total_tokens: number
|
||||
total_cost: number
|
||||
avg_response_time?: number
|
||||
}
|
||||
|
||||
export interface UsageByUser {
|
||||
user_id: string // UUID
|
||||
email: string
|
||||
username: string
|
||||
request_count: number
|
||||
total_tokens: number
|
||||
total_cost: number
|
||||
}
|
||||
|
||||
export interface UsageByProvider {
|
||||
provider_id: string
|
||||
provider: string
|
||||
request_count: number
|
||||
total_tokens: number
|
||||
total_cost: number
|
||||
actual_cost: number
|
||||
avg_response_time_ms: number
|
||||
success_rate: number
|
||||
error_count: number
|
||||
}
|
||||
|
||||
export interface UsageByApiFormat {
|
||||
api_format: string
|
||||
request_count: number
|
||||
total_tokens: number
|
||||
total_cost: number
|
||||
actual_cost: number
|
||||
avg_response_time_ms: number
|
||||
}
|
||||
|
||||
export interface UsageFilters {
|
||||
user_id?: string // UUID
|
||||
provider_id?: string // UUID
|
||||
model?: string
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
export const usageApi = {
|
||||
async getUsageRecords(filters?: UsageFilters): Promise<{
|
||||
records: UsageRecord[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}> {
|
||||
const response = await apiClient.get('/api/usage', { params: filters })
|
||||
return response.data
|
||||
},
|
||||
|
||||
async getUsageStats(filters?: UsageFilters): Promise<UsageStats> {
|
||||
// 为统计数据添加30秒缓存
|
||||
const cacheKey = `usage-stats-${JSON.stringify(filters || {})}`
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<UsageStats>('/api/admin/usage/stats', { params: filters })
|
||||
return response.data
|
||||
},
|
||||
30000 // 30秒缓存
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get usage aggregation by dimension (RESTful API)
|
||||
* @param groupBy Aggregation dimension: 'model', 'user', 'provider', or 'api_format'
|
||||
* @param filters Optional filters
|
||||
*/
|
||||
async getUsageAggregation<T = UsageByModel[] | UsageByUser[] | UsageByProvider[] | UsageByApiFormat[]>(
|
||||
groupBy: 'model' | 'user' | 'provider' | 'api_format',
|
||||
filters?: UsageFilters & { limit?: number }
|
||||
): Promise<T> {
|
||||
const cacheKey = `usage-aggregation-${groupBy}-${JSON.stringify(filters || {})}`
|
||||
return cachedRequest(
|
||||
cacheKey,
|
||||
async () => {
|
||||
const response = await apiClient.get<T>('/api/admin/usage/aggregation/stats', {
|
||||
params: { group_by: groupBy, ...filters }
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
30000 // 30秒缓存
|
||||
)
|
||||
},
|
||||
|
||||
// Shorthand methods using getUsageAggregation
|
||||
async getUsageByModel(filters?: UsageFilters & { limit?: number }): Promise<UsageByModel[]> {
|
||||
return this.getUsageAggregation<UsageByModel[]>('model', filters)
|
||||
},
|
||||
|
||||
async getUsageByUser(filters?: UsageFilters & { limit?: number }): Promise<UsageByUser[]> {
|
||||
return this.getUsageAggregation<UsageByUser[]>('user', filters)
|
||||
},
|
||||
|
||||
async getUsageByProvider(filters?: UsageFilters & { limit?: number }): Promise<UsageByProvider[]> {
|
||||
return this.getUsageAggregation<UsageByProvider[]>('provider', filters)
|
||||
},
|
||||
|
||||
async getUsageByApiFormat(filters?: UsageFilters & { limit?: number }): Promise<UsageByApiFormat[]> {
|
||||
return this.getUsageAggregation<UsageByApiFormat[]>('api_format', filters)
|
||||
},
|
||||
|
||||
async getUserUsage(userId: string, filters?: UsageFilters): Promise<{
|
||||
records: UsageRecord[]
|
||||
stats: UsageStats
|
||||
}> {
|
||||
const response = await apiClient.get(`/api/users/${userId}/usage`, { params: filters })
|
||||
return response.data
|
||||
},
|
||||
|
||||
async exportUsage(format: 'csv' | 'json', filters?: UsageFilters): Promise<Blob> {
|
||||
const response = await apiClient.get('/api/usage/export', {
|
||||
params: { ...filters, format },
|
||||
responseType: 'blob'
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
async getAllUsageRecords(params?: {
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
user_id?: string // UUID
|
||||
username?: string
|
||||
model?: string
|
||||
provider?: string
|
||||
status?: string // 'stream' | 'standard' | 'error'
|
||||
limit?: number
|
||||
offset?: number
|
||||
}): Promise<{
|
||||
records: any[]
|
||||
total: number
|
||||
limit: number
|
||||
offset: number
|
||||
}> {
|
||||
const response = await apiClient.get('/api/admin/usage/records', { params })
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取活跃请求的状态(轻量级接口,用于轮询更新)
|
||||
* @param ids 可选,逗号分隔的请求 ID 列表
|
||||
*/
|
||||
async getActiveRequests(ids?: string[]): Promise<{
|
||||
requests: Array<{
|
||||
id: string
|
||||
status: 'pending' | 'streaming' | 'completed' | 'failed'
|
||||
input_tokens: number
|
||||
output_tokens: number
|
||||
cost: number
|
||||
response_time_ms: number | null
|
||||
}>
|
||||
}> {
|
||||
const params = ids?.length ? { ids: ids.join(',') } : {}
|
||||
const response = await apiClient.get('/api/admin/usage/active', { params })
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
106
frontend/src/api/users.ts
Normal file
106
frontend/src/api/users.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import apiClient from './client'
|
||||
|
||||
export interface User {
|
||||
id: string // UUID
|
||||
username: string
|
||||
email: string
|
||||
role: 'admin' | 'user'
|
||||
is_active: boolean
|
||||
quota_usd: number | null
|
||||
used_usd: number
|
||||
total_usd: number
|
||||
allowed_providers: string[] | null // 允许使用的提供商 ID 列表
|
||||
allowed_endpoints: string[] | null // 允许使用的端点 ID 列表
|
||||
allowed_models: string[] | null // 允许使用的模型名称列表
|
||||
created_at: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export interface CreateUserRequest {
|
||||
username: string
|
||||
password: string
|
||||
email: string
|
||||
role?: 'admin' | 'user'
|
||||
quota_usd?: number | null
|
||||
allowed_providers?: string[] | null
|
||||
allowed_endpoints?: string[] | null
|
||||
allowed_models?: string[] | null
|
||||
}
|
||||
|
||||
export interface UpdateUserRequest {
|
||||
email?: string
|
||||
is_active?: boolean
|
||||
role?: 'admin' | 'user'
|
||||
quota_usd?: number | null
|
||||
password?: string
|
||||
allowed_providers?: string[] | null
|
||||
allowed_endpoints?: string[] | null
|
||||
allowed_models?: string[] | null
|
||||
}
|
||||
|
||||
export interface ApiKey {
|
||||
id: string // UUID
|
||||
key?: string // 完整的 key,只在创建时返回
|
||||
key_display?: string // 脱敏后的密钥显示
|
||||
name?: string
|
||||
created_at: string
|
||||
last_used_at?: string
|
||||
expires_at?: string // 过期时间
|
||||
is_active: boolean
|
||||
is_standalone: boolean // 是否为独立余额Key
|
||||
balance_used_usd?: number // 已使用余额(仅独立Key)
|
||||
current_balance_usd?: number | null // 当前余额(独立Key预付费模式,null表示无限制)
|
||||
rate_limit?: number // 速率限制(请求/分钟)
|
||||
total_requests?: number // 总请求数
|
||||
total_cost_usd?: number // 总费用
|
||||
}
|
||||
|
||||
export const usersApi = {
|
||||
async getAllUsers(): Promise<User[]> {
|
||||
const response = await apiClient.get<User[]>('/api/admin/users')
|
||||
return response.data
|
||||
},
|
||||
|
||||
async getUser(userId: string): Promise<User> {
|
||||
const response = await apiClient.get<User>(`/api/admin/users/${userId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async createUser(user: CreateUserRequest): Promise<User> {
|
||||
const response = await apiClient.post<User>('/api/admin/users', user)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async updateUser(userId: string, updates: UpdateUserRequest): Promise<User> {
|
||||
const response = await apiClient.put<User>(`/api/admin/users/${userId}`, updates)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async deleteUser(userId: string): Promise<void> {
|
||||
await apiClient.delete(`/api/admin/users/${userId}`)
|
||||
},
|
||||
|
||||
async getUserApiKeys(userId: string): Promise<ApiKey[]> {
|
||||
const response = await apiClient.get<{ api_keys: ApiKey[] }>(`/api/admin/users/${userId}/api-keys`)
|
||||
return response.data.api_keys
|
||||
},
|
||||
|
||||
async createApiKey(userId: string, name?: string): Promise<ApiKey & { key: string }> {
|
||||
const response = await apiClient.post<ApiKey & { key: string }>(`/api/admin/users/${userId}/api-keys`, { name })
|
||||
return response.data
|
||||
},
|
||||
|
||||
async deleteApiKey(userId: string, keyId: string): Promise<void> {
|
||||
await apiClient.delete(`/api/admin/users/${userId}/api-keys/${keyId}`)
|
||||
},
|
||||
|
||||
async resetUserQuota(userId: string): Promise<void> {
|
||||
await apiClient.patch(`/api/admin/users/${userId}/quota`)
|
||||
},
|
||||
|
||||
// 管理员统计
|
||||
async getUsageStats(): Promise<any> {
|
||||
const response = await apiClient.get('/api/admin/usage/stats')
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user