/** * Agent 交互模块 */ import { App } from './state.js'; import { escapeHtml } from './utils.js'; import { addChatMessage, showStatus } from './chat.js'; import { createSSEConnection, handleDoneResult } from './sse.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; case 'agent_max_rounds': _handleAgentError(msg); break; case 'agent_extract_status': _handleAgentStateChange(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); } } /** * 请求补充材料 — 将 suggestion 以消息气泡形式单独展示 */ export function _handleAgentRequestSupplement(msg) { const suggestion = msg.suggestion || '请补充上传相关材料'; showStatus('信息不完整,请补充材料'); addChatMessage(suggestion, 'system'); } /** * 信息完整 — 在消息气泡中提醒用户,最终结果由 done 事件处理 */ function _handleAgentReady(msg) { addChatMessage(msg.message || '信息完整,正在提交到财务系统', 'done'); } /** * 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('补充信息已收到,正在重新分析...'); App.isProcessing = true; createSSEConnection(App.sessionId, { onDone: (result) => { handleDoneResult(result); }, handleFileProgress: false, handleLLMStream: false, }); } 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'; createSSEConnection(App.sessionId, { onDone: (result) => { handleDoneResult(result); }, handleFileProgress: false, handleLLMStream: false, }); } } 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') { App.isProcessing = true; createSSEConnection(App.sessionId, { onDone: (result) => { handleDoneResult(result); }, }); } } catch (e) { addChatMessage(`请求失败:${e.message}`, 'error'); } }