Files
X-Financial/web/src/views/scripts/useStewardPlanFlow.js

159 lines
4.8 KiB
JavaScript
Raw Normal View History

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
}
}