import { computed, nextTick, 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 } from '../../services/reimbursements.js' import { useApplicationPreviewEditor } from '../../views/scripts/useApplicationPreviewEditor.js' import { deleteAiWorkbenchConversation, loadAiWorkbenchConversationHistory, saveAiWorkbenchConversation } 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' const AI_SEARCH_CONVERSATION_ID = 'ai-search' const INLINE_ANSWER_STREAM_CHUNK_SIZE = 6 const INLINE_ANSWER_STREAM_DELAY_MS = 24 const INLINE_AUTO_SCROLL_THRESHOLD = 96 const INLINE_LAYOUT_SETTLE_SCROLL_DELAY_MS = 260 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 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 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 }) 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 }) 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, emit, expenseFlow, focusAiModeInput, hasInlineAttachmentOcrDetails, resolveLatestInlineUserPrompt, 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 }) 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 focusAiModeInput() { nextTick(() => { assistantInputRef.value?.focus() }) } function setAssistantInputRef(element) { assistantInputRef.value = element } function isInlineConversationNearBottom() { const el = conversationScrollRef.value if (!el) { return true } return el.scrollHeight - el.clientHeight - el.scrollTop <= INLINE_AUTO_SCROLL_THRESHOLD } function handleInlineConversationScroll() { inlineConversationAutoScrollPinned.value = isInlineConversationNearBottom() } function forceInlineConversationToBottom() { const el = conversationScrollRef.value if (el) { el.scrollTop = el.scrollHeight inlineConversationAutoScrollPinned.value = true } } function scrollInlineConversationToBottom(options = {}) { const shouldScroll = options.force !== false nextTick(() => { if (!shouldScroll) { return } forceInlineConversationToBottom() window.requestAnimationFrame(() => { forceInlineConversationToBottom() }) window.setTimeout(() => { if (inlineConversationAutoScrollPinned.value) { forceInlineConversationToBottom() } }, INLINE_LAYOUT_SETTLE_SCROLL_DELAY_MS) }) } function scrollInlineConversationToTop() { nextTick(() => { const el = conversationScrollRef.value if (el) { inlineConversationAutoScrollPinned.value = false el.scrollTo({ top: 0, behavior: 'smooth' }) } }) } function updateInlineMessageContent(message, content) { if (!message) { return } message.content = String(content || '') message.paragraphs = String(message.content || '') .split(/\n{2,}|\n/) .map((item) => item.trim()) .filter(Boolean) } function appendInlineMessageContent(message, delta) { const nextDelta = String(delta || '') if (!nextDelta) { return } updateInlineMessageContent(message, `${message.content || ''}${nextDelta}`) } function waitInlineAnswerStreamFrame() { return new Promise((resolve) => { window.setTimeout(resolve, INLINE_ANSWER_STREAM_DELAY_MS) }) } async function streamInlineAssistantContent(messageId, content) { const targetContent = String(content || '').trim() let streamedContent = '' for (let index = 0; index < targetContent.length; index += INLINE_ANSWER_STREAM_CHUNK_SIZE) { const message = conversationMessages.value.find((item) => item.id === messageId) if (!message || !message.pending) { return } const shouldAutoScroll = inlineConversationAutoScrollPinned.value streamedContent += targetContent.slice(index, index + INLINE_ANSWER_STREAM_CHUNK_SIZE) updateInlineMessageContent(message, streamedContent) scrollInlineConversationToBottom({ force: shouldAutoScroll }) await waitInlineAnswerStreamFrame() } } async function streamOrSetInlineAssistantContent(messageId, content) { const targetContent = String(content || '').trim() if (//.test(targetContent)) { const message = conversationMessages.value.find((item) => item.id === messageId) if (message?.pending) { updateInlineMessageContent(message, targetContent) scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value }) } return } await streamInlineAssistantContent(messageId, targetContent) } function refreshConversationHistory() { const history = loadAiWorkbenchConversationHistory(currentUser.value || {}) emit('conversation-history-change', history) return history } function isPersistableInlineConversation() { return Boolean( conversationId.value && conversationId.value !== AI_SEARCH_CONVERSATION_ID && conversationMessages.value.length ) } function persistCurrentConversation() { if (!isPersistableInlineConversation()) { refreshConversationHistory() return [] } const history = saveAiWorkbenchConversation(currentUser.value || {}, { id: conversationId.value, conversationId: conversationId.value, title: activeConversationTitle.value, source: 'workbench', sessionType: 'steward', stewardState: stewardState.value, messages: conversationMessages.value.map((message) => serializeRuntimeMessage(message)) }) emit('conversation-history-change', history) return history } function resetInlineConversationState() { conversationStarted.value = false conversationMessages.value = [] conversationId.value = '' stewardState.value = null activeConversationTitle.value = '' assistantDraft.value = '' thinkingExpandedMessageIds.value = new Set() thinkingCollapsedMessageIds.value = new Set() attachmentOcrExpandedMessageIds.value = new Set() deleteDialogOpen.value = false applicationSubmitConfirmOpen.value = false applicationSubmitConfirmContext.value = null clearWorkbenchDateSelection() filesFlow.clearAiModeFiles() } function replaceInlineMessage(id, nextMessage) { const index = conversationMessages.value.findIndex((item) => item.id === id) if (index === -1) { conversationMessages.value.push(nextMessage) return } conversationMessages.value.splice(index, 1, nextMessage) } function activateInlineConversation(options = {}) { conversationStarted.value = true if (!conversationId.value) { conversationId.value = options.id || `inline-${Date.now()}` } activeConversationTitle.value = options.title || activeConversationTitle.value || '新对话' emit('conversation-change', { id: conversationId.value, title: activeConversationTitle.value }) } function renderInlineConversationHtml(content) { return renderAiConversationHtml(content) } 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 } 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() emit('open-document', buildAiDocumentDetailRequest(detailReference)) } function startInlineConversation(prompt, entry = {}, files = []) { if (!ensureAiModeCanStartConversation()) { return } const cleanPrompt = buildInlinePromptText(prompt, files) if (!cleanPrompt || sending.value) { return } if (aiExpenseDraft.value && !isAiExpenseDraftComplete(aiExpenseDraft.value)) { expenseFlow.advanceAiExpenseDraft(cleanPrompt, files) return } if (applicationFlow.handleInlineApplicationPreviewTextAction(cleanPrompt, applicationPreviewEstimatePending)) { 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 } }