import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { useSystemState } from '../useSystemState.js' import { useToast } from '../useToast.js' import { useWorkbenchComposerDate } from '../useWorkbenchComposerDate.js' import { fetchSettings } from '../../services/settings.js' import { calculateTravelReimbursement, fetchExpenseClaimDetail } from '../../services/reimbursements.js' import { useApplicationPreviewEditor } from '../../views/scripts/useApplicationPreviewEditor.js' import { deleteAiWorkbenchConversation, markAiWorkbenchConversationDocumentDeleted } from '../../utils/aiWorkbenchConversationStore.js' import { renderAiConversationHtml } from '../../utils/aiConversationHtmlRenderer.js' import { buildAiDocumentDetailRequest, parseAiApplicationDetailHref, parseAiDocumentDetailHref } from '../../utils/aiDocumentDetailReference.js' import { AI_MODE_ACTION_ITEMS, shouldRunAiAttachmentAutoAssociation } from './workbenchAiComposerModel.js' import { createWorkbenchAiMessageRuntime, formatMessageTime } from './workbenchAiMessageModel.js' import { useWorkbenchAiActionRouter } from './useWorkbenchAiActionRouter.js' import { useWorkbenchAiAttachmentAssociationFlow } from './useWorkbenchAiAttachmentAssociationFlow.js' import { useWorkbenchAiApplicationPreviewFlow } from './useWorkbenchAiApplicationPreviewFlow.js' import { useWorkbenchAiComposerFiles } from './useWorkbenchAiComposerFiles.js' import { useWorkbenchAiDocumentQueryFlow } from './useWorkbenchAiDocumentQueryFlow.js' import { useWorkbenchAiExpenseFlow } from './useWorkbenchAiExpenseFlow.js' import { useWorkbenchAiFilePreview } from './useWorkbenchAiFilePreview.js' import { useWorkbenchAiMessageActions } from './useWorkbenchAiMessageActions.js' import { useWorkbenchAiMessageExpansion } from './useWorkbenchAiMessageExpansion.js' import { useWorkbenchAiSessionCommands } from './useWorkbenchAiSessionCommands.js' import { useWorkbenchAiStewardFlow } from './useWorkbenchAiStewardFlow.js' import { isReimbursementCreationIntent } from './workbenchAiApplicationGateModel.js' import { useWorkbenchAiCommandIntents } from './useWorkbenchAiCommandIntents.js' import { shouldRequestWorkbenchAiIntentPlan } from './workbenchAiIntentPlannerModel.js' import { useWorkbenchAiConversationRuntime } from './useWorkbenchAiConversationRuntime.js' import { useWorkbenchAiIntentExecution } from './useWorkbenchAiIntentExecution.js' const AI_SEARCH_CONVERSATION_ID = 'ai-search' export function usePersonalWorkbenchAiMode(props, emit) { const { currentUser } = useSystemState() const { toast } = useToast() const assistantDraft = ref('') const assistantInputRef = ref(null) const fileInputRef = ref(null) const conversationScrollRef = ref(null) const inlineConversationAutoScrollPinned = ref(true) const selectedFiles = ref([]) const systemSettings = ref(null) const conversationStarted = ref(false) const conversationMessages = ref([]) const conversationId = ref('') const activeConversationTitle = ref('') const sending = ref(false) const stewardState = ref(null) const aiExpenseDraft = ref(null) const thinkingExpandedMessageIds = ref(new Set()) const thinkingCollapsedMessageIds = ref(new Set()) const attachmentOcrExpandedMessageIds = ref(new Set()) const deleteDialogOpen = ref(false) const applicationSubmitConfirmOpen = ref(false) const applicationSubmitConfirmContext = ref(null) const aiAttachmentAssociationRuntime = new Map() const messageRuntime = createWorkbenchAiMessageRuntime() const { createAiAttachmentAssociationId, createInlineMessage, normalizeRuntimeMessage, serializeRuntimeMessage } = messageRuntime const { applicationPreviewEditor, resolveApplicationPreviewEditorControl, resolveApplicationPreviewEditorDateMax, resolveApplicationPreviewEditorDateMin, resolveApplicationPreviewEditorOptions, refreshApplicationPreviewEstimate, isApplicationPreviewEditing, openApplicationPreviewEditor, commitApplicationPreviewEditor, cancelApplicationPreviewEditor, handleApplicationPreviewEditorKeydown } = useApplicationPreviewEditor({ persistSessionState: () => persistCurrentConversation(), toast, calculateTravelReimbursement, currentUser }) const { workbenchDatePickerOpen, workbenchDateMode, workbenchSingleDate, workbenchRangeStartDate, workbenchRangeEndDate, workbenchDateTagLabel, workbenchCanApplyDateSelection, clearWorkbenchDateSelection, toggleWorkbenchDatePicker, closeWorkbenchDatePicker, setWorkbenchDateMode, handleWorkbenchDatePickerOutside, applyWorkbenchDateSelection, handleWorkbenchDateInputChange, removeWorkbenchDateTag, buildWorkbenchPromptText } = useWorkbenchComposerDate({ draft: assistantDraft, focusInput: () => focusAiModeInput() }) const conversationRuntime = useWorkbenchAiConversationRuntime({ activeConversationTitle, applicationSubmitConfirmContext, applicationSubmitConfirmOpen, assistantDraft, assistantInputRef, attachmentOcrExpandedMessageIds, clearAiModeFiles: () => filesFlow.clearAiModeFiles(), clearWorkbenchDateSelection, conversationId, conversationMessages, conversationScrollRef, conversationStarted, currentUser, deleteDialogOpen, emit, inlineConversationAutoScrollPinned, searchConversationId: AI_SEARCH_CONVERSATION_ID, serializeRuntimeMessage, stewardState, thinkingCollapsedMessageIds, thinkingExpandedMessageIds }) const { activateInlineConversation, appendInlineMessageContent, focusAiModeInput, handleInlineConversationScroll, persistCurrentConversation, refreshConversationHistory, replaceInlineMessage, resetInlineConversationState, scrollInlineConversationToBottom, scrollInlineConversationToTop, setAssistantInputRef, streamInlineAssistantContent, streamOrSetInlineAssistantContent, updateInlineMessageContent } = conversationRuntime const aiModeActionItems = AI_MODE_ACTION_ITEMS const displayUserName = computed(() => { const user = currentUser.value || {} return String(user.name || user.username || '同事').trim() || '同事' }) const displayModelName = computed(() => { const llmForm = systemSettings.value?.llmForm if (!llmForm) return 'Axiom Ultra 3.1' const model = llmForm.mainModel || '' const provider = llmForm.mainProvider || '' if (!model) return 'Axiom Ultra 3.1' return provider ? `${provider} / ${model}` : model }) const modelSelectorTitle = computed(() => { const llmForm = systemSettings.value?.llmForm if (!llmForm) return '当前模型:Axiom Ultra 3.1' const model = llmForm.mainModel || 'Axiom Ultra 3.1' const provider = llmForm.mainProvider || '' return provider ? `当前模型:${provider} / ${model}` : `当前模型:${model}` }) const filesFlow = useWorkbenchAiComposerFiles({ fileInputRef, focusAiModeInput, isInputLocked: () => isAiModeInputLocked.value, resolveInputLockedMessage: () => resolveAiModeInputLockMessage(), selectedFiles, toast }) const documentQueryFlow = useWorkbenchAiDocumentQueryFlow({ conversationMessages, createInlineMessage, inlineConversationAutoScrollPinned, persistCurrentConversation, replaceInlineMessage, scrollInlineConversationToBottom }) const commandIntents = useWorkbenchAiCommandIntents({ activateInlineConversation, activeConversationTitle, assistantDraft, clearAiModeFiles: filesFlow.clearAiModeFiles, closeWorkbenchDatePicker, conversationId, conversationMessages, createInlineMessage, documentQueryFlow, inlineConversationAutoScrollPinned, persistCurrentConversation, removeWorkbenchDateTag, scrollInlineConversationToBottom, searchConversationId: AI_SEARCH_CONVERSATION_ID, sending }) const attachmentFlow = useWorkbenchAiAttachmentAssociationFlow({ aiAttachmentAssociationRuntime, conversationId, conversationMessages, createAiAttachmentAssociationId, createInlineMessage, inlineConversationAutoScrollPinned, persistCurrentConversation, replaceInlineMessage, scrollInlineConversationToBottom, sending, streamOrSetInlineAssistantContent, notifyRequestUpdated: (payload) => emit('request-updated', payload), toast }) const filePreview = useWorkbenchAiFilePreview({ attachmentFlow, conversationStarted, scrollInlineConversationToBottom, selectedFiles }) const { hasInlineAttachmentOcrDetails, hasInlineThinking, isInlineAttachmentOcrExpanded, isInlineThinkingExpanded, resolveInlineAttachmentOcrDocuments, resolveInlineAttachmentOcrFileCount, resolveInlineThinkingEvents, toggleInlineAttachmentOcrDetails, toggleInlineThinking } = useWorkbenchAiMessageExpansion({ attachmentOcrExpandedMessageIds, inlineConversationAutoScrollPinned, scrollInlineConversationToBottom, thinkingCollapsedMessageIds, thinkingExpandedMessageIds }) let intentExecution = null const applicationFlow = useWorkbenchAiApplicationPreviewFlow({ activateInlineConversation, applicationPreviewEditor, applicationSubmitConfirmContext, applicationSubmitConfirmOpen, assistantDraft, cancelApplicationPreviewEditor, clearAiModeFiles: filesFlow.clearAiModeFiles, closeWorkbenchDatePicker, commitApplicationPreviewEditor, conversationId, conversationMessages, conversationStarted, createInlineMessage, currentUser, handleApplicationPreviewEditorKeydown, inlineConversationAutoScrollPinned, isApplicationPreviewEditing, openApplicationPreviewEditor, persistCurrentConversation, pushInlineApplicationActionUserMessage, pushInlineUserMessage, refreshApplicationPreviewEstimate, removeWorkbenchDateTag, replaceInlineMessage, resolveApplicationPreviewEditorDateMax, resolveApplicationPreviewEditorDateMin, resolveApplicationPreviewEditorControl, resolveApplicationPreviewEditorOptions, resolveInlineThinkingEvents, resolveLatestInlineUserPrompt, scrollInlineConversationToBottom, sending, toast, onApplicationActionCompleted: (remainingTasks) => intentExecution?.startModelPlannedNextTask(remainingTasks) }) const expenseFlow = useWorkbenchAiExpenseFlow({ activateInlineConversation, aiExpenseDraft, assistantDraft, clearAiModeFiles: filesFlow.clearAiModeFiles, closeWorkbenchDatePicker, conversationMessages, conversationStarted, createInlineMessage, currentUser, persistCurrentConversation, pushInlineUserMessage, replaceInlineMessage, removeWorkbenchDateTag, resolveLatestInlineUserPrompt, scrollInlineConversationToBottom, startAiApplicationPreview: applicationFlow.startAiApplicationPreview }) const actionRouter = useWorkbenchAiActionRouter({ aiExpenseDraft, applicationFlow, assistantDraft, attachmentFlow, conversationMessages, createInlineMessage, emit, expenseFlow, focusAiModeInput, hasInlineAttachmentOcrDetails, persistCurrentConversation, replaceInlineMessage, resolveLatestInlineUserPrompt, scrollInlineConversationToBottom, selectedFiles, startInlineConversation, toast, toggleInlineAttachmentOcrDetails }) const sessionCommands = useWorkbenchAiSessionCommands({ activeConversationTitle, attachmentOcrExpandedMessageIds, conversationId, conversationMessages, conversationStarted, createInlineMessage, currentUser, deleteAiWorkbenchConversation, emit, focusAiModeInput, inlineConversationAutoScrollPinned, normalizeRuntimeMessage, refreshConversationHistory, resetInlineConversationState, scrollInlineConversationToBottom, stewardState, thinkingCollapsedMessageIds, thinkingExpandedMessageIds, toast }) const messageActions = useWorkbenchAiMessageActions({ assistantDraft, focusAiModeInput, persistCurrentConversation, toast }) const stewardFlow = useWorkbenchAiStewardFlow({ activeConversationTitle, collectAiModeReceiptContext: attachmentFlow.collectAiModeReceiptContext, conversationId, conversationMessages, createInlineMessage, currentUser, deleteAiWorkbenchConversation, emit, handleAiDocumentQueryIntent: documentQueryFlow.handleAiDocumentQueryIntent, inlineConversationAutoScrollPinned, persistCurrentConversation, replaceInlineMessage, resolveInlineThinkingEvents, scrollInlineConversationToBottom, sending, stewardState, streamInlineAssistantContent, updateInlineMessageContent, appendInlineMessageContent, toast }) intentExecution = useWorkbenchAiIntentExecution({ actionRouter, activeConversationTitle, activateInlineConversation, applicationFlow, assistantDraft, clearAiModeFiles: filesFlow.clearAiModeFiles, closeWorkbenchDatePicker, conversationId, conversationMessages, createInlineMessage, expenseFlow, inlineConversationAutoScrollPinned, persistCurrentConversation, removeWorkbenchDateTag, replaceInlineMessage, resolveInlineThinkingEvents, scrollInlineConversationToBottom, searchConversationId: AI_SEARCH_CONVERSATION_ID, sending, stewardFlow }) const applicationPreviewEstimatePending = computed(() => ( conversationMessages.value.some((message) => applicationFlow.isApplicationPreviewEstimatePending(message)) )) const isAiModeReceiptRecognitionPending = computed(() => attachmentFlow.hasPendingAiModeReceiptRecognition(selectedFiles.value)) const hasAiModeReceiptRecognitionFailure = computed(() => attachmentFlow.hasFailedAiModeReceiptRecognition(selectedFiles.value)) const isAiModeInputLocked = computed(() => applicationPreviewEstimatePending.value || isAiModeReceiptRecognitionPending.value) const aiModeInputLockMessage = computed(() => resolveAiModeInputLockMessage()) const canSubmitAiModePrompt = computed(() => ( !isAiModeInputLocked.value && !hasAiModeReceiptRecognitionFailure.value && ( Boolean(assistantDraft.value.trim()) || selectedFiles.value.length > 0 || Boolean(workbenchDateTagLabel.value) ) )) async function loadSystemSettings() { try { systemSettings.value = await fetchSettings() } catch { systemSettings.value = { llmForm: {} } } } function renderInlineConversationHtml(content) { return renderAiConversationHtml(content) } function isUnavailableDocumentDetailError(error) { const message = String(error?.message || '').trim() return /not\s*found|不存在|已删除|不可访问|无权|forbidden|404/i.test(message) } function resolveDocumentDetailLookupClaimId(detailRequest = {}) { return String(detailRequest.claimId || detailRequest.claim_id || '').trim() } function markActiveAiDocumentDetailLinkDeleted(detailRequest = {}) { const nextHistory = markAiWorkbenchConversationDocumentDeleted(currentUser.value || {}, { claimId: detailRequest.claimId, claim_id: detailRequest.claimId, claimNo: detailRequest.claimNo, claim_no: detailRequest.claimNo, documentNo: detailRequest.documentNo, document_no: detailRequest.documentNo, id: detailRequest.id }) emit('conversation-history-change', nextHistory) const activeConversation = nextHistory.find((item) => ( String(item.id || item.conversationId || '').trim() === String(conversationId.value || '').trim() )) if (activeConversation?.messages?.length) { conversationMessages.value = activeConversation.messages.map((message) => normalizeRuntimeMessage(message)) scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value }) } } async function ensureAiDocumentDetailStillAvailable(detailRequest = {}) { const claimId = resolveDocumentDetailLookupClaimId(detailRequest) if (!claimId) { return true } try { await fetchExpenseClaimDetail(claimId) return true } catch (error) { if (!isUnavailableDocumentDetailError(error)) { console.warn('AI document detail availability check failed, continuing navigation:', error) return true } markActiveAiDocumentDetailLinkDeleted(detailRequest) toast('该单据已经删除或不可访问,已将这条历史入口标记为不可查看。') return false } } function buildInlinePromptText(rawPrompt, files = []) { const prompt = buildWorkbenchPromptText(rawPrompt) if (prompt) return prompt return files.length ? '请帮我处理已上传的附件。' : '' } function resolveAiModeInputLockMessage() { if (isAiModeReceiptRecognitionPending.value) { return '附件识别中,请稍等...' } if (applicationPreviewEstimatePending.value) { return '费用测算中,请稍等...' } return '' } function resolveAiModeSubmitBlockedMessage() { if (applicationPreviewEstimatePending.value) { return '请等待费用测算完成后再继续操作。' } if (isAiModeReceiptRecognitionPending.value) { return '附件 OCR 识别中,请稍等,识别完成后再继续对话。' } if (hasAiModeReceiptRecognitionFailure.value) { return '请先移除识别失败的附件或重新上传。' } return '' } function ensureAiModeCanStartConversation() { const blockedMessage = resolveAiModeSubmitBlockedMessage() if (!blockedMessage) { return true } toast(blockedMessage) focusAiModeInput() return false } async function handleAiAnswerMarkdownClick(event) { const target = event?.target const link = target?.closest?.('a[href^="#ai-open-document-detail:"], a[href^="#ai-open-application-detail:"]') if (!link) { return } const href = link.getAttribute('href') const detailReference = parseAiDocumentDetailHref(href) || parseAiApplicationDetailHref(href) if (!detailReference) { return } event.preventDefault() event.stopPropagation() const detailRequest = buildAiDocumentDetailRequest(detailReference) if (!(await ensureAiDocumentDetailStillAvailable(detailRequest))) { return } emit('open-document', detailRequest) } function startInlineConversation(prompt, entry = {}, files = []) { if (!ensureAiModeCanStartConversation()) { return } const cleanPrompt = buildInlinePromptText(prompt, files) if (!cleanPrompt || sending.value) { return } if (commandIntents.handleInlineDraftDeletionIntent(cleanPrompt, entry)) { return } if (applicationFlow.handleInlineApplicationPreviewTextAction(cleanPrompt, applicationPreviewEstimatePending)) { return } if (aiExpenseDraft.value && !isAiExpenseDraftComplete(aiExpenseDraft.value)) { expenseFlow.advanceAiExpenseDraft(cleanPrompt, files) return } if (shouldRequestWorkbenchAiIntentPlan(cleanPrompt)) { void intentExecution.executeModelPlannedWorkbenchIntent(cleanPrompt, entry, files) return } if (isReimbursementCreationIntent(cleanPrompt)) { void expenseFlow.startAiReimbursementAssociationGate(cleanPrompt, entry.label || '我要报销') return } if (conversationId.value === AI_SEARCH_CONVERSATION_ID) { conversationId.value = '' conversationMessages.value = [] activeConversationTitle.value = '' } sending.value = true activateInlineConversation({ title: entry.label || cleanPrompt.slice(0, 18) || '新对话' }) inlineConversationAutoScrollPinned.value = true conversationMessages.value.push(createInlineMessage('user', cleanPrompt)) assistantDraft.value = '' removeWorkbenchDateTag() closeWorkbenchDatePicker() filesFlow.clearAiModeFiles() scrollInlineConversationToBottom() persistCurrentConversation() if (shouldRunAiAttachmentAutoAssociation(entry, files, cleanPrompt)) { void attachmentFlow.requestAiAttachmentAssociationReply(cleanPrompt, entry, files) return } void stewardFlow.requestInlineAssistantReply(cleanPrompt, entry, files) } function submitAiModePrompt() { if (!ensureAiModeCanStartConversation()) { return } if (!canSubmitAiModePrompt.value) { toast('请输入需求后再发送。') focusAiModeInput() return } startInlineConversation(assistantDraft.value, { source: 'workbench', sessionType: 'steward' }, Array.from(selectedFiles.value)) } function runAiModeAction(item) { if (!ensureAiModeCanStartConversation()) { return } if (String(item?.label || '').trim() === '发起报销') { void expenseFlow.startAiReimbursementAssociationGate(item.prompt, item.label) return } startInlineConversation(item.prompt, item, Array.from(selectedFiles.value)) } function regenerateLastReply() { const lastUserMessage = [...conversationMessages.value].reverse().find((message) => message.role === 'user') if (!lastUserMessage || sending.value) { return } const lastAssistantIndex = conversationMessages.value.map((message) => message.role).lastIndexOf('assistant') if (lastAssistantIndex >= 0) { conversationMessages.value.splice(lastAssistantIndex, 1) } sending.value = true void stewardFlow.requestInlineAssistantReply(lastUserMessage.content, { source: 'workbench', sessionType: 'steward' }, []) } function pushInlineUserMessage(text) { conversationMessages.value.push(createInlineMessage('user', String(text || '').trim())) } function pushInlineApplicationActionUserMessage(text) { pushInlineUserMessage(text) assistantDraft.value = '' removeWorkbenchDateTag() closeWorkbenchDatePicker() filesFlow.clearAiModeFiles() } function resolveLatestInlineUserPrompt() { return String([...conversationMessages.value].reverse().find((message) => message.role === 'user')?.content || '').trim() } function handleVoiceInput() { if (!ensureAiModeCanStartConversation()) { return } toast('语音输入正在准备中,您可以先输入文字需求。') focusAiModeInput() } watch( () => props.sidebarCommand?.seq, () => { const command = props.sidebarCommand || {} if (command.type === 'new-chat') { sessionCommands.startNewInlineConversation() return } if (command.type === 'search-chat') { sessionCommands.openInlineSearchConversation(activateInlineConversation) return } if (command.type === 'open-recent') { sessionCommands.openInlineRecentConversation(command.payload || {}) attachmentFlow.resumePendingAiAttachmentAssociationJobs() expenseFlow.resumePendingLinkedReimbursementDraftJobs() } } ) onMounted(() => { loadSystemSettings() refreshConversationHistory() attachmentFlow.resumePendingAiAttachmentAssociationJobs() expenseFlow.resumePendingLinkedReimbursementDraftJobs() document.addEventListener('click', handleWorkbenchDatePickerOutside) }) onBeforeUnmount(() => { document.removeEventListener('click', handleWorkbenchDatePickerOutside) }) return { activeConversationTitle, aiModeActionItems, aiModeInputLockMessage, applicationPreviewEditor, applicationSubmitConfirmOpen, assistantInputRef, assistantDraft, canShowInlineSuggestedActions: applicationFlow.canShowInlineSuggestedActions, canSubmitAiModePrompt, cancelDeleteConversation: () => sessionCommands.cancelDeleteConversation(deleteDialogOpen), cancelInlineApplicationSubmitConfirm: applicationFlow.cancelInlineApplicationSubmitConfirm, clearWorkbenchDateSelection, commitInlineApplicationPreviewEditor: applicationFlow.commitInlineApplicationPreviewEditor, confirmDeleteConversation: () => sessionCommands.confirmDeleteConversation(deleteDialogOpen), confirmInlineApplicationSubmit: applicationFlow.confirmInlineApplicationSubmit, conversationMessages, conversationScrollRef, conversationStarted, deleteDialogOpen, displayModelName, displayUserName, fileInputRef, handleAiAnswerMarkdownClick, handleAiModeFilesChange: filesFlow.handleAiModeFilesChange, handleInlineApplicationPreviewEditorKeydown: applicationFlow.handleInlineApplicationPreviewEditorKeydown, handleInlineConversationScroll, handleInlineSuggestedAction: actionRouter.handleInlineSuggestedAction, handleVoiceInput, hasInlineAttachmentOcrDetails, hasInlineThinking, isAiModeInputLocked, isApplicationPreviewEditing: applicationFlow.isApplicationPreviewEditing, isApplicationPreviewEstimatePending: applicationFlow.isApplicationPreviewEstimatePending, isInlineAttachmentOcrExpanded, isInlineSuggestedActionDisabled: applicationFlow.isInlineSuggestedActionDisabled, isInlineThinkingExpanded, markInlineMessageFeedback: messageActions.markInlineMessageFeedback, modelSelectorTitle, openApplicationPreviewEditor: applicationFlow.openApplicationPreviewEditor, quoteInlineMessage: messageActions.quoteInlineMessage, regenerateLastReply, removeAiModeFile: filesFlow.removeAiModeFile, removeWorkbenchDateTag, renderInlineConversationHtml, requestDeleteCurrentConversation: () => sessionCommands.requestDeleteCurrentConversation(deleteDialogOpen), resolveApplicationPreviewEditorOptions: applicationFlow.resolveApplicationPreviewEditorOptions, resolveInlineApplicationPreviewEditorControl: applicationFlow.resolveInlineApplicationPreviewEditorControl, resolveInlineApplicationPreviewEditorDateMax: applicationFlow.resolveInlineApplicationPreviewEditorDateMax, resolveInlineApplicationPreviewEditorDateMin: applicationFlow.resolveInlineApplicationPreviewEditorDateMin, resolveInlineApplicationPreviewMissingFields: applicationFlow.resolveInlineApplicationPreviewMissingFields, resolveInlineApplicationPreviewRows: applicationFlow.resolveInlineApplicationPreviewRows, resolveInlineAttachmentOcrDocuments, resolveInlineAttachmentOcrFileCount, resolveInlineThinkingEvents, runAiModeAction, scrollInlineConversationToTop, ...filePreview, sending, setAssistantInputRef, setWorkbenchDateMode, submitAiModePrompt, toggleInlineAttachmentOcrDetails, toggleInlineThinking, toggleWorkbenchDatePicker, triggerAiModeFileUpload: filesFlow.triggerAiModeFileUpload, workbenchCanApplyDateSelection, workbenchDateMode, workbenchDatePickerOpen, workbenchDateTagLabel, workbenchRangeEndDate, workbenchRangeStartDate, workbenchSingleDate, applyWorkbenchDateSelection, buildInlineApplicationPreviewFooterText: applicationFlow.buildInlineApplicationPreviewFooterText, copyInlineMessage: messageActions.copyInlineMessage, formatMessageTime, handleWorkbenchDateInputChange } }