From 96446a25dfa392f95dceb7ee090d85726aa12cb1 Mon Sep 17 00:00:00 2001 From: kris Date: Sat, 4 Apr 2026 07:16:24 +0800 Subject: [PATCH] feat: tighten playbook and recorder landings --- CHANGELOG.md | 6 +++ web/storyforge-web-v4/assets/app.js | 39 ++++++++++++++++--- .../tests/workbench-pages.test.mjs | 24 ++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04b83a4..bad9611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ## 2026-04-04 +### Playbook 与录制维护落点继续收口 + +- `创建 Agent / 编辑 Agent` 成功后,现在会直接回到 `Agent -> 当前 Agent / Agent 列表` 工作区,并把刚保存的 Agent 聚焦出来,不再只停在通用成功提示。 +- `新增录制源 / 编辑录制源 / 导入 URL 配置 / 启停录制源 / 删除录制源` 成功后,都会统一回到 `生产中心 -> 录制维护`,让用户顺着同一个维护区继续做下一步。 +- `当前 Agent` 面板新增显式锚点,Agent 列表项补了稳定的 `data-assistant-id`,前端回归也补齐到了这两条业务流,避免后续又退回成“成功了但要自己找结果”。 + ### 主 Agent 配置与执行落点继续收口 - 发现页里三类关键动作现在会落到更精确的业务区域:账号分析会直接切到快照/字段/报告区域,高分作品分析会直接滚到“最近高分拆解”,相似账号生成会直接滚到“相似对标 / 已绑关系”。 diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 5620e38..5c516ad 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -5651,6 +5651,29 @@ function focusRecentGeneratedCopy() { }); } +function focusPlaybookWorkspace(assistantId = "") { + appState.playbookDetailTab = "workspace"; + if (assistantId) appState.selectedAssistantId = assistantId; + setScreen("playbook"); + renderAll(); + window.requestAnimationFrame(() => { + const selector = assistantId + ? `[data-assistant-id="${String(assistantId).replaceAll('"', '\\"')}"]` + : "#current-agent-anchor"; + (document.querySelector(selector) || document.getElementById("current-agent-anchor")) + ?.scrollIntoView({ behavior: "smooth", block: "start" }); + }); +} + +function focusLiveRecorderMaintenance() { + appState.productionDetailTab = "recorder"; + setScreen("production"); + renderAll(); + window.requestAnimationFrame(() => { + document.getElementById("live-recorder-maintenance-anchor")?.scrollIntoView({ behavior: "smooth", block: "start" }); + }); +} + function focusReviewWorkspace(reviewId = "") { appState.reviewFocusId = reviewId || ""; setScreen("review"); @@ -6507,6 +6530,7 @@ function renderPlaybookScreen() { })}
+

当前 Agent

