Files
X-Financial/web/src/composables/workbenchAiMode/workbenchAiComposerModel.js
caoxiaozhu 8417a9f542 feat(web): 设置中心缓存管理与文件预览资产工具
- 新增 documentPreviewAssets 工具,统一从 URL/Blob/File 推断预览类型(image/pdf/file/unsupported)
- SettingsView/SettingsView.js/settingsModelHelper 新增系统缓存管理区块,调用 /settings/cache/clear 并展示清理结果;useSettings/services 适配
- WorkbenchAiFilePreviewDialog/useWorkbenchAiFilePreview 接入预览资产工具,workbenchAiComposerModel 调整文件处理
- ReceiptFolder/LogDetailView/DigitalEmployeeWorkRecords/travelReimbursementAttachmentModel 配套适配
- 新增 settings-cache-management-section 测试,更新 settings-llm/rendering/receipt-folder-view/composer-components/attachment-association 测试
2026-06-24 12:35:59 +08:00

116 lines
4.4 KiB
JavaScript

import { buildFileIdentity } from '../../views/scripts/travelReimbursementAttachmentModel.js'
import { resolveDocumentPreviewAsset } from '../../utils/documentPreviewAssets.js'
export const AI_COMPOSER_FILE_TYPE_META = {
pdf: { label: 'PDF', icon: 'mdi mdi-file-pdf-box', tone: 'pdf' },
image: { label: '图片', icon: 'mdi mdi-file-image-outline', tone: 'image' },
spreadsheet: { label: '表格', icon: 'mdi mdi-file-excel-outline', tone: 'spreadsheet' },
document: { label: '文档', icon: 'mdi mdi-file-document-outline', tone: 'document' },
archive: { label: '压缩包', icon: 'mdi mdi-folder-zip-outline', tone: 'archive' },
file: { label: '文件', icon: 'mdi mdi-file-outline', tone: 'file' }
}
export const AI_MODE_ACTION_ITEMS = [
{
label: '发起报销',
icon: 'mdi mdi-file-document-plus-outline',
prompt: '帮我发起一笔报销,并检查需要准备哪些票据材料。',
source: 'workbench',
sessionType: 'expense'
},
{
label: '查询预算',
icon: 'mdi mdi-chart-pie-outline',
prompt: '帮我查询当前预算余额和近期费用占用情况。',
source: 'budget',
sessionType: 'budget'
},
{
label: '解释制度',
icon: 'mdi mdi-book-open-page-variant-outline',
prompt: '帮我解释公司报销制度,并列出这次需要注意的条款。',
source: 'workbench',
sessionType: 'knowledge'
},
{
label: '催办审批',
icon: 'mdi mdi-bell-ring-outline',
prompt: '帮我查询待审批单据,并生成一段礼貌的催办说明。',
source: 'workbench',
sessionType: 'approval'
}
]
export function resolveAiComposerFileName(file) {
return String(file?.name || '未命名附件').trim() || '未命名附件'
}
export function resolveAiComposerFileType(file, previewAsset = null) {
if (previewAsset?.kind === 'image') {
return AI_COMPOSER_FILE_TYPE_META.image
}
if (previewAsset?.kind === 'pdf') {
return AI_COMPOSER_FILE_TYPE_META.pdf
}
const fileName = resolveAiComposerFileName(file).toLowerCase()
const mimeType = String(file?.type || '').toLowerCase()
const extension = fileName.includes('.') ? fileName.split('.').pop() : ''
if (extension === 'pdf' || mimeType.includes('pdf')) {
return AI_COMPOSER_FILE_TYPE_META.pdf
}
if (/^(png|jpe?g|gif|webp|bmp|svg|heic)$/.test(extension) || mimeType.startsWith('image/')) {
return AI_COMPOSER_FILE_TYPE_META.image
}
if (/^(xls|xlsx|csv|numbers)$/.test(extension) || mimeType.includes('spreadsheet') || mimeType.includes('excel')) {
return AI_COMPOSER_FILE_TYPE_META.spreadsheet
}
if (/^(doc|docx|txt|md|pages)$/.test(extension) || mimeType.includes('word') || mimeType.includes('text')) {
return AI_COMPOSER_FILE_TYPE_META.document
}
if (/^(zip|rar|7z|tar|gz)$/.test(extension) || mimeType.includes('zip') || mimeType.includes('compressed')) {
return AI_COMPOSER_FILE_TYPE_META.archive
}
return AI_COMPOSER_FILE_TYPE_META.file
}
export function buildSelectedFileCards(files = [], resolveRecognitionState = null) {
return files.map((file, index) => {
const recognitionState = typeof resolveRecognitionState === 'function'
? resolveRecognitionState(file, index)
: null
const previewAsset = resolveDocumentPreviewAsset(recognitionState?.document || null)
return {
key: buildFileIdentity(file),
name: resolveAiComposerFileName(file),
...resolveAiComposerFileType(file, previewAsset),
previewAsset: previewAsset || null
}
})
}
export function isLikelyAiModeOcrFile(file = {}) {
const name = String(file?.name || '').trim()
const type = String(file?.type || '').trim()
return /\.(pdf|jpe?g|png|webp|bmp)$/i.test(name) || /^(image\/|application\/pdf)/i.test(type)
}
export function isLikelyReceiptAssociationFile(file = {}) {
return isLikelyAiModeOcrFile(file)
}
export function shouldKeepAiAttachmentInAssistantReply(prompt = '') {
const compact = String(prompt || '').replace(/\s+/g, '')
return /(OCR|ocr|识别|票面|票据内容|发票内容|文字|读一下|看一下)/.test(compact)
}
export function shouldRunAiAttachmentAutoAssociation(entry = {}, files = [], prompt = '') {
return Boolean(
Array.isArray(files) &&
files.length &&
files.every((file) => isLikelyReceiptAssociationFile(file)) &&
!shouldKeepAiAttachmentInAssistantReply(prompt) &&
String(entry?.sessionType || '').trim() === 'steward'
)
}