9043886b6f
- src/doc/ 拆分为 src/core/extraction/, matching/, validation/(核心业务逻辑) - src/bot/ 重命名为 src/infra/browser/(浏览器自动化基础设施) - fill_consumable_doc.py → src/infra/documents/consumable.py - 新增 Agent 调度模块:coordinator.py, events.py, session.py,重构 orchestrator.py - 更新 AGENTS.md、README.md 及所有子目录 README
291 lines
7.7 KiB
JavaScript
291 lines
7.7 KiB
JavaScript
/**
|
|
* 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 = `
|
|
<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('补充信息已收到,正在重新分析...');
|
|
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<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') {
|
|
App.isProcessing = true;
|
|
|
|
createSSEConnection(App.sessionId, {
|
|
onDone: (result) => {
|
|
handleDoneResult(result);
|
|
},
|
|
});
|
|
}
|
|
} catch (e) {
|
|
addChatMessage(`请求失败:${e.message}`, 'error');
|
|
}
|
|
} |