import { buildStewardPlanMessageText, buildStewardPlanRequest, buildStewardSuggestedActions, normalizeStewardPlan } from './stewardPlanModel.js' import { SESSION_TYPE_STEWARD } from './travelReimbursementConversationModel.js' export function useStewardPlanFlow({ activeSessionType, attachedFiles, composerDraft, currentUser, fileInputRef, messages, createMessage, fetchStewardPlan, fetchStewardPlanStream, nextTick, persistSessionState, replaceMessage, scrollToBottom, adjustComposerTextareaHeight, submitting, reviewActionBusy, sessionSwitchBusy, toast }) { function isStewardSession() { return String(activeSessionType.value || '').trim() === SESSION_TYPE_STEWARD } function clearStewardThinkingTimers() { // 保留给页面卸载调用;流式版不再使用前端延时器。 } async function submitStewardPlan(options = {}) { if (!isStewardSession()) return false if (submitting.value || reviewActionBusy.value || sessionSwitchBusy.value) return true const rawText = String(options.rawText ?? composerDraft.value ?? '').trim() const files = Array.from(options.files ?? attachedFiles.value ?? []) if (!rawText && !files.length) return true const fileNames = files.map((file) => file.name).filter(Boolean) const userText = String(options.userText || rawText || `我上传了 ${fileNames.length} 份附件,请小财管家先归集任务。`).trim() submitting.value = true if (!options.skipUserMessage) { messages.value.push(createMessage('user', userText, fileNames)) } const pendingPlan = normalizeStewardPlan({ plan_status: 'streaming', summary: '', thinking_events: [] }) const pendingMessage = createMessage('assistant', '', [], { assistantName: '小财管家', meta: ['小财管家', '流式分析中'], stewardPlan: { ...pendingPlan, streamStatus: 'streaming' } }) messages.value.push(pendingMessage) composerDraft.value = '' nextTick(() => { adjustComposerTextareaHeight() scrollToBottom() }) try { const requestPayload = buildStewardPlanRequest({ rawText, files, currentUser: currentUser.value || {} }) const plan = await fetchPlanWithStreaming(pendingMessage.id, requestPayload) const normalizedPlan = normalizeStewardPlan(plan, { visibleThinkingEventCount: Number.MAX_SAFE_INTEGER }) replaceMessage(pendingMessage.id, createMessage('assistant', buildStewardPlanMessageText(plan), [], { assistantName: '小财管家', meta: ['小财管家', '等待确认'], stewardPlan: { ...normalizedPlan, streamStatus: 'completed' }, suggestedActions: buildStewardSuggestedActions(plan) })) persistSessionState() nextTick(scrollToBottom) } catch (error) { replaceMessage(pendingMessage.id, createMessage('assistant', error?.message || '小财管家规划失败,请稍后重试。', [], { assistantName: '小财管家', meta: ['小财管家', '规划失败'] })) toast(error?.message || '小财管家规划失败,请稍后重试。') persistSessionState() } finally { submitting.value = false if (fileInputRef.value) { fileInputRef.value.value = '' } nextTick(() => { adjustComposerTextareaHeight() scrollToBottom() }) } return true } function fetchPlanWithStreaming(messageId, requestPayload) { if (typeof fetchStewardPlanStream === 'function') { return fetchStewardPlanStream(requestPayload, { onEvent: (event) => handleStreamEvent(messageId, event) }, { timeoutMs: 20000, timeoutMessage: '小财管家任务规划超时,请稍后重试。' }) } return fetchStewardPlan(requestPayload, { timeoutMs: 16000, timeoutMessage: '小财管家任务规划超时,请稍后重试。' }) } function handleStreamEvent(messageId, event) { if (event.event !== 'thinking') { return } const message = messages.value.find((item) => item.id === messageId) if (!message?.stewardPlan) return const existingEvents = Array.isArray(message.stewardPlan.thinkingEvents) ? message.stewardPlan.thinkingEvents : [] const normalizedPlan = normalizeStewardPlan({ ...message.stewardPlan, thinking_events: [...existingEvents, event.data] }, { visibleThinkingEventCount: existingEvents.length + 1 }) message.stewardPlan = { ...message.stewardPlan, ...normalizedPlan, streamStatus: 'streaming' } persistSessionState() nextTick(scrollToBottom) } return { isStewardSession, submitStewardPlan, clearStewardThinkingTimers } }