import { CheckCircle2, Clock3, ListChecks, Loader2, XCircle, } from 'lucide-react'; import { type WheelEvent as ReactWheelEvent, type UIEvent as ReactUIEvent, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration'; import { listExternalGenerationTasks } from '../../services/external-generation'; import { EditorIconButton } from './ImageCanvasEditorPrimitives'; import type { CanvasTaskStatus } from './ImageCanvasEditorTypes'; import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; const COMPLETED_TASK_LIST_LIMIT = 20; const COMPLETED_TASK_LIST_MAX_LIMIT = 100; const ACTIVE_TASK_LIST_LIMIT = 100; const ACTIVE_TASK_POLL_INTERVAL_MS = 4000; type TaskSidebarTab = 'active' | 'completed'; type TaskSidebarItem = { id: string; title: string; detail: string; progressDetail?: string; status: CanvasTaskStatus; startedAt?: number; finishedAt?: number; elapsedMs?: number; errorMessage?: string; onClick?: () => void; sortTime: number; }; type ImageCanvasTaskSidebarViewProps = { refreshKey?: number; open: boolean; onToggleOpen: () => void; onFocusExternalTask: (task: ExternalGenerationTaskRecord) => void; onExternalTasksCompleted?: (tasks: ExternalGenerationTaskRecord[]) => void; }; function parseTaskTimeMs(value?: string | null) { if (!value) { return undefined; } const timestamp = Date.parse(value); return Number.isFinite(timestamp) ? timestamp : undefined; } function formatElapsedMs(elapsedMs?: number) { if (typeof elapsedMs !== 'number' || !Number.isFinite(elapsedMs)) { return null; } const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1000)); if (totalSeconds >= 60) { const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return seconds > 0 ? `${minutes}分${seconds}秒` : `${minutes}分`; } return `${Math.max(1, totalSeconds)}秒`; } function formatTaskTimeMs(timestampMs?: number) { if (typeof timestampMs !== 'number' || !Number.isFinite(timestampMs)) { return null; } const date = new Date(timestampMs); const pad = (value: number) => String(value).padStart(2, '0'); return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad( date.getHours(), )}:${pad(date.getMinutes())}`; } function normalizeTaskPrompt(value?: string | null) { const prompt = value?.trim(); return prompt || null; } function externalTaskDetail( task: ExternalGenerationTaskRecord, status: CanvasTaskStatus, ) { if (status === 'failed') { return task.error?.trim() || task.phaseDetail || task.updatedAt; } if (status === 'done') { return ( normalizeTaskPrompt(task.requestPrompt) || task.requestLabel.trim() || task.jobKind ); } const phaseLabel = task.phaseLabel.trim(); const requestLabel = task.requestLabel.trim(); const prompt = normalizeTaskPrompt(task.requestPrompt); return ( (prompt && prompt !== requestLabel ? prompt : null) || (phaseLabel && phaseLabel !== requestLabel ? phaseLabel : null) || '' ); } function externalTaskProgressDetail( task: ExternalGenerationTaskRecord, status: CanvasTaskStatus, ) { if (!isActiveTaskStatus(status)) { return null; } const values = [task.phaseDetail.trim()].filter((value): value is string => Boolean(value), ); return values.length > 0 ? values.join(' · ') : null; } function stopTaskSidebarWheel(event: ReactWheelEvent) { event.stopPropagation(); } function canvasTaskStatusFromExternalTask( status: ExternalGenerationTaskRecord['status'], ): CanvasTaskStatus { if (status === 'running') { return 'running'; } if (status === 'queued') { return 'pending'; } if (status === 'failed') { return 'failed'; } return 'done'; } function statusLabel(status: CanvasTaskStatus) { if (status === 'pending') { return '排队中'; } if (status === 'running') { return '进行中'; } if (status === 'failed') { return '失败'; } return '完成'; } function statusIcon(status: CanvasTaskStatus) { if (status === 'pending') { return