mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
refactor: 重构异步任务系统和计费服务架构
- 重构任务系统:新增 lifecycle (TaskStatus/BillingStatus)、context、application 模块 - 将 video tasks 泛化为 async tasks,支持更通用的异步任务管理 - 新增 Gemini Files 管理模块和管理界面 - 重构 billing 服务:拆分 schema.py 和 service.py - 新增 candidate 服务模块用于请求候选管理 - 数据库迁移:添加 billing_status、request_id、gemini_file_mappings 表和索引 - 移除废弃的 video_telemetry、task orchestrator 等模块
This commit is contained in:
@@ -1,17 +1,21 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// 视频任务状态
|
||||
export type VideoTaskStatus = 'pending' | 'submitted' | 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled'
|
||||
// 异步任务状态
|
||||
export type AsyncTaskStatus = 'pending' | 'submitted' | 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled'
|
||||
|
||||
// 视频任务列表项
|
||||
export interface VideoTaskItem {
|
||||
// 异步任务类型
|
||||
export type AsyncTaskType = 'video'
|
||||
|
||||
// 异步任务列表项
|
||||
export interface AsyncTaskItem {
|
||||
id: string
|
||||
external_task_id: string
|
||||
user_id: string
|
||||
username: string
|
||||
task_type: AsyncTaskType
|
||||
model: string
|
||||
prompt: string
|
||||
status: VideoTaskStatus
|
||||
status: AsyncTaskStatus
|
||||
progress_percent: number
|
||||
progress_message: string | null
|
||||
provider_id: string
|
||||
@@ -44,7 +48,7 @@ export interface CandidateKeyInfo {
|
||||
}
|
||||
|
||||
// 请求元数据
|
||||
export interface VideoTaskRequestMetadata {
|
||||
export interface AsyncTaskRequestMetadata {
|
||||
candidate_keys: CandidateKeyInfo[]
|
||||
selected_key_id: string
|
||||
selected_endpoint_id: string
|
||||
@@ -52,10 +56,12 @@ export interface VideoTaskRequestMetadata {
|
||||
user_agent: string
|
||||
request_id: string
|
||||
request_headers?: Record<string, string>
|
||||
poll_raw_response?: any // 轮询完成时的原始响应
|
||||
billing_snapshot?: any // 计费快照
|
||||
}
|
||||
|
||||
// 视频任务详情
|
||||
export interface VideoTaskDetail extends VideoTaskItem {
|
||||
// 异步任务详情
|
||||
export interface AsyncTaskDetail extends AsyncTaskItem {
|
||||
api_key_id: string
|
||||
endpoint_id: string
|
||||
key_id: string
|
||||
@@ -81,73 +87,76 @@ export interface VideoTaskDetail extends VideoTaskItem {
|
||||
base_url: string
|
||||
api_format: string
|
||||
} | null
|
||||
request_metadata: VideoTaskRequestMetadata | null
|
||||
request_metadata: AsyncTaskRequestMetadata | null
|
||||
}
|
||||
|
||||
// 视频任务列表响应
|
||||
export interface VideoTaskListResponse {
|
||||
items: VideoTaskItem[]
|
||||
// 异步任务列表响应
|
||||
export interface AsyncTaskListResponse {
|
||||
items: AsyncTaskItem[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
// 视频任务统计响应
|
||||
export interface VideoTaskStatsResponse {
|
||||
// 异步任务统计响应
|
||||
export interface AsyncTaskStatsResponse {
|
||||
total: number
|
||||
by_status: Record<VideoTaskStatus, number>
|
||||
by_status: Record<AsyncTaskStatus, number>
|
||||
by_model: Record<string, number>
|
||||
today_count: number
|
||||
active_users?: number // 仅管理员
|
||||
processing_count?: number // 仅管理员
|
||||
}
|
||||
|
||||
// 视频任务查询参数
|
||||
export interface VideoTaskQueryParams {
|
||||
status?: VideoTaskStatus
|
||||
// 异步任务查询参数
|
||||
export interface AsyncTaskQueryParams {
|
||||
status?: AsyncTaskStatus
|
||||
task_type?: AsyncTaskType
|
||||
user_id?: string
|
||||
model?: string
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
export const videoTasksApi = {
|
||||
export const asyncTasksApi = {
|
||||
/**
|
||||
* 获取视频任务列表
|
||||
* 获取异步任务列表
|
||||
*/
|
||||
async list(params: VideoTaskQueryParams = {}): Promise<VideoTaskListResponse> {
|
||||
async list(params: AsyncTaskQueryParams = {}): Promise<AsyncTaskListResponse> {
|
||||
const searchParams = new URLSearchParams()
|
||||
if (params.status) searchParams.append('status', params.status)
|
||||
if (params.task_type) searchParams.append('task_type', params.task_type)
|
||||
if (params.user_id) searchParams.append('user_id', params.user_id)
|
||||
if (params.model) searchParams.append('model', params.model)
|
||||
if (params.page) searchParams.append('page', params.page.toString())
|
||||
if (params.page_size) searchParams.append('page_size', params.page_size.toString())
|
||||
|
||||
const query = searchParams.toString()
|
||||
// 后端 API 路径保持不变,前端抽象为异步任务
|
||||
const url = query ? `/api/admin/video-tasks?${query}` : '/api/admin/video-tasks'
|
||||
const response = await apiClient.get(url)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取视频任务统计
|
||||
* 获取异步任务统计
|
||||
*/
|
||||
async getStats(): Promise<VideoTaskStatsResponse> {
|
||||
async getStats(): Promise<AsyncTaskStatsResponse> {
|
||||
const response = await apiClient.get('/api/admin/video-tasks/stats')
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取视频任务详情
|
||||
* 获取异步任务详情
|
||||
*/
|
||||
async getDetail(taskId: string): Promise<VideoTaskDetail> {
|
||||
async getDetail(taskId: string): Promise<AsyncTaskDetail> {
|
||||
const response = await apiClient.get(`/api/admin/video-tasks/${taskId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 取消视频任务
|
||||
* 取消异步任务
|
||||
*/
|
||||
async cancel(taskId: string): Promise<{ id: string; status: string; message: string }> {
|
||||
const response = await apiClient.post(`/api/admin/video-tasks/${taskId}/cancel`)
|
||||
@@ -155,4 +164,4 @@ export const videoTasksApi = {
|
||||
},
|
||||
}
|
||||
|
||||
export default videoTasksApi
|
||||
export default asyncTasksApi
|
||||
@@ -340,6 +340,7 @@ export interface ProviderWithEndpointsSummary {
|
||||
website?: string
|
||||
provider_priority: number
|
||||
keep_priority_on_conversion: boolean // 格式转换时是否保持优先级
|
||||
enable_format_conversion: boolean // 是否允许格式转换(提供商级别开关)
|
||||
billing_type?: 'monthly_quota' | 'pay_as_you_go' | 'free_tier'
|
||||
monthly_quota_usd?: number
|
||||
monthly_used_usd?: number
|
||||
|
||||
124
frontend/src/api/gemini-files.ts
Normal file
124
frontend/src/api/gemini-files.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Gemini Files 管理 API
|
||||
*/
|
||||
|
||||
import apiClient from './client'
|
||||
|
||||
export interface FileMappingResponse {
|
||||
id: string
|
||||
file_name: string
|
||||
key_id: string
|
||||
key_name: string | null
|
||||
user_id: string | null
|
||||
username: string | null
|
||||
display_name: string | null
|
||||
mime_type: string | null
|
||||
created_at: string
|
||||
expires_at: string
|
||||
is_expired: boolean
|
||||
}
|
||||
|
||||
export interface FileMappingListResponse {
|
||||
items: FileMappingResponse[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export interface FileMappingStatsResponse {
|
||||
total_mappings: number
|
||||
active_mappings: number
|
||||
expired_mappings: number
|
||||
by_mime_type: Record<string, number>
|
||||
capable_keys_count: number
|
||||
}
|
||||
|
||||
export interface ListMappingsParams {
|
||||
page?: number
|
||||
page_size?: number
|
||||
include_expired?: boolean
|
||||
search?: string
|
||||
}
|
||||
|
||||
export interface CapableKeyResponse {
|
||||
id: string
|
||||
name: string
|
||||
provider_name: string | null
|
||||
}
|
||||
|
||||
export interface UploadResultItem {
|
||||
key_id: string
|
||||
key_name: string | null
|
||||
success: boolean
|
||||
file_name: string | null
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export interface UploadResponse {
|
||||
display_name: string
|
||||
mime_type: string
|
||||
size_bytes: number
|
||||
results: UploadResultItem[]
|
||||
success_count: number
|
||||
fail_count: number
|
||||
}
|
||||
|
||||
export const geminiFilesApi = {
|
||||
/**
|
||||
* 获取文件映射统计
|
||||
*/
|
||||
async getStats(): Promise<FileMappingStatsResponse> {
|
||||
const response = await apiClient.get('/api/admin/gemini-files/stats')
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 列出文件映射
|
||||
*/
|
||||
async listMappings(params?: ListMappingsParams): Promise<FileMappingListResponse> {
|
||||
const response = await apiClient.get('/api/admin/gemini-files/mappings', { params })
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除指定映射
|
||||
*/
|
||||
async deleteMapping(mappingId: string): Promise<{ message: string; file_name: string }> {
|
||||
const response = await apiClient.delete(`/api/admin/gemini-files/mappings/${mappingId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 清理过期映射
|
||||
*/
|
||||
async cleanupExpired(): Promise<{ message: string; deleted_count: number }> {
|
||||
const response = await apiClient.delete('/api/admin/gemini-files/mappings')
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取可用的 Key 列表
|
||||
*/
|
||||
async getCapableKeys(): Promise<CapableKeyResponse[]> {
|
||||
const response = await apiClient.get('/api/admin/gemini-files/capable-keys')
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 上传文件到指定的 Keys
|
||||
*/
|
||||
async uploadFile(file: File, keyIds: string[]): Promise<UploadResponse> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const response = await apiClient.post(
|
||||
`/api/admin/gemini-files/upload?key_ids=${keyIds.join(',')}`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
}
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user