feat: 报销预审会话状态管理与工作台交互增强
- 新增差旅报销会话状态管理与对话模型重构 - 增强风险观测服务与运行时聊天上下文作用域 - 优化工作台图标资源、助理意图识别与摘要工具 - 完善报销创建视图样式与差旅详情页标准调整交互 - 补充风险观测、运行时聊天与报销端点测试覆盖
This commit is contained in:
158
web/src/views/scripts/useStewardPlanFlow.js
Normal file
158
web/src/views/scripts/useStewardPlanFlow.js
Normal file
@@ -0,0 +1,158 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user