mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
Fix Codex image progress heartbeat merge regressions
This commit is contained in:
@@ -236,6 +236,28 @@ export interface EmailTemplateResetResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export interface CleanupRunRecord {
|
||||
id: string
|
||||
kind: string
|
||||
trigger: string
|
||||
status: 'processing' | 'completed' | 'failed'
|
||||
message: string
|
||||
started_at_unix_secs: number
|
||||
completed_at_unix_secs: number | null
|
||||
duration_ms: number | null
|
||||
summary: Record<string, unknown>
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export interface CleanupRunListResponse {
|
||||
items: CleanupRunRecord[]
|
||||
}
|
||||
|
||||
export interface CleanupTaskResponse {
|
||||
message: string
|
||||
task: CleanupRunRecord
|
||||
}
|
||||
|
||||
// 检查更新响应
|
||||
export interface CheckUpdateResponse {
|
||||
current_version: string
|
||||
@@ -1051,12 +1073,29 @@ export const adminApi = {
|
||||
},
|
||||
|
||||
// 数据清空
|
||||
purgeConfig: () => purge<{ message: string; deleted: Record<string, number> }>('config'),
|
||||
purgeUsers: () => purge<{ message: string; deleted: Record<string, number> }>('users'),
|
||||
purgeUsage: () => purge<{ message: string; deleted: Record<string, number> }>('usage'),
|
||||
purgeAuditLogs: () => purge<{ message: string; deleted: Record<string, number> }>('audit-logs'),
|
||||
purgeRequestBodies: () => purge<{ message: string; cleaned: Record<string, number> }>('request-bodies'),
|
||||
purgeStats: () => purge<{ message: string }>('stats'),
|
||||
purgeConfig: () => purge<CleanupTaskResponse>('config'),
|
||||
purgeUsers: () => purge<CleanupTaskResponse>('users'),
|
||||
async purgeUsage(): Promise<CleanupTaskResponse> {
|
||||
const response = await apiClient.post<CleanupTaskResponse>('/api/admin/system/purge/usage')
|
||||
return response.data
|
||||
},
|
||||
async purgeAuditLogs(): Promise<CleanupTaskResponse> {
|
||||
const response = await apiClient.post<CleanupTaskResponse>('/api/admin/system/purge/audit-logs')
|
||||
return response.data
|
||||
},
|
||||
purgeRequestBodies: () => purge<CleanupTaskResponse>('request-bodies'),
|
||||
async purgeRequestBodiesAsync(): Promise<CleanupTaskResponse> {
|
||||
const response = await apiClient.post<CleanupTaskResponse>('/api/admin/system/purge/request-bodies/task')
|
||||
return response.data
|
||||
},
|
||||
async purgeStats(): Promise<CleanupTaskResponse> {
|
||||
const response = await apiClient.post<CleanupTaskResponse>('/api/admin/system/purge/stats')
|
||||
return response.data
|
||||
},
|
||||
async getCleanupRuns(): Promise<CleanupRunListResponse> {
|
||||
const response = await apiClient.get<CleanupRunListResponse>('/api/admin/system/cleanup/runs')
|
||||
return response.data
|
||||
},
|
||||
|
||||
async getTimeSeries(params?: {
|
||||
start_date?: string
|
||||
|
||||
@@ -1,39 +1,78 @@
|
||||
import apiClient from './client'
|
||||
|
||||
// 异步任务状态
|
||||
export type AsyncTaskStatus = 'pending' | 'submitted' | 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled'
|
||||
export type AsyncTaskStatus =
|
||||
| 'queued'
|
||||
| 'running'
|
||||
| 'retrying'
|
||||
| 'succeeded'
|
||||
| 'failed'
|
||||
| 'cancelled'
|
||||
| 'skipped'
|
||||
| 'pending'
|
||||
| 'submitted'
|
||||
| 'processing'
|
||||
| 'completed'
|
||||
|
||||
// 异步任务类型
|
||||
export type AsyncTaskType = 'video'
|
||||
export type AsyncTaskKind = 'scheduled' | 'daemon' | 'on_demand' | 'fire_and_forget'
|
||||
export type AsyncTaskType = AsyncTaskKind | 'video'
|
||||
|
||||
// 异步任务列表项
|
||||
export interface AsyncTaskItem {
|
||||
id: string
|
||||
external_task_id: string
|
||||
user_id: string
|
||||
username: string
|
||||
task_type: AsyncTaskType
|
||||
model: string
|
||||
prompt: string
|
||||
status: AsyncTaskStatus
|
||||
progress_percent: number
|
||||
progress_message: string | null
|
||||
provider_id: string
|
||||
provider_name: string
|
||||
duration_seconds: number
|
||||
resolution: string
|
||||
aspect_ratio: string
|
||||
video_url: string | null
|
||||
error_code: string | null
|
||||
error_message: string | null
|
||||
poll_count: number
|
||||
max_poll_count: number
|
||||
created_at: string
|
||||
completed_at: string | null
|
||||
submitted_at: string | null
|
||||
export interface AsyncTaskDefinition {
|
||||
task_key: string
|
||||
kind: AsyncTaskKind
|
||||
trigger: string
|
||||
max_attempts: number
|
||||
singleton: boolean
|
||||
persist_history: boolean
|
||||
}
|
||||
|
||||
export interface AsyncTaskItem {
|
||||
id: string
|
||||
task_key?: string
|
||||
kind?: AsyncTaskKind
|
||||
trigger?: string
|
||||
task_type?: AsyncTaskType
|
||||
external_task_id?: string
|
||||
user_id?: string
|
||||
username?: string
|
||||
model?: string
|
||||
prompt?: string
|
||||
status: AsyncTaskStatus
|
||||
attempt?: number
|
||||
max_attempts?: number
|
||||
owner_instance?: string | null
|
||||
progress_percent: number
|
||||
progress_message: string | null
|
||||
payload?: unknown
|
||||
result?: unknown
|
||||
error_message: string | null
|
||||
cancel_requested?: boolean
|
||||
created_by?: string | null
|
||||
provider_id?: string
|
||||
provider_name?: string
|
||||
duration_seconds?: number
|
||||
resolution?: string
|
||||
aspect_ratio?: string
|
||||
video_url?: string | null
|
||||
error_code?: string | null
|
||||
poll_count?: number
|
||||
max_poll_count?: number
|
||||
created_at: string
|
||||
started_at?: string | null
|
||||
updated_at?: string | null
|
||||
finished_at?: string | null
|
||||
completed_at?: string | null
|
||||
submitted_at?: string | null
|
||||
}
|
||||
|
||||
export interface AsyncTaskEvent {
|
||||
id: string
|
||||
run_id: string
|
||||
event_type: string
|
||||
message: string
|
||||
payload: unknown
|
||||
created_at: string
|
||||
}
|
||||
|
||||
// 候选 Key 信息
|
||||
export interface CandidateKeyInfo {
|
||||
index: number
|
||||
provider_id: string
|
||||
@@ -47,7 +86,6 @@ export interface CandidateKeyInfo {
|
||||
selected?: boolean
|
||||
}
|
||||
|
||||
// 请求元数据
|
||||
export interface AsyncTaskRequestMetadata {
|
||||
candidate_keys: CandidateKeyInfo[]
|
||||
selected_key_id: string
|
||||
@@ -56,111 +94,118 @@ export interface AsyncTaskRequestMetadata {
|
||||
user_agent: string
|
||||
request_id: string
|
||||
request_headers?: Record<string, string>
|
||||
poll_raw_response?: unknown // 轮询完成时的原始响应
|
||||
billing_snapshot?: unknown // 计费快照
|
||||
poll_raw_response?: unknown
|
||||
billing_snapshot?: unknown
|
||||
}
|
||||
|
||||
// 异步任务详情
|
||||
export interface AsyncTaskDetail extends AsyncTaskItem {
|
||||
api_key_id: string
|
||||
endpoint_id: string
|
||||
key_id: string
|
||||
client_api_format: string
|
||||
provider_api_format: string
|
||||
format_converted: boolean
|
||||
original_request_body: unknown
|
||||
converted_request_body: unknown
|
||||
size: string | null
|
||||
video_urls: string[] | null
|
||||
thumbnail_url: string | null
|
||||
video_size_bytes: number | null
|
||||
video_duration_seconds: number | null // 实际视频时长(秒)
|
||||
video_expires_at: string | null
|
||||
stored_video_path: string | null
|
||||
storage_provider: string | null
|
||||
retry_count: number
|
||||
max_retries: number
|
||||
poll_interval_seconds: number
|
||||
next_poll_at: string | null
|
||||
updated_at: string | null
|
||||
endpoint: {
|
||||
api_key_id?: string
|
||||
endpoint_id?: string
|
||||
key_id?: string
|
||||
client_api_format?: string
|
||||
provider_api_format?: string
|
||||
format_converted?: boolean
|
||||
original_request_body?: unknown
|
||||
converted_request_body?: unknown
|
||||
size?: string | null
|
||||
video_urls?: string[] | null
|
||||
thumbnail_url?: string | null
|
||||
video_size_bytes?: number | null
|
||||
video_duration_seconds?: number | null
|
||||
video_expires_at?: string | null
|
||||
stored_video_path?: string | null
|
||||
storage_provider?: string | null
|
||||
retry_count?: number
|
||||
max_retries?: number
|
||||
poll_interval_seconds?: number
|
||||
next_poll_at?: string | null
|
||||
endpoint?: {
|
||||
id: string
|
||||
base_url: string
|
||||
api_format: string
|
||||
} | null
|
||||
request_metadata: AsyncTaskRequestMetadata | null
|
||||
request_metadata?: AsyncTaskRequestMetadata | null
|
||||
}
|
||||
|
||||
// 异步任务列表响应
|
||||
export interface AsyncTaskListResponse {
|
||||
items: AsyncTaskItem[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
pages: number
|
||||
definitions?: AsyncTaskDefinition[]
|
||||
}
|
||||
|
||||
// 异步任务统计响应
|
||||
export interface AsyncTaskStatsResponse {
|
||||
total: number
|
||||
by_status: Record<AsyncTaskStatus, number>
|
||||
by_model: Record<string, number>
|
||||
today_count: number
|
||||
active_users?: number // 仅管理员
|
||||
processing_count?: number // 仅管理员
|
||||
running_count?: number
|
||||
registered_tasks?: number
|
||||
by_status: Partial<Record<AsyncTaskStatus, number>>
|
||||
by_kind?: Partial<Record<AsyncTaskKind, number>>
|
||||
by_model?: Record<string, number>
|
||||
today_count?: number
|
||||
active_users?: number
|
||||
processing_count?: number
|
||||
}
|
||||
|
||||
// 异步任务查询参数
|
||||
export interface AsyncTaskQueryParams {
|
||||
status?: AsyncTaskStatus
|
||||
kind?: AsyncTaskKind
|
||||
task_type?: AsyncTaskType
|
||||
task_key?: string
|
||||
trigger?: string
|
||||
user_id?: string
|
||||
model?: string
|
||||
page?: number
|
||||
page_size?: number
|
||||
}
|
||||
|
||||
function normalizeStatus(status: AsyncTaskStatus): AsyncTaskStatus {
|
||||
if (status === 'processing') return 'running'
|
||||
if (status === 'submitted' || status === 'pending') return 'queued'
|
||||
if (status === 'completed') return 'succeeded'
|
||||
return status
|
||||
}
|
||||
|
||||
export const asyncTasksApi = {
|
||||
/**
|
||||
* 获取异步任务列表
|
||||
*/
|
||||
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.status) searchParams.append('status', normalizeStatus(params.status))
|
||||
if (params.kind) searchParams.append('kind', params.kind)
|
||||
if (params.task_type && params.task_type !== 'video') searchParams.append('kind', params.task_type)
|
||||
if (params.task_key) searchParams.append('task_key', params.task_key)
|
||||
if (params.trigger) searchParams.append('trigger', params.trigger)
|
||||
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 url = query ? `/api/admin/tasks?${query}` : '/api/admin/tasks'
|
||||
const response = await apiClient.get(url)
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取异步任务统计
|
||||
*/
|
||||
async getStats(): Promise<AsyncTaskStatsResponse> {
|
||||
const response = await apiClient.get('/api/admin/video-tasks/stats')
|
||||
const response = await apiClient.get('/api/admin/tasks/stats')
|
||||
return response.data
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取异步任务详情
|
||||
*/
|
||||
async getDetail(taskId: string): Promise<AsyncTaskDetail> {
|
||||
const response = await apiClient.get(`/api/admin/video-tasks/${taskId}`)
|
||||
const response = await apiClient.get(`/api/admin/tasks/${taskId}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async getEvents(taskId: string): Promise<{ items: AsyncTaskEvent[] }> {
|
||||
const response = await apiClient.get(`/api/admin/tasks/${taskId}/events`)
|
||||
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`)
|
||||
const response = await apiClient.post(`/api/admin/tasks/${taskId}/cancel`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async trigger(taskKey: string, payload: Record<string, unknown> = {}): Promise<{ run_id: string; status: string }> {
|
||||
const response = await apiClient.post(`/api/admin/tasks/${taskKey}/trigger`, payload)
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
|
||||
@@ -171,6 +171,21 @@ export interface ApiKey {
|
||||
force_capabilities?: Record<string, boolean> | null // 强制能力配置
|
||||
}
|
||||
|
||||
export type InstallTargetCli = 'claude_code' | 'codex_cli' | 'gemini_cli'
|
||||
export type InstallTargetSystem = 'macos' | 'linux' | 'windows' | 'auto'
|
||||
|
||||
export interface ApiKeyInstallSession {
|
||||
install_code: string
|
||||
expires_at_unix_secs: number
|
||||
expires_in_seconds: number
|
||||
target_cli: InstallTargetCli
|
||||
target_cli_label: string
|
||||
target_system: InstallTargetSystem
|
||||
target_system_label: string
|
||||
unix_command: string
|
||||
powershell_command: string
|
||||
}
|
||||
|
||||
// 不再需要 ProviderBinding 接口
|
||||
|
||||
export interface ChangePasswordRequest {
|
||||
@@ -270,6 +285,17 @@ export const meApi = {
|
||||
return response.data
|
||||
},
|
||||
|
||||
async createApiKeyInstallSession(
|
||||
keyId: string,
|
||||
data: { target_cli: InstallTargetCli; target_system: InstallTargetSystem }
|
||||
): Promise<ApiKeyInstallSession> {
|
||||
const response = await apiClient.post<ApiKeyInstallSession>(
|
||||
`/api/users/me/api-keys/${keyId}/install-sessions`,
|
||||
data
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 使用统计
|
||||
async getUsage(params?: {
|
||||
start_date?: string
|
||||
|
||||
@@ -22,6 +22,15 @@ export interface ImageProgress {
|
||||
downstream_heartbeat_interval_ms?: number | null
|
||||
}
|
||||
|
||||
export interface CandidateResponseBoundary {
|
||||
source?: string
|
||||
status_code?: number | null
|
||||
headers?: Record<string, unknown> | null
|
||||
body?: unknown
|
||||
body_ref?: string | null
|
||||
body_state?: string | null
|
||||
}
|
||||
|
||||
export interface CandidateRecord {
|
||||
id: string
|
||||
request_id: string
|
||||
@@ -60,7 +69,10 @@ export interface CandidateRecord {
|
||||
concurrent_requests?: number
|
||||
ranking?: CandidateRankingMetadata | null
|
||||
image_progress?: ImageProgress | null
|
||||
extra_data?: Record<string, unknown>
|
||||
extra_data?: Record<string, unknown> & {
|
||||
upstream_response?: CandidateResponseBoundary
|
||||
image_progress?: ImageProgress | null
|
||||
}
|
||||
created_at: string
|
||||
started_at?: string
|
||||
finished_at?: string
|
||||
@@ -68,6 +80,9 @@ export interface CandidateRecord {
|
||||
|
||||
export interface RequestTrace {
|
||||
request_id: string
|
||||
request_path?: string
|
||||
request_query_string?: string
|
||||
request_path_and_query?: string
|
||||
total_candidates: number
|
||||
final_status: 'success' | 'failed' | 'streaming' | 'pending' | 'cancelled'
|
||||
total_latency_ms: number
|
||||
|
||||
Reference in New Issue
Block a user