import { WorkflowLog, WorkflowProgress, TimelineItem } from './dashboardChatAreaTypes'; const parseLogProgress = (msg: string) => { const m = msg.trim(); const taskStart = m.match(/^Executing task (\d+)\/(\d+)$/i); if (taskStart) return { taskNumber: parseInt(taskStart[1]), totalTasks: parseInt(taskStart[2]), type: 'task_start' as const }; const actionStart = m.match(/^Task (\d+) - Starting action (\d+)\/(\d+)$/i); if (actionStart) return { taskNumber: parseInt(actionStart[1]), actionNumber: parseInt(actionStart[2]), totalActions: parseInt(actionStart[3]), type: 'action_start' as const }; const actionComplete = m.match(/^(?:✅\s+)?Task (\d+) - Action (\d+)\/(\d+) completed$/i); if (actionComplete) return { taskNumber: parseInt(actionComplete[1]), actionNumber: parseInt(actionComplete[2]), totalActions: parseInt(actionComplete[3]), isCompleted: true, type: 'action_complete' as const }; const taskComplete = m.match(/^(?:🎯\s+)?Task (\d+)\/(\d+) completed$/i); if (taskComplete) return { taskNumber: parseInt(taskComplete[1]), totalTasks: parseInt(taskComplete[2]), isCompleted: true, type: 'task_complete' as const }; return { type: 'unknown' as const }; }; export const calculateWorkflowProgress = (messages: any[], logs: WorkflowLog[] = []): WorkflowProgress | null => { if (messages.length === 0 && logs.length === 0) return null; const lastMessage = messages[messages.length - 1]; if (lastMessage?.role === 'user') return { current: 0, total: 0, percentage: 0, isLoading: true }; let totalTasks = 0; const taskCounts: { [key: number]: { total: number; completed: Set } } = {}; logs.forEach(log => { if (!log.message) return; const p = parseLogProgress(log.message); if (p.type === 'task_start' && p.totalTasks) totalTasks = Math.max(totalTasks, p.totalTasks); if (p.type === 'action_start' && p.taskNumber && p.totalActions) { if (!taskCounts[p.taskNumber]) taskCounts[p.taskNumber] = { total: 0, completed: new Set() }; taskCounts[p.taskNumber].total = Math.max(taskCounts[p.taskNumber].total, p.totalActions); } if (p.type === 'action_complete' && p.taskNumber && p.actionNumber && p.totalActions) { if (!taskCounts[p.taskNumber]) taskCounts[p.taskNumber] = { total: p.totalActions, completed: new Set() }; taskCounts[p.taskNumber].completed.add(p.actionNumber); taskCounts[p.taskNumber].total = Math.max(taskCounts[p.taskNumber].total, p.totalActions); } if (p.type === 'task_complete' && p.taskNumber && p.totalTasks) { totalTasks = Math.max(totalTasks, p.totalTasks); if (taskCounts[p.taskNumber]) { const task = taskCounts[p.taskNumber]; for (let i = 1; i <= task.total; i++) task.completed.add(i); } } }); messages.forEach(msg => { if (msg.role !== 'assistant' || !msg.content) return; const match = msg.content.match(/✅\s+Task (\d+) - Action\s+(?:(\d+)\/(\d+)|.*completed)/i); if (match) { const taskNum = parseInt(match[1]); if (match[2] && match[3]) { const actionNum = parseInt(match[2]); const totalActions = parseInt(match[3]); if (!taskCounts[taskNum]) taskCounts[taskNum] = { total: totalActions, completed: new Set() }; taskCounts[taskNum].completed.add(actionNum); taskCounts[taskNum].total = Math.max(taskCounts[taskNum].total, totalActions); } else { if (!taskCounts[taskNum]) taskCounts[taskNum] = { total: 1, completed: new Set() }; taskCounts[taskNum].completed.add(1); if (taskCounts[taskNum].total === 0) taskCounts[taskNum].total = 1; } } }); let completed = 0; for (let i = 1; i <= totalTasks; i++) { const task = taskCounts[i]; if (task && task.total > 0 && task.completed.size === task.total) completed++; } return totalTasks > 0 ? { current: completed, total: totalTasks, percentage: Math.round((completed / totalTasks) * 100), isLoading: false } : null; }; export const safeParseDate = (ts: any, fallback = Date.now()): Date => { if (!ts) return new Date(fallback); let d = ts; if (typeof ts === 'number') d = ts < 10000000000 ? ts * 1000 : ts; else if (typeof ts === 'string' && /^\d+$/.test(ts)) { const n = parseInt(ts); d = n < 10000000000 ? n * 1000 : n; } const date = new Date(d); return isNaN(date.getTime()) ? new Date(fallback) : date; }; export const mergeMessagesAndLogs = (messages: any[], logs: WorkflowLog[]): TimelineItem[] => { const items: TimelineItem[] = []; messages.forEach((msg, i) => { const ts = safeParseDate(msg.timestamp || msg.publishedAt, Date.now() - ((messages.length + logs.length) - i) * 1000); items.push({ type: 'message', item: msg, timestamp: ts }); }); logs.forEach((log, i) => { const ts = safeParseDate(log.timestamp, Date.now() - (logs.length - i) * 1000); items.push({ type: 'log', item: log, timestamp: ts }); }); return items.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); }; export const transformWorkflowMessage = async (msg: any, request: any): Promise => { let docs: any[] = []; if (msg.documents?.length > 0) { docs = msg.documents.map((d: any) => ({ id: d.id || d.fileId, fileId: typeof d.fileId === 'string' ? parseInt(d.fileId) : d.fileId, name: d.filename || `File_${d.id || d.fileId || 'unknown'}`, ext: (d.filename && d.filename.includes('.')) ? d.filename.split('.').pop() : 'unknown', type: d.mimeType, size: d.fileSize, downloadUrl: `/api/workflows/files/${d.fileId}/download` })); } else if (msg.fileIds?.length > 0) { const promises = msg.fileIds.map(async (id: number) => { try { const res = await request({ url: `/api/workflows/files/${id}/preview`, method: 'get' }); return { id: id.toString(), fileId: id, name: res.name || res.fileName || `File_${id}`, ext: res.extension || res.ext || (res.name ? res.name.split('.').pop() : 'txt'), type: res.mimeType || res.type || 'application/octet-stream', size: res.size || 0, downloadUrl: res.downloadUrl || res.url }; } catch { return { id: id.toString(), fileId: id, name: `File_${id}`, ext: 'unknown', type: 'application/octet-stream', size: 0 }; } }); docs = await Promise.all(promises); } return { id: msg.id, role: msg.role, agentName: msg.role === 'user' ? 'You' : 'Assistant', content: msg.message || msg.content || msg.text || msg.body || '', timestamp: msg.publishedAt ? (typeof msg.publishedAt === 'number' ? new Date(msg.publishedAt * 1000).toISOString() : msg.publishedAt) : msg.timestamp, documents: docs }; };