feat(steward): 前端支持 off_topic 与引导话术
- assistantSessionScope.js:新增 ASSISTANT_SCOPE_ACTION_FILL_COMPOSER 常量 - assistantSuggestedActionPrefill.js:识别 fill_composer 与 payload.fill_text - stewardPlanModel.js:normalizeStewardPlan 透传 suggestedPrompts; buildStewardPlanMessageText / buildStewardSuggestedActions 新增 off_topic 分支,按钮填充输入框不提交 - useStewardPlanFlow.js:isPendingStewardActionMessage 排除 off_topic - steward-plan-off-topic.test.mjs:覆盖 normalize/文案/按钮/兼容路径
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
ASSISTANT_SCOPE_ACTION_SWITCH,
|
||||
ASSISTANT_SCOPE_ACTION_FILL_COMPOSER
|
||||
} from '../../utils/assistantSessionScope.js'
|
||||
import {
|
||||
SESSION_TYPE_APPLICATION,
|
||||
@@ -200,12 +201,18 @@ export function normalizeStewardPlan(rawPlan = {}, options = {}) {
|
||||
? rawPlan.confirmation_groups
|
||||
: [],
|
||||
pendingFlowConfirmation,
|
||||
candidateFlows: pendingFlowConfirmation.candidateFlows
|
||||
candidateFlows: pendingFlowConfirmation.candidateFlows,
|
||||
suggestedPrompts: Array.isArray(rawPlan.suggested_prompts)
|
||||
? rawPlan.suggested_prompts.map((item) => String(item || '').trim()).filter(Boolean)
|
||||
: []
|
||||
}
|
||||
}
|
||||
|
||||
export function buildStewardPlanMessageText(plan) {
|
||||
const normalized = normalizeStewardPlan(plan)
|
||||
if (isOffTopicPlan(normalized)) {
|
||||
return buildOffTopicMessageText(normalized)
|
||||
}
|
||||
if (isPendingFlowConfirmationPlan(normalized)) {
|
||||
return buildPendingFlowConfirmationMessageText(normalized)
|
||||
}
|
||||
@@ -215,13 +222,13 @@ export function buildStewardPlanMessageText(plan) {
|
||||
`${index + 1}. **${buildTaskOrderVerb(index)}${buildTaskOrderTarget(task)}**\n - ${buildTaskOrderActionDescription(task)}`
|
||||
)
|
||||
return [
|
||||
'### 我会这样推进',
|
||||
'### 我先帮你把步骤理清楚',
|
||||
'',
|
||||
`我识别到 **${normalized.tasks.length} 个财务事项**,会按顺序逐步处理,不会一次性把所有动作都执行掉。`,
|
||||
buildStewardPlanFriendlyIntro(normalized),
|
||||
'',
|
||||
...taskLines,
|
||||
'',
|
||||
'如果这个顺序没问题,请回复 **确定**。我会先进入第一步,并在具体步骤里再判断需要你补充哪些信息。'
|
||||
'你看这个顺序是否合适?如果没问题,回复 **确定** 就行。我会先帮你进入第一步,需要补充的信息会在具体步骤里再温和提醒你。'
|
||||
].filter((line, index, lines) => line || lines[index - 1]).join('\n')
|
||||
}
|
||||
|
||||
@@ -284,6 +291,18 @@ export function formatStewardOntologyFields(fields = {}, taskType = '') {
|
||||
|
||||
export function buildStewardSuggestedActions(plan) {
|
||||
const normalized = normalizeStewardPlan(plan)
|
||||
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
|
||||
}
|
||||
}))
|
||||
}
|
||||
if (isPendingFlowConfirmationPlan(normalized)) {
|
||||
return normalized.candidateFlows.map((flow) => ({
|
||||
label: flow.label,
|
||||
@@ -383,6 +402,28 @@ function isPendingFlowConfirmationPlan(normalized) {
|
||||
) && Array.isArray(normalized?.candidateFlows) && normalized.candidateFlows.length > 0
|
||||
}
|
||||
|
||||
function isOffTopicPlan(normalized) {
|
||||
return String(normalized?.planStatus || '').trim() === 'off_topic'
|
||||
}
|
||||
|
||||
export function isOffTopicStewardPlan(rawPlan) {
|
||||
return isOffTopicPlan(normalizeStewardPlan(rawPlan))
|
||||
}
|
||||
|
||||
function buildOffTopicMessageText(normalized) {
|
||||
const summary = String(normalized?.summary || '').trim()
|
||||
const summaryLine = summary && summary !== '这看起来跟财务任务没什么关系...'
|
||||
? summary
|
||||
: '这看起来跟财务任务没什么关系,我目前只能帮你处理**费用申请**和**费用报销**两类事项。'
|
||||
return [
|
||||
'### 小财管家没看懂这件事',
|
||||
'',
|
||||
summaryLine,
|
||||
'',
|
||||
'你可以试试下面这些方式告诉我:'
|
||||
].join('\n')
|
||||
}
|
||||
|
||||
function buildPendingFlowConfirmationMessageText(normalized) {
|
||||
const fields = normalized.candidateFlows[0]?.ontologyFields || {}
|
||||
const knownParts = formatStewardOntologyFields(fields, 'expense_application')
|
||||
@@ -511,10 +552,10 @@ function buildTaskOrderVerb(index) {
|
||||
function buildTaskOrderTarget(task) {
|
||||
const title = task.title || task.taskTypeLabel
|
||||
if (task.taskType === 'expense_application') {
|
||||
return `创建“${title}”`
|
||||
return `整理“${title}”`
|
||||
}
|
||||
if (task.taskType === 'reimbursement') {
|
||||
return `处理“${title}”`
|
||||
return `核对“${title}”`
|
||||
}
|
||||
return `处理“${title}”`
|
||||
}
|
||||
@@ -522,12 +563,19 @@ function buildTaskOrderTarget(task) {
|
||||
function buildTaskOrderActionDescription(task) {
|
||||
const agent = task.assignedAgentLabel || '对应助手'
|
||||
if (task.taskType === 'expense_application') {
|
||||
return `交给${agent}生成申请单核对结果,确认无误后再进入后续动作。`
|
||||
return `我会请${agent}先把申请单草稿整理出来,方便你核对关键信息,再决定是否继续。`
|
||||
}
|
||||
if (task.taskType === 'reimbursement') {
|
||||
return `交给${agent}整理报销核对结果,等前一步完成后再继续推进。`
|
||||
return `我会请${agent}把票据、金额和制度口径先核清楚,前一步确认后再继续往下走。`
|
||||
}
|
||||
return `交给${agent}处理,执行前会先让你确认。`
|
||||
return `我会请${agent}先整理可核对的结果,真正执行前仍会让你确认。`
|
||||
}
|
||||
|
||||
function buildStewardPlanFriendlyIntro(normalized) {
|
||||
const taskCountText = normalized.tasks.length > 1
|
||||
? `${normalized.tasks.length} 个相关事项`
|
||||
: '1 个事项'
|
||||
return `我先看了一下,你这次主要是 **${taskCountText}**。为了不让步骤混在一起,我会先把要做的事拆开,让你每一步都能看清楚、确认后再继续。`
|
||||
}
|
||||
|
||||
function buildTaskOrderDescription(normalized) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
buildStewardPlanMessageText,
|
||||
buildStewardPlanRequest,
|
||||
buildStewardSuggestedActions,
|
||||
isOffTopicStewardPlan,
|
||||
normalizeStewardPlan
|
||||
} from './stewardPlanModel.js'
|
||||
import { SESSION_TYPE_STEWARD } from './travelReimbursementConversationModel.js'
|
||||
@@ -384,7 +385,14 @@ export function useStewardPlanFlow({
|
||||
|
||||
function isPendingStewardActionMessage(message) {
|
||||
if (message?.stewardPlan) {
|
||||
return message.stewardPlan.streamStatus !== 'streaming'
|
||||
if (message.stewardPlan.streamStatus === 'streaming') {
|
||||
return false
|
||||
}
|
||||
// off_topic 是引导用户重新编辑输入,不参与"确定/确认"快捷回复链路。
|
||||
if (isOffTopicStewardPlan(message.stewardPlan)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return (
|
||||
String(message?.assistantName || '').trim() === '小财管家'
|
||||
|
||||
Reference in New Issue
Block a user