refactor: enforce 800 line source limits
This commit is contained in:
230
web/src/views/scripts/travelReimbursementStewardFollowupFlow.js
Normal file
230
web/src/views/scripts/travelReimbursementStewardFollowupFlow.js
Normal file
@@ -0,0 +1,230 @@
|
||||
import { ASSISTANT_SCOPE_ACTION_SWITCH } from '../../utils/assistantSessionScope.js'
|
||||
import { STEWARD_ASSISTANT_NAME } from './useTravelReimbursementStewardRuntime.js'
|
||||
import { SESSION_TYPE_APPLICATION, SESSION_TYPE_EXPENSE } from './travelReimbursementConversationModel.js'
|
||||
import { resolveStewardTypewriterNextIndex } from './stewardTypewriter.js'
|
||||
|
||||
const STEWARD_FOLLOWUP_TYPEWRITER_INTERVAL_MS = 10
|
||||
const STEWARD_FOLLOWUP_THINKING_INTERVAL_MS = 8
|
||||
const STEWARD_FOLLOWUP_TYPEWRITER_CHUNK_SIZE = 4
|
||||
const STEWARD_FOLLOWUP_THINKING_CHUNK_SIZE = 5
|
||||
|
||||
export function useTravelReimbursementStewardFollowupFlow({
|
||||
buildStewardFieldItems,
|
||||
createMessage,
|
||||
formatStewardMissingFieldList,
|
||||
formatStewardOntologyFields,
|
||||
messages,
|
||||
nextTick,
|
||||
persistSessionState,
|
||||
scrollToBottom
|
||||
}) {
|
||||
function buildStewardContinuationAfterAction(message, completedLabel = '当前动作已完成') {
|
||||
const continuation = message?.stewardContinuation || null
|
||||
const remainingTasks = Array.isArray(continuation?.remainingTasks) ? continuation.remainingTasks : []
|
||||
if (!remainingTasks.length) return null
|
||||
|
||||
const nextTask = remainingTasks[0]
|
||||
const nextTaskType = String(nextTask.task_type || nextTask.taskType || '').trim()
|
||||
const targetSessionType = nextTaskType === 'expense_application' ? SESSION_TYPE_APPLICATION : SESSION_TYPE_EXPENSE
|
||||
const nextLabel = targetSessionType === SESSION_TYPE_APPLICATION ? '继续创建申请单' : '继续填写报销单'
|
||||
const restTasks = remainingTasks.slice(1)
|
||||
return createMessage(
|
||||
'assistant',
|
||||
[
|
||||
`**${completedLabel}。**`,
|
||||
'',
|
||||
'我会重新检查剩余任务队列。',
|
||||
`下一步:${nextTask.title || (targetSessionType === SESSION_TYPE_APPLICATION ? '费用申请' : '费用报销')}。`,
|
||||
'请回复“确定”,我再继续执行。'
|
||||
].join('\n'),
|
||||
[],
|
||||
{
|
||||
assistantName: STEWARD_ASSISTANT_NAME,
|
||||
meta: [STEWARD_ASSISTANT_NAME, '等待用户确认'],
|
||||
suggestedActions: [
|
||||
{
|
||||
label: nextLabel,
|
||||
description: '确认后小财管家继续调用对应助手完成下一步。',
|
||||
icon: targetSessionType === SESSION_TYPE_APPLICATION ? 'mdi mdi-file-plus-outline' : 'mdi mdi-receipt-text-plus-outline',
|
||||
action_type: ASSISTANT_SCOPE_ACTION_SWITCH,
|
||||
payload: {
|
||||
session_type: targetSessionType,
|
||||
carry_text: buildStewardContinuationCarryText(nextTask, restTasks),
|
||||
carry_files: targetSessionType !== SESSION_TYPE_APPLICATION,
|
||||
auto_submit: true,
|
||||
steward_plan_id: String(continuation.planId || '').trim() || 'steward_continuation',
|
||||
steward_next_task_id: String(nextTask.task_id || nextTask.taskId || '').trim(),
|
||||
steward_current_task: nextTask,
|
||||
steward_remaining_tasks: restTasks
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function buildStewardFollowupPlan(thinkingEvents = [], streamStatus = 'streaming', planId = '') {
|
||||
return {
|
||||
planId: planId || `steward-followup-${Date.now()}`,
|
||||
planStatus: 'delegating',
|
||||
summary: '',
|
||||
visibleThinkingEventCount: Number.MAX_SAFE_INTEGER,
|
||||
initialSummaryOnly: true,
|
||||
thinkingEvents,
|
||||
tasks: [],
|
||||
attachmentGroups: [],
|
||||
confirmationGroups: [],
|
||||
streamStatus
|
||||
}
|
||||
}
|
||||
|
||||
function extractStewardCarryLine(text = '', label = '') {
|
||||
const escapedLabel = String(label || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
const match = String(text || '').match(new RegExp(`(?:^|\\n)${escapedLabel}[::]([^\\n]+)`, 'u'))
|
||||
return match ? match[1].trim() : ''
|
||||
}
|
||||
|
||||
function extractStewardFollowupNextTitle(text = '') {
|
||||
const taskMatch = String(text || '').match(/请(?:先)?(?:创建申请单|填写报销单|继续填写报销单)[::]([^。\n]+)/u)
|
||||
if (taskMatch?.[1]) return taskMatch[1].trim()
|
||||
const nextMatch = String(text || '').match(/下一步[::]([^。\n]+)/u)
|
||||
return nextMatch?.[1]?.trim() || '下一项财务任务'
|
||||
}
|
||||
|
||||
function buildStewardFollowupThinkingEvents(finalMessage = null, actions = []) {
|
||||
const eventPrefix = `steward-followup-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||
const firstAction = Array.isArray(actions) ? actions[0] : null
|
||||
const actionPayload = firstAction?.payload && typeof firstAction.payload === 'object' ? firstAction.payload : {}
|
||||
const carryText = String(actionPayload.carry_text || '').trim()
|
||||
const finalText = String(finalMessage?.text || '').trim()
|
||||
const nextTitle = extractStewardFollowupNextTitle(carryText || finalText)
|
||||
const nextSummary = extractStewardCarryLine(carryText, '任务摘要')
|
||||
const nextMissing = extractStewardCarryLine(carryText, '还需要补充')
|
||||
return [
|
||||
{
|
||||
eventId: `${eventPrefix}-review`,
|
||||
title: '复盘结果',
|
||||
content: finalText.includes('申请单已完成')
|
||||
? '申请单已经完成,我把当前出差申请标记为已处理,不会重复创建。'
|
||||
: '当前动作已经完成,我会把已完成事项从任务队列中移除。'
|
||||
},
|
||||
{
|
||||
eventId: `${eventPrefix}-next`,
|
||||
title: '读取剩余任务',
|
||||
content: nextSummary ? `剩余队列里的下一项是“${nextTitle}”:${nextSummary}。` : `剩余队列里的下一项是“${nextTitle}”。`
|
||||
},
|
||||
{
|
||||
eventId: `${eventPrefix}-gate`,
|
||||
title: '判断下一步条件',
|
||||
content: nextMissing
|
||||
? `这一步还需要补充${nextMissing},进入对应核对环节后我会继续追问,不会直接提交。`
|
||||
: '我会先等你确认,再进入下一项核对;创建草稿、绑定附件或提交前仍会再次确认。'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function waitStewardFollowupTick(intervalMs) {
|
||||
return new Promise((resolve) => {
|
||||
window.setTimeout(resolve, intervalMs)
|
||||
})
|
||||
}
|
||||
|
||||
async function pushStewardContinuationMessage(finalMessage) {
|
||||
if (!finalMessage) return
|
||||
|
||||
const finalText = String(finalMessage.text || '')
|
||||
const followupPlanId = `steward-followup-${Date.now()}-${Math.random().toString(16).slice(2)}`
|
||||
const finalActions = Array.isArray(finalMessage.suggestedActions) ? finalMessage.suggestedActions : []
|
||||
finalMessage.text = ''
|
||||
finalMessage.assistantName = STEWARD_ASSISTANT_NAME
|
||||
finalMessage.meta = [STEWARD_ASSISTANT_NAME, '思考中']
|
||||
finalMessage.suggestedActions = []
|
||||
finalMessage.stewardPlan = buildStewardFollowupPlan([], 'streaming', followupPlanId)
|
||||
messages.value.push(finalMessage)
|
||||
persistSessionState()
|
||||
nextTick(scrollToBottom)
|
||||
|
||||
const typedEvents = []
|
||||
for (const eventData of buildStewardFollowupThinkingEvents(finalMessage, finalActions)) {
|
||||
const event = {
|
||||
eventId: eventData.eventId,
|
||||
stage: 'steward_followup',
|
||||
title: eventData.title,
|
||||
content: '',
|
||||
status: 'running'
|
||||
}
|
||||
typedEvents.push(event)
|
||||
finalMessage.stewardPlan = buildStewardFollowupPlan([...typedEvents], 'streaming', followupPlanId)
|
||||
persistSessionState()
|
||||
nextTick(scrollToBottom)
|
||||
|
||||
const chars = Array.from(eventData.content)
|
||||
for (let index = 0; index < chars.length;) {
|
||||
await waitStewardFollowupTick(STEWARD_FOLLOWUP_THINKING_INTERVAL_MS)
|
||||
index = resolveStewardTypewriterNextIndex(chars, index, STEWARD_FOLLOWUP_THINKING_CHUNK_SIZE)
|
||||
event.content = chars.slice(0, index).join('')
|
||||
finalMessage.stewardPlan = buildStewardFollowupPlan([...typedEvents], 'streaming', followupPlanId)
|
||||
if (index % STEWARD_FOLLOWUP_THINKING_CHUNK_SIZE === 0 || index === chars.length) nextTick(scrollToBottom)
|
||||
}
|
||||
event.content = eventData.content
|
||||
event.status = 'completed'
|
||||
finalMessage.stewardPlan = buildStewardFollowupPlan([...typedEvents], 'streaming', followupPlanId)
|
||||
persistSessionState()
|
||||
}
|
||||
|
||||
finalMessage.meta = [STEWARD_ASSISTANT_NAME, '输出中']
|
||||
finalMessage.stewardPlan = buildStewardFollowupPlan([...typedEvents], 'typing', followupPlanId)
|
||||
const chars = Array.from(finalText)
|
||||
for (let index = 0; index < chars.length;) {
|
||||
await waitStewardFollowupTick(STEWARD_FOLLOWUP_TYPEWRITER_INTERVAL_MS)
|
||||
index = resolveStewardTypewriterNextIndex(chars, index, STEWARD_FOLLOWUP_TYPEWRITER_CHUNK_SIZE)
|
||||
finalMessage.text = chars.slice(0, index).join('')
|
||||
finalMessage.meta = [STEWARD_ASSISTANT_NAME, '输出中']
|
||||
finalMessage.stewardPlan = buildStewardFollowupPlan([...typedEvents], 'typing', followupPlanId)
|
||||
if (index % STEWARD_FOLLOWUP_TYPEWRITER_CHUNK_SIZE === 0 || index === chars.length) nextTick(scrollToBottom)
|
||||
}
|
||||
|
||||
finalMessage.text = finalText
|
||||
finalMessage.meta = [STEWARD_ASSISTANT_NAME, '等待用户确认']
|
||||
finalMessage.suggestedActions = finalActions
|
||||
finalMessage.stewardPlan = buildStewardFollowupPlan([...typedEvents], 'completed', followupPlanId)
|
||||
persistSessionState()
|
||||
nextTick(scrollToBottom)
|
||||
}
|
||||
|
||||
function buildStewardContinuationCarryText(task, restTasks = []) {
|
||||
const taskType = String(task?.task_type || task?.taskType || '').trim()
|
||||
const fields = formatStewardOntologyFields(task?.ontology_fields || task?.ontologyFields || {}, taskType)
|
||||
const missingFields = formatStewardMissingFieldList(task?.missing_fields || task?.missingFields || [], taskType, { includeHints: false })
|
||||
const lines = [
|
||||
taskType === 'expense_application'
|
||||
? `小财管家继续执行剩余任务,请创建申请单:${task.title || '费用申请'}。`
|
||||
: `小财管家继续执行剩余任务,请填写报销单:${task.title || '费用报销'}。`,
|
||||
task.summary ? `任务摘要:${task.summary}` : '',
|
||||
fields ? `已识别信息:${fields}` : '',
|
||||
missingFields ? `还需要补充:${missingFields}` : '',
|
||||
missingFields ? '请先追问上述缺失信息,不要直接生成核对结果,也不要替用户默认填写。' : '请生成核对结果;创建草稿、绑定附件或提交审批前仍需让我确认。'
|
||||
]
|
||||
if (restTasks.length) {
|
||||
lines.push('当前步骤完成后,请继续引导我处理剩余任务:')
|
||||
restTasks.forEach((item, index) => {
|
||||
lines.push(`${index + 1}. ${item.title || item.task_type || item.taskType}`)
|
||||
})
|
||||
}
|
||||
return lines.filter(Boolean).join('\n')
|
||||
}
|
||||
|
||||
function resolveStewardMissingFieldItems(task) {
|
||||
if (Array.isArray(task?.missingFieldItems) && task.missingFieldItems.length) return task.missingFieldItems
|
||||
const fields = task?.missingFields || task?.missing_fields || []
|
||||
const taskType = String(task?.taskType || task?.task_type || '').trim()
|
||||
return buildStewardFieldItems(fields, taskType)
|
||||
}
|
||||
|
||||
return {
|
||||
buildStewardContinuationAfterAction,
|
||||
buildStewardContinuationCarryText,
|
||||
pushStewardContinuationMessage,
|
||||
resolveStewardMissingFieldItems
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user