This commit is contained in:
1 parent
aac030912b
commit
d733c21145
4 files changed
+137
-46
No files matched your search
@@ -374,7 +374,10 @@ function ExportPage() {
|
||||
)}
|
||||
|
||||
{isAutomationModalOpen && (
|
||||
<AutomationModal onClose={() => setIsAutomationModalOpen(false)} />
|
||||
<AutomationModal
|
||||
onClose={() => setIsAutomationModalOpen(false)}
|
||||
onRunNow={enqueueAutomationTask}
|
||||
/>
|
||||
)}
|
||||
|
||||
{draftAutomationPayload && (
|
||||
|
||||
@@ -9,20 +9,38 @@ import './Automation.scss'
|
||||
|
||||
interface AutomationModalProps {
|
||||
onClose: () => void
|
||||
onRunNow: (task: ExportAutomationTask) => Promise<{ queued: boolean; reason?: string }>
|
||||
}
|
||||
|
||||
export const AutomationModal: React.FC<AutomationModalProps> = ({ onClose }) => {
|
||||
export const AutomationModal: React.FC<AutomationModalProps> = ({ onClose, onRunNow }) => {
|
||||
const { tasks, updateTask, deleteTask } = useAutomationStore()
|
||||
const [editingTask, setEditingTask] = useState<ExportAutomationTask | null>(null)
|
||||
const [triggeringTaskIds, setTriggeringTaskIds] = useState<Set<string>>(new Set())
|
||||
|
||||
const handleToggleEnable = (task: ExportAutomationTask, enabled: boolean) => {
|
||||
void updateTask(task.id, prev => ({ ...prev, enabled, updatedAt: Date.now() }))
|
||||
}
|
||||
|
||||
const handleRunNow = (_task: ExportAutomationTask) => {
|
||||
// Manual trigger is handled by the automation runner's evaluateSchedules
|
||||
// For now, just show a hint
|
||||
alert('手动触发功能需要自动化运行器的支持,任务将在下次调度周期中执行。')
|
||||
const handleRunNow = async (task: ExportAutomationTask) => {
|
||||
if (triggeringTaskIds.has(task.id)) return
|
||||
setTriggeringTaskIds(prev => new Set(prev).add(task.id))
|
||||
|
||||
try {
|
||||
const result = await onRunNow(task)
|
||||
if (result.queued) {
|
||||
alert('已手动触发自动化导出,任务已加入导出队列。')
|
||||
} else {
|
||||
alert(result.reason || '手动触发失败')
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : '手动触发失败')
|
||||
} finally {
|
||||
setTriggeringTaskIds(prev => {
|
||||
const next = new Set(prev)
|
||||
next.delete(task.id)
|
||||
return next
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = (task: ExportAutomationTask) => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { ExportAutomationTask } from '../../../types/exportAutomation'
|
||||
import type { ExportTaskPayload } from '../types'
|
||||
import {
|
||||
resolveAutomationDueScheduleKey,
|
||||
resolveAutomationDateRangeSelection,
|
||||
} from '../utils/automation'
|
||||
|
||||
// ─── Core Logic Helpers ─────────────────────────────────────
|
||||
@@ -48,6 +49,30 @@ const resolveAutomationHasNewMessages = async (task: ExportAutomationTask): Prom
|
||||
}
|
||||
}
|
||||
|
||||
const buildAutomationExportOptions = (task: ExportAutomationTask): any => {
|
||||
const dateRangeSelection = resolveAutomationDateRangeSelection(task.template.dateRangeConfig)
|
||||
return {
|
||||
...task.template.optionTemplate,
|
||||
exportPathStyle: task.template.optionTemplate?.exportPathStyle || 'auto',
|
||||
useAllTime: dateRangeSelection.useAllTime,
|
||||
dateRange: dateRangeSelection.useAllTime ? null : dateRangeSelection.dateRange
|
||||
}
|
||||
}
|
||||
|
||||
const resolveAutomationOutputDir = async (task: ExportAutomationTask): Promise<string> => {
|
||||
const taskOutputDir = String(task.outputDir || '').trim()
|
||||
if (taskOutputDir) return taskOutputDir
|
||||
|
||||
const configuredOutputDir = String(await configService.getExportPath() || '').trim()
|
||||
if (configuredOutputDir) return configuredOutputDir
|
||||
|
||||
try {
|
||||
return String(await window.electronAPI.app.getDownloadsPath() || '').trim()
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Zustand Store ──────────────────────────────────────────
|
||||
|
||||
interface AutomationState {
|
||||
@@ -201,30 +226,13 @@ export function useAutomationRunner(startTask: (payload: ExportTaskPayload) => v
|
||||
}
|
||||
|
||||
// Enqueue
|
||||
const outputDir = String(task.outputDir || '').trim() || await configService.getExportPath()
|
||||
const outputDir = await resolveAutomationOutputDir(task)
|
||||
if (!outputDir) {
|
||||
markSkipped(task.id, '导出目录未设置', scheduleKey)
|
||||
continue
|
||||
}
|
||||
|
||||
const exportOptions: any = {
|
||||
...task.template.optionTemplate,
|
||||
exportPathStyle: task.template.optionTemplate?.exportPathStyle || 'auto',
|
||||
dateRange: typeof task.template.dateRangeConfig === 'string'
|
||||
? JSON.parse(task.template.dateRangeConfig)
|
||||
: task.template.dateRangeConfig
|
||||
}
|
||||
|
||||
startTaskRef.current({
|
||||
sessionIds: task.sessionIds,
|
||||
sessionNames: task.sessionNames,
|
||||
outputDir,
|
||||
options: exportOptions,
|
||||
scope: task.template.scope,
|
||||
source: 'automation',
|
||||
automationTaskId: task.id,
|
||||
contentType: task.template.contentType
|
||||
})
|
||||
const exportOptions = buildAutomationExportOptions(task)
|
||||
|
||||
patchTask(task.id, (prev) => ({
|
||||
...prev,
|
||||
@@ -238,6 +246,17 @@ export function useAutomationRunner(startTask: (payload: ExportTaskPayload) => v
|
||||
lastScheduleKey: scheduleKey
|
||||
}
|
||||
}))
|
||||
|
||||
startTaskRef.current({
|
||||
sessionIds: task.sessionIds,
|
||||
sessionNames: task.sessionNames,
|
||||
outputDir,
|
||||
options: exportOptions,
|
||||
scope: task.template.scope,
|
||||
source: 'automation',
|
||||
automationTaskId: task.id,
|
||||
contentType: task.template.contentType
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Automation Schedule Evaluation Failed', error)
|
||||
@@ -272,31 +291,14 @@ export function useAutomationRunner(startTask: (payload: ExportTaskPayload) => v
|
||||
task: ExportAutomationTask,
|
||||
_options?: { scheduleKey?: string }
|
||||
): Promise<{ queued: boolean; reason?: string }> => {
|
||||
const outputDir = String(task.outputDir || '').trim() || await configService.getExportPath()
|
||||
const outputDir = await resolveAutomationOutputDir(task)
|
||||
if (!outputDir) {
|
||||
return { queued: false, reason: '导出目录未设置' }
|
||||
}
|
||||
|
||||
const exportOptions: any = {
|
||||
...task.template.optionTemplate,
|
||||
exportPathStyle: task.template.optionTemplate?.exportPathStyle || 'auto',
|
||||
dateRange: typeof task.template.dateRangeConfig === 'string'
|
||||
? JSON.parse(task.template.dateRangeConfig)
|
||||
: task.template.dateRangeConfig
|
||||
}
|
||||
const exportOptions = buildAutomationExportOptions(task)
|
||||
|
||||
startTaskRef.current({
|
||||
sessionIds: task.sessionIds,
|
||||
sessionNames: task.sessionNames,
|
||||
outputDir,
|
||||
options: exportOptions,
|
||||
scope: task.template.scope,
|
||||
source: 'automation',
|
||||
automationTaskId: task.id,
|
||||
contentType: task.template.contentType
|
||||
})
|
||||
|
||||
void updateTask(task.id, (prev) => ({
|
||||
await updateTask(task.id, (prev) => ({
|
||||
...prev,
|
||||
updatedAt: Date.now(),
|
||||
runState: {
|
||||
@@ -309,6 +311,17 @@ export function useAutomationRunner(startTask: (payload: ExportTaskPayload) => v
|
||||
}
|
||||
}))
|
||||
|
||||
startTaskRef.current({
|
||||
sessionIds: task.sessionIds,
|
||||
sessionNames: task.sessionNames,
|
||||
outputDir,
|
||||
options: exportOptions,
|
||||
scope: task.template.scope,
|
||||
source: 'automation',
|
||||
automationTaskId: task.id,
|
||||
contentType: task.template.contentType
|
||||
})
|
||||
|
||||
return { queued: true }
|
||||
}, [updateTask])
|
||||
|
||||
|
||||
@@ -12,7 +12,9 @@ import { useExportTaskStore } from '../../../stores/exportTaskStore'
|
||||
import { buildProgressPayloadSignature } from '../utils/progress'
|
||||
import { resolvePerfStageByPhase, applyProgressToTaskPerformance } from '../utils/performance'
|
||||
import { emitExportSessionStatus, onExportSessionStatusRequest } from '../../../services/exportBridge'
|
||||
import { useAutomationStore } from './useAutomation'
|
||||
import type { ExportProgress } from '../types'
|
||||
import type { ExportAutomationTask } from '../../../types/exportAutomation'
|
||||
|
||||
export interface ExportTasksResult {
|
||||
tasks: ExportTask[]
|
||||
@@ -25,6 +27,14 @@ export interface ExportTasksResult {
|
||||
|
||||
const generateTaskId = () => `task-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`
|
||||
|
||||
const updateAutomationRunState = (
|
||||
automationTaskId: string | undefined,
|
||||
updater: (prev: ExportAutomationTask) => ExportAutomationTask
|
||||
) => {
|
||||
if (!automationTaskId) return
|
||||
void useAutomationStore.getState().updateTask(automationTaskId, updater)
|
||||
}
|
||||
|
||||
export function useExportTasks(): ExportTasksResult {
|
||||
const [tasks, setTasks] = useState<ExportTask[]>([])
|
||||
|
||||
@@ -82,6 +92,20 @@ export function useExportTasks(): ExportTasksResult {
|
||||
|
||||
setTasks(prev => [newTask, ...prev])
|
||||
|
||||
if (payload.source === 'automation') {
|
||||
updateAutomationRunState(payload.automationTaskId, (prev) => ({
|
||||
...prev,
|
||||
updatedAt: Date.now(),
|
||||
runState: {
|
||||
...(prev.runState || {}),
|
||||
lastRunStatus: 'running',
|
||||
lastStartedAt: Date.now(),
|
||||
lastSkipReason: undefined,
|
||||
lastError: undefined
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// Kick off via electron API
|
||||
const run = async () => {
|
||||
let progressUnsubscribe: (() => void) | null = null
|
||||
@@ -138,14 +162,47 @@ export function useExportTasks(): ExportTasksResult {
|
||||
sessionOutputPaths: result?.sessionOutputPaths
|
||||
}))
|
||||
|
||||
if (payload.source === 'automation') {
|
||||
const finishedAt = Date.now()
|
||||
updateAutomationRunState(payload.automationTaskId, (prev) => {
|
||||
const previousSuccessCount = Math.max(0, Math.floor(Number(prev.runState?.successCount || 0)))
|
||||
return {
|
||||
...prev,
|
||||
updatedAt: finishedAt,
|
||||
runState: {
|
||||
...(prev.runState || {}),
|
||||
lastRunStatus: result?.success ? 'success' : 'error',
|
||||
lastFinishedAt: finishedAt,
|
||||
lastSuccessAt: result?.success ? finishedAt : prev.runState?.lastSuccessAt,
|
||||
lastError: result?.success ? undefined : (result?.error || '导出失败'),
|
||||
successCount: result?.success ? previousSuccessCount + 1 : previousSuccessCount
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
} catch (err: any) {
|
||||
console.error('[useExportTasks] Task failed:', err)
|
||||
const errorMessage = err.message || '未知错误'
|
||||
updateTask(taskId, (task) => ({
|
||||
...task,
|
||||
status: 'error',
|
||||
finishedAt: Date.now(),
|
||||
error: err.message || '未知错误'
|
||||
error: errorMessage
|
||||
}))
|
||||
if (payload.source === 'automation') {
|
||||
const finishedAt = Date.now()
|
||||
updateAutomationRunState(payload.automationTaskId, (prev) => ({
|
||||
...prev,
|
||||
updatedAt: finishedAt,
|
||||
runState: {
|
||||
...(prev.runState || {}),
|
||||
lastRunStatus: 'error',
|
||||
lastFinishedAt: finishedAt,
|
||||
lastError: errorMessage
|
||||
}
|
||||
}))
|
||||
}
|
||||
} finally {
|
||||
if (progressUnsubscribe) {
|
||||
progressUnsubscribe()
|
||||
|
||||
Reference in new issue
Block a user