2026-06-22 11:58:53 +08:00
|
|
|
|
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,
|
|
|
|
|
|
buildSelectedFileCards,
|
|
|
|
|
|
shouldRunAiAttachmentAutoAssociation
|
|
|
|
|
|
} from './workbenchAiComposerModel.js'
|
|
|
|
|
|
import {
|
|
|
|
|
|
createWorkbenchAiMessageRuntime,
|
|
|
|
|
|
formatMessageTime,
|
|
|
|
|
|
normalizeInlineAttachmentOcrDetails
|
|
|
|
|
|
} 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 { useWorkbenchAiMessageActions } from './useWorkbenchAiMessageActions.js'
|
|
|
|
|
|
import { useWorkbenchAiSessionCommands } from './useWorkbenchAiSessionCommands.js'
|
|
|
|
|
|
import { useWorkbenchAiStewardFlow } from './useWorkbenchAiStewardFlow.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,
|
|
|
|
|
|
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 selectedFileCards = computed(() => buildSelectedFileCards(selectedFiles.value))
|
|
|
|
|
|
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,
|
|
|
|
|
|
selectedFiles,
|
|
|
|
|
|
toast
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const documentQueryFlow = useWorkbenchAiDocumentQueryFlow({
|
|
|
|
|
|
conversationMessages,
|
|
|
|
|
|
createInlineMessage,
|
|
|
|
|
|
inlineConversationAutoScrollPinned,
|
|
|
|
|
|
persistCurrentConversation,
|
|
|
|
|
|
replaceInlineMessage,
|
|
|
|
|
|
scrollInlineConversationToBottom
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const attachmentFlow = useWorkbenchAiAttachmentAssociationFlow({
|
|
|
|
|
|
aiAttachmentAssociationRuntime,
|
|
|
|
|
|
conversationMessages,
|
|
|
|
|
|
createAiAttachmentAssociationId,
|
|
|
|
|
|
createInlineMessage,
|
|
|
|
|
|
inlineConversationAutoScrollPinned,
|
|
|
|
|
|
persistCurrentConversation,
|
|
|
|
|
|
replaceInlineMessage,
|
|
|
|
|
|
scrollInlineConversationToBottom,
|
|
|
|
|
|
sending,
|
|
|
|
|
|
streamOrSetInlineAssistantContent,
|
|
|
|
|
|
toast
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
resolveApplicationPreviewEditorControl,
|
|
|
|
|
|
resolveApplicationPreviewEditorOptions,
|
|
|
|
|
|
resolveInlineThinkingEvents,
|
|
|
|
|
|
resolveLatestInlineUserPrompt,
|
|
|
|
|
|
scrollInlineConversationToBottom,
|
|
|
|
|
|
sending,
|
|
|
|
|
|
toast
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const expenseFlow = useWorkbenchAiExpenseFlow({
|
|
|
|
|
|
activateInlineConversation,
|
|
|
|
|
|
aiExpenseDraft,
|
|
|
|
|
|
assistantDraft,
|
|
|
|
|
|
clearAiModeFiles: filesFlow.clearAiModeFiles,
|
|
|
|
|
|
closeWorkbenchDatePicker,
|
|
|
|
|
|
conversationMessages,
|
|
|
|
|
|
conversationStarted,
|
|
|
|
|
|
createInlineMessage,
|
|
|
|
|
|
currentUser,
|
|
|
|
|
|
persistCurrentConversation,
|
|
|
|
|
|
pushInlineUserMessage,
|
2026-06-22 15:56:06 +08:00
|
|
|
|
replaceInlineMessage,
|
2026-06-22 11:58:53 +08:00
|
|
|
|
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 isAiModeInputLocked = computed(() => applicationPreviewEstimatePending.value)
|
|
|
|
|
|
const canSubmitAiModePrompt = computed(() => (
|
|
|
|
|
|
!isAiModeInputLocked.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 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 (/<!--\s*ai-trusted-html:start\s*-->/.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 resolveInlineThinkingEvents(message) {
|
|
|
|
|
|
return Array.isArray(message?.stewardPlan?.thinkingEvents) ? message.stewardPlan.thinkingEvents : []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function hasInlineThinking(message) {
|
|
|
|
|
|
return resolveInlineThinkingEvents(message).length > 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isInlineThinkingExpanded(message) {
|
|
|
|
|
|
if (!message?.id) {
|
|
|
|
|
|
return Boolean(message?.pending)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (thinkingCollapsedMessageIds.value.has(message.id)) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return Boolean(message.pending || thinkingExpandedMessageIds.value.has(message.id))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleInlineThinking(message) {
|
|
|
|
|
|
if (!message?.id) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const nextExpandedIds = new Set(thinkingExpandedMessageIds.value)
|
|
|
|
|
|
const nextCollapsedIds = new Set(thinkingCollapsedMessageIds.value)
|
|
|
|
|
|
if (isInlineThinkingExpanded(message)) {
|
|
|
|
|
|
nextExpandedIds.delete(message.id)
|
|
|
|
|
|
nextCollapsedIds.add(message.id)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
nextCollapsedIds.delete(message.id)
|
|
|
|
|
|
nextExpandedIds.add(message.id)
|
|
|
|
|
|
}
|
|
|
|
|
|
thinkingExpandedMessageIds.value = nextExpandedIds
|
|
|
|
|
|
thinkingCollapsedMessageIds.value = nextCollapsedIds
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function hasInlineAttachmentOcrDetails(message = {}) {
|
|
|
|
|
|
const details = normalizeInlineAttachmentOcrDetails(message?.attachmentOcrDetails || null)
|
|
|
|
|
|
return Boolean(details?.documents?.length || details?.fileNames?.length)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveInlineAttachmentOcrDocuments(message = {}) {
|
|
|
|
|
|
return normalizeInlineAttachmentOcrDetails(message?.attachmentOcrDetails || null)?.documents || []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveInlineAttachmentOcrFileCount(message = {}) {
|
|
|
|
|
|
const details = normalizeInlineAttachmentOcrDetails(message?.attachmentOcrDetails || null)
|
|
|
|
|
|
return Math.max(details?.documents?.length || 0, details?.fileNames?.length || 0)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isInlineAttachmentOcrExpanded(message = {}) {
|
|
|
|
|
|
return Boolean(message?.id && attachmentOcrExpandedMessageIds.value.has(message.id))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleInlineAttachmentOcrDetails(message = {}, forceExpanded = null) {
|
|
|
|
|
|
if (!message?.id || !hasInlineAttachmentOcrDetails(message)) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
const nextExpandedIds = new Set(attachmentOcrExpandedMessageIds.value)
|
|
|
|
|
|
const shouldExpand = forceExpanded === null
|
|
|
|
|
|
? !nextExpandedIds.has(message.id)
|
|
|
|
|
|
: Boolean(forceExpanded)
|
|
|
|
|
|
if (shouldExpand) {
|
|
|
|
|
|
nextExpandedIds.add(message.id)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
nextExpandedIds.delete(message.id)
|
|
|
|
|
|
}
|
|
|
|
|
|
attachmentOcrExpandedMessageIds.value = nextExpandedIds
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildInlinePromptText(rawPrompt, files = []) {
|
|
|
|
|
|
const prompt = buildWorkbenchPromptText(rawPrompt)
|
|
|
|
|
|
if (prompt) {
|
|
|
|
|
|
return prompt
|
|
|
|
|
|
}
|
|
|
|
|
|
return files.length ? '请帮我处理已上传的附件。' : ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-22 15:56:06 +08:00
|
|
|
|
function isReimbursementCreationIntent(prompt = '') {
|
|
|
|
|
|
const compact = String(prompt || '').replace(/\s+/g, '')
|
|
|
|
|
|
if (!compact || !/报销|报账/.test(compact)) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
if (/标准|制度|规则|政策|口径|怎么|如何|能不能|可以吗|查一下|查询|状态|进度|多少钱|多少/.test(compact)) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
|
|
|
/^(我要|我想|我需要|帮我|请帮我|需要|发起|新建|创建|提交|办理|走).{0,16}(报销|报账)/.test(compact) ||
|
|
|
|
|
|
/^(报销|报账)(一下|一笔|单|流程)?$/.test(compact)
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-22 11:58:53 +08:00
|
|
|
|
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 (isAiModeInputLocked.value) {
|
|
|
|
|
|
toast('请等待费用测算完成后再继续操作。')
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-22 15:56:06 +08:00
|
|
|
|
if (isReimbursementCreationIntent(cleanPrompt)) {
|
|
|
|
|
|
void expenseFlow.startAiReimbursementAssociationGate(cleanPrompt, entry.label || '我要报销')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-22 11:58:53 +08:00
|
|
|
|
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 (!canSubmitAiModePrompt.value) {
|
|
|
|
|
|
toast('请输入需求后再发送。')
|
|
|
|
|
|
focusAiModeInput()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
startInlineConversation(assistantDraft.value, { source: 'workbench', sessionType: 'steward' }, Array.from(selectedFiles.value))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function runAiModeAction(item) {
|
|
|
|
|
|
if (String(item?.label || '').trim() === '发起报销') {
|
2026-06-22 15:56:06 +08:00
|
|
|
|
void expenseFlow.startAiReimbursementAssociationGate(item.prompt, item.label)
|
2026-06-22 11:58:53 +08:00
|
|
|
|
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() {
|
|
|
|
|
|
const latestUserMessage = [...conversationMessages.value].reverse().find((message) => message.role === 'user')
|
|
|
|
|
|
return String(latestUserMessage?.content || '').trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleVoiceInput() {
|
|
|
|
|
|
if (isAiModeInputLocked.value) {
|
|
|
|
|
|
toast('请等待费用测算完成后再继续操作。')
|
|
|
|
|
|
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 || {})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
loadSystemSettings()
|
|
|
|
|
|
refreshConversationHistory()
|
|
|
|
|
|
document.addEventListener('click', handleWorkbenchDatePickerOutside)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
document.removeEventListener('click', handleWorkbenchDatePickerOutside)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
activeConversationTitle,
|
|
|
|
|
|
aiModeActionItems,
|
|
|
|
|
|
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,
|
|
|
|
|
|
resolveInlineApplicationPreviewMissingFields: applicationFlow.resolveInlineApplicationPreviewMissingFields,
|
|
|
|
|
|
resolveInlineApplicationPreviewRows: applicationFlow.resolveInlineApplicationPreviewRows,
|
|
|
|
|
|
resolveInlineAttachmentOcrDocuments,
|
|
|
|
|
|
resolveInlineAttachmentOcrFileCount,
|
|
|
|
|
|
resolveInlineThinkingEvents,
|
|
|
|
|
|
runAiModeAction,
|
|
|
|
|
|
scrollInlineConversationToTop,
|
|
|
|
|
|
selectedFileCards,
|
|
|
|
|
|
sending,
|
|
|
|
|
|
setWorkbenchDateMode,
|
|
|
|
|
|
submitAiModePrompt,
|
|
|
|
|
|
toggleInlineAttachmentOcrDetails,
|
|
|
|
|
|
toggleInlineThinking,
|
|
|
|
|
|
toggleWorkbenchDatePicker,
|
|
|
|
|
|
triggerAiModeFileUpload: filesFlow.triggerAiModeFileUpload,
|
|
|
|
|
|
workbenchCanApplyDateSelection,
|
|
|
|
|
|
workbenchDateMode,
|
|
|
|
|
|
workbenchDatePickerOpen,
|
|
|
|
|
|
workbenchDateTagLabel,
|
|
|
|
|
|
workbenchRangeEndDate,
|
|
|
|
|
|
workbenchRangeStartDate,
|
|
|
|
|
|
workbenchSingleDate,
|
|
|
|
|
|
applyWorkbenchDateSelection,
|
|
|
|
|
|
buildInlineApplicationPreviewFooterText: applicationFlow.buildInlineApplicationPreviewFooterText,
|
|
|
|
|
|
copyInlineMessage: messageActions.copyInlineMessage,
|
|
|
|
|
|
formatMessageTime,
|
|
|
|
|
|
handleWorkbenchDateInputChange
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|