/** * ExportV2 — useExportTasks hook * * Manages the queue and state of manual export tasks. * Includes starting tasks via electronAPI, tracking progress, and aborting. */ import { useState, useCallback, useRef, useEffect } from 'react' import type { ExportTask, ExportTaskPayload, TaskStatus } from '../types' import { createEmptyProgress } from '../constants' import { useExportTaskStore } from '../../../stores/exportTaskStore' import { buildProgressPayloadSignature } from '../utils/progress' import { resolvePerfStageByPhase, applyProgressToTaskPerformance } from '../utils/performance' import type { ExportProgress } from '../types' export interface ExportTasksResult { tasks: ExportTask[] activeTasks: ExportTask[] completedTasks: ExportTask[] startTask: (payload: ExportTaskPayload) => void cancelTask: (taskId: string) => void clearCompletedTasks: () => void } const generateTaskId = () => `task-${Date.now()}-${Math.random().toString(36).substr(2, 6)}` export function useExportTasks(): ExportTasksResult { const [tasks, setTasks] = useState([]) const tasksRef = useRef([]) tasksRef.current = tasks const { setSessionStatus } = useExportTaskStore() // Track the ongoing tasks to update the global zustand store badge useEffect(() => { const activeTasks = tasksRef.current.filter(t => t.status === 'running' || t.status === 'cancel_requested') const inProgressSessionIds = new Set() activeTasks.forEach(task => { task.payload.sessionIds.forEach(id => inProgressSessionIds.add(id)) }) setSessionStatus({ activeTaskCount: activeTasks.length, inProgressSessionIds: Array.from(inProgressSessionIds) }) }, [tasks, setSessionStatus]) const updateTask = useCallback((taskId: string, updater: (prev: ExportTask) => ExportTask) => { setTasks(prev => prev.map(t => t.id === taskId ? updater(t) : t)) }, []) const startTask = useCallback((payload: ExportTaskPayload) => { const taskId = generateTaskId() const title = payload.sessionIds.length > 1 ? `批量导出 ${payload.sessionIds.length} 个会话` : `导出 ${payload.sessionNames[0] || '会话'}` const newTask: ExportTask = { id: taskId, title, status: 'running', createdAt: Date.now(), startedAt: Date.now(), payload, progress: createEmptyProgress() } setTasks(prev => [newTask, ...prev]) // Kick off via electron API const run = async () => { let progressUnsubscribe: (() => void) | null = null let lastProgressSig = '' try { // Subscribe to progress progressUnsubscribe = window.electronAPI.export.onProgress((progressPayload: ExportProgress) => { if ((progressPayload as any).taskId && (progressPayload as any).taskId !== taskId) return const sig = buildProgressPayloadSignature(progressPayload) if (sig === lastProgressSig) return lastProgressSig = sig updateTask(taskId, (task) => { if (task.status !== 'running') return task const nextProgress = { ...task.progress, ...progressPayload } // The original expected performance from task or undefined. // The signature is applyProgressToTaskPerformance(task: ExportTask, payload: ExportProgress, now: number) const nextPerf = applyProgressToTaskPerformance(task, progressPayload, Date.now()) return { ...task, progress: nextProgress, performance: nextPerf } }) }) const frontendOptions = payload.options as any const electronOptions = { ...frontendOptions } if (frontendOptions?.useAllTime) { electronOptions.dateRange = null } else if (frontendOptions?.dateRange) { electronOptions.dateRange = { start: frontendOptions.dateRange.start.getTime(), end: frontendOptions.dateRange.end.getTime() } } const result = await window.electronAPI.export.exportSessions( payload.sessionIds, payload.outputDir, electronOptions, { taskId } ) updateTask(taskId, (task) => ({ ...task, status: result?.success ? 'success' : 'error', finishedAt: Date.now(), error: result?.error || undefined, sessionOutputPaths: result?.sessionOutputPaths })) } catch (err: any) { console.error('[useExportTasks] Task failed:', err) updateTask(taskId, (task) => ({ ...task, status: 'error', finishedAt: Date.now(), error: err.message || '未知错误' })) } finally { if (progressUnsubscribe) { progressUnsubscribe() } } } void run() }, [updateTask]) const cancelTask = useCallback((taskId: string) => { updateTask(taskId, (task) => { if (task.status === 'running') { window.electronAPI.export.cancelTask(taskId) return { ...task, status: 'cancel_requested' } } return task }) }, [updateTask]) const clearCompletedTasks = useCallback(() => { setTasks(prev => prev.filter(t => t.status === 'running' || t.status === 'cancel_requested')) }, []) const activeTasks = tasks.filter(t => t.status === 'running' || t.status === 'cancel_requested') const completedTasks = tasks.filter(t => t.status === 'success' || t.status === 'error') return { tasks, activeTasks, completedTasks, startTask, cancelTask, clearCompletedTasks } }