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 测试
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||
import { buildSelectedFileCards } from './workbenchAiComposerModel.js'
|
||||
import { fetchReceiptFolderAsset } from '../../services/receiptFolder.js'
|
||||
import {
|
||||
inferPreviewKindFromBlob,
|
||||
inferPreviewKindFromFile,
|
||||
isInlinePreviewUrl,
|
||||
isTemporaryPreviewUrl,
|
||||
resolveDocumentPreviewAsset
|
||||
} from '../../utils/documentPreviewAssets.js'
|
||||
|
||||
function normalizePreviewText(value) {
|
||||
return String(value ?? '').replace(/\s+/g, ' ').trim()
|
||||
@@ -40,10 +48,6 @@ function normalizePreviewField(field = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDocumentPreviewUrl(document = null) {
|
||||
return normalizePreviewText(document?.preview_data_url || document?.previewDataUrl)
|
||||
}
|
||||
|
||||
function resolveSourceKind(sourceUrl, rawFile = {}) {
|
||||
const type = normalizePreviewText(rawFile?.type).toLowerCase()
|
||||
const name = normalizePreviewText(rawFile?.name).toLowerCase()
|
||||
@@ -72,8 +76,18 @@ export function useWorkbenchAiFilePreview({
|
||||
scrollInlineConversationToBottom,
|
||||
selectedFiles
|
||||
}) {
|
||||
const filePreviewState = ref({ open: false, key: '', objectUrl: '' })
|
||||
const selectedFileCards = computed(() => buildSelectedFileCards(selectedFiles.value).map((card, index) => ({
|
||||
const filePreviewState = ref({
|
||||
open: false,
|
||||
key: '',
|
||||
objectUrl: '',
|
||||
objectKind: '',
|
||||
objectSource: '',
|
||||
loading: false
|
||||
})
|
||||
const selectedFileCards = computed(() => buildSelectedFileCards(
|
||||
selectedFiles.value,
|
||||
(file) => attachmentFlow.resolveAiModeReceiptRecognitionState(file)
|
||||
).map((card, index) => ({
|
||||
...card,
|
||||
ocrState: attachmentFlow.resolveAiModeReceiptRecognitionState(selectedFiles.value[index])
|
||||
})))
|
||||
@@ -97,22 +111,94 @@ export function useWorkbenchAiFilePreview({
|
||||
}
|
||||
}
|
||||
|
||||
function resolveTargetDocument(target) {
|
||||
if (!target?.rawFile) {
|
||||
return null
|
||||
}
|
||||
const recognitionState = attachmentFlow.resolveAiModeReceiptRecognitionState(target.rawFile) || target.card.ocrState || null
|
||||
return recognitionState?.document || null
|
||||
}
|
||||
|
||||
function resolveRemotePreviewAsset(target) {
|
||||
const asset = resolveDocumentPreviewAsset(resolveTargetDocument(target))
|
||||
if (!asset?.url || isInlinePreviewUrl(asset.url) || isTemporaryPreviewUrl(asset.url)) {
|
||||
return null
|
||||
}
|
||||
return asset
|
||||
}
|
||||
|
||||
async function loadRemotePreviewAsset(fileKey) {
|
||||
const target = findSelectedFile(fileKey)
|
||||
const asset = resolveRemotePreviewAsset(target)
|
||||
if (!target?.rawFile || !asset?.url) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const blob = await fetchReceiptFolderAsset(asset.url)
|
||||
const objectUrl = createObjectUrl(blob)
|
||||
const objectKind = inferPreviewKindFromBlob(blob) || asset.kind
|
||||
if (!objectUrl || !['image', 'pdf'].includes(objectKind)) {
|
||||
return
|
||||
}
|
||||
if (!filePreviewState.value.open || filePreviewState.value.key !== fileKey) {
|
||||
URL.revokeObjectURL(objectUrl)
|
||||
return
|
||||
}
|
||||
clearFilePreviewObjectUrl()
|
||||
filePreviewState.value = {
|
||||
...filePreviewState.value,
|
||||
objectUrl,
|
||||
objectKind,
|
||||
objectSource: asset.source,
|
||||
loading: false
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('AI mode remote attachment preview unavailable:', error)
|
||||
if (!filePreviewState.value.open || filePreviewState.value.key !== fileKey) {
|
||||
return
|
||||
}
|
||||
clearFilePreviewObjectUrl()
|
||||
filePreviewState.value = {
|
||||
...filePreviewState.value,
|
||||
objectUrl: createObjectUrl(target.rawFile),
|
||||
objectKind: inferPreviewKindFromFile(target.rawFile),
|
||||
objectSource: 'file',
|
||||
loading: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openAiModeFilePreview(fileKey) {
|
||||
const target = findSelectedFile(fileKey)
|
||||
if (!target?.rawFile) {
|
||||
return
|
||||
}
|
||||
clearFilePreviewObjectUrl()
|
||||
const remoteAsset = resolveRemotePreviewAsset(target)
|
||||
filePreviewState.value = {
|
||||
open: true,
|
||||
key: fileKey,
|
||||
objectUrl: createObjectUrl(target.rawFile)
|
||||
objectUrl: remoteAsset ? '' : createObjectUrl(target.rawFile),
|
||||
objectKind: remoteAsset ? '' : inferPreviewKindFromFile(target.rawFile),
|
||||
objectSource: remoteAsset ? remoteAsset.source : 'file',
|
||||
loading: Boolean(remoteAsset)
|
||||
}
|
||||
if (remoteAsset) {
|
||||
void loadRemotePreviewAsset(fileKey)
|
||||
}
|
||||
}
|
||||
|
||||
function closeAiModeFilePreview() {
|
||||
clearFilePreviewObjectUrl()
|
||||
filePreviewState.value = { open: false, key: '', objectUrl: '' }
|
||||
filePreviewState.value = {
|
||||
open: false,
|
||||
key: '',
|
||||
objectUrl: '',
|
||||
objectKind: '',
|
||||
objectSource: '',
|
||||
loading: false
|
||||
}
|
||||
}
|
||||
|
||||
const activeAiModeFilePreview = computed(() => {
|
||||
@@ -128,9 +214,16 @@ export function useWorkbenchAiFilePreview({
|
||||
const document = recognitionState?.document || null
|
||||
const documentFields = Array.isArray(document?.document_fields) ? document.document_fields : document?.fields || []
|
||||
const ocrFields = documentFields.map((field) => normalizePreviewField(field)).filter(Boolean)
|
||||
const documentPreviewUrl = resolveDocumentPreviewUrl(document)
|
||||
const sourceUrl = documentPreviewUrl || filePreviewState.value.objectUrl
|
||||
const sourceKind = documentPreviewUrl ? 'image' : resolveSourceKind(sourceUrl, rawFile)
|
||||
const documentPreviewAsset = resolveDocumentPreviewAsset(document)
|
||||
const inlinePreviewAvailable = documentPreviewAsset?.url && isInlinePreviewUrl(documentPreviewAsset.url)
|
||||
const sourceUrl = inlinePreviewAvailable
|
||||
? documentPreviewAsset.url
|
||||
: filePreviewState.value.objectUrl
|
||||
const sourceKind = inlinePreviewAvailable
|
||||
? documentPreviewAsset.kind
|
||||
: filePreviewState.value.loading
|
||||
? 'loading'
|
||||
: filePreviewState.value.objectKind || resolveSourceKind(sourceUrl, rawFile)
|
||||
const documentTypeLabel = normalizePreviewText(
|
||||
document?.document_type_label ||
|
||||
document?.scene_label ||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
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' },
|
||||
@@ -44,7 +45,14 @@ export function resolveAiComposerFileName(file) {
|
||||
return String(file?.name || '未命名附件').trim() || '未命名附件'
|
||||
}
|
||||
|
||||
export function resolveAiComposerFileType(file) {
|
||||
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() : ''
|
||||
@@ -66,12 +74,19 @@ export function resolveAiComposerFileType(file) {
|
||||
return AI_COMPOSER_FILE_TYPE_META.file
|
||||
}
|
||||
|
||||
export function buildSelectedFileCards(files = []) {
|
||||
return files.map((file) => ({
|
||||
key: buildFileIdentity(file),
|
||||
name: resolveAiComposerFileName(file),
|
||||
...resolveAiComposerFileType(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 = {}) {
|
||||
|
||||
Reference in New Issue
Block a user