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:
180
web/tests/steward-plan-off-topic.test.mjs
Normal file
180
web/tests/steward-plan-off-topic.test.mjs
Normal file
@@ -0,0 +1,180 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import {
|
||||
ASSISTANT_SCOPE_ACTION_FILL_COMPOSER
|
||||
} from '../src/utils/assistantSessionScope.js'
|
||||
import {
|
||||
buildStewardPlanMessageText,
|
||||
buildStewardSuggestedActions,
|
||||
isOffTopicStewardPlan,
|
||||
normalizeStewardPlan
|
||||
} from '../src/views/scripts/stewardPlanModel.js'
|
||||
import {
|
||||
resolveSuggestedActionPrefill
|
||||
} from '../src/utils/assistantSuggestedActionPrefill.js'
|
||||
|
||||
const OFF_TOPIC_PLAN = {
|
||||
plan_id: 'steward-plan-off-topic',
|
||||
plan_status: 'off_topic',
|
||||
next_action: 'none',
|
||||
tasks: [],
|
||||
attachment_groups: [],
|
||||
confirmation_groups: [],
|
||||
candidate_flows: [],
|
||||
summary: '这看起来跟财务任务没什么关系...',
|
||||
suggested_prompts: [
|
||||
'我想要申请明天去北京出差3天,支撑客户现场实施',
|
||||
'我要报销上周去上海的高铁票',
|
||||
'差旅住宿标准是多少'
|
||||
],
|
||||
thinking_events: [
|
||||
{
|
||||
event_id: 'off-topic-explain',
|
||||
stage: 'intent_review',
|
||||
title: '未识别到财务业务意图',
|
||||
content: '用户输入的内容没有匹配到费用申请或费用报销相关的业务信号。',
|
||||
status: 'completed'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
test('normalizeStewardPlan passes suggested_prompts through as trimmed strings', () => {
|
||||
const normalized = normalizeStewardPlan({
|
||||
plan_id: 'p-1',
|
||||
plan_status: 'off_topic',
|
||||
suggested_prompts: [
|
||||
' 申请出差 ',
|
||||
'',
|
||||
null,
|
||||
'报销高铁票'
|
||||
]
|
||||
})
|
||||
assert.deepEqual(normalized.suggestedPrompts, ['申请出差', '报销高铁票'])
|
||||
})
|
||||
|
||||
test('normalizeStewardPlan falls back to empty suggested prompts when missing', () => {
|
||||
const normalized = normalizeStewardPlan({ plan_id: 'p-2', plan_status: 'off_topic' })
|
||||
assert.deepEqual(normalized.suggestedPrompts, [])
|
||||
})
|
||||
|
||||
test('isOffTopicStewardPlan returns true only for off_topic plan status', () => {
|
||||
assert.equal(isOffTopicStewardPlan({ plan_status: 'off_topic' }), true)
|
||||
assert.equal(isOffTopicStewardPlan({ plan_status: 'needs_flow_confirmation' }), false)
|
||||
assert.equal(isOffTopicStewardPlan({}), false)
|
||||
})
|
||||
|
||||
test('buildStewardPlanMessageText renders friendly off_topic guidance', () => {
|
||||
const text = buildStewardPlanMessageText(OFF_TOPIC_PLAN)
|
||||
assert.match(text, /小财管家没看懂这件事/)
|
||||
// 推荐话术本身不在正文里展示,而是作为按钮单独渲染,避免重复。
|
||||
for (const prompt of OFF_TOPIC_PLAN.suggested_prompts) {
|
||||
assert.equal(text.includes(prompt), false, `正文不应包含推荐话术:${prompt}`)
|
||||
}
|
||||
})
|
||||
|
||||
test('buildStewardPlanMessageText keeps off_topic branch ahead of pending flow branch', () => {
|
||||
// 即使 summary 缺省,也走 off_topic 分支而非默认任务文案
|
||||
const text = buildStewardPlanMessageText({
|
||||
plan_id: 'p-off-topic-default',
|
||||
plan_status: 'off_topic',
|
||||
next_action: 'none',
|
||||
suggested_prompts: ['申请出差']
|
||||
})
|
||||
assert.match(text, /小财管家没看懂这件事/)
|
||||
assert.match(text, /费用申请.*费用报销|费用报销.*费用申请/)
|
||||
})
|
||||
|
||||
test('buildStewardSuggestedActions returns fill_composer actions for off_topic plan', () => {
|
||||
const actions = buildStewardSuggestedActions(OFF_TOPIC_PLAN)
|
||||
assert.equal(actions.length, OFF_TOPIC_PLAN.suggested_prompts.length)
|
||||
for (const action of actions) {
|
||||
assert.equal(action.action_type, ASSISTANT_SCOPE_ACTION_FILL_COMPOSER)
|
||||
assert.equal(typeof action.payload.fill_text, 'string')
|
||||
assert.ok(action.payload.fill_text.length > 0)
|
||||
assert.equal(action.payload.steward_plan_id, OFF_TOPIC_PLAN.plan_id)
|
||||
}
|
||||
})
|
||||
|
||||
test('buildStewardSuggestedActions truncates long off_topic prompt labels', () => {
|
||||
const longPrompt = '我想要申请明天去北京出差3天,支撑客户现场实施,需要预订酒店和高铁票'
|
||||
const actions = buildStewardSuggestedActions({
|
||||
plan_id: 'p-long',
|
||||
plan_status: 'off_topic',
|
||||
suggested_prompts: [longPrompt]
|
||||
})
|
||||
assert.equal(actions.length, 1)
|
||||
assert.ok(actions[0].label.length <= 27) // 24 字符 + "..."
|
||||
assert.ok(actions[0].label.endsWith('...'))
|
||||
// payload.fill_text 必须保留完整话术,不被截断
|
||||
assert.equal(actions[0].payload.fill_text, longPrompt)
|
||||
})
|
||||
|
||||
test('buildStewardSuggestedActions keeps short off_topic prompt labels intact', () => {
|
||||
const shortPrompt = '差旅住宿标准是多少'
|
||||
const actions = buildStewardSuggestedActions({
|
||||
plan_id: 'p-short',
|
||||
plan_status: 'off_topic',
|
||||
suggested_prompts: [shortPrompt]
|
||||
})
|
||||
assert.equal(actions[0].label, shortPrompt)
|
||||
assert.equal(actions[0].payload.fill_text, shortPrompt)
|
||||
})
|
||||
|
||||
test('buildStewardSuggestedActions returns empty array for off_topic plan without prompts', () => {
|
||||
const actions = buildStewardSuggestedActions({
|
||||
plan_id: 'p-empty',
|
||||
plan_status: 'off_topic',
|
||||
suggested_prompts: []
|
||||
})
|
||||
assert.deepEqual(actions, [])
|
||||
})
|
||||
|
||||
test('off_topic fill_composer action is resolved as composer prefill (fill not submit)', () => {
|
||||
const actions = buildStewardSuggestedActions(OFF_TOPIC_PLAN)
|
||||
const firstAction = actions[0]
|
||||
const prefill = resolveSuggestedActionPrefill(firstAction)
|
||||
assert.equal(prefill, OFF_TOPIC_PLAN.suggested_prompts[0])
|
||||
})
|
||||
|
||||
test('resolveSuggestedActionPrefill reads payload.fill_text directly', () => {
|
||||
assert.equal(
|
||||
resolveSuggestedActionPrefill({
|
||||
action_type: ASSISTANT_SCOPE_ACTION_FILL_COMPOSER,
|
||||
payload: { fill_text: '帮我申请下周出差' }
|
||||
}),
|
||||
'帮我申请下周出差'
|
||||
)
|
||||
// 空字符串/空白应被忽略
|
||||
assert.equal(
|
||||
resolveSuggestedActionPrefill({
|
||||
action_type: ASSISTANT_SCOPE_ACTION_FILL_COMPOSER,
|
||||
payload: { fill_text: ' ' }
|
||||
}),
|
||||
''
|
||||
)
|
||||
})
|
||||
|
||||
test('off_topic branch does not break pending flow confirmation actions', () => {
|
||||
// pending flow 不应被 off_topic 分支拦截
|
||||
const actions = buildStewardSuggestedActions({
|
||||
plan_id: 'steward-plan-pending-flow',
|
||||
plan_status: 'needs_flow_confirmation',
|
||||
next_action: 'confirm_flow',
|
||||
pending_flow_confirmation: {
|
||||
status: 'pending',
|
||||
reason: '缺少申请或报销动作词。',
|
||||
candidate_flows: [
|
||||
{
|
||||
flow_id: 'travel_application',
|
||||
label: '补办出差申请',
|
||||
confidence: 0.6,
|
||||
ontology_fields: { location: '上海' },
|
||||
missing_fields: []
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
assert.equal(actions.length, 1)
|
||||
assert.equal(actions[0].payload.flow_id, 'travel_application')
|
||||
})
|
||||
Reference in New Issue
Block a user