@@ -6533,7 +6557,7 @@ function renderPlaybookScreen() {

Agent 列表

当前接的是后端 assistants
${assistants.map((assistant) => ` -
+

${escapeHtml(assistant.name)}

${escapeHtml(assistant.description || assistant.generation_goal || "暂无说明")}

@@ -9804,6 +9828,7 @@ function openCreateAssistantAction() { appState.selectedAssistantId = assistant.id; rememberAction("Agent 已创建", `已创建 Agent「${assistant.name}」。`, "green", assistant); await bootstrap(); + focusPlaybookWorkspace(assistant.id); } }); } @@ -9842,6 +9867,7 @@ function openEditAssistantAction(assistantId = "") { appState.selectedAssistantId = updated.id; rememberAction("Agent 已更新", `已更新 Agent「${updated.name}」。`, "green", updated); await bootstrap(); + focusPlaybookWorkspace(updated.id); } }); } @@ -10626,11 +10652,7 @@ function openCreateRealCutAction(defaults = {}) { } function openLiveRecorderAction() { - setScreen("production"); - renderAll(); - window.requestAnimationFrame(() => { - document.getElementById("live-recorder-maintenance-anchor")?.scrollIntoView({ behavior: "smooth", block: "start" }); - }); + focusLiveRecorderMaintenance(); } function openLiveRecorderCreateAction() { @@ -10677,6 +10699,7 @@ function openLiveRecorderCreateAction() { } rememberAction("直播录制已下发", "当前租户的直播源已经保存到服务端并同步到 NAS。", "green", { saved, started }); await bootstrap(); + focusLiveRecorderMaintenance(); } }); } @@ -10731,6 +10754,7 @@ function openLiveRecorderSourceAction(sourceId) { }); rememberAction("录制源已更新", `已保存「${saved.item?.title || source.title || "录制源"}」。`, "green", saved); await bootstrap(); + focusLiveRecorderMaintenance(); } }); } @@ -10763,6 +10787,7 @@ function openLiveRecorderImportAction() { }); rememberAction("URL 配置已导入", `已导入 ${formatNumber(saved.count || 0)} 条录制源。`, "green", saved); await bootstrap(); + focusLiveRecorderMaintenance(); } }); } @@ -10784,6 +10809,7 @@ async function toggleLiveRecorderSourceAction(sourceId, nextEnabled) { }); rememberAction(nextEnabled ? "录制源已启用" : "录制源已停用", `${source.title || source.source_url || "录制源"} 已更新。`, "green"); await bootstrap(); + focusLiveRecorderMaintenance(); } finally { setBusy(false, ""); } @@ -10808,6 +10834,7 @@ async function deleteLiveRecorderSourceAction(sourceId) { }); rememberAction("录制源已删除", `${source.title || source.source_url || "录制源"} 已从租户视图中移除。`, "green"); await bootstrap(); + focusLiveRecorderMaintenance(); } finally { setBusy(false, ""); } diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index ca273e0..e2cdca9 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -906,6 +906,30 @@ test("review actions return to the review workspace with the saved review in foc assert.match(APP, /data-review-id="\$\{escapeHtml\(review\.id\)\}"/); }); +test("assistant actions return to the playbook workspace with the saved assistant in focus", () => { + const createAssistant = extractBetween(APP, "function openCreateAssistantAction()", "function openEditAssistantAction(assistantId = \"\")"); + const editAssistant = extractBetween(APP, "function openEditAssistantAction(assistantId = \"\")", "function openAnalyzeSelectedAccountAction()"); + assert.match(APP, /function focusPlaybookWorkspace\(assistantId = ""\)/); + assert.match(createAssistant, /focusPlaybookWorkspace\(assistant\.id\)/); + assert.match(editAssistant, /focusPlaybookWorkspace\(updated\.id\)/); + assert.match(APP, /id="current-agent-anchor"/); + assert.match(APP, /data-assistant-id="\$\{escapeHtml\(assistant\.id\)\}"/); +}); + +test("live recorder mutations return to the recorder maintenance panel after success", () => { + const createSource = extractBetween(APP, "function openLiveRecorderCreateAction()", "function openLiveRecorderSourceAction(sourceId)"); + const editSource = extractBetween(APP, "function openLiveRecorderSourceAction(sourceId)", "function openLiveRecorderImportAction()"); + const importSource = extractBetween(APP, "function openLiveRecorderImportAction()", "async function toggleLiveRecorderSourceAction(sourceId, nextEnabled)"); + const toggleSource = extractBetween(APP, "async function toggleLiveRecorderSourceAction(sourceId, nextEnabled)", "async function deleteLiveRecorderSourceAction(sourceId)"); + const deleteSource = extractBetween(APP, "async function deleteLiveRecorderSourceAction(sourceId)", "async function openLiveRecorderFileAction(fileId)"); + assert.match(APP, /function focusLiveRecorderMaintenance\(\)/); + assert.match(createSource, /focusLiveRecorderMaintenance\(\)/); + assert.match(editSource, /focusLiveRecorderMaintenance\(\)/); + assert.match(importSource, /focusLiveRecorderMaintenance\(\)/); + assert.match(toggleSource, /focusLiveRecorderMaintenance\(\)/); + assert.match(deleteSource, /focusLiveRecorderMaintenance\(\)/); +}); + test("main agent execution cards can jump to oneliner and platform profile history", () => { const messages = extractBetween(APP, "function renderOneLinerMessagesHtml()", "function renderAutoConnectingScreen(screenTitle, nextStepText)"); assert.match(messages, /data-action="open-oneliner-profile-history"/);