439 lines
13 KiB
JavaScript
439 lines
13 KiB
JavaScript
/**
|
|
* 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 = `
|
|
<div class="agent-request-materials">
|
|
<strong>需要补充:</strong>
|
|
${data.missing_materials.map(m => `<span class="material-tag">${escapeHtml(m)}</span>`).join(' ')}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
panel.innerHTML = `
|
|
<div class="agent-request-header">
|
|
<span class="agent-request-icon"><img src="/static/icon/file.svg" alt=""></span>
|
|
<span>第 ${data.round || 1} 轮分析 - 信息不完整</span>
|
|
</div>
|
|
${materialsHtml}
|
|
<div class="agent-request-actions">
|
|
<button class="btn btn-sm btn-primary" onclick="document.getElementById('file-input').click()">
|
|
上传补充材料
|
|
</button>
|
|
<button class="btn btn-sm btn-outline-secondary" onclick="handleForceSubmit()">
|
|
直接提交
|
|
</button>
|
|
</div>
|
|
`;
|
|
|
|
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<string>} 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');
|
|
}
|
|
} |