import { CheckCircle2, Clock3, ListChecks, Loader2, XCircle, } from 'lucide-react'; import { type WheelEvent as ReactWheelEvent, 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 { CanvasTaskListItem, CanvasTaskStatus, } from './ImageCanvasEditorTypes'; const COMPLETED_TASK_LIST_LIMIT = 20; 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; status: CanvasTaskStatus; startedAt?: number; finishedAt?: number; elapsedMs?: number; errorMessage?: string; onClick?: () => void; sortTime: number; }; type ImageCanvasTaskSidebarViewProps = { projectId?: string | null; localTasks?: CanvasTaskListItem[]; open: boolean; onToggleOpen: () => void; onFocusExternalTask: (task: ExternalGenerationTaskRecord) => void; onFocusLocalTask?: (task: CanvasTaskListItem) => 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 phaseDetail = task.phaseDetail.trim(); const progressLabel = Number.isFinite(task.progress) && task.progress > 0 ? `总进度 ${Math.min(100, Math.max(0, Math.round(task.progress)))}%` : null; return [phaseLabel, phaseDetail, progressLabel] .filter((value): value is string => Boolean(value)) .join(' · '); } 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