/** * Agent 交互模块 */ import { App } from './state.js'; import { escapeHtml } from './utils.js'; import { addChatMessage, showStatus } from './chat.js'; // ================================================================ // Agent 事件处理 // ================================================================ /** * 处理 Agent SSE 事件 * * @param {Object} msg - SSE 传来的 agent 事件对象 */ export function handleAgentEvent(msg) { switch (msg.type) { case 'agent_state_change': _handleAgentStateChange(msg); break; case 'agent_request_supplement': _handleAgentRequestSupplement(msg); break; case 'agent_ready': _handleAgentReady(msg); break; case 'agent_error': _handleAgentError(msg); break; case 'agent_supplement_received': _handleSupplementReceived(msg); break; case 'agent_force_submit': _handleForceSubmit(msg); break; } } /** * 状态变更通知 — 使用瞬态消息,状态变化时更新而非追加 */ function _handleAgentStateChange(msg) { const stateLabels = { 'extracting': '正在分析文件...', 'validating': '正在校验信息完整性...', 'awaiting_supplement': '等待补充材料', 'ready': '信息完整,可以提交', 'submitting': '正在提交...', }; const label = stateLabels[msg.state] || msg.message || '处理中...'; if (msg.state === 'ready') { showStatus(label, 'done'); } else { showStatus(label); } } /** * 请求补充材料 — 仅更新瞬态状态,持久提示消息由 done 事件分支负责 */ export function _handleAgentRequestSupplement(msg) { const suggestion = msg.suggestion || '请补充上传相关材料'; showStatus(suggestion); } /** * 信息完整 — 已由 process.js 的 done 处理覆盖,此处不再重复显示 */ function _handleAgentReady(msg) { // agent_ready 事件的信息展示统一由 process.js done 分支处理 } /** * 自动触发财务系统提交(由 done 事件调用) */ export async function handleAutoSubmit() { if (!App.sessionId) { addChatMessage('请先上传文件并处理', 'error'); return; } if (App.agentEventSource) { App.agentEventSource.close(); App.agentEventSource = null; } App.isProcessing = true; App.processState = 'submitting'; try { showStatus('正在自动提交到财务系统...'); const response = await fetch(`/api/submit-financial/${App.sessionId}`, { method: 'POST', }); const result = await response.json(); if (result.status === 'started') { const es = new EventSource(`/api/logs/${App.sessionId}`); es.addEventListener('message', e => { try { const msg = JSON.parse(e.data); if (msg.type === 'done') { es.close(); App.isProcessing = false; App.processState = 'done'; if (msg.result.submit_ok) { addChatMessage('提交完成!', 'done'); } else { addChatMessage(`提交失败:${msg.result.submit_error || '未知错误'}`, 'error'); } } } catch (err) { // 普通日志行 } }); es.onerror = () => { es.close(); App.isProcessing = false; App.processState = 'done'; addChatMessage('连接中断', 'error'); }; } else if (result.error) { App.isProcessing = false; App.processState = 'done'; addChatMessage(`提交失败:${result.error}`, 'error'); } } catch (e) { App.isProcessing = false; App.processState = 'done'; addChatMessage(`请求失败:${e.message}`, 'error'); } } /** * Agent 错误 */ function _handleAgentError(msg) { addChatMessage(msg.message || 'Agent 处理出错', 'error'); App.isProcessing = false; App.processState = 'done'; } /** * 收到补充文件 */ function _handleSupplementReceived(msg) { const fileNames = (msg.files || []).join('、'); addChatMessage(`已收到补充文件:${fileNames}`, 'done'); } /** * 强制提交 */ function _handleForceSubmit(msg) { showStatus('已跳过校验,开始提交到财务系统'); } // ================================================================ // Agent 请求面板 // ================================================================ /** * 显示 Agent 请求补充材料的 UI */ export function showAgentRequest(data) { const existing = document.getElementById('agent-request-panel'); if (existing) existing.remove(); const chatMessages = document.getElementById('chat-messages'); if (!chatMessages) return; const panel = document.createElement('div'); panel.id = 'agent-request-panel'; panel.className = 'agent-request-panel'; let materialsHtml = ''; if (data.missing_materials && data.missing_materials.length > 0) { materialsHtml = `
需要补充: ${data.missing_materials.map(m => `${escapeHtml(m)}`).join(' ')}
`; } panel.innerHTML = `
第 ${data.round || 1} 轮分析 - 信息不完整
${materialsHtml}
`; chatMessages.appendChild(panel); chatMessages.scrollTop = chatMessages.scrollHeight; } /** * 移除 Agent 请求面板 */ export function hideAgentRequest() { const panel = document.getElementById('agent-request-panel'); if (panel) panel.remove(); } // ================================================================ // 用户文字补充 // ================================================================ /** * 处理用户通过输入框补充的文字信息 * * @param {string} text - 用户输入的文字 */ export async function handleUserSupplement(text) { if (!App.sessionId) return; try { showStatus('正在解析补充信息...'); const response = await fetch(`/api/agent/user-supplement/${App.sessionId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: text }), }); const result = await response.json(); if (result.status === 'started') { showStatus('补充信息已收到,正在重新分析...'); const es = new EventSource(`/api/logs/${App.sessionId}`); es.addEventListener('message', e => { try { const msg = JSON.parse(e.data); if (msg.type && msg.type.startsWith('agent_')) { handleAgentEvent(msg); return; } if (msg.type === 'done') { es.close(); App.isProcessing = false; if (msg.result.waiting_for_supplement) { App.processState = 'awaiting_supplement'; _handleAgentRequestSupplement(msg.result); addChatMessage('您可通过上传区补充文件,或直接输入文字说明补充信息,或输入"直接提交"跳过校验', 'system'); } else { App.processState = 'done'; if (msg.result.ok) { if (msg.result.submit_ok !== false) { addChatMessage('信息完整,已自动提交到财务系统', 'done'); } else { addChatMessage(`校验通过但提交失败:${msg.result.submit_error || '未知错误'}`, 'error'); } } else { addChatMessage(`分析失败:${msg.result.error || '未知错误'}`, 'error'); } } } } catch (err) { // 普通日志 } }); es.onerror = () => { es.close(); App.isProcessing = false; App.processState = 'done'; addChatMessage('连接中断', 'error'); }; } else if (result.error) { addChatMessage(`处理失败:${result.error}`, 'error'); } } catch (e) { addChatMessage(`请求失败:${e.message}`, 'error'); } } // ================================================================ // 用户输入处理 // ================================================================ /** * 处理强制提交 */ export async function handleForceSubmit() { if (!App.sessionId) { addChatMessage('请先上传文件并处理', 'error'); return; } if (App.agentEventSource) { App.agentEventSource.close(); App.agentEventSource = null; } App.forceSubmitting = true; try { showStatus('正在跳过校验并提交...'); hideAgentRequest(); const response = await fetch(`/api/agent/force-submit/${App.sessionId}`, { method: 'POST', }); const result = await response.json(); if (result.status === 'started') { showStatus('已跳过校验,开始提交到财务系统'); App.isProcessing = true; App.processState = 'submitting'; const es = new EventSource(`/api/logs/${App.sessionId}`); es.addEventListener('message', e => { try { const msg = JSON.parse(e.data); if (msg.type === 'done') { es.close(); App.isProcessing = false; App.processState = 'done'; App.forceSubmitting = false; App.lastProcessedFileCount = App.allFiles.length; if (msg.result.ok) { addChatMessage('提交完成!', 'done'); } else { addChatMessage(`提交失败:${msg.result.submit_error || '未知错误'}`, 'error'); } } } catch (err) { // 普通日志行 } }); es.onerror = () => { es.close(); App.isProcessing = false; App.processState = 'done'; App.forceSubmitting = false; App.lastProcessedFileCount = App.allFiles.length; addChatMessage('连接中断', 'error'); }; } } catch (e) { App.forceSubmitting = false; addChatMessage(`请求失败:${e.message}`, 'error'); } } // ================================================================ // 补充文件处理 // ================================================================ /** * 处理补充文件上传完成后的重新分析 * * @param {Array} filenames - 新上传的文件名列表 */ export async function handleSupplementUpload(filenames) { if (!App.sessionId) return; try { showStatus('收到补充文件,正在重新分析...'); hideAgentRequest(); const response = await fetch(`/api/agent/supplement/${App.sessionId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ files: filenames }), }); const result = await response.json(); if (result.status === 'started') { const es = new EventSource(`/api/logs/${App.sessionId}`); es.addEventListener('message', e => { try { const msg = JSON.parse(e.data); if (msg.type && msg.type.startsWith('agent_')) { handleAgentEvent(msg); return; } if (msg.type === 'done') { es.close(); App.isProcessing = false; if (msg.result.waiting_for_supplement) { App.processState = 'awaiting_supplement'; showStatus('信息不完整,请补充上传相关材料'); addChatMessage('您可通过上传区补充文件,或直接输入文字说明补充信息,或输入"直接提交"跳过校验', 'system'); } else { App.processState = 'done'; if (msg.result.ok) { // 后端已自动提交,根据 submit_ok 显示结果 if (msg.result.submit_ok !== false) { addChatMessage('信息完整,已自动提交到财务系统', 'done'); } else { addChatMessage(`校验通过但提交失败:${msg.result.submit_error || '未知错误'}`, 'error'); } } else { addChatMessage(`分析失败:${msg.result.error || '未知错误'}`, 'error'); } } } } catch (err) { // 普通日志 } }); es.onerror = () => { es.close(); App.isProcessing = false; App.processState = 'done'; addChatMessage('连接中断', 'error'); }; } } catch (e) { addChatMessage(`请求失败:${e.message}`, 'error'); } }