feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { useSystemState } from '../useSystemState.js'
|
||||
import { useToast } from '../useToast.js'
|
||||
import { useWorkbenchComposerDate } from '../useWorkbenchComposerDate.js'
|
||||
@@ -7,9 +7,7 @@ import { calculateTravelReimbursement, fetchExpenseClaimDetail } from '../../ser
|
||||
import { useApplicationPreviewEditor } from '../../views/scripts/useApplicationPreviewEditor.js'
|
||||
import {
|
||||
deleteAiWorkbenchConversation,
|
||||
loadAiWorkbenchConversationHistory,
|
||||
markAiWorkbenchConversationDocumentDeleted,
|
||||
saveAiWorkbenchConversation
|
||||
markAiWorkbenchConversationDocumentDeleted
|
||||
} from '../../utils/aiWorkbenchConversationStore.js'
|
||||
import { renderAiConversationHtml } from '../../utils/aiConversationHtmlRenderer.js'
|
||||
import {
|
||||
@@ -41,23 +39,12 @@ import {
|
||||
} from './workbenchAiApplicationGateModel.js'
|
||||
import { useWorkbenchAiCommandIntents } from './useWorkbenchAiCommandIntents.js'
|
||||
import {
|
||||
buildRuleFallbackWorkbenchAiIntentPlan,
|
||||
isLowConfidenceTravelApplicationPlan,
|
||||
normalizeWorkbenchAiIntentPlan,
|
||||
resolveExecutableTravelApplicationPlan,
|
||||
shouldRequestWorkbenchAiIntentPlan
|
||||
} from './workbenchAiIntentPlannerModel.js'
|
||||
import {
|
||||
buildInitialModelPlanningThinkingEvents,
|
||||
buildModelPlanningProgressSchedule,
|
||||
mergeWorkbenchAiThinkingEvents
|
||||
} from './workbenchAiPlanningThinkingModel.js'
|
||||
import { useWorkbenchAiConversationRuntime } from './useWorkbenchAiConversationRuntime.js'
|
||||
import { useWorkbenchAiIntentExecution } from './useWorkbenchAiIntentExecution.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()
|
||||
@@ -126,7 +113,47 @@ export function usePersonalWorkbenchAiMode(props, emit) {
|
||||
handleWorkbenchDateInputChange,
|
||||
removeWorkbenchDateTag,
|
||||
buildWorkbenchPromptText
|
||||
} = useWorkbenchComposerDate({ draft: assistantDraft, focusInput: focusAiModeInput })
|
||||
} = 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(() => {
|
||||
@@ -225,6 +252,8 @@ export function usePersonalWorkbenchAiMode(props, emit) {
|
||||
thinkingExpandedMessageIds
|
||||
})
|
||||
|
||||
let intentExecution = null
|
||||
|
||||
const applicationFlow = useWorkbenchAiApplicationPreviewFlow({
|
||||
activateInlineConversation,
|
||||
applicationPreviewEditor,
|
||||
@@ -259,7 +288,7 @@ export function usePersonalWorkbenchAiMode(props, emit) {
|
||||
scrollInlineConversationToBottom,
|
||||
sending,
|
||||
toast,
|
||||
onApplicationActionCompleted: startModelPlannedNextTask
|
||||
onApplicationActionCompleted: (remainingTasks) => intentExecution?.startModelPlannedNextTask(remainingTasks)
|
||||
})
|
||||
|
||||
const expenseFlow = useWorkbenchAiExpenseFlow({
|
||||
@@ -354,6 +383,29 @@ export function usePersonalWorkbenchAiMode(props, emit) {
|
||||
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))
|
||||
))
|
||||
@@ -378,187 +430,6 @@ export function usePersonalWorkbenchAiMode(props, emit) {
|
||||
}
|
||||
}
|
||||
|
||||
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 (/<!--\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 isUnavailableDocumentDetailError(error) {
|
||||
@@ -648,238 +519,6 @@ export function usePersonalWorkbenchAiMode(props, emit) {
|
||||
return false
|
||||
}
|
||||
|
||||
function isModelPlannedReimbursementTask(modelPlan = {}) {
|
||||
const tasks = Array.isArray(modelPlan?.tasks) ? modelPlan.tasks : []
|
||||
return tasks.some((task) => {
|
||||
const taskType = String(task?.task_type || task?.taskType || '').trim()
|
||||
const assignedAgent = String(task?.assigned_agent || task?.assignedAgent || '').trim()
|
||||
return taskType === 'reimbursement' || assignedAgent === 'reimbursement_assistant'
|
||||
})
|
||||
}
|
||||
|
||||
function updateModelPlanningThinkingEvent(messageId, event) {
|
||||
const message = conversationMessages.value.find((item) => item.id === messageId)
|
||||
if (!message) {
|
||||
return
|
||||
}
|
||||
const currentPlan = message.stewardPlan || {}
|
||||
message.stewardPlan = {
|
||||
...currentPlan,
|
||||
streamStatus: 'streaming',
|
||||
thinkingEvents: mergeWorkbenchAiThinkingEvents(resolveInlineThinkingEvents(message), [event])
|
||||
}
|
||||
persistCurrentConversation()
|
||||
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
|
||||
}
|
||||
|
||||
function startModelPlanningProgressUpdates(messageId) {
|
||||
const timerIds = buildModelPlanningProgressSchedule().map(({ delayMs, event }) => (
|
||||
globalThis.setTimeout(() => {
|
||||
updateModelPlanningThinkingEvent(messageId, event)
|
||||
}, delayMs)
|
||||
))
|
||||
return () => {
|
||||
timerIds.forEach((timerId) => globalThis.clearTimeout(timerId))
|
||||
}
|
||||
}
|
||||
|
||||
function startModelPlanningConversation(cleanPrompt, entry = {}) {
|
||||
if (conversationId.value === AI_SEARCH_CONVERSATION_ID) {
|
||||
conversationId.value = ''
|
||||
conversationMessages.value = []
|
||||
activeConversationTitle.value = ''
|
||||
}
|
||||
activateInlineConversation({
|
||||
title: entry.label || cleanPrompt.slice(0, 18) || '新对话'
|
||||
})
|
||||
inlineConversationAutoScrollPinned.value = true
|
||||
conversationMessages.value.push(createInlineMessage('user', cleanPrompt))
|
||||
assistantDraft.value = ''
|
||||
removeWorkbenchDateTag()
|
||||
closeWorkbenchDatePicker()
|
||||
filesFlow.clearAiModeFiles()
|
||||
const pendingMessage = createInlineMessage('assistant', '正在识别意图,准备拆解申请、报销和附件任务。', {
|
||||
pending: true,
|
||||
stewardPlan: {
|
||||
streamStatus: 'streaming',
|
||||
thinkingEvents: buildInitialModelPlanningThinkingEvents()
|
||||
}
|
||||
})
|
||||
conversationMessages.value.push(pendingMessage)
|
||||
scrollInlineConversationToBottom()
|
||||
persistCurrentConversation()
|
||||
return pendingMessage
|
||||
}
|
||||
|
||||
function buildModelPlannedNextTaskAction(remainingTasks = []) {
|
||||
const tasks = Array.isArray(remainingTasks) ? remainingTasks : []
|
||||
const nextTask = tasks[0]
|
||||
if (!nextTask || typeof nextTask !== 'object') {
|
||||
return null
|
||||
}
|
||||
const taskType = String(nextTask.task_type || nextTask.taskType || '').trim()
|
||||
const assignedAgent = String(nextTask.assigned_agent || nextTask.assignedAgent || '').trim()
|
||||
const isApplication = taskType === 'expense_application' || assignedAgent === 'application_assistant'
|
||||
const isReimbursement = taskType === 'reimbursement' || assignedAgent === 'reimbursement_assistant'
|
||||
if (!isApplication && !isReimbursement) {
|
||||
return null
|
||||
}
|
||||
const ontologyFields = nextTask.ontology_fields || nextTask.ontologyFields || {}
|
||||
const flowId = isApplication ? 'travel_application' : 'travel_reimbursement'
|
||||
const taskLabel = isApplication ? '出差申请' : '费用报销'
|
||||
return {
|
||||
label: `继续处理${taskLabel}`,
|
||||
action_type: 'steward_continue_next_task',
|
||||
payload: {
|
||||
steward_confirm_flow: true,
|
||||
flow_id: flowId,
|
||||
steward_current_task: nextTask,
|
||||
expense_type: String(ontologyFields.expense_type || 'travel').trim() || 'travel',
|
||||
expense_type_label: String(ontologyFields.expense_type_label || '差旅费').trim() || '差旅费',
|
||||
ontology_fields: ontologyFields,
|
||||
original_message: String(nextTask.summary || nextTask.title || `继续处理${taskLabel}`).trim(),
|
||||
steward_remaining_tasks: tasks.slice(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startModelPlannedNextTask(remainingTasks = []) {
|
||||
const nextTaskAction = buildModelPlannedNextTaskAction(remainingTasks)
|
||||
if (!nextTaskAction) {
|
||||
return
|
||||
}
|
||||
actionRouter.handleInlineSuggestedAction(nextTaskAction)
|
||||
}
|
||||
|
||||
function startModelPlannedApplicationPreview(travelApplicationRequest, plannerPendingMessage = null) {
|
||||
void applicationFlow.startAiApplicationPreview(
|
||||
travelApplicationRequest.expenseType,
|
||||
travelApplicationRequest.expenseTypeLabel,
|
||||
travelApplicationRequest.sourceText,
|
||||
{
|
||||
userMessage: travelApplicationRequest.sourceText,
|
||||
pushUserMessage: !plannerPendingMessage,
|
||||
pendingMessageId: plannerPendingMessage?.id,
|
||||
ontologyFields: travelApplicationRequest.ontologyFields,
|
||||
autoSubmit: travelApplicationRequest.autoSubmit,
|
||||
autoSaveDraft: travelApplicationRequest.autoSaveDraft,
|
||||
requestedSubmit: travelApplicationRequest.requestedSubmit,
|
||||
submitRequiresConfirmation: travelApplicationRequest.submitRequiresConfirmation,
|
||||
stewardRemainingTasks: travelApplicationRequest.stewardRemainingTasks,
|
||||
onPreviewReadyForNextTask: startModelPlannedNextTask,
|
||||
onApplicationActionCompleted: startModelPlannedNextTask
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function startModelPlannedTravelApplicationConfirmation(travelApplicationRequest, plan, plannerPendingMessage) {
|
||||
const confirmText = buildLowConfidenceTravelApplicationConfirmationText(travelApplicationRequest, plan)
|
||||
const confirmAction = {
|
||||
label: '确认发起出差申请',
|
||||
description: '根据上面识别到的信息生成出差申请预览。',
|
||||
icon: 'mdi mdi-check-circle-outline',
|
||||
action_type: 'ai_application_confirm_intent',
|
||||
payload: {
|
||||
ontologyFields: travelApplicationRequest.ontologyFields,
|
||||
sourceText: travelApplicationRequest.sourceText,
|
||||
autoSubmit: travelApplicationRequest.autoSubmit,
|
||||
autoSaveDraft: travelApplicationRequest.autoSaveDraft,
|
||||
requestedSubmit: travelApplicationRequest.requestedSubmit,
|
||||
submitRequiresConfirmation: travelApplicationRequest.submitRequiresConfirmation,
|
||||
stewardRemainingTasks: travelApplicationRequest.stewardRemainingTasks
|
||||
}
|
||||
}
|
||||
replaceInlineMessage(plannerPendingMessage.id, createInlineMessage('assistant', confirmText, {
|
||||
id: plannerPendingMessage.id,
|
||||
suggestedActions: [confirmAction],
|
||||
stewardPlan: {
|
||||
streamStatus: 'completed',
|
||||
thinkingEvents: resolveInlineThinkingEvents(plannerPendingMessage).map((item) => ({ ...item, status: 'completed' }))
|
||||
}
|
||||
}))
|
||||
persistCurrentConversation()
|
||||
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
|
||||
}
|
||||
|
||||
function buildLowConfidenceTravelApplicationConfirmationText(request, plan) {
|
||||
const fields = request.ontologyFields || {}
|
||||
const summaryParts = []
|
||||
if (fields.time_range) {
|
||||
summaryParts.push(`时间:${fields.time_range}`)
|
||||
}
|
||||
if (fields.location) {
|
||||
summaryParts.push(`地点:${fields.location}`)
|
||||
}
|
||||
if (fields.reason) {
|
||||
summaryParts.push(`事由:${fields.reason}`)
|
||||
}
|
||||
if (fields.transport_mode) {
|
||||
summaryParts.push(`交通:${fields.transport_mode}`)
|
||||
}
|
||||
const summary = summaryParts.length ? `\n\n${summaryParts.join(';')}` : ''
|
||||
const confidenceNote = Number.isFinite(Number(plan?.confidence))
|
||||
? `(模型识别置信度较低,约 ${Math.round(Number(plan.confidence) * 100)}%)`
|
||||
: '(模型识别置信度较低)'
|
||||
return [
|
||||
'### 需要确认:您是要发起出差申请吗?',
|
||||
'',
|
||||
`小财管家把这句话理解成了“发起差旅申请”${confidenceNote},为避免误操作,先请您确认。`,
|
||||
summary,
|
||||
'',
|
||||
'点击下方「确认发起出差申请」即可继续;如果理解有误,请补充说明您的实际需求。'
|
||||
].filter(Boolean).join('\n')
|
||||
}
|
||||
|
||||
async function executeModelPlannedWorkbenchIntent(cleanPrompt, entry = {}, files = []) {
|
||||
let intentPlan = null
|
||||
let modelPlan = null
|
||||
const plannerPendingMessage = startModelPlanningConversation(cleanPrompt, entry)
|
||||
const stopPlanningProgressUpdates = startModelPlanningProgressUpdates(plannerPendingMessage.id)
|
||||
sending.value = true
|
||||
try {
|
||||
modelPlan = await stewardFlow.resolveInlineExecutionPlan(cleanPrompt, entry, files, {
|
||||
pendingMessageId: plannerPendingMessage.id
|
||||
})
|
||||
intentPlan = normalizeWorkbenchAiIntentPlan(modelPlan, { prompt: cleanPrompt })
|
||||
} catch (error) {
|
||||
console.warn('AI mode intent planner failed, using local fallback:', error)
|
||||
const rulePlan = buildRuleFallbackWorkbenchAiIntentPlan(cleanPrompt)
|
||||
const ruleRequest = resolveExecutableTravelApplicationPlan(rulePlan)
|
||||
if (ruleRequest) {
|
||||
sending.value = false
|
||||
startModelPlannedApplicationPreview(ruleRequest, plannerPendingMessage)
|
||||
return
|
||||
}
|
||||
} finally {
|
||||
stopPlanningProgressUpdates()
|
||||
sending.value = false
|
||||
}
|
||||
|
||||
const travelApplicationRequest = resolveExecutableTravelApplicationPlan(intentPlan)
|
||||
if (travelApplicationRequest) {
|
||||
if (isLowConfidenceTravelApplicationPlan(intentPlan)) {
|
||||
startModelPlannedTravelApplicationConfirmation(travelApplicationRequest, intentPlan, plannerPendingMessage)
|
||||
return
|
||||
}
|
||||
startModelPlannedApplicationPreview(travelApplicationRequest, plannerPendingMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if (isModelPlannedReimbursementTask(modelPlan) || isReimbursementCreationIntent(cleanPrompt)) {
|
||||
replaceInlineMessage(plannerPendingMessage.id, createInlineMessage('assistant', '已识别为报销任务,正在进入报销流程。', {
|
||||
id: plannerPendingMessage.id,
|
||||
stewardPlan: {
|
||||
streamStatus: 'completed',
|
||||
thinkingEvents: resolveInlineThinkingEvents(plannerPendingMessage).map((item) => ({ ...item, status: 'completed' }))
|
||||
}
|
||||
}))
|
||||
void expenseFlow.startAiReimbursementAssociationGate(cleanPrompt, entry.label || cleanPrompt)
|
||||
return
|
||||
}
|
||||
|
||||
void stewardFlow.requestInlineAssistantReply(cleanPrompt, entry, files, { pendingMessageId: plannerPendingMessage.id })
|
||||
}
|
||||
|
||||
async function handleAiAnswerMarkdownClick(event) {
|
||||
const target = event?.target
|
||||
const link = target?.closest?.('a[href^="#ai-open-document-detail:"], a[href^="#ai-open-application-detail:"]')
|
||||
@@ -923,7 +562,7 @@ export function usePersonalWorkbenchAiMode(props, emit) {
|
||||
}
|
||||
|
||||
if (shouldRequestWorkbenchAiIntentPlan(cleanPrompt)) {
|
||||
void executeModelPlannedWorkbenchIntent(cleanPrompt, entry, files)
|
||||
void intentExecution.executeModelPlannedWorkbenchIntent(cleanPrompt, entry, files)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user