Files
weflow-new/src/pages/Export/hooks/useExportTasks.ts
T
2026-06-30 00:18:31 +08:00

171 lines
5.6 KiB
TypeScript

/**
* 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<ExportTask[]>([])
const tasksRef = useRef<ExportTask[]>([])
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<string>()
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
}
}