'use client' import { useMemo } from 'react' import { useQuery } from '@tanstack/react-query' import { queryKeys } from '../keys' import { apiFetch } from '@/lib/api-fetch' export type TaskItem = { id: string type: string targetType: string targetId: string episodeId?: string | null status: string progress?: number | null errorCode?: string | null errorMessage?: string | null error?: { code: string message: string retryable: boolean category: string userMessageKey: string details?: Record | null } | null createdAt: string updatedAt: string } const ACTIVE_STATUS = ['queued', 'processing'] as const const SNAPSHOT_STATUS = ['queued', 'processing', 'completed', 'failed'] as const function buildTaskSearch(params: { projectId: string targetType?: string targetId?: string type?: string[] statuses: readonly string[] limit?: number }) { const search = new URLSearchParams() search.set('projectId', params.projectId) if (params.targetType) search.set('targetType', params.targetType) if (params.targetId) search.set('targetId', params.targetId) for (const status of params.statuses) { search.append('status', status) } if (typeof params.limit === 'number') { search.set('limit', String(params.limit)) } for (const taskType of params.type || []) { search.append('type', taskType) } return search } export function useTaskList(params: { projectId?: string | null targetType?: string | null targetId?: string | null type?: string[] statuses?: string[] limit?: number enabled?: boolean }) { const enabled = (params.enabled ?? true) && !!params.projectId const statusKey = (params.statuses || []).slice().sort().join(',') const typeKey = (params.type || []).slice().sort().join(',') const queryKey = [ ...queryKeys.tasks.all(params.projectId || ''), params.targetType || '', params.targetId || '', statusKey, typeKey, params.limit ?? '', ] as const return useQuery({ queryKey, enabled, staleTime: 5000, queryFn: async () => { const search = buildTaskSearch({ projectId: params.projectId!, targetType: params.targetType || undefined, targetId: params.targetId || undefined, type: params.type, statuses: (params.statuses || SNAPSHOT_STATUS), limit: params.limit, }) const res = await apiFetch(`/api/tasks?${search}`) if (!res.ok) throw new Error('Failed to fetch tasks') const data = await res.json() return (data.tasks || []) as TaskItem[] }, }) } export function useActiveTasks(params: { projectId?: string | null targetType?: string | null targetId?: string | null type?: string[] enabled?: boolean }) { const enabled = (params.enabled ?? true) && !!params.projectId const typeKey = (params.type || []).slice().sort().join(',') const queryKey = params.targetType && params.targetId ? [...queryKeys.tasks.target(params.projectId || '', params.targetType, params.targetId), typeKey] as const : [...queryKeys.tasks.all(params.projectId || ''), typeKey] as const return useQuery({ queryKey, enabled, staleTime: 5000, queryFn: async () => { const search = buildTaskSearch({ projectId: params.projectId!, targetType: params.targetType || undefined, targetId: params.targetId || undefined, type: params.type, statuses: ACTIVE_STATUS, }) const res = await apiFetch(`/api/tasks?${search}`) if (!res.ok) throw new Error('Failed to fetch active tasks') const data = await res.json() return (data.tasks || []) as TaskItem[] }, }) } export function useTaskSnapshot(params: { projectId?: string | null targetType?: string | null targetId?: string | null enabled?: boolean type?: string[] }) { const enabled = (params.enabled ?? true) && !!params.projectId && !!params.targetType && !!params.targetId const typeKey = (params.type || []).slice().sort().join(',') return useQuery({ queryKey: queryKeys.tasks.snapshot(params.projectId || '', params.targetType || '', params.targetId || '', typeKey), enabled, staleTime: 5000, queryFn: async () => { const search = buildTaskSearch({ projectId: params.projectId!, targetType: params.targetType || undefined, targetId: params.targetId || undefined, type: params.type, statuses: SNAPSHOT_STATUS, limit: 1, }) const res = await apiFetch(`/api/tasks?${search}`) if (!res.ok) throw new Error('Failed to fetch task snapshot') const data = await res.json() const tasks = (data.tasks || []) as TaskItem[] return tasks[0] || null }, }) } export function useTaskStatus(params: { projectId?: string | null targetType?: string | null targetId?: string | null enabled?: boolean type?: string[] }) { const query = useActiveTasks({ projectId: params.projectId, targetType: params.targetType, targetId: params.targetId, enabled: params.enabled, type: params.type, }) const snapshotQuery = useTaskSnapshot({ projectId: params.projectId, targetType: params.targetType, targetId: params.targetId, enabled: params.enabled, type: params.type, }) const data = useMemo(() => { const tasks = query.data || [] const latest = snapshotQuery.data || tasks[0] || null const lastFailed = latest?.status === 'failed' || latest?.status === 'canceled' ? (latest.error || null) : null return { active: tasks, hasActive: tasks.length > 0, latest, lastFailed, lastTerminal: lastFailed, // Backward compatibility: keep lastError but only represent FAILED. lastError: lastFailed, } }, [query.data, snapshotQuery.data]) return { ...query, isFetching: query.isFetching || snapshotQuery.isFetching, isError: query.isError || snapshotQuery.isError, error: query.error || snapshotQuery.error, data, } }