2026-06-04 11:03:29 +08:00
|
|
|
|
import {
|
|
|
|
|
|
ASSISTANT_SCOPE_ACTION_SWITCH,
|
2026-06-18 14:15:30 +08:00
|
|
|
|
ASSISTANT_SCOPE_ACTION_FILL_COMPOSER
|
2026-06-04 11:03:29 +08:00
|
|
|
|
} from '../../utils/assistantSessionScope.js'
|
|
|
|
|
|
import {
|
|
|
|
|
|
SESSION_TYPE_APPLICATION,
|
|
|
|
|
|
SESSION_TYPE_EXPENSE
|
|
|
|
|
|
} from './travelReimbursementConversationModel.js'
|
2026-06-22 11:58:53 +08:00
|
|
|
|
import {
|
|
|
|
|
|
APPLICATION_NON_BLOCKING_MISSING_FIELDS,
|
|
|
|
|
|
FLOW_EXPENSE_TYPE_LABELS,
|
|
|
|
|
|
formatStewardFieldDisplayValue,
|
|
|
|
|
|
normalizeFieldKey,
|
|
|
|
|
|
resolveFieldDisplay
|
|
|
|
|
|
} from './stewardPlanFields.js'
|
2026-06-04 11:03:29 +08:00
|
|
|
|
|
|
|
|
|
|
const TASK_TYPE_LABELS = {
|
|
|
|
|
|
expense_application: '费用申请',
|
|
|
|
|
|
reimbursement: '费用报销'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const AGENT_LABELS = {
|
|
|
|
|
|
application_assistant: '申请助手',
|
2026-06-04 14:25:14 +08:00
|
|
|
|
application: '申请助手',
|
|
|
|
|
|
expense_application: '申请助手',
|
|
|
|
|
|
reimbursement_assistant: '报销助手',
|
|
|
|
|
|
reimbursement: '报销助手',
|
|
|
|
|
|
expense: '报销助手'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-15 22:55:18 +08:00
|
|
|
|
export function buildStewardPlanRequest({
|
|
|
|
|
|
rawText = '',
|
|
|
|
|
|
files = [],
|
|
|
|
|
|
currentUser = {},
|
|
|
|
|
|
conversationId = '',
|
|
|
|
|
|
stewardState = null
|
|
|
|
|
|
} = {}) {
|
2026-06-04 11:03:29 +08:00
|
|
|
|
const safeFiles = Array.isArray(files) ? files : []
|
2026-06-15 22:55:18 +08:00
|
|
|
|
const normalizedConversationId = String(conversationId || '').trim()
|
2026-06-04 11:03:29 +08:00
|
|
|
|
return {
|
|
|
|
|
|
message: String(rawText || '').trim(),
|
|
|
|
|
|
user_id: String(currentUser.username || currentUser.name || 'anonymous').trim() || 'anonymous',
|
|
|
|
|
|
client_now_iso: new Date().toISOString(),
|
|
|
|
|
|
attachments: safeFiles.map((file) => ({
|
|
|
|
|
|
name: String(file?.name || '').trim(),
|
|
|
|
|
|
media_type: String(file?.type || '').trim()
|
|
|
|
|
|
})).filter((item) => item.name),
|
|
|
|
|
|
context_json: {
|
|
|
|
|
|
entry_source: 'workbench',
|
|
|
|
|
|
session_type: 'steward',
|
2026-06-15 22:55:18 +08:00
|
|
|
|
conversation_id: normalizedConversationId,
|
|
|
|
|
|
steward_state: stewardState && typeof stewardState === 'object' ? stewardState : null,
|
2026-06-04 11:03:29 +08:00
|
|
|
|
role_codes: Array.isArray(currentUser.roleCodes) ? currentUser.roleCodes : [],
|
|
|
|
|
|
username: currentUser.username || '',
|
|
|
|
|
|
name: currentUser.name || currentUser.username || '',
|
|
|
|
|
|
department_name: currentUser.departmentName || currentUser.department || '',
|
|
|
|
|
|
employee_grade: currentUser.grade || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function normalizeStewardPlan(rawPlan = {}, options = {}) {
|
|
|
|
|
|
const visibleThinkingEventCount = Number.isFinite(options.visibleThinkingEventCount)
|
|
|
|
|
|
? Number(options.visibleThinkingEventCount)
|
|
|
|
|
|
: Number(rawPlan.visibleThinkingEventCount || rawPlan.visible_thinking_event_count || 0)
|
2026-06-15 22:55:18 +08:00
|
|
|
|
const pendingFlowConfirmation = normalizePendingFlowConfirmation(rawPlan)
|
2026-06-04 11:03:29 +08:00
|
|
|
|
return {
|
|
|
|
|
|
planId: String(rawPlan.plan_id || rawPlan.planId || ''),
|
|
|
|
|
|
planStatus: String(rawPlan.plan_status || rawPlan.planStatus || ''),
|
2026-06-15 22:55:18 +08:00
|
|
|
|
nextAction: String(rawPlan.next_action || rawPlan.nextAction || ''),
|
|
|
|
|
|
conversationId: String(rawPlan.conversation_id || rawPlan.conversationId || ''),
|
|
|
|
|
|
stewardState: rawPlan.steward_state || rawPlan.stewardState || null,
|
2026-06-04 11:03:29 +08:00
|
|
|
|
summary: String(rawPlan.summary || ''),
|
|
|
|
|
|
visibleThinkingEventCount,
|
2026-06-04 14:25:14 +08:00
|
|
|
|
initialSummaryOnly: Boolean(rawPlan.initial_summary_only || rawPlan.initialSummaryOnly || options.initialSummaryOnly),
|
2026-06-04 11:03:29 +08:00
|
|
|
|
thinkingEvents: Array.isArray(rawPlan.thinking_events)
|
|
|
|
|
|
? rawPlan.thinking_events.map((item) => ({
|
|
|
|
|
|
eventId: String(item.event_id || item.eventId || ''),
|
|
|
|
|
|
stage: String(item.stage || ''),
|
|
|
|
|
|
title: String(item.title || ''),
|
|
|
|
|
|
content: String(item.content || ''),
|
|
|
|
|
|
status: String(item.status || 'completed')
|
|
|
|
|
|
}))
|
|
|
|
|
|
: [],
|
|
|
|
|
|
tasks: Array.isArray(rawPlan.tasks)
|
2026-06-04 14:25:14 +08:00
|
|
|
|
? rawPlan.tasks.map((item) => {
|
|
|
|
|
|
const taskType = String(item.task_type || item.taskType || '')
|
2026-06-06 17:19:07 +08:00
|
|
|
|
const rawMissingFields = Array.isArray(item.missing_fields || item.missingFields)
|
2026-06-04 11:03:29 +08:00
|
|
|
|
? item.missing_fields || item.missingFields
|
2026-06-04 14:25:14 +08:00
|
|
|
|
: []
|
2026-06-06 17:19:07 +08:00
|
|
|
|
const missingFields = filterStewardBlockingMissingFields(rawMissingFields, taskType)
|
2026-06-04 14:25:14 +08:00
|
|
|
|
return {
|
|
|
|
|
|
taskId: String(item.task_id || item.taskId || ''),
|
|
|
|
|
|
taskType,
|
|
|
|
|
|
taskTypeLabel: TASK_TYPE_LABELS[taskType] || '财务任务',
|
|
|
|
|
|
assignedAgent: String(item.assigned_agent || item.assignedAgent || ''),
|
|
|
|
|
|
assignedAgentLabel:
|
|
|
|
|
|
AGENT_LABELS[String(item.assigned_agent || item.assignedAgent || '')] ||
|
|
|
|
|
|
AGENT_LABELS[taskType] ||
|
|
|
|
|
|
'小财管家',
|
|
|
|
|
|
title: String(item.title || ''),
|
|
|
|
|
|
summary: String(item.summary || ''),
|
|
|
|
|
|
status: String(item.status || ''),
|
|
|
|
|
|
confidence: Number(item.confidence || 0),
|
|
|
|
|
|
ontologyFields: item.ontology_fields || item.ontologyFields || {},
|
|
|
|
|
|
missingFields,
|
|
|
|
|
|
missingFieldItems: buildStewardFieldItems(missingFields, taskType),
|
|
|
|
|
|
confirmationRequired: item.confirmation_required ?? item.confirmationRequired ?? true
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-06-04 11:03:29 +08:00
|
|
|
|
: [],
|
|
|
|
|
|
attachmentGroups: Array.isArray(rawPlan.attachment_groups)
|
|
|
|
|
|
? rawPlan.attachment_groups.map((item) => ({
|
|
|
|
|
|
groupId: String(item.group_id || item.groupId || ''),
|
|
|
|
|
|
targetTaskId: String(item.target_task_id || item.targetTaskId || ''),
|
|
|
|
|
|
scene: String(item.scene || ''),
|
|
|
|
|
|
sceneLabel: String(item.scene_label || item.sceneLabel || ''),
|
|
|
|
|
|
attachmentNames: Array.isArray(item.attachment_names || item.attachmentNames)
|
|
|
|
|
|
? item.attachment_names || item.attachmentNames
|
|
|
|
|
|
: [],
|
|
|
|
|
|
excludedAttachmentNames: Array.isArray(item.excluded_attachment_names || item.excludedAttachmentNames)
|
|
|
|
|
|
? item.excluded_attachment_names || item.excludedAttachmentNames
|
|
|
|
|
|
: [],
|
|
|
|
|
|
confidence: Number(item.confidence || 0),
|
|
|
|
|
|
rationale: String(item.rationale || ''),
|
|
|
|
|
|
confirmationRequired: item.confirmation_required ?? item.confirmationRequired ?? true
|
|
|
|
|
|
}))
|
|
|
|
|
|
: [],
|
|
|
|
|
|
confirmationGroups: Array.isArray(rawPlan.confirmation_groups)
|
|
|
|
|
|
? rawPlan.confirmation_groups
|
2026-06-15 22:55:18 +08:00
|
|
|
|
: [],
|
|
|
|
|
|
pendingFlowConfirmation,
|
2026-06-18 14:15:30 +08:00
|
|
|
|
candidateFlows: pendingFlowConfirmation.candidateFlows,
|
|
|
|
|
|
suggestedPrompts: Array.isArray(rawPlan.suggested_prompts)
|
|
|
|
|
|
? rawPlan.suggested_prompts.map((item) => String(item || '').trim()).filter(Boolean)
|
|
|
|
|
|
: []
|
2026-06-04 11:03:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildStewardPlanMessageText(plan) {
|
|
|
|
|
|
const normalized = normalizeStewardPlan(plan)
|
2026-06-18 14:15:30 +08:00
|
|
|
|
if (isOffTopicPlan(normalized)) {
|
|
|
|
|
|
return buildOffTopicMessageText(normalized)
|
|
|
|
|
|
}
|
2026-06-15 22:55:18 +08:00
|
|
|
|
if (isPendingFlowConfirmationPlan(normalized)) {
|
|
|
|
|
|
return buildPendingFlowConfirmationMessageText(normalized)
|
|
|
|
|
|
}
|
2026-06-18 22:12:24 +08:00
|
|
|
|
const genericReimbursementTask = normalized.tasks.find((task) => isGenericReimbursementTask(task))
|
|
|
|
|
|
if (genericReimbursementTask && normalized.tasks.length === 1) {
|
|
|
|
|
|
return buildGenericReimbursementIntentMessageText(genericReimbursementTask)
|
|
|
|
|
|
}
|
2026-06-04 14:25:14 +08:00
|
|
|
|
const nextContext = resolveNextActionContext(normalized)
|
|
|
|
|
|
const orderedTasks = buildOrderedStewardTasks(normalized, nextContext?.task)
|
|
|
|
|
|
const taskLines = orderedTasks.map((task, index) =>
|
|
|
|
|
|
`${index + 1}. **${buildTaskOrderVerb(index)}${buildTaskOrderTarget(task)}**\n - ${buildTaskOrderActionDescription(task)}`
|
2026-06-04 11:03:29 +08:00
|
|
|
|
)
|
|
|
|
|
|
return [
|
2026-06-18 14:15:30 +08:00
|
|
|
|
'### 我先帮你把步骤理清楚',
|
2026-06-04 11:03:29 +08:00
|
|
|
|
'',
|
2026-06-18 14:15:30 +08:00
|
|
|
|
buildStewardPlanFriendlyIntro(normalized),
|
2026-06-04 11:03:29 +08:00
|
|
|
|
'',
|
2026-06-04 14:25:14 +08:00
|
|
|
|
...taskLines,
|
|
|
|
|
|
'',
|
2026-06-18 14:15:30 +08:00
|
|
|
|
'你看这个顺序是否合适?如果没问题,回复 **确定** 就行。我会先帮你进入第一步,需要补充的信息会在具体步骤里再温和提醒你。'
|
2026-06-04 14:25:14 +08:00
|
|
|
|
].filter((line, index, lines) => line || lines[index - 1]).join('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function buildStewardFieldItems(fields = [], taskType = '') {
|
2026-06-06 17:19:07 +08:00
|
|
|
|
const safeFields = filterStewardBlockingMissingFields(fields, taskType)
|
2026-06-04 14:25:14 +08:00
|
|
|
|
const seen = new Set()
|
|
|
|
|
|
return safeFields
|
|
|
|
|
|
.map((field) => normalizeFieldKey(field))
|
|
|
|
|
|
.filter((field) => {
|
|
|
|
|
|
if (!field || seen.has(field)) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
seen.add(field)
|
|
|
|
|
|
return true
|
|
|
|
|
|
})
|
|
|
|
|
|
.map((field) => resolveFieldDisplay(field, taskType))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-06 17:19:07 +08:00
|
|
|
|
export function formatStewardMissingFieldList(fields = [], taskType = '', options = {}) {
|
|
|
|
|
|
const includeHints = options.includeHints !== false
|
2026-06-04 14:25:14 +08:00
|
|
|
|
return buildStewardFieldItems(fields, taskType)
|
2026-06-06 17:19:07 +08:00
|
|
|
|
.map((item) => includeHints && item.hint ? `${item.label}(${item.hint})` : item.label)
|
2026-06-04 14:25:14 +08:00
|
|
|
|
.join('、')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-06 17:19:07 +08:00
|
|
|
|
export function filterStewardBlockingMissingFields(fields = [], taskType = '') {
|
|
|
|
|
|
const safeFields = Array.isArray(fields) ? fields : []
|
|
|
|
|
|
const seen = new Set()
|
|
|
|
|
|
if (taskType !== 'expense_application') {
|
|
|
|
|
|
return safeFields
|
|
|
|
|
|
.map((field) => normalizeFieldKey(field))
|
|
|
|
|
|
.filter((field) => {
|
|
|
|
|
|
if (!field || seen.has(field)) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
seen.add(field)
|
|
|
|
|
|
return true
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return safeFields
|
|
|
|
|
|
.map((field) => normalizeFieldKey(field))
|
|
|
|
|
|
.filter((field) => {
|
|
|
|
|
|
if (!field || seen.has(field) || APPLICATION_NON_BLOCKING_MISSING_FIELDS.has(field)) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
seen.add(field)
|
|
|
|
|
|
return true
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 14:25:14 +08:00
|
|
|
|
export function formatStewardOntologyFields(fields = {}, taskType = '') {
|
|
|
|
|
|
return Object.entries(fields || {})
|
|
|
|
|
|
.filter(([, value]) => String(value || '').trim())
|
|
|
|
|
|
.map(([key, value]) => {
|
|
|
|
|
|
const field = resolveFieldDisplay(key, taskType)
|
2026-06-06 17:19:07 +08:00
|
|
|
|
return `${field.label}:${formatStewardFieldDisplayValue(field.key, value)}`
|
2026-06-04 14:25:14 +08:00
|
|
|
|
})
|
|
|
|
|
|
.join(';')
|
2026-06-04 11:03:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-18 22:12:24 +08:00
|
|
|
|
function buildStewardOntologyFieldRows(fields = {}, taskType = '') {
|
|
|
|
|
|
return Object.entries(fields || {})
|
|
|
|
|
|
.filter(([, value]) => String(value || '').trim())
|
|
|
|
|
|
.map(([key, value]) => {
|
|
|
|
|
|
const field = resolveFieldDisplay(key, taskType)
|
|
|
|
|
|
return {
|
|
|
|
|
|
label: field.label,
|
|
|
|
|
|
value: formatStewardFieldDisplayValue(field.key, value)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function escapeMarkdownTableCell(value) {
|
|
|
|
|
|
return String(value || '').replace(/\|/g, '\\|').replace(/\n+/g, ' ').trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatStewardOntologyFieldsTable(fields = {}, taskType = '') {
|
|
|
|
|
|
const rows = buildStewardOntologyFieldRows(fields, taskType)
|
|
|
|
|
|
if (!rows.length) {
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
return [
|
|
|
|
|
|
'| 字段 | 内容 |',
|
|
|
|
|
|
'| --- | --- |',
|
|
|
|
|
|
...rows.map((row) => `| ${escapeMarkdownTableCell(row.label)} | ${escapeMarkdownTableCell(row.value)} |`)
|
|
|
|
|
|
].join('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resolveCandidateFlowExpenseType(flow = {}) {
|
|
|
|
|
|
const rawType = String(flow?.ontologyFields?.expense_type || flow?.ontologyFields?.expenseType || '').trim()
|
|
|
|
|
|
if (rawType === '差旅' || rawType === 'travel') {
|
|
|
|
|
|
return 'travel'
|
|
|
|
|
|
}
|
|
|
|
|
|
return rawType
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 11:03:29 +08:00
|
|
|
|
export function buildStewardSuggestedActions(plan) {
|
|
|
|
|
|
const normalized = normalizeStewardPlan(plan)
|
2026-06-18 14:15:30 +08:00
|
|
|
|
if (isOffTopicPlan(normalized)) {
|
|
|
|
|
|
return normalized.suggestedPrompts.map((prompt) => ({
|
|
|
|
|
|
label: prompt.length > 24 ? `${prompt.slice(0, 24)}...` : prompt,
|
|
|
|
|
|
description: '点击填入输入框,可编辑后发送',
|
|
|
|
|
|
icon: 'mdi mdi-comment-text-outline',
|
|
|
|
|
|
action_type: ASSISTANT_SCOPE_ACTION_FILL_COMPOSER,
|
|
|
|
|
|
payload: {
|
|
|
|
|
|
steward_plan_id: normalized.planId,
|
|
|
|
|
|
fill_text: prompt
|
|
|
|
|
|
}
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
2026-06-15 22:55:18 +08:00
|
|
|
|
if (isPendingFlowConfirmationPlan(normalized)) {
|
2026-06-18 22:12:24 +08:00
|
|
|
|
return normalized.candidateFlows.map((flow) => {
|
|
|
|
|
|
const expenseType = resolveCandidateFlowExpenseType(flow)
|
|
|
|
|
|
return {
|
|
|
|
|
|
label: flow.label,
|
|
|
|
|
|
description: flow.reason || '选择后小财管家会继续整理对应流程材料。',
|
|
|
|
|
|
icon: flow.flowId === 'travel_application'
|
|
|
|
|
|
? 'mdi mdi-file-plus-outline'
|
|
|
|
|
|
: 'mdi mdi-receipt-text-plus-outline',
|
|
|
|
|
|
action_type: ASSISTANT_SCOPE_ACTION_SWITCH,
|
|
|
|
|
|
payload: {
|
|
|
|
|
|
steward_confirm_flow: true,
|
|
|
|
|
|
steward_plan_id: normalized.planId,
|
|
|
|
|
|
flow_id: flow.flowId,
|
|
|
|
|
|
session_type: flow.flowId === 'travel_application'
|
|
|
|
|
|
? SESSION_TYPE_APPLICATION
|
|
|
|
|
|
: SESSION_TYPE_EXPENSE,
|
|
|
|
|
|
selected_flow_label: flow.label,
|
|
|
|
|
|
expense_type: expenseType,
|
|
|
|
|
|
expense_type_label: FLOW_EXPENSE_TYPE_LABELS[expenseType] || '',
|
|
|
|
|
|
requires_application_before_reimbursement: flow.flowId === 'travel_reimbursement' && expenseType === 'travel',
|
|
|
|
|
|
carry_text: flow.flowId === 'travel_reimbursement' && expenseType === 'travel' ? '我要报销' : flow.label,
|
|
|
|
|
|
auto_submit: true,
|
|
|
|
|
|
steward_state: normalized.stewardState || null
|
|
|
|
|
|
}
|
2026-06-15 22:55:18 +08:00
|
|
|
|
}
|
2026-06-18 22:12:24 +08:00
|
|
|
|
})
|
2026-06-15 22:55:18 +08:00
|
|
|
|
}
|
2026-06-04 14:25:14 +08:00
|
|
|
|
const nextContext = resolveNextActionContext(normalized)
|
|
|
|
|
|
if (!nextContext) {
|
|
|
|
|
|
return []
|
|
|
|
|
|
}
|
|
|
|
|
|
const { action, actionType, task, group } = nextContext
|
|
|
|
|
|
const targetSessionType = actionType === 'confirm_create_application'
|
|
|
|
|
|
? SESSION_TYPE_APPLICATION
|
|
|
|
|
|
: SESSION_TYPE_EXPENSE
|
|
|
|
|
|
return [
|
|
|
|
|
|
{
|
2026-06-18 22:12:24 +08:00
|
|
|
|
label: buildNextActionLabel(actionType, task),
|
2026-06-04 14:25:14 +08:00
|
|
|
|
description: buildNextActionDescription(actionType, normalized, task, group),
|
2026-06-04 11:03:29 +08:00
|
|
|
|
icon: actionType === 'confirm_create_application'
|
|
|
|
|
|
? 'mdi mdi-file-plus-outline'
|
|
|
|
|
|
: actionType === 'confirm_attachment_group'
|
|
|
|
|
|
? 'mdi mdi-folder-check-outline'
|
|
|
|
|
|
: 'mdi mdi-receipt-text-plus-outline',
|
|
|
|
|
|
action_type: ASSISTANT_SCOPE_ACTION_SWITCH,
|
|
|
|
|
|
payload: {
|
|
|
|
|
|
session_type: targetSessionType,
|
2026-06-04 14:25:14 +08:00
|
|
|
|
carry_text: buildStewardCarryText(actionType, task, group, normalized),
|
2026-06-04 11:03:29 +08:00
|
|
|
|
carry_files: actionType !== 'confirm_create_application',
|
|
|
|
|
|
auto_submit: true,
|
|
|
|
|
|
steward_confirmation_id: String(action.confirmation_id || action.confirmationId || ''),
|
2026-06-04 14:25:14 +08:00
|
|
|
|
steward_plan_id: normalized.planId,
|
|
|
|
|
|
steward_next_task_id: task?.taskId || '',
|
2026-06-06 17:19:07 +08:00
|
|
|
|
steward_current_task: buildStewardTaskPayload(task),
|
2026-06-04 14:25:14 +08:00
|
|
|
|
steward_remaining_task_count: normalized.tasks.filter((item) => item.taskId !== task?.taskId).length,
|
|
|
|
|
|
steward_remaining_tasks: buildRemainingTaskPayload(normalized, task?.taskId)
|
2026-06-04 11:03:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-04 14:25:14 +08:00
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-15 22:55:18 +08:00
|
|
|
|
function normalizePendingFlowConfirmation(rawPlan = {}) {
|
|
|
|
|
|
const rawPending = rawPlan.pending_flow_confirmation || rawPlan.pendingFlowConfirmation || {}
|
|
|
|
|
|
const rawCandidates = Array.isArray(rawPlan.candidate_flows || rawPlan.candidateFlows)
|
|
|
|
|
|
? rawPlan.candidate_flows || rawPlan.candidateFlows
|
|
|
|
|
|
: rawPending?.candidate_flows || rawPending?.candidateFlows || []
|
|
|
|
|
|
const candidateFlows = Array.isArray(rawCandidates)
|
|
|
|
|
|
? rawCandidates
|
|
|
|
|
|
.map((item) => normalizeCandidateFlow(item))
|
|
|
|
|
|
.filter((item) => item.flowId)
|
|
|
|
|
|
: []
|
|
|
|
|
|
return {
|
|
|
|
|
|
status: String(rawPending?.status || '').trim(),
|
|
|
|
|
|
sourceMessage: String(rawPending?.source_message || rawPending?.sourceMessage || '').trim(),
|
|
|
|
|
|
reason: String(rawPending?.reason || '').trim(),
|
|
|
|
|
|
candidateFlows
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeCandidateFlow(item = {}) {
|
|
|
|
|
|
const flowId = String(item.flow_id || item.flowId || '').trim()
|
|
|
|
|
|
if (!['travel_application', 'travel_reimbursement'].includes(flowId)) {
|
|
|
|
|
|
return { flowId: '' }
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
flowId,
|
|
|
|
|
|
label: String(item.label || (flowId === 'travel_application' ? '补办出差申请' : '发起费用报销')).trim(),
|
|
|
|
|
|
confidence: Number(item.confidence || 0),
|
|
|
|
|
|
reason: String(item.reason || '').trim(),
|
|
|
|
|
|
ontologyFields: item.ontology_fields || item.ontologyFields || {},
|
|
|
|
|
|
missingFields: Array.isArray(item.missing_fields || item.missingFields)
|
|
|
|
|
|
? item.missing_fields || item.missingFields
|
|
|
|
|
|
: []
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isPendingFlowConfirmationPlan(normalized) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
String(normalized?.nextAction || '').trim() === 'confirm_flow' ||
|
|
|
|
|
|
String(normalized?.planStatus || '').trim() === 'needs_flow_confirmation' ||
|
|
|
|
|
|
String(normalized?.pendingFlowConfirmation?.status || '').trim() === 'pending'
|
|
|
|
|
|
) && Array.isArray(normalized?.candidateFlows) && normalized.candidateFlows.length > 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-18 14:15:30 +08:00
|
|
|
|
function isOffTopicPlan(normalized) {
|
|
|
|
|
|
return String(normalized?.planStatus || '').trim() === 'off_topic'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function isOffTopicStewardPlan(rawPlan) {
|
|
|
|
|
|
return isOffTopicPlan(normalizeStewardPlan(rawPlan))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildOffTopicMessageText(normalized) {
|
2026-06-18 22:12:24 +08:00
|
|
|
|
// off_topic 计划的引导文案完全由后端生成(含 ### 标题 + 正文 + 引导句),
|
|
|
|
|
|
// 前端透传 summary 即可,避免重复拼接导致与后端表达不一致。
|
2026-06-18 14:15:30 +08:00
|
|
|
|
const summary = String(normalized?.summary || '').trim()
|
2026-06-18 22:12:24 +08:00
|
|
|
|
if (summary) {
|
|
|
|
|
|
return summary
|
|
|
|
|
|
}
|
|
|
|
|
|
return (
|
|
|
|
|
|
'### 这句话我暂时没识别到财务事项\n\n' +
|
|
|
|
|
|
'很抱歉主人,目前小财管家只能帮您整理**费用申请**和**费用报销**这两类事项。\n\n' +
|
|
|
|
|
|
'要不您换种说法告诉我:'
|
|
|
|
|
|
)
|
2026-06-18 14:15:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-15 22:55:18 +08:00
|
|
|
|
function buildPendingFlowConfirmationMessageText(normalized) {
|
|
|
|
|
|
const fields = normalized.candidateFlows[0]?.ontologyFields || {}
|
2026-06-18 22:12:24 +08:00
|
|
|
|
const knownTable = formatStewardOntologyFieldsTable(fields, 'expense_application')
|
2026-06-15 22:55:18 +08:00
|
|
|
|
const candidateLines = normalized.candidateFlows.map((flow, index) =>
|
|
|
|
|
|
`${index + 1}. **${flow.label}**${flow.reason ? `\n - ${flow.reason}` : ''}`
|
|
|
|
|
|
)
|
2026-06-18 22:12:24 +08:00
|
|
|
|
const singleCandidate = normalized.candidateFlows.length === 1
|
2026-06-15 22:55:18 +08:00
|
|
|
|
return [
|
|
|
|
|
|
'### 需要先确认流程方向',
|
|
|
|
|
|
'',
|
2026-06-18 22:12:24 +08:00
|
|
|
|
knownTable
|
|
|
|
|
|
? ['我识别到这是一项财务事项,已提取到:', '', knownTable].join('\n')
|
2026-06-15 22:55:18 +08:00
|
|
|
|
: '我识别到这是一项财务事项,但还需要确认你要进入哪个流程。',
|
|
|
|
|
|
'',
|
|
|
|
|
|
normalized.pendingFlowConfirmation.reason || normalized.summary || '当前还不能确定你要补办申请还是发起报销。',
|
|
|
|
|
|
'',
|
|
|
|
|
|
...candidateLines,
|
|
|
|
|
|
'',
|
2026-06-18 22:12:24 +08:00
|
|
|
|
singleCandidate
|
|
|
|
|
|
? `请先点击下方 **${normalized.candidateFlows[0].label}**,我会继续整理对应材料。`
|
|
|
|
|
|
: '请先选择一个方向,我会继续整理对应材料。'
|
2026-06-15 22:55:18 +08:00
|
|
|
|
].filter((line, index, lines) => line || lines[index - 1]).join('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-18 22:12:24 +08:00
|
|
|
|
function buildGenericReimbursementIntentMessageText() {
|
|
|
|
|
|
return [
|
|
|
|
|
|
'### 我来带你发起报销',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'你现在只说了要报销,还没告诉我具体是哪类费用。先不用一次性补全所有信息,我会按报销流程一步步带你填。',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'1. **先选报销场景**',
|
|
|
|
|
|
' - 例如差旅费、交通费、住宿费、业务招待费或办公用品费,不同场景需要的材料不一样。',
|
|
|
|
|
|
'2. **再补关键材料**',
|
|
|
|
|
|
' - 我会继续追问事由、发生时间、金额和票据附件;如果是差旅或招待,还会先帮你核对是否需要关联事前申请。',
|
|
|
|
|
|
'',
|
|
|
|
|
|
'点击下面的 **确定,选择报销场景**,我会进入报销助手继续引导。'
|
|
|
|
|
|
].join('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 14:25:14 +08:00
|
|
|
|
function resolveNextActionContext(normalized) {
|
|
|
|
|
|
const applicationTask = normalized.tasks.find((task) => task.taskType === 'expense_application')
|
|
|
|
|
|
const applicationAction = applicationTask
|
|
|
|
|
|
? findConfirmationAction(normalized, 'confirm_create_application', applicationTask.taskId)
|
|
|
|
|
|
: null
|
|
|
|
|
|
if (applicationAction) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
action: applicationAction,
|
|
|
|
|
|
actionType: 'confirm_create_application',
|
|
|
|
|
|
task: applicationTask,
|
|
|
|
|
|
group: null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const reimbursementTask = normalized.tasks.find((task) => task.taskType === 'reimbursement')
|
|
|
|
|
|
const reimbursementAction = reimbursementTask
|
|
|
|
|
|
? findConfirmationAction(normalized, 'confirm_create_reimbursement_draft', reimbursementTask.taskId)
|
|
|
|
|
|
: null
|
|
|
|
|
|
if (reimbursementAction) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
action: reimbursementAction,
|
|
|
|
|
|
actionType: 'confirm_create_reimbursement_draft',
|
|
|
|
|
|
task: reimbursementTask,
|
|
|
|
|
|
group: findAttachmentGroupForTask(normalized, reimbursementTask.taskId)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const attachmentAction = normalized.confirmationGroups.find((action) =>
|
|
|
|
|
|
normalizeActionType(action) === 'confirm_attachment_group'
|
|
|
|
|
|
)
|
|
|
|
|
|
if (attachmentAction) {
|
|
|
|
|
|
const groupId = String(attachmentAction.attachment_group_id || attachmentAction.attachmentGroupId || '').trim()
|
|
|
|
|
|
const group = normalized.attachmentGroups.find((item) => item.groupId === groupId)
|
|
|
|
|
|
const task = normalized.tasks.find((item) => item.taskId === group?.targetTaskId)
|
|
|
|
|
|
return {
|
|
|
|
|
|
action: attachmentAction,
|
|
|
|
|
|
actionType: 'confirm_attachment_group',
|
|
|
|
|
|
task,
|
|
|
|
|
|
group
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const fallbackAction = normalized.confirmationGroups[0]
|
|
|
|
|
|
if (!fallbackAction) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
const actionType = normalizeActionType(fallbackAction)
|
|
|
|
|
|
const taskId = String(fallbackAction.target_task_id || fallbackAction.targetTaskId || '').trim()
|
|
|
|
|
|
return {
|
|
|
|
|
|
action: fallbackAction,
|
|
|
|
|
|
actionType,
|
|
|
|
|
|
task: normalized.tasks.find((task) => task.taskId === taskId),
|
|
|
|
|
|
group: null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function findConfirmationAction(normalized, actionType, taskId) {
|
|
|
|
|
|
return normalized.confirmationGroups.find((action) =>
|
|
|
|
|
|
normalizeActionType(action) === actionType
|
|
|
|
|
|
&& String(action.target_task_id || action.targetTaskId || '').trim() === taskId
|
|
|
|
|
|
) || normalized.confirmationGroups.find((action) => normalizeActionType(action) === actionType)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function findAttachmentGroupForTask(normalized, taskId) {
|
|
|
|
|
|
return normalized.attachmentGroups.find((group) => group.targetTaskId === taskId)
|
|
|
|
|
|
|| normalized.attachmentGroups[0]
|
|
|
|
|
|
|| null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeActionType(action) {
|
|
|
|
|
|
return String(action?.action_type || action?.actionType || '').trim()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildStewardExecutionSummary(normalized) {
|
|
|
|
|
|
const attachmentCount = normalized.attachmentGroups
|
|
|
|
|
|
.reduce((total, group) => total + group.attachmentNames.length, 0)
|
|
|
|
|
|
const summary = [`我识别到 **${normalized.tasks.length} 个待处理任务**`]
|
|
|
|
|
|
if (attachmentCount) {
|
|
|
|
|
|
summary.push(`并形成 ${attachmentCount} 份附件的归集建议`)
|
|
|
|
|
|
}
|
|
|
|
|
|
summary.push(`。${buildTaskOrderDescription(normalized)}`)
|
|
|
|
|
|
return summary.join('')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildOrderedStewardTasks(normalized, nextTask = null) {
|
|
|
|
|
|
if (!nextTask?.taskId) {
|
|
|
|
|
|
return normalized.tasks
|
|
|
|
|
|
}
|
|
|
|
|
|
return [
|
|
|
|
|
|
nextTask,
|
|
|
|
|
|
...normalized.tasks.filter((task) => task.taskId !== nextTask.taskId)
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildTaskOrderVerb(index) {
|
|
|
|
|
|
if (index === 0) {
|
|
|
|
|
|
return '先'
|
|
|
|
|
|
}
|
|
|
|
|
|
if (index === 1) {
|
|
|
|
|
|
return '再'
|
|
|
|
|
|
}
|
|
|
|
|
|
return '然后'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildTaskOrderTarget(task) {
|
|
|
|
|
|
const title = task.title || task.taskTypeLabel
|
|
|
|
|
|
if (task.taskType === 'expense_application') {
|
2026-06-18 14:15:30 +08:00
|
|
|
|
return `整理“${title}”`
|
2026-06-04 14:25:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (task.taskType === 'reimbursement') {
|
2026-06-18 14:15:30 +08:00
|
|
|
|
return `核对“${title}”`
|
2026-06-04 14:25:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
return `处理“${title}”`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildTaskOrderActionDescription(task) {
|
|
|
|
|
|
const agent = task.assignedAgentLabel || '对应助手'
|
|
|
|
|
|
if (task.taskType === 'expense_application') {
|
2026-06-18 14:15:30 +08:00
|
|
|
|
return `我会请${agent}先把申请单草稿整理出来,方便你核对关键信息,再决定是否继续。`
|
2026-06-04 14:25:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (task.taskType === 'reimbursement') {
|
2026-06-18 22:12:24 +08:00
|
|
|
|
if (isGenericReimbursementTask(task)) {
|
|
|
|
|
|
return `我会请${agent}先带你选择报销场景,再逐步补齐事由、时间、金额和票据。`
|
|
|
|
|
|
}
|
2026-06-18 14:15:30 +08:00
|
|
|
|
return `我会请${agent}把票据、金额和制度口径先核清楚,前一步确认后再继续往下走。`
|
2026-06-04 14:25:14 +08:00
|
|
|
|
}
|
2026-06-18 14:15:30 +08:00
|
|
|
|
return `我会请${agent}先整理可核对的结果,真正执行前仍会让你确认。`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildStewardPlanFriendlyIntro(normalized) {
|
|
|
|
|
|
const taskCountText = normalized.tasks.length > 1
|
|
|
|
|
|
? `${normalized.tasks.length} 个相关事项`
|
|
|
|
|
|
: '1 个事项'
|
|
|
|
|
|
return `我先看了一下,你这次主要是 **${taskCountText}**。为了不让步骤混在一起,我会先把要做的事拆开,让你每一步都能看清楚、确认后再继续。`
|
2026-06-04 14:25:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildTaskOrderDescription(normalized) {
|
|
|
|
|
|
const hasApplication = normalized.tasks.some((task) => task.taskType === 'expense_application')
|
|
|
|
|
|
const hasReimbursement = normalized.tasks.some((task) => task.taskType === 'reimbursement')
|
|
|
|
|
|
if (hasApplication && hasReimbursement) {
|
|
|
|
|
|
return '处理顺序是:先创建申请单,再引导填写报销单。'
|
|
|
|
|
|
}
|
|
|
|
|
|
if (hasApplication) {
|
|
|
|
|
|
return '我会先引导创建申请单并等待你确认。'
|
|
|
|
|
|
}
|
|
|
|
|
|
if (hasReimbursement) {
|
|
|
|
|
|
return '我会引导填写报销单并等待你确认。'
|
|
|
|
|
|
}
|
|
|
|
|
|
return '我会按识别顺序逐项推进,并在执行前等待你确认。'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildNextTaskLead(task) {
|
|
|
|
|
|
if (task.taskType === 'expense_application') {
|
|
|
|
|
|
return `先创建“${task.title || task.taskTypeLabel}”`
|
|
|
|
|
|
}
|
|
|
|
|
|
if (task.taskType === 'reimbursement') {
|
|
|
|
|
|
return `继续填写“${task.title || task.taskTypeLabel}”`
|
|
|
|
|
|
}
|
|
|
|
|
|
return `处理“${task.title || task.taskTypeLabel}”`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-18 22:12:24 +08:00
|
|
|
|
function buildNextActionLabel(actionType, task = null) {
|
2026-06-04 14:25:14 +08:00
|
|
|
|
if (actionType === 'confirm_create_application') {
|
|
|
|
|
|
return '确定,先创建申请单'
|
|
|
|
|
|
}
|
|
|
|
|
|
if (actionType === 'confirm_attachment_group') {
|
|
|
|
|
|
return '确定,确认附件归集'
|
|
|
|
|
|
}
|
2026-06-18 22:12:24 +08:00
|
|
|
|
if (isGenericReimbursementTask(task)) {
|
|
|
|
|
|
return '确定,选择报销场景'
|
|
|
|
|
|
}
|
2026-06-04 14:25:14 +08:00
|
|
|
|
return '确定,继续填写报销单'
|
2026-06-04 11:03:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-04 14:25:14 +08:00
|
|
|
|
function buildNextActionDescription(actionType, normalized, task, group) {
|
|
|
|
|
|
const remainingCount = normalized.tasks.filter((item) => item.taskId !== task?.taskId).length
|
|
|
|
|
|
if (actionType === 'confirm_create_application') {
|
|
|
|
|
|
return remainingCount > 0
|
|
|
|
|
|
? '申请助手会先生成申请单核对结果,完成后再继续引导后续报销。'
|
|
|
|
|
|
: '申请助手会生成申请单核对结果,入库前仍需确认。'
|
|
|
|
|
|
}
|
|
|
|
|
|
if (actionType === 'confirm_attachment_group') {
|
|
|
|
|
|
return group?.attachmentNames?.length
|
|
|
|
|
|
? `先归集 ${group.attachmentNames.length} 份附件,再进入报销核对。`
|
|
|
|
|
|
: '先确认附件归集,再进入报销核对。'
|
|
|
|
|
|
}
|
|
|
|
|
|
return group?.attachmentNames?.length
|
|
|
|
|
|
? `报销助手会带入 ${group.attachmentNames.length} 份相关附件生成核对结果。`
|
2026-06-18 22:12:24 +08:00
|
|
|
|
: isGenericReimbursementTask(task)
|
|
|
|
|
|
? '先进入报销助手选择具体费用类型,再按场景补齐事由、时间、金额和票据。'
|
|
|
|
|
|
: '报销助手会根据当前任务生成报销核对结果。'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isGenericReimbursementTask(task) {
|
|
|
|
|
|
if (!task || task.taskType !== 'reimbursement') {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
const fields = task.ontologyFields || {}
|
|
|
|
|
|
const expenseType = String(fields.expense_type || '').trim()
|
|
|
|
|
|
const hasSpecificField = ['time_range', 'location', 'amount', 'attachments', 'transport_mode']
|
|
|
|
|
|
.some((key) => String(fields[key] || '').trim())
|
|
|
|
|
|
|| isSpecificReimbursementReason(fields.reason)
|
|
|
|
|
|
return !hasSpecificField && (!expenseType || expenseType === 'other')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isSpecificReimbursementReason(value) {
|
|
|
|
|
|
const text = String(value || '').trim().replace(/\s+/g, '')
|
|
|
|
|
|
if (!text) {
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
return !/^(?:我想要|我想|我要|还需要|需要|请帮我|帮我)?报销(?:费用|报销单|报销流程)?$/.test(text)
|
2026-06-04 14:25:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildStewardCarryText(actionType, task, group, normalized = null) {
|
2026-06-04 11:03:29 +08:00
|
|
|
|
if (actionType === 'confirm_attachment_group' && group) {
|
|
|
|
|
|
return [
|
|
|
|
|
|
`我确认将以下附件归集为${group.sceneLabel || '当前报销任务'},请继续整理报销核对信息。`,
|
|
|
|
|
|
`附件:${group.attachmentNames.join('、') || '待确认'}`,
|
|
|
|
|
|
group.excludedAttachmentNames.length
|
|
|
|
|
|
? `暂不归集:${group.excludedAttachmentNames.join('、')}`
|
|
|
|
|
|
: ''
|
|
|
|
|
|
].filter(Boolean).join('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!task) {
|
|
|
|
|
|
return '我确认继续处理这项财务任务,请按现有流程核对信息。'
|
|
|
|
|
|
}
|
2026-06-18 22:12:24 +08:00
|
|
|
|
if (actionType === 'confirm_create_reimbursement_draft' && isGenericReimbursementTask(task)) {
|
|
|
|
|
|
return '我要报销'
|
|
|
|
|
|
}
|
2026-06-04 11:03:29 +08:00
|
|
|
|
|
2026-06-04 14:25:14 +08:00
|
|
|
|
const fields = formatStewardOntologyFields(task.ontologyFields || {}, task.taskType)
|
2026-06-06 17:19:07 +08:00
|
|
|
|
const missingFields = formatStewardMissingFieldList(
|
|
|
|
|
|
task.missingFields || [],
|
|
|
|
|
|
task.taskType,
|
|
|
|
|
|
{ includeHints: false }
|
|
|
|
|
|
)
|
2026-06-04 14:25:14 +08:00
|
|
|
|
const lines = [
|
|
|
|
|
|
actionType === 'confirm_create_application'
|
|
|
|
|
|
? `小财管家已完成意图识别,请先创建申请单:${task.title || task.taskTypeLabel}。`
|
|
|
|
|
|
: `小财管家已完成意图识别,请继续填写报销单:${task.title || task.taskTypeLabel}。`,
|
2026-06-04 11:03:29 +08:00
|
|
|
|
task.summary ? `任务摘要:${task.summary}` : '',
|
2026-06-04 14:25:14 +08:00
|
|
|
|
fields ? `已识别信息:${fields}` : '',
|
|
|
|
|
|
group?.attachmentNames?.length ? `相关附件:${group.attachmentNames.join('、')}` : '',
|
|
|
|
|
|
group?.excludedAttachmentNames?.length ? `暂不归集附件:${group.excludedAttachmentNames.join('、')}` : '',
|
|
|
|
|
|
missingFields ? `还需要补充:${missingFields}` : '',
|
2026-06-15 22:55:18 +08:00
|
|
|
|
actionType === 'confirm_create_application'
|
|
|
|
|
|
? missingFields
|
|
|
|
|
|
? '请先追问上述缺失信息,不要直接生成申请单核对表,也不要替用户默认填写。'
|
|
|
|
|
|
: '请直接生成申请单核对结果;信息足够时生成申请单,但在入库或提交审批前仍需让我确认。'
|
|
|
|
|
|
: missingFields
|
|
|
|
|
|
? '请先追问上述缺失信息,不要直接生成报销核对结果,也不要替用户默认填写。'
|
|
|
|
|
|
: '请直接生成报销核对结果;需要创建草稿、绑定附件或提交审批前仍需让我确认。'
|
2026-06-04 14:25:14 +08:00
|
|
|
|
]
|
|
|
|
|
|
const remainingTaskText = normalized ? buildRemainingTaskText(normalized, task.taskId) : ''
|
|
|
|
|
|
if (remainingTaskText) {
|
|
|
|
|
|
lines.push(remainingTaskText)
|
|
|
|
|
|
}
|
|
|
|
|
|
return lines.filter(Boolean).join('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildRemainingTaskText(normalized, currentTaskId) {
|
|
|
|
|
|
const remainingTasks = normalized.tasks.filter((task) => task.taskId !== currentTaskId)
|
|
|
|
|
|
if (!remainingTasks.length) {
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
const taskLines = remainingTasks.map((task, index) =>
|
|
|
|
|
|
`${index + 1}. ${task.title || task.taskTypeLabel}(${task.assignedAgentLabel}):${task.summary || '待继续核对'}`
|
|
|
|
|
|
)
|
|
|
|
|
|
return [
|
|
|
|
|
|
'当前步骤完成后,请继续引导我处理后续任务:',
|
|
|
|
|
|
...taskLines
|
|
|
|
|
|
].join('\n')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildRemainingTaskPayload(normalized, currentTaskId) {
|
|
|
|
|
|
return normalized.tasks
|
|
|
|
|
|
.filter((task) => task.taskId !== currentTaskId)
|
2026-06-06 17:19:07 +08:00
|
|
|
|
.map((task) => buildStewardTaskPayload(task))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function buildStewardTaskPayload(task) {
|
|
|
|
|
|
if (!task) {
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
task_id: task.taskId || task.task_id || '',
|
|
|
|
|
|
task_type: task.taskType || task.task_type || '',
|
|
|
|
|
|
title: task.title || '',
|
|
|
|
|
|
summary: task.summary || '',
|
|
|
|
|
|
assigned_agent: task.assignedAgent || task.assigned_agent || '',
|
|
|
|
|
|
ontology_fields: task.ontologyFields || task.ontology_fields || {},
|
|
|
|
|
|
missing_fields: task.missingFields || task.missing_fields || []
|
|
|
|
|
|
}
|
2026-06-04 11:03:29 +08:00
|
|
|
|
}
|