agent 模式补充文件功能已经实现
This commit is contained in:
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user