fix(web): 多 task 草稿自动保存后继续推进下一 task
useWorkbenchAiApplicationPreviewFlow 在自动保存草稿分支透传 onApplicationActionCompleted, 确保草稿/提交完成后仍能按 remaining tasks 推进,修复多 task 报销场景后续步骤不启动。 更新 application-context-submit/intent-planner-model 测试,补充 bug 日志与开发日志。
This commit is contained in:
@@ -28,3 +28,10 @@
|
||||
- 操作:先补红灯断言,要求工作台入口向申请预览流传 `onApplicationActionCompleted`,且申请预览流在保存/提交成功分支调用该回调;然后按同一条 `steward_continue_next_task` 路由复用原有报销启动逻辑。
|
||||
- 验证:红灯阶段 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 失败在缺少 `onApplicationActionCompleted`;修复后 `node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 17/17 通过,`node --test web/tests/workbench-ai-action-router.test.mjs` 7/7 通过;`git diff --check` 通过;`npm --prefix web run build` 通过。
|
||||
- 影响:复合任务里用户保存第一张出差申请草稿后,系统会立即进入第二个业务招待费报销任务,不再要求用户手动点击“继续处理费用报销”;低置信确认后再保存草稿也保持同样行为。
|
||||
|
||||
- 22:45:继续修复同一 bug:真实页面已经显示“申请草稿已保存”,但保存动作结束后没有继续拉起第二个业务招待费报销 task。
|
||||
- Git 提交检查:`git fetch --all --prune` 仍因 `LibreSSL SSL_connect: SSL_ERROR_SYSCALL` 失败;`HEAD..@{u}` 为空,未发现可见 upstream 新提交;`@{u}..HEAD` 本地领先 13 个提交:`6bdaeed6 chore: 忽略 .zcode 本地目录并更新规则表与开发日志`、`d5a8f847 refactor(web): 应用外壳/差旅详情/报销创建视图适配主题与多 task`、`c4b5fcc0 feat(web): AI 工作台多 task 串行推进与会话适配`、`5753899e feat(web): 主题皮肤系统与 LLM 设置面板重构`、`9c3fa80d feat(server): 设置持久化新增 LLM 模型表与主题字段`,以及前面记录过的 8 个本地 ahead 提交。
|
||||
- 修改:`useWorkbenchAiApplicationPreviewFlow.js` 在保存草稿/提交申请成功分支新增 `actionCompletedHandler`,优先使用本次 `executeInlineApplicationPreviewAction` / `startAiApplicationPreview` options 传入的 `onApplicationActionCompleted`,没有时再回落到 composable 初始化回调;自动保存草稿时同步把 `options.onApplicationActionCompleted` 透传给保存动作。`workbench-ai-application-context-submit.test.mjs` 新增“自动保存申请草稿后继续剩余 steward task”的行为测试;`workbench-ai-intent-planner-model.test.mjs` 更新结构断言,锁住 options 回调优先和自动保存透传契约。
|
||||
- 操作:先用新增测试复现红灯:自动保存时确实只触发初始化回调,`options` 里的续跑回调没有被使用;再做最小修复,让保存动作按本次调用上下文续跑下一任务。
|
||||
- 验证:红灯阶段 `node --test web/tests/workbench-ai-application-context-submit.test.mjs` 失败在 `fromOptions` 为 `undefined`;修复后该测试 2/2 通过;`node --test web/tests/workbench-ai-intent-planner-model.test.mjs` 17/17 通过;`node --test web/tests/workbench-ai-action-router.test.mjs` 7/7 通过;真实 `http://127.0.0.1:5173/api/v1/steward/plans` 采样确认该用户句子仍返回 `expense_application` + `reimbursement` 两个 task;`npm --prefix web run build` 通过;`git diff --check` 通过。
|
||||
- 影响:保存草稿结果消息到达后,前端会使用当前任务链路传下来的续跑回调立即处理剩余 task;用户截图里的“申请草稿已保存”不再是终点,后续业务招待费报销会自动进入现有报销流程。
|
||||
|
||||
17
document/development/2026-06-27/work-logs.med
Normal file
17
document/development/2026-06-27/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
||||
# 2026-06-27 综合工作日志
|
||||
|
||||
生成时间:2026-06-27 17:43:24 CST
|
||||
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||
|
||||
## 今日功能点
|
||||
|
||||
- 今日未发现功能点文档。
|
||||
|
||||
## 今日 Bugs
|
||||
|
||||
- 今日未发现 bug 修复记录。
|
||||
|
||||
## 综合分析
|
||||
|
||||
- 今日目录下暂无功能点或 bug 记录。
|
||||
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||
17
document/development/2026-06-28/work-logs.med
Normal file
17
document/development/2026-06-28/work-logs.med
Normal file
@@ -0,0 +1,17 @@
|
||||
# 2026-06-28 综合工作日志
|
||||
|
||||
生成时间:2026-06-28 18:30:35 CST
|
||||
来源:`feature/` 功能点文档与 `dev-logs/bugs/` bug 记录
|
||||
|
||||
## 今日功能点
|
||||
|
||||
- 今日未发现功能点文档。
|
||||
|
||||
## 今日 Bugs
|
||||
|
||||
- 今日未发现 bug 修复记录。
|
||||
|
||||
## 综合分析
|
||||
|
||||
- 今日目录下暂无功能点或 bug 记录。
|
||||
- 后续复盘优先看本文件,再回到对应功能点或 bug 文件追溯证据。
|
||||
@@ -463,9 +463,12 @@ export function useWorkbenchAiApplicationPreviewFlow({
|
||||
targetMessage.suggestedActions = []
|
||||
const detailActions = buildInlineApplicationDetailAction(draftPayload)
|
||||
const nextTaskAction = buildApplicationPreviewNextTaskAction(targetMessage)
|
||||
const actionCompletedHandler = typeof options.onApplicationActionCompleted === 'function'
|
||||
? options.onApplicationActionCompleted
|
||||
: onApplicationActionCompleted
|
||||
const shouldAutoContinueNextTask = Boolean(
|
||||
nextTaskAction &&
|
||||
typeof onApplicationActionCompleted === 'function' &&
|
||||
typeof actionCompletedHandler === 'function' &&
|
||||
Array.isArray(targetMessage.stewardRemainingTasks) &&
|
||||
targetMessage.stewardRemainingTasks.length
|
||||
)
|
||||
@@ -485,7 +488,7 @@ export function useWorkbenchAiApplicationPreviewFlow({
|
||||
persistCurrentConversation()
|
||||
scrollInlineConversationToBottom({ force: inlineConversationAutoScrollPinned.value })
|
||||
if (shouldAutoContinueNextTask) {
|
||||
onApplicationActionCompleted(targetMessage.stewardRemainingTasks, targetMessage)
|
||||
actionCompletedHandler(targetMessage.stewardRemainingTasks, targetMessage)
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
@@ -613,7 +616,8 @@ export function useWorkbenchAiApplicationPreviewFlow({
|
||||
if (options.autoSaveDraft) {
|
||||
await executeInlineApplicationPreviewAction(AI_APPLICATION_ACTION_SAVE_DRAFT, previewMessage, {
|
||||
skipUserMessage: true,
|
||||
userText: options.userMessage || '保存草稿'
|
||||
userText: options.userMessage || '保存草稿',
|
||||
onApplicationActionCompleted: options.onApplicationActionCompleted
|
||||
})
|
||||
} else if (
|
||||
typeof options.onPreviewReadyForNextTask === 'function' &&
|
||||
|
||||
@@ -19,7 +19,7 @@ function createInlineMessage(role, content, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function buildApplicationPreviewFlowHarness(messages) {
|
||||
function buildApplicationPreviewFlowHarness(messages, options = {}) {
|
||||
const conversationMessages = createRef(messages)
|
||||
const applicationSubmitConfirmOpen = createRef(false)
|
||||
const applicationSubmitConfirmContext = createRef(null)
|
||||
@@ -69,7 +69,8 @@ function buildApplicationPreviewFlowHarness(messages) {
|
||||
resolveLatestInlineUserPrompt: () => '2026-02-20 至 2026-02-23,去上海出差,交通火车,保存草稿',
|
||||
scrollInlineConversationToBottom: () => {},
|
||||
sending: createRef(false),
|
||||
toast: () => {}
|
||||
toast: () => {},
|
||||
onApplicationActionCompleted: options.onApplicationActionCompleted
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -80,6 +81,72 @@ function buildApplicationPreviewFlowHarness(messages) {
|
||||
}
|
||||
}
|
||||
|
||||
test('workbench auto-saved application draft continues remaining steward task', async () => {
|
||||
const originalFetch = globalThis.fetch
|
||||
const requests = []
|
||||
globalThis.fetch = async (url, options = {}) => {
|
||||
const normalizedUrl = String(url)
|
||||
if (normalizedUrl.includes('/reimbursements/application-preview-action')) {
|
||||
const body = JSON.parse(String(options.body || '{}'))
|
||||
requests.push({ url: normalizedUrl, body })
|
||||
return {
|
||||
ok: true,
|
||||
async json() {
|
||||
return {
|
||||
status: 'succeeded',
|
||||
result: {
|
||||
draft_payload: {
|
||||
claim_id: 'claim-auto-saved-draft',
|
||||
claim_no: 'AEW2DDAFL',
|
||||
status: 'draft'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(`unexpected request: ${normalizedUrl}`)
|
||||
}
|
||||
|
||||
try {
|
||||
const continuedTasks = []
|
||||
const remainingTasks = [{
|
||||
task_id: 'task-reimbursement-2',
|
||||
task_type: 'reimbursement',
|
||||
assigned_agent: 'reimbursement_assistant',
|
||||
summary: '报销昨天的业务招待费 2000 元',
|
||||
ontology_fields: {
|
||||
expense_type: 'entertainment',
|
||||
amount: '2000元',
|
||||
time_range: '2026-06-25',
|
||||
reason: '业务招待费报销'
|
||||
}
|
||||
}]
|
||||
const harness = buildApplicationPreviewFlowHarness([], {
|
||||
onApplicationActionCompleted: (tasks, sourceMessage) => {
|
||||
continuedTasks.push({ tasks, sourceMessage })
|
||||
}
|
||||
})
|
||||
|
||||
await harness.flow.startAiApplicationPreview('travel', '差旅费', '2月20-23日去上海出差3天,服务国网服务器部署,并且报销昨天的业务招待费2000元', {
|
||||
autoSaveDraft: true,
|
||||
stewardRemainingTasks: remainingTasks,
|
||||
onApplicationActionCompleted: (tasks, sourceMessage) => {
|
||||
continuedTasks.push({ tasks, sourceMessage, fromOptions: true })
|
||||
}
|
||||
})
|
||||
|
||||
assert.equal(requests.length, 1)
|
||||
assert.equal(continuedTasks.length, 1)
|
||||
assert.deepEqual(continuedTasks[0].tasks, remainingTasks)
|
||||
assert.equal(continuedTasks[0].sourceMessage.stewardRemainingTasks, remainingTasks)
|
||||
assert.equal(continuedTasks[0].fromOptions, true)
|
||||
assert.doesNotMatch(harness.conversationMessages.value.at(-1).content, /继续处理费用报销/)
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch
|
||||
}
|
||||
})
|
||||
|
||||
test('workbench saved application draft can be submitted by contextual text without re-planning', async () => {
|
||||
const originalFetch = globalThis.fetch
|
||||
const requests = []
|
||||
|
||||
@@ -351,7 +351,9 @@ test('workbench AI mode asks steward model plan before fallback execution', () =
|
||||
assert.match(personalWorkbenchAiModeScript, /onApplicationActionCompleted:\s*startModelPlannedNextTask/)
|
||||
assert.match(applicationPreviewFlowScript, /options\.autoSaveDraft/)
|
||||
assert.match(applicationPreviewFlowScript, /options\.onPreviewReadyForNextTask/)
|
||||
assert.match(applicationPreviewFlowScript, /onApplicationActionCompleted\(\s*targetMessage\.stewardRemainingTasks/)
|
||||
assert.match(applicationPreviewFlowScript, /const actionCompletedHandler = typeof options\.onApplicationActionCompleted === 'function'/)
|
||||
assert.match(applicationPreviewFlowScript, /actionCompletedHandler\(targetMessage\.stewardRemainingTasks/)
|
||||
assert.match(applicationPreviewFlowScript, /onApplicationActionCompleted:\s*options\.onApplicationActionCompleted/)
|
||||
assert.doesNotMatch(applicationPreviewFlowScript, /options\.autoSubmit && normalizeApplicationPreview\(preview\)\.readyToSubmit/)
|
||||
assert.match(applicationPreviewFlowScript, /ontologyFields:\s*options\.ontologyFields/)
|
||||
assert.match(applicationPreviewFlowScript, /executeInlineApplicationPreviewAction\(AI_APPLICATION_ACTION_SAVE_DRAFT/)
|
||||
|
||||
Reference in New Issue
Block a user