refactor: enforce 800 line source limits
This commit is contained in:
195
web/src/composables/workbenchAiMode/workbenchAiMessageModel.js
Normal file
195
web/src/composables/workbenchAiMode/workbenchAiMessageModel.js
Normal file
@@ -0,0 +1,195 @@
|
||||
export const AI_ATTACHMENT_ASSOCIATION_CONFIRM_ACTION = 'confirm_ai_attachment_association'
|
||||
export const AI_ATTACHMENT_OCR_DETAIL_ACTION = 'show_ai_attachment_ocr_details'
|
||||
|
||||
function normalizeParagraphs(content) {
|
||||
return String(content || '')
|
||||
.split(/\n{2,}|\n/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function stripInlineAssociationMarkdown(value = '') {
|
||||
return String(value || '')
|
||||
.replace(/\*\*/g, '')
|
||||
.replace(/`/g, '')
|
||||
.trim()
|
||||
}
|
||||
|
||||
export function resolveLegacyAiAttachmentAssociationPayload(content = '') {
|
||||
const text = String(content || '')
|
||||
if (!/我已先识别票据,并(?:匹配到最可能的报销单|找到一张可能关联的报销单)/.test(text)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const claimNo = stripInlineAssociationMarkdown(
|
||||
text.match(/推荐关联[::]\s*([^\n]+)/u)?.[1] || ''
|
||||
)
|
||||
if (!claimNo) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
claim_no: claimNo,
|
||||
document_type: 'expense'
|
||||
}
|
||||
}
|
||||
|
||||
export function hydrateInlineAttachmentAssociationSuggestedActions(actions = [], content = '') {
|
||||
const safeActions = Array.isArray(actions) ? actions : []
|
||||
const hasConfirmAction = safeActions.some(
|
||||
(action) => String(action?.action_type || '').trim() === AI_ATTACHMENT_ASSOCIATION_CONFIRM_ACTION
|
||||
)
|
||||
if (hasConfirmAction) {
|
||||
return safeActions
|
||||
}
|
||||
|
||||
const payload = resolveLegacyAiAttachmentAssociationPayload(content)
|
||||
if (!payload) {
|
||||
return safeActions
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
label: '确认自动关联',
|
||||
description: '将本次票据自动归集到推荐单据。',
|
||||
icon: 'mdi mdi-link-variant',
|
||||
action_type: AI_ATTACHMENT_ASSOCIATION_CONFIRM_ACTION,
|
||||
payload
|
||||
},
|
||||
...safeActions
|
||||
]
|
||||
}
|
||||
|
||||
function normalizeInlineAttachmentOcrField(field = {}) {
|
||||
if (!field || typeof field !== 'object') {
|
||||
return null
|
||||
}
|
||||
const value = String(field.value ?? field.text ?? '').trim()
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
label: String(field.label || field.key || field.name || '识别字段').trim() || '识别字段',
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeInlineAttachmentOcrDocument(document = {}, index = 0) {
|
||||
const fields = (Array.isArray(document?.document_fields) ? document.document_fields : document?.fields || [])
|
||||
.map((field) => normalizeInlineAttachmentOcrField(field))
|
||||
.filter(Boolean)
|
||||
.slice(0, 12)
|
||||
const summary = String(document?.summary || document?.text || '').replace(/\s+/g, ' ').trim()
|
||||
const filename = String(document?.filename || document?.name || '').trim()
|
||||
if (!filename && !summary && !fields.length) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
filename: filename || `附件 ${index + 1}`,
|
||||
summary,
|
||||
fields
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeInlineAttachmentOcrDetails(details = null) {
|
||||
if (!details || typeof details !== 'object') {
|
||||
return null
|
||||
}
|
||||
const documents = (Array.isArray(details.documents) ? details.documents : details.ocrDocuments || [])
|
||||
.map((document, index) => normalizeInlineAttachmentOcrDocument(document, index))
|
||||
.filter(Boolean)
|
||||
const fileNames = (Array.isArray(details.fileNames) ? details.fileNames : [])
|
||||
.map((name) => String(name || '').trim())
|
||||
.filter(Boolean)
|
||||
if (!documents.length && !fileNames.length) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
fileNames,
|
||||
documents
|
||||
}
|
||||
}
|
||||
|
||||
export function buildInlineAttachmentOcrDetails(collected = {}, files = []) {
|
||||
return normalizeInlineAttachmentOcrDetails({
|
||||
fileNames: files.map((file) => file?.name || '').filter(Boolean),
|
||||
documents: collected?.ocrDocuments || []
|
||||
})
|
||||
}
|
||||
|
||||
export function formatMessageTime(timestamp) {
|
||||
if (!timestamp) return ''
|
||||
return new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
|
||||
export function createWorkbenchAiMessageRuntime() {
|
||||
let messageSeq = 0
|
||||
|
||||
function nextMessageId() {
|
||||
messageSeq += 1
|
||||
return `${Date.now()}-${messageSeq}`
|
||||
}
|
||||
|
||||
function createAiAttachmentAssociationId() {
|
||||
messageSeq += 1
|
||||
return `ai-attachment-${Date.now()}-${messageSeq}`
|
||||
}
|
||||
|
||||
function createInlineMessage(role, content, options = {}) {
|
||||
const normalizedContent = String(content || '').trim()
|
||||
const suggestedActions = Array.isArray(options.suggestedActions) ? options.suggestedActions : []
|
||||
return {
|
||||
id: options.id || nextMessageId(),
|
||||
role,
|
||||
content: normalizedContent,
|
||||
paragraphs: normalizeParagraphs(normalizedContent),
|
||||
pending: Boolean(options.pending),
|
||||
feedback: String(options.feedback || ''),
|
||||
stewardPlan: options.stewardPlan || null,
|
||||
suggestedActions: role === 'assistant'
|
||||
? hydrateInlineAttachmentAssociationSuggestedActions(suggestedActions, normalizedContent)
|
||||
: suggestedActions,
|
||||
applicationPreview: options.applicationPreview || null,
|
||||
draftPayload: options.draftPayload || null,
|
||||
attachmentOcrDetails: normalizeInlineAttachmentOcrDetails(options.attachmentOcrDetails || null),
|
||||
text: options.text || normalizedContent,
|
||||
createdAt: options.createdAt || Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeRuntimeMessage(message = {}) {
|
||||
return createInlineMessage(message.role || 'assistant', message.content || '', {
|
||||
id: message.id,
|
||||
pending: false,
|
||||
feedback: message.feedback || '',
|
||||
stewardPlan: message.stewardPlan || null,
|
||||
suggestedActions: Array.isArray(message.suggestedActions) ? message.suggestedActions : [],
|
||||
applicationPreview: message.applicationPreview || null,
|
||||
draftPayload: message.draftPayload || null,
|
||||
attachmentOcrDetails: message.attachmentOcrDetails || null,
|
||||
text: message.text || message.content || ''
|
||||
})
|
||||
}
|
||||
|
||||
function serializeRuntimeMessage(message = {}) {
|
||||
return {
|
||||
id: message.id,
|
||||
role: message.role,
|
||||
content: message.content,
|
||||
text: message.text || message.content || '',
|
||||
feedback: message.feedback || '',
|
||||
stewardPlan: message.stewardPlan || null,
|
||||
suggestedActions: Array.isArray(message.suggestedActions) ? message.suggestedActions : [],
|
||||
applicationPreview: message.applicationPreview || null,
|
||||
draftPayload: message.draftPayload || null,
|
||||
attachmentOcrDetails: message.attachmentOcrDetails || null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
createAiAttachmentAssociationId,
|
||||
createInlineMessage,
|
||||
normalizeRuntimeMessage,
|
||||
serializeRuntimeMessage
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user