diff --git a/CHANGELOG.md b/CHANGELOG.md index cc27258..e1488bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ - `create_assistant / import_homepage / track_account / generate_copy / ai_video / real_cut` 这批高频意图动作现在统一注册成 `direct-*`,不再回退到旧的 `open-*` 表单入口。 - 这样主 Agent 结果卡、动作注册表和工作台高频按钮现在共用同一套直执行链,后续回跳与结果落点也更一致。 +- `analyze_account / analyze_top_videos` 现在也统一切到 `direct-*`,并且在缺少当前选中账号时会自动回退到旧表单,不会把用户卡死在“缺少上下文”的提示上。 +- `direct-search-similar / direct-save-benchmark-link` 现在也会在缺少当前账号或相似候选时自动回退到旧表单,避免“查相似 / 存对标”入口因为上下文不完整直接报错。 ### 依赖健康卡开始显示服务部署位置 diff --git a/collector-service/app/oneliner_features.py b/collector-service/app/oneliner_features.py index a4ac293..9492e3c 100644 --- a/collector-service/app/oneliner_features.py +++ b/collector-service/app/oneliner_features.py @@ -207,8 +207,8 @@ INTENT_ACTIONS: dict[str, list[dict[str, Any]]] = { "create_assistant": [{"key": "direct-create-assistant", "label": "创建 Agent", "kind": "ui_action"}], "import_homepage": [{"key": "direct-import-selected-account", "label": "导入主页", "kind": "ui_action"}], "track_account": [{"key": "direct-track-selected-account", "label": "跟踪当前账号", "kind": "ui_action"}], - "analyze_account": [{"key": "analyze-selected-account", "label": "分析当前账号", "kind": "ui_action"}], - "analyze_top_videos": [{"key": "analyze-top-videos", "label": "分析高分作品", "kind": "ui_action"}], + "analyze_account": [{"key": "direct-analyze-selected-account", "label": "分析当前账号", "kind": "ui_action"}], + "analyze_top_videos": [{"key": "direct-analyze-top-videos", "label": "分析高分作品", "kind": "ui_action"}], "generate_copy": [{"key": "direct-generate-copy", "label": "生成文案", "kind": "ui_action"}], "ai_video": [{"key": "direct-create-ai-video", "label": "做 AI 视频", "kind": "ui_action"}], "real_cut": [{"key": "direct-create-real-cut", "label": "做实拍剪辑", "kind": "ui_action"}], diff --git a/tests/test_main_agent_governance.py b/tests/test_main_agent_governance.py index 4e01a48..af0e1a2 100644 --- a/tests/test_main_agent_governance.py +++ b/tests/test_main_agent_governance.py @@ -954,6 +954,8 @@ class MainAgentGovernanceTests(unittest.TestCase): "create_assistant": "direct-create-assistant", "import_homepage": "direct-import-selected-account", "track_account": "direct-track-selected-account", + "analyze_account": "direct-analyze-selected-account", + "analyze_top_videos": "direct-analyze-top-videos", "generate_copy": "direct-generate-copy", "ai_video": "direct-create-ai-video", "real_cut": "direct-create-real-cut", diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 17a51f6..fa3e70d 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -12594,7 +12594,11 @@ document.addEventListener("click", async (event) => { return; } if (name === "direct-analyze-selected-account") { - const account = requireSelectedAccountRow(); + const account = getSelectedAccount(); + if (!account?.id) { + openAnalyzeSelectedAccountAction(); + return; + } await runDirectDiscoveryAction("analyze-account", { target_account_id: account.id, max_videos: 6, @@ -12614,7 +12618,11 @@ document.addEventListener("click", async (event) => { return; } if (name === "direct-analyze-top-videos") { - const account = requireSelectedAccountRow(); + const account = getSelectedAccount(); + if (!account?.id) { + openAnalyzeTopVideosAction(); + return; + } await runDirectDiscoveryAction("analyze-top-videos", { target_account_id: account.id, top_video_count: 5, @@ -12794,7 +12802,11 @@ document.addEventListener("click", async (event) => { return; } if (name === "direct-search-similar") { - const account = requireSelectedAccountRow(); + const account = getSelectedAccount(); + if (!account?.id) { + openSimilaritySearchAction(); + return; + } await runDirectDiscoveryAction("search-similar-accounts", { target_account_id: account.id, max_candidates: 8, @@ -12827,7 +12839,11 @@ document.addEventListener("click", async (event) => { return; } if (name === "direct-save-benchmark-link") { - const account = requireSelectedAccountRow(); + const account = getSelectedAccount(); + if (!account?.id) { + openBenchmarkLinkAction(); + return; + } const candidate = safeArray(appState.lastSimilaritySearch?.candidates)[0] || null; if (!candidate) { openBenchmarkLinkAction(); diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index ad9ae78..10124e4 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -486,6 +486,13 @@ test("discovery page promotes selected-account actions into direct execute flows assert.match(APP, /if \(name === "direct-save-benchmark-link"\)/); }); +test("direct discovery analysis actions gracefully fall back to forms when no account is selected", () => { + assert.match(APP, /if \(name === "direct-analyze-selected-account"\)[\s\S]*const account = getSelectedAccount\(\);/); + assert.match(APP, /if \(name === "direct-analyze-selected-account"\)[\s\S]*openAnalyzeSelectedAccountAction\(\);/); + assert.match(APP, /if \(name === "direct-analyze-top-videos"\)[\s\S]*const account = getSelectedAccount\(\);/); + assert.match(APP, /if \(name === "direct-analyze-top-videos"\)[\s\S]*openAnalyzeTopVideosAction\(\);/); +}); + test("mobile discovery prioritizes the selected-account task flow before the scrollable account list", () => { const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()"); assert.match(discovery, /mobile-discovery-priority/); @@ -1514,6 +1521,17 @@ test("smart discovery entrypoints prefer direct execute before falling back to f assert.match(clicks, /name === "open-track-selected-account"[\s\S]*openTrackSelectedAccountAction\(\);/); }); +test("direct discovery relation actions gracefully fall back to forms when context is incomplete", () => { + const clicks = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {"); + assert.match(clicks, /name === "direct-search-similar"[\s\S]*const account = getSelectedAccount\(\);/); + assert.match(clicks, /name === "direct-search-similar"[\s\S]*if \(!account\?\.id\) \{[\s\S]*openSimilaritySearchAction\(\);[\s\S]*return;[\s\S]*\}/); + assert.match(clicks, /name === "direct-search-similar"[\s\S]*runDirectDiscoveryAction\("search-similar-accounts"/); + assert.match(clicks, /name === "direct-save-benchmark-link"[\s\S]*const account = getSelectedAccount\(\);/); + assert.match(clicks, /name === "direct-save-benchmark-link"[\s\S]*if \(!account\?\.id\) \{[\s\S]*openBenchmarkLinkAction\(\);[\s\S]*return;[\s\S]*\}/); + assert.match(clicks, /name === "direct-save-benchmark-link"[\s\S]*if \(!candidate\) \{[\s\S]*openBenchmarkLinkAction\(\);[\s\S]*return;[\s\S]*\}/); + assert.match(clicks, /name === "direct-save-benchmark-link"[\s\S]*runDirectDiscoveryAction\("save-benchmark-link"/); +}); + test("declared static workbench actions are wired into explicit handlers", () => { const declared = new Set([...APP.matchAll(/data-action="([a-zA-Z0-9_-]+)"/g)].map((match) => match[1])); const clickHandled = new Set([...APP.matchAll(/if \(name === "([a-zA-Z0-9_-]+)"\)/g)].map((match) => match[1]));