"use client"; import { ChevronDownIcon, ChevronUpIcon, CircleCheckIcon, CircleStopIcon, Clock3Icon, ListChecksIcon, LoaderCircleIcon, MessageCircleQuestionIcon, TriangleAlertIcon, } from "lucide-react"; import { useState } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, } from "@/components/ui/sheet"; import { isActiveBackgroundTask, type BackgroundTask, type BackgroundTaskDetail, type BackgroundTaskStatus, useBackgroundTask, useBackgroundTasks, useCancelBackgroundTask, } from "@/core/background-tasks"; import { useMcpTasksEnabled } from "@/core/features"; import { useI18n } from "@/core/i18n/hooks"; import { formatTimeAgo } from "@/core/utils/datetime"; import { cn } from "@/lib/utils"; export function ThreadBackgroundTasks({ threadId }: { threadId: string }) { const { t } = useI18n(); const { enabled: mcpTasksEnabled } = useMcpTasksEnabled(); const tasksQuery = useBackgroundTasks(threadId, { enabled: mcpTasksEnabled, }); const cancelTask = useCancelBackgroundTask(threadId); const tasks = tasksQuery.data ?? []; const activeTasks = tasks.filter(isActiveBackgroundTask); const recentTasks = tasks.filter((task) => !isActiveBackgroundTask(task)); if (!mcpTasksEnabled) { return null; } return ( {t.backgroundTasks.title} {t.backgroundTasks.description}
{tasksQuery.isLoading ? (
{t.common.loading}
) : tasksQuery.isError ? (

{t.backgroundTasks.loadFailed}

{tasksQuery.error.message}

) : tasks.length === 0 ? (

{t.backgroundTasks.empty}

{t.backgroundTasks.emptyHint}

) : (
{activeTasks.length > 0 && ( cancelTask.mutate(taskId)} /> )} {recentTasks.length > 0 && ( )}
)}
); } function TaskSection({ threadId, title, tasks, cancellingTaskId, onCancel, }: { threadId: string; title: string; tasks: BackgroundTask[]; cancellingTaskId?: string; onCancel?: (taskId: string) => void; }) { return (

{title}

{tasks.map((task) => ( ))}
); } function BackgroundTaskCard({ threadId, task, isCancelling, onCancel, }: { threadId: string; task: BackgroundTask; isCancelling: boolean; onCancel?: (taskId: string) => void; }) { const { t } = useI18n(); const [detailsOpen, setDetailsOpen] = useState(false); const detailsQuery = useBackgroundTask(threadId, task.task_id, { enabled: detailsOpen, }); const active = isActiveBackgroundTask(task); const cancelling = active && (task.cancel_requested || isCancelling); const status = taskStatusPresentation(task.status, t.backgroundTasks.status); const canShowDetails = task.cancel_requested || (task.status !== "submitted" && (task.status !== "working" || task.tracking_degraded)); return (

{task.task_name}

{t.backgroundTasks.created(formatTimeAgo(task.created_at))} {t.backgroundTasks.updated(formatTimeAgo(task.updated_at))}
{cancelling ? t.backgroundTasks.cancelling : status.label}
{task.tracking_degraded && (

{t.backgroundTasks.trackingDegraded}

)} {task.error && (

{task.error}

)} {(canShowDetails || (active && onCancel)) && (
{canShowDetails ? ( ) : ( )} {active && onCancel && ( )}
)} {detailsOpen && ( void detailsQuery.refetch()} /> )}
); } function BackgroundTaskDetails({ task, isLoading, error, onRetry, }: { task: BackgroundTaskDetail | undefined; isLoading: boolean; error: Error | null; onRetry: () => void; }) { const { t } = useI18n(); if (isLoading) { return (
{t.common.loading}
); } if (error) { return (

{t.backgroundTasks.detailsFailed}

{error.message}

); } if (!task) return null; return (
{task.last_cancel_error && (

{t.backgroundTasks.cancellationRetrying( task.cancel_attempt_count, )}

{task.last_cancel_error}

)} {task.notification_error && (

{task.notification_status === "dead_letter" ? t.backgroundTasks.notificationStopped : t.backgroundTasks.notificationRetrying( task.notification_attempt_count, )}

{task.notification_error}

)} {task.input_required != null && (

{t.backgroundTasks.inputUnavailable}

)}
); } function TaskDetailField({ label, value }: { label: string; value: unknown }) { const formatted = formatTaskDetailValue(value); if (formatted === null) return null; return (

{label}

        {formatted}
      
); } function formatTaskDetailValue(value: unknown): string | null { if (value === null || value === undefined) return null; if (typeof value === "string") return value; if (typeof value === "number" || typeof value === "boolean") { return String(value); } try { return JSON.stringify(value, null, 2) ?? null; } catch { return null; } } type StatusTranslations = { submitted: string; working: string; inputRequired: string; completed: string; failed: string; cancelled: string; }; function taskStatusPresentation( status: BackgroundTaskStatus, labels: StatusTranslations, ) { switch (status) { case "submitted": return { Icon: Clock3Icon, label: labels.submitted, className: "text-blue-700 dark:text-blue-300", spinning: false, }; case "working": return { Icon: LoaderCircleIcon, label: labels.working, className: "text-blue-700 dark:text-blue-300", spinning: true, }; case "input_required": return { Icon: MessageCircleQuestionIcon, label: labels.inputRequired, className: "text-amber-700 dark:text-amber-300", spinning: false, }; case "completed": return { Icon: CircleCheckIcon, label: labels.completed, className: "text-emerald-700 dark:text-emerald-300", spinning: false, }; case "failed": return { Icon: TriangleAlertIcon, label: labels.failed, className: "text-destructive", spinning: false, }; case "cancelled": return { Icon: CircleStopIcon, label: labels.cancelled, className: "text-muted-foreground", spinning: false, }; } }