feat: 增强知识库索引与设置页面模块化拆分
扩展知识库索引任务和 RAG 检索支持增量入库和文档去重,优 化本体检测和规则匹配精度,前端设置页面拆分为 LLM、邮件 和 Hermes 员工同步子面板并重构样式,新增日志详情组件和 知识入库日志模型,补充单元测试覆盖。
This commit is contained in:
@@ -7,7 +7,10 @@ import {
|
||||
ATTACHMENT_ASSOCIATION_CONFIRM_HREF,
|
||||
buildAttachmentAssociationConfirmationMessage,
|
||||
buildOcrFilePreviews,
|
||||
buildReviewFilePreviewsFromReviewPayload
|
||||
buildReviewFilePreviewsFromReviewPayload,
|
||||
buildUnsavedDraftAttachmentConfirmationMessage,
|
||||
filterPersistableFilePreviews,
|
||||
mergeFilePreviews
|
||||
} from '../src/views/scripts/travelReimbursementAttachmentModel.js'
|
||||
import {
|
||||
buildDraftAssociationQueryPayload,
|
||||
@@ -99,6 +102,14 @@ test('attachment upload association uses conversation selection instead of legac
|
||||
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementSubmitComposer.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const flowSource = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/scripts/useTravelReimbursementFlow.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
const conversationSource = readFileSync(
|
||||
fileURLToPath(new URL('../src/views/scripts/travelReimbursementConversationModel.js', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
|
||||
assert.doesNotMatch(viewSource, /检测到你已有单据事件|uploadDecisionDialogOpen|continueExistingUpload|createNewUploadDocument/)
|
||||
assert.doesNotMatch(submitComposerSource, /uploadDecisionDialogOpen|hasExistingDocumentEvent|skipUploadDecisionPrompt/)
|
||||
@@ -112,6 +123,26 @@ test('attachment upload association uses conversation selection instead of legac
|
||||
submitComposerSource,
|
||||
/files\.length[\s\S]*!resolvedUploadDisposition[\s\S]*!options\.skipDraftAssociationPrompt[\s\S]*!reviewAction/
|
||||
)
|
||||
assert.match(submitComposerSource, /mode:\s*'save_then_associate'/)
|
||||
assert.match(submitComposerSource, /review_action:\s*'save_draft'[\s\S]*review_action:\s*'link_to_existing_draft'/)
|
||||
assert.match(submitComposerSource, /appendToCurrentFlow:\s*true/)
|
||||
assert.match(submitComposerSource, /const appendToCurrentFlow = Boolean\(options\.appendToCurrentFlow\)/)
|
||||
assert.match(submitComposerSource, /if \(!appendToCurrentFlow\) \{\s*resetFlowRun\(\)\s*\} else \{\s*clearFlowSimulationTimers\(\)/)
|
||||
assert.match(flowSource, /link_to_existing_draft:\s*\{[\s\S]*key:\s*'attachment-association'/)
|
||||
assert.match(flowSource, /responseMessage\.includes\('关联'\)[\s\S]*key:\s*'attachment-association'/)
|
||||
assert.match(conversationSource, /'attachment-association':\s*\{[\s\S]*title:\s*'票据关联草稿'/)
|
||||
})
|
||||
|
||||
test('unsaved review attachment prompt asks for explicit rich-text confirmation', () => {
|
||||
const message = buildUnsavedDraftAttachmentConfirmationMessage({
|
||||
fileNames: ['taxi.pdf']
|
||||
})
|
||||
const rendered = renderMarkdown(message)
|
||||
|
||||
assert.match(message, /当前这笔报销信息还没有保存为草稿/)
|
||||
assert.match(message, /本次待归集附件:1 份/)
|
||||
assert.match(message, new RegExp(`\\*\\*\\[确定\\]\\(${ATTACHMENT_ASSOCIATION_CONFIRM_HREF}\\)\\*\\*`))
|
||||
assert.match(rendered, /<strong><a href="#confirm-attachment-association" class="markdown-action-link markdown-action-link-confirm">确定<\/a><\/strong>/)
|
||||
})
|
||||
|
||||
test('OCR preview builders keep hotel receipt image previews when preview kind is omitted', () => {
|
||||
@@ -137,6 +168,26 @@ test('OCR preview builders keep hotel receipt image previews when preview kind i
|
||||
assert.deepEqual(reviewPreviews, [{ filename: 'hotel.png', kind: 'image', url: dataUrl }])
|
||||
})
|
||||
|
||||
test('file preview cache replaces temporary object urls and never persists them', () => {
|
||||
const merged = mergeFilePreviews(
|
||||
[
|
||||
{ filename: 'invoice.pdf', kind: 'pdf', url: 'blob:http://localhost/old-preview' },
|
||||
{ filename: 'hotel.png', kind: 'image', url: 'data:image/png;base64,stable' }
|
||||
],
|
||||
[
|
||||
{ filename: 'invoice.pdf', kind: 'pdf', url: 'blob:http://localhost/new-preview' },
|
||||
{ filename: 'hotel.png', kind: 'image' }
|
||||
]
|
||||
)
|
||||
|
||||
assert.equal(merged.length, 2)
|
||||
assert.equal(merged[0].url, 'blob:http://localhost/new-preview')
|
||||
assert.equal(merged[1].url, 'data:image/png;base64,stable')
|
||||
assert.deepEqual(filterPersistableFilePreviews(merged), [
|
||||
{ filename: 'hotel.png', kind: 'image', url: 'data:image/png;base64,stable' }
|
||||
])
|
||||
})
|
||||
|
||||
test('draft association query keeps a single candidate selectable in the conversation', () => {
|
||||
const payload = buildDraftAssociationQueryPayload([
|
||||
{
|
||||
@@ -182,6 +233,30 @@ test('expense query payload keeps structured risk items for claim-level risk dri
|
||||
assert.equal(payload.records[0].riskItems[0].summary, '住宿金额超过城市标准')
|
||||
})
|
||||
|
||||
test('expense query info items render as prompts instead of low risk', () => {
|
||||
const payload = normalizeExpenseQueryPayload({
|
||||
result_type: 'expense_claim_list',
|
||||
records: [
|
||||
{
|
||||
claim_id: 'claim-info',
|
||||
claim_no: 'EXP-202605-010',
|
||||
amount: 59.1,
|
||||
risk_flags: [
|
||||
{
|
||||
key: 'normal-tip',
|
||||
level: 'info',
|
||||
title: '票据提示',
|
||||
summary: '票据已识别,当前没有异常。'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
assert.equal(payload.records[0].riskItems[0].levelLabel, '提示')
|
||||
assert.notEqual(payload.records[0].riskItems[0].levelLabel, '低风险')
|
||||
})
|
||||
|
||||
test('expense query hint guides users to the reimbursement center after the top five results', () => {
|
||||
const payload = normalizeExpenseQueryPayload({
|
||||
result_type: 'expense_claim_list',
|
||||
|
||||
Reference in New Issue
Block a user