let currentEventSource = null; function createTask() { const promptInput = document.getElementById('prompt-input'); const prompt = promptInput.value.trim(); if (!prompt) { alert("请输入有效的提示内容"); promptInput.focus(); return; } if (currentEventSource) { currentEventSource.close(); currentEventSource = null; } const container = document.getElementById('task-container'); container.innerHTML = '
任务初始化中...
'; document.getElementById('input-container').classList.add('bottom'); fetch('/tasks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt }) }) .then(response => { if (!response.ok) { return response.json().then(err => { throw new Error(err.detail || '请求失败') }); } return response.json(); }) .then(data => { if (!data.task_id) { throw new Error('无效的任务ID'); } setupSSE(data.task_id); loadHistory(); }) .catch(error => { container.innerHTML = `
错误: ${error.message}
`; console.error('创建任务失败:', error); }); } function setupSSE(taskId) { let retryCount = 0; const maxRetries = 3; const retryDelay = 2000; function connect() { const eventSource = new EventSource(`/tasks/${taskId}/events`); currentEventSource = eventSource; const container = document.getElementById('task-container'); let heartbeatTimer = setInterval(() => { container.innerHTML += '
·
'; }, 5000); const pollInterval = setInterval(() => { fetch(`/tasks/${taskId}`) .then(response => response.json()) .then(task => { updateTaskStatus(task); }) .catch(error => { console.error('轮询失败:', error); }); }, 10000); if (!eventSource._listenersAdded) { eventSource._listenersAdded = true; let lastResultContent = ''; eventSource.addEventListener('status', (event) => { clearInterval(heartbeatTimer); try { const data = JSON.parse(event.data); container.querySelector('.loading')?.remove(); container.classList.add('active'); const welcomeMessage = document.querySelector('.welcome-message'); if (welcomeMessage) { welcomeMessage.style.display = 'none'; } let stepContainer = container.querySelector('.step-container'); if (!stepContainer) { container.innerHTML = '
'; stepContainer = container.querySelector('.step-container'); } // 保存result内容 if (data.steps && data.steps.length > 0) { // 遍历所有步骤,找到最后一个result类型 for (let i = data.steps.length - 1; i >= 0; i--) { if (data.steps[i].type === 'result') { lastResultContent = data.steps[i].result; break; } } } // Parse and display each step with proper formatting stepContainer.innerHTML = data.steps.map(step => { const content = step.result; const timestamp = new Date().toLocaleTimeString(); return `
${getEventIcon(step.type)} [${timestamp}] ${getEventLabel(step.type)}:
${content}
`; }).join(''); // Auto-scroll to bottom container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); } catch (e) { console.error('状态更新失败:', e); } }); // 添加对think事件的处理 eventSource.addEventListener('think', (event) => { clearInterval(heartbeatTimer); try { const data = JSON.parse(event.data); container.querySelector('.loading')?.remove(); let stepContainer = container.querySelector('.step-container'); if (!stepContainer) { container.innerHTML = '
'; stepContainer = container.querySelector('.step-container'); } const content = data.result; const timestamp = new Date().toLocaleTimeString(); const step = document.createElement('div'); step.className = 'step-item think'; step.innerHTML = `
${getEventIcon('think')} [${timestamp}] ${getEventLabel('think')}:
${content}
`; stepContainer.appendChild(step); container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); // 更新任务状态 fetch(`/tasks/${taskId}`) .then(response => response.json()) .then(task => { updateTaskStatus(task); }) .catch(error => { console.error('状态更新失败:', error); }); } catch (e) { console.error('思考事件处理失败:', e); } }); // 添加对tool事件的处理 eventSource.addEventListener('tool', (event) => { clearInterval(heartbeatTimer); try { const data = JSON.parse(event.data); container.querySelector('.loading')?.remove(); let stepContainer = container.querySelector('.step-container'); if (!stepContainer) { container.innerHTML = '
'; stepContainer = container.querySelector('.step-container'); } const content = data.result; const timestamp = new Date().toLocaleTimeString(); const step = document.createElement('div'); step.className = 'step-item tool'; step.innerHTML = `
${getEventIcon('tool')} [${timestamp}] ${getEventLabel('tool')}:
${content}
`; stepContainer.appendChild(step); container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); // 更新任务状态 fetch(`/tasks/${taskId}`) .then(response => response.json()) .then(task => { updateTaskStatus(task); }) .catch(error => { console.error('状态更新失败:', error); }); } catch (e) { console.error('工具事件处理失败:', e); } }); // 添加对act事件的处理 eventSource.addEventListener('act', (event) => { clearInterval(heartbeatTimer); try { const data = JSON.parse(event.data); container.querySelector('.loading')?.remove(); let stepContainer = container.querySelector('.step-container'); if (!stepContainer) { container.innerHTML = '
'; stepContainer = container.querySelector('.step-container'); } const content = data.result; const timestamp = new Date().toLocaleTimeString(); const step = document.createElement('div'); step.className = 'step-item act'; step.innerHTML = `
${getEventIcon('act')} [${timestamp}] ${getEventLabel('act')}:
${content}
`; stepContainer.appendChild(step); container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); // 更新任务状态 fetch(`/tasks/${taskId}`) .then(response => response.json()) .then(task => { updateTaskStatus(task); }) .catch(error => { console.error('状态更新失败:', error); }); } catch (e) { console.error('执行事件处理失败:', e); } }); // 添加对run事件的处理 // 添加对log事件的处理 eventSource.addEventListener('log', (event) => { clearInterval(heartbeatTimer); try { const data = JSON.parse(event.data); container.querySelector('.loading')?.remove(); let stepContainer = container.querySelector('.step-container'); if (!stepContainer) { container.innerHTML = '
'; stepContainer = container.querySelector('.step-container'); } const content = data.result; const timestamp = new Date().toLocaleTimeString(); const step = document.createElement('div'); step.className = 'step-item log'; step.innerHTML = `
${getEventIcon('log')} [${timestamp}] ${getEventLabel('log')}:
${content}
`; stepContainer.appendChild(step); container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); // 更新任务状态 fetch(`/tasks/${taskId}`) .then(response => response.json()) .then(task => { updateTaskStatus(task); }) .catch(error => { console.error('状态更新失败:', error); }); } catch (e) { console.error('日志事件处理失败:', e); } }); eventSource.addEventListener('run', (event) => { clearInterval(heartbeatTimer); try { const data = JSON.parse(event.data); container.querySelector('.loading')?.remove(); let stepContainer = container.querySelector('.step-container'); if (!stepContainer) { container.innerHTML = '
'; stepContainer = container.querySelector('.step-container'); } const content = data.result; const timestamp = new Date().toLocaleTimeString(); const step = document.createElement('div'); step.className = 'step-item run'; step.innerHTML = `
${getEventIcon('run')} [${timestamp}] ${getEventLabel('run')}:
${content}
`; stepContainer.appendChild(step); container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); // 更新任务状态 fetch(`/tasks/${taskId}`) .then(response => response.json()) .then(task => { updateTaskStatus(task); }) .catch(error => { console.error('状态更新失败:', error); }); } catch (e) { console.error('运行事件处理失败:', e); } }); eventSource.addEventListener('message', (event) => { clearInterval(heartbeatTimer); try { const data = JSON.parse(event.data); container.querySelector('.loading')?.remove(); let stepContainer = container.querySelector('.step-container'); if (!stepContainer) { container.innerHTML = '
'; stepContainer = container.querySelector('.step-container'); } // Create new step element const step = document.createElement('div'); step.className = `step-item ${data.type || 'step'}`; // Format content and timestamp const content = data.result; const timestamp = new Date().toLocaleTimeString(); step.innerHTML = `
${getEventIcon(data.type)} [${timestamp}] ${getEventLabel(data.type)}:
${content}
`; // Add step to container with animation stepContainer.prepend(step); setTimeout(() => { step.classList.add('show'); }, 10); // Auto-scroll to bottom container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); } catch (e) { console.error('消息处理失败:', e); } }); let isTaskComplete = false; eventSource.addEventListener('complete', (event) => { isTaskComplete = true; clearInterval(heartbeatTimer); clearInterval(pollInterval); container.innerHTML += `
✅ 任务完成
${lastResultContent}
`; eventSource.close(); currentEventSource = null; lastResultContent = ''; // 清空结果内容 }); eventSource.addEventListener('error', (event) => { clearInterval(heartbeatTimer); clearInterval(pollInterval); try { const data = JSON.parse(event.data); container.innerHTML += `
❌ 错误: ${data.message}
`; eventSource.close(); currentEventSource = null; } catch (e) { console.error('错误处理失败:', e); } }); } container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); eventSource.onerror = (err) => { if (isTaskComplete) { return; } console.error('SSE连接错误:', err); clearInterval(heartbeatTimer); clearInterval(pollInterval); eventSource.close(); if (retryCount < maxRetries) { retryCount++; container.innerHTML += `
⚠ 连接中断,${retryDelay/1000}秒后重试 (${retryCount}/${maxRetries})...
`; setTimeout(connect, retryDelay); } else { container.innerHTML += `
⚠ 连接中断,请尝试刷新页面
`; } }; } connect(); } function getEventIcon(eventType) { switch(eventType) { case 'think': return '🤔'; case 'tool': return '🛠️'; case 'act': return '🚀'; case 'result': return '🏁'; case 'error': return '❌'; case 'complete': return '✅'; case 'warning': return '⚠️'; case 'log': return '📝'; default: return '⚡'; } } function getEventLabel(eventType) { switch(eventType) { case 'think': return '思考'; case 'tool': return '工具执行'; case 'act': return '执行'; case 'result': return '结果'; case 'error': return '错误'; case 'complete': return '完成'; case 'warning': return '警告'; case 'log': return '日志'; default: return '步骤'; } } function formatContent(content) { // Remove timestamp and log level prefixes content = content.replace(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3} \| [A-Z]+\s*\| /gm, ''); // Format the remaining content return content .replace(/\n/g, '
') .replace(/ /g, '  ') .replace(/✨ Manus's thoughts:/g, '') .replace(/🛠️ Manus selected/g, '') .replace(/🧰 Tools being prepared:/g, '') .replace(/🔧 Activating tool:/g, '') .replace(/🎯 Tool/g, '') .replace(/📝 Oops!/g, '') .replace(/🏁 Special tool/g, ''); } function updateTaskStatus(task) { const taskCard = document.querySelector(`.task-card[data-task-id="${task.id}"]`); if (taskCard) { const statusEl = taskCard.querySelector('.task-meta .status'); if (statusEl) { statusEl.className = `status-${task.status ? task.status.toLowerCase() : 'unknown'}`; statusEl.textContent = task.status || '未知状态'; } } } function loadHistory() { fetch('/tasks') .then(response => { if (!response.ok) { return response.text().then(text => { throw new Error(`请求失败: ${response.status} - ${text.substring(0, 100)}`); }); } return response.json(); }) .then(tasks => { const listContainer = document.getElementById('task-list'); listContainer.innerHTML = tasks.map(task => `
${task.prompt}
${new Date(task.created_at).toLocaleString()} - ${task.status || '未知状态'}
`).join(''); }) .catch(error => { console.error('加载历史记录失败:', error); const listContainer = document.getElementById('task-list'); listContainer.innerHTML = `
加载失败: ${error.message}
`; }); } document.addEventListener('DOMContentLoaded', function() { const welcomeMessage = document.querySelector('.welcome-message'); if (welcomeMessage) { welcomeMessage.style.display = 'flex'; } // 监听任务容器显示状态 const taskContainer = document.getElementById('task-container'); if (taskContainer) { const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { if (mutation.attributeName === 'class') { const welcomeMessage = document.querySelector('.welcome-message'); if (taskContainer.classList.contains('active')) { if (welcomeMessage) { welcomeMessage.style.display = 'none'; } } else { if (welcomeMessage) { welcomeMessage.style.display = 'block'; } } } }); }); observer.observe(taskContainer, { attributes: true }); } });