XHS / frontend /src /lib /tasks.ts
Trae Bot
Fix .gitignore excluding frontend/src/lib and add missing TS modules
98277cb
import axios from 'axios'
import type { ApiError } from './api'
import { apiGetWrapped, apiPostWrapped, http, unwrapApiResponse } from './api'
export type TaskStatus =
| 'queued'
| 'running'
| 'retrying'
| 'fallback_running'
| 'waiting_rpa'
| 'rpa_running'
| 'rpa_imported'
| 'rpa_failed'
| 'risk_paused'
| 'succeeded'
| 'failed'
export type TaskError = {
kind?: string
message?: string
[key: string]: unknown
}
export type TaskRecord = {
id: string
status: TaskStatus
task_type: string
target: string
payload: Record<string, unknown>
engine?: string | null
callback?: unknown
created: number
started?: number | null
finished?: number | null
retry_count: number
error?: TaskError | null
}
export type TaskListResponse = {
tasks: TaskRecord[]
total: number
limit: number
offset: number
}
export type TaskStatusResponse = {
task: TaskRecord
}
export type TaskCreateRequest = {
task_type: string
target?: string | null
engine?: string | null
payload?: Record<string, unknown>
}
export type TaskCreateResponse = {
task: TaskRecord
}
export type TaskResultResponse = {
task_id: string
status: TaskStatus
raw: unknown | null
normalized: unknown | null
meta: Record<string, unknown>
}
export type ListTasksParams = {
limit: number
offset: number
status?: string[]
task_type?: string[]
engine?: string[]
error_kind?: string[]
sort?: string
}
function normalizePath(path: string) {
return path.replace(/^\/+/, '')
}
function toApiError(error: unknown): ApiError {
if (axios.isAxiosError(error)) {
if (!error.response) {
return { status: 0, message: error.message }
}
const status = error.response?.status ?? 0
const body = error.response?.data
const wrapped = unwrapApiResponse<unknown>(body)
const message =
wrapped.ok || wrapped.error === '响应缺少 code/msg 字段'
? error.message
: wrapped.error
return { status, message, data: body }
}
return {
status: 0,
message: error instanceof Error ? error.message : String(error),
}
}
function join(values?: string[]) {
const parts = (values || []).map((v) => String(v).trim()).filter((v) => v !== '')
return parts.length ? parts.join(',') : undefined
}
export async function listTasks(params: ListTasksParams) {
const search = new URLSearchParams()
search.set('limit', String(params.limit))
search.set('offset', String(params.offset))
const status = join(params.status)
const taskType = join(params.task_type)
const engine = join(params.engine)
const errorKind = join(params.error_kind)
const sort = String(params.sort || '').trim()
if (status) search.set('status', status)
if (taskType) search.set('task_type', taskType)
if (engine) search.set('engine', engine)
if (errorKind) search.set('error_kind', errorKind)
if (sort) search.set('sort', sort)
return apiGetWrapped<TaskListResponse>(`tasks?${search.toString()}`)
}
export async function createTask(payload: TaskCreateRequest) {
const res = await apiPostWrapped<TaskCreateResponse, TaskCreateRequest>('tasks', payload)
return res.task
}
export async function retryTask(taskId: string) {
const res = await apiPostWrapped<TaskStatusResponse, {}>(`tasks/${encodeURIComponent(taskId)}/retry`, {})
return res.task
}
export async function cancelTask(taskId: string) {
const res = await apiPostWrapped<TaskStatusResponse, {}>(`tasks/${encodeURIComponent(taskId)}/cancel`, {})
return res.task
}
export async function markTaskRpa(taskId: string) {
const res = await apiPostWrapped<TaskStatusResponse, {}>(`tasks/${encodeURIComponent(taskId)}/mark-rpa`, {})
return res.task
}
export async function getTask(taskId: string) {
const res = await apiGetWrapped<TaskStatusResponse>(`tasks/${encodeURIComponent(taskId)}`)
return res.task
}
export async function getTaskResult(taskId: string, signal?: AbortSignal) {
try {
const res = await http.get<unknown>(normalizePath(`tasks/${encodeURIComponent(taskId)}/result`), {
validateStatus: () => true,
signal,
})
if (res.status === 409) return { ready: false as const, body: res.data }
if (res.status !== 200) {
const wrapped = unwrapApiResponse<unknown>(res.data)
const message =
wrapped.ok || wrapped.error === '响应缺少 code/msg 字段'
? `HTTP ${res.status}`
: wrapped.error
const apiError: ApiError = { status: res.status, message, data: res.data }
throw apiError
}
const wrapped = unwrapApiResponse<TaskResultResponse>(res.data)
if (!wrapped.ok) {
const apiError: ApiError = { status: res.status, message: wrapped.error, data: res.data }
throw apiError
}
return { ready: true as const, data: wrapped.data ?? null }
} catch (e) {
if (axios.isAxiosError(e) && e.code === 'ERR_CANCELED') {
throw new DOMException('Aborted', 'AbortError')
}
if (
!!e &&
typeof e === 'object' &&
'status' in e &&
typeof (e as { status: unknown }).status === 'number' &&
'message' in e &&
typeof (e as { message: unknown }).message === 'string'
) {
throw e as ApiError
}
throw toApiError(e)
}
}