agent 模式补充文件功能已经实现

This commit is contained in:
wandering
2026-06-15 10:39:01 +08:00
parent e252896de9
commit 7c137c5214
34 changed files with 1664 additions and 1651 deletions

View File

@@ -123,11 +123,14 @@ function _appendLLMStreamReasoning(text) {
}
/**
* 关闭流式气泡end 阶段)— 气泡保留在聊天历史中作为持久消息
* 关闭流式气泡end 阶段)— 气泡保留在聊天历史中作为持久消息
* 如果累积内容是提取结果 JSON则渲染为美观的摘要卡片。
*/
function _closeLLMStreamBubble(label) {
if (!llmStreamState.bubble) return;
const accumulated = llmStreamState.accumulated;
llmStreamState.bubble.classList.remove('processing');
llmStreamState.bubble.classList.add('done');
@@ -141,6 +144,16 @@ function _closeLLMStreamBubble(label) {
reasoningSection.open = false;
}
// 尝试将累积文本解析为提取结果 JSON渲染为摘要卡片
const textEl = llmStreamState.bubble.querySelector('.llm-stream-text');
if (textEl && accumulated) {
const parsed = _tryParseExtractionJson(accumulated);
if (parsed) {
textEl.innerHTML = '';
textEl.appendChild(_buildExtractionSummary(parsed));
}
}
scrollToBottom();
llmStreamState = {
@@ -196,4 +209,199 @@ function _errorLLMStreamBubble(errorMsg) {
reasoningContent: null,
reasoningAccumulated: '',
};
}
/**
* 尝试将 LLM 输出文本解析为提取结果 JSON
* 返回解析后的对象,或 null非提取结果 JSON
*/
function _tryParseExtractionJson(text) {
try {
const trimmed = text.trim();
if (!trimmed.startsWith('{')) return null;
const parsed = JSON.parse(trimmed);
if (parsed && typeof parsed === 'object' && (parsed.basic_info || parsed.reimbursement_details)) {
return parsed;
}
return null;
} catch {
return null;
}
}
/**
* 将提取结果 JSON 渲染为美观的摘要卡片 DOM 元素
*
* @param {Object} data - 提取结果 JSON
* @returns {HTMLElement}
*/
function _buildExtractionSummary(data) {
const container = document.createElement('div');
container.className = 'extraction-summary';
// 基本信息
if (data.basic_info) {
const bi = data.basic_info;
const section = _summarySection('基本信息');
_summaryRow(section, '出差事由', bi.travel_purpose || '');
_summaryRow(section, '出差地点', bi.travel_location || '');
const dates = bi.start_date && bi.end_date ? `${bi.start_date}${bi.end_date}` : (bi.start_date || bi.end_date || '');
_summaryRow(section, '出差日期', dates);
container.appendChild(section);
}
// 交通费
if (data.reimbursement_details?.transport_fee?.length) {
const section = _summarySection('交通费');
const table = _summaryTable(['日期', '出发', '到达', '金额', '备注']);
for (const item of data.reimbursement_details.transport_fee) {
_summaryTableRow(table, [
item.start_date || '',
item.departure_place || '',
item.arrival_place || '',
item.amount != null ? `¥${item.amount.toFixed(2)}` : '',
item.remark || '',
]);
}
section.appendChild(table);
container.appendChild(section);
}
// 住宿费
if (data.reimbursement_details?.hotel_fee?.length) {
const section = _summarySection('住宿费');
const table = _summaryTable(['入住日期', '离店日期', '天数', '发票金额', '报销金额', '备注']);
for (const item of data.reimbursement_details.hotel_fee) {
_summaryTableRow(table, [
item.checkin_date || '',
item.checkout_date || '',
item.days != null ? String(item.days) : '',
item.invoice_amount != null ? `¥${item.invoice_amount.toFixed(2)}` : '',
item.reimburse_amount != null ? `¥${item.reimburse_amount.toFixed(2)}` : '',
item.remark || '',
]);
}
section.appendChild(table);
container.appendChild(section);
}
// 会议/培训费
if (data.reimbursement_details?.conference_fee?.length) {
const section = _summarySection('会议/培训费');
const table = _summaryTable(['日期', '金额', '备注']);
for (const item of data.reimbursement_details.conference_fee) {
_summaryTableRow(table, [
item.start_date || '',
item.amount != null ? `¥${item.amount.toFixed(2)}` : '',
item.remark || '',
]);
}
section.appendChild(table);
container.appendChild(section);
}
// 支付记录
if (data.payment_methods?.length) {
const section = _summarySection('支付记录');
const table = _summaryTable(['日期', '金额', '商户', '备注']);
for (const item of data.payment_methods) {
_summaryTableRow(table, [
item.card_date || '',
item.card_amount != null ? `¥${item.card_amount.toFixed(2)}` : '',
item.merchant || '',
item.remark || '',
]);
}
section.appendChild(table);
container.appendChild(section);
}
// 补助
if (data.subsidy_list?.length) {
const section = _summarySection('补助');
const table = _summaryTable(['姓名', '开始日期', '结束日期', '天数']);
for (const item of data.subsidy_list) {
_summaryTableRow(table, [
item.person_name || '',
item.start_date || '',
item.end_date || '',
item.days != null ? String(item.days) : '',
]);
}
section.appendChild(table);
container.appendChild(section);
}
// 附件
if (data.attachments?.length) {
const section = _summarySection('附件');
const list = document.createElement('ul');
list.className = 'summary-attachment-list';
for (const item of data.attachments) {
const li = document.createElement('li');
li.className = `summary-attachment-item summary-attachment-${item.attachment_type || 'other'}`;
li.textContent = `${item.attachment_desc || item.filename || ''}`;
list.appendChild(li);
}
section.appendChild(list);
container.appendChild(section);
}
return container;
}
/* ---- 提取摘要卡片构建辅助函数 ---- */
function _summarySection(title) {
const section = document.createElement('div');
section.className = 'summary-section';
const titleEl = document.createElement('div');
titleEl.className = 'summary-section-title';
titleEl.textContent = title;
section.appendChild(titleEl);
return section;
}
function _summaryRow(section, label, value) {
if (!value) return;
const row = document.createElement('div');
row.className = 'summary-row';
const labelEl = document.createElement('span');
labelEl.className = 'summary-label';
labelEl.textContent = label;
const valueEl = document.createElement('span');
valueEl.className = 'summary-value';
valueEl.textContent = value;
row.appendChild(labelEl);
row.appendChild(valueEl);
section.appendChild(row);
}
function _summaryTable(headers) {
const table = document.createElement('table');
table.className = 'summary-table';
const thead = document.createElement('thead');
const tr = document.createElement('tr');
for (const h of headers) {
const th = document.createElement('th');
th.textContent = h;
tr.appendChild(th);
}
thead.appendChild(tr);
table.appendChild(thead);
const tbody = document.createElement('tbody');
table.appendChild(tbody);
return table;
}
function _summaryTableRow(table, cells) {
const tbody = table.querySelector('tbody');
if (!tbody) return;
const tr = document.createElement('tr');
for (const cell of cells) {
const td = document.createElement('td');
td.textContent = cell;
tr.appendChild(td);
}
tbody.appendChild(tr);
}