feat: 增强知识库索引与设置页面模块化拆分
扩展知识库索引任务和 RAG 检索支持增量入库和文档去重,优 化本体检测和规则匹配精度,前端设置页面拆分为 LLM、邮件 和 Hermes 员工同步子面板并重构样式,新增日志详情组件和 知识入库日志模型,补充单元测试覆盖。
This commit is contained in:
@@ -39,6 +39,26 @@ export const MAX_OCR_DOCUMENTS = 10
|
||||
export const VISIBLE_ATTACHMENT_CHIPS = 2
|
||||
export const ATTACHMENT_ASSOCIATION_CONFIRM_HREF = '#confirm-attachment-association'
|
||||
|
||||
export function buildUnsavedDraftAttachmentConfirmationMessage({ fileNames = [] } = {}) {
|
||||
const names = (Array.isArray(fileNames) ? fileNames : [])
|
||||
.map((item) => String(item || '').trim())
|
||||
.filter(Boolean)
|
||||
const attachmentLine = names.length
|
||||
? `本次待归集附件:${names.length} 份(${names.join('、')})`
|
||||
: '本次待归集附件:待识别'
|
||||
|
||||
return [
|
||||
'当前这笔报销信息还没有保存为草稿。',
|
||||
'',
|
||||
'如果继续上传票据,我需要先把当前已识别的信息保存成一张草稿单据,再识别并归集本次附件。',
|
||||
'',
|
||||
attachmentLine,
|
||||
'',
|
||||
'',
|
||||
`如果 **[确定](${ATTACHMENT_ASSOCIATION_CONFIRM_HREF})**,我会先保存这笔未保存单据,再把此次上传的附件归集到该单据。`
|
||||
].join('\n').trim()
|
||||
}
|
||||
|
||||
export function normalizeOcrDocuments(payload) {
|
||||
const documents = Array.isArray(payload?.documents) ? payload.documents : []
|
||||
return documents.slice(0, MAX_OCR_DOCUMENTS).map((item) => ({
|
||||
@@ -333,6 +353,10 @@ export function resolveDocumentPreview(filePreviews, filename) {
|
||||
)
|
||||
}
|
||||
|
||||
export function isTemporaryPreviewUrl(url) {
|
||||
return String(url || '').trim().toLowerCase().startsWith('blob:')
|
||||
}
|
||||
|
||||
export function buildFileIdentity(file) {
|
||||
return [file?.name, file?.size, file?.lastModified, file?.type].join('__')
|
||||
}
|
||||
@@ -374,18 +398,39 @@ export function mergeFilesWithLimit(existingFiles, incomingFiles, limit = MAX_AT
|
||||
|
||||
export function mergeFilePreviews(existingPreviews, incomingPreviews) {
|
||||
const result = []
|
||||
const seen = new Set()
|
||||
const indexByKey = new Map()
|
||||
|
||||
for (const preview of [...(existingPreviews || []), ...(incomingPreviews || [])]) {
|
||||
const key = [preview?.filename, preview?.kind].join('__')
|
||||
if (!preview?.filename || seen.has(key)) continue
|
||||
seen.add(key)
|
||||
result.push(preview)
|
||||
if (!preview?.filename) continue
|
||||
|
||||
const existingIndex = indexByKey.get(key)
|
||||
if (existingIndex === undefined) {
|
||||
indexByKey.set(key, result.length)
|
||||
result.push(preview)
|
||||
continue
|
||||
}
|
||||
|
||||
const existingPreview = result[existingIndex]
|
||||
const nextUrl = String(preview?.url || '').trim()
|
||||
const existingUrl = String(existingPreview?.url || '').trim()
|
||||
if (nextUrl && (!existingUrl || isTemporaryPreviewUrl(existingUrl) || nextUrl !== existingUrl)) {
|
||||
result[existingIndex] = preview
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function filterPersistableFilePreviews(filePreviews) {
|
||||
return (Array.isArray(filePreviews) ? filePreviews : [])
|
||||
.filter((preview) => {
|
||||
const filename = String(preview?.filename || '').trim()
|
||||
const url = String(preview?.url || '').trim()
|
||||
return filename && !isTemporaryPreviewUrl(url)
|
||||
})
|
||||
}
|
||||
|
||||
function inferPreviewKindFromUrl(url) {
|
||||
const normalized = String(url || '').trim().toLowerCase()
|
||||
if (!normalized) return ''
|
||||
|
||||
Reference in New Issue
Block a user