From ad05a4dfbc9d5ea5617119c81061e43bf68c5004 Mon Sep 17 00:00:00 2001 From: kris Date: Sun, 5 Apr 2026 07:36:01 +0800 Subject: [PATCH] feat: direct-execute review and playbook quick actions --- CHANGELOG.md | 1 + collector-service/app/oneliner_features.py | 13 +++++++++- tests/test_main_agent_governance.py | 17 +++++++++++++ web/storyforge-web-v4/assets/app.js | 25 +++++++++++-------- .../tests/workbench-pages.test.mjs | 8 ++++++ 5 files changed, 52 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 054e1a7..ebf38be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -457,3 +457,4 @@ - 主 Agent 落到 `找对标 / Agent / 生产中心 / 发布与复盘` 后,快捷动作里原先的 `高分分析 / 新建 Agent / 写复盘 / 做 AI 视频 / 做实拍剪辑` 已优先改成 direct-execute。 - 这些动作现在直接调用 `OneLiner` 执行器并按真实结果继续落到对象详情、Agent 编辑页、复盘页或任务详情,而不是先打开旧表单。 +- `review-draft` 现在支持显式 `source_job_id`,所以从任务详情、复盘页和最近完成任务入口点“写复盘”,会围绕指定任务直接生成草稿,不再总是退回“最近一条任务”。 diff --git a/collector-service/app/oneliner_features.py b/collector-service/app/oneliner_features.py index 59ad89a..0e0c1ce 100644 --- a/collector-service/app/oneliner_features.py +++ b/collector-service/app/oneliner_features.py @@ -5734,7 +5734,18 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None: } async def _run_review_draft() -> dict[str, Any]: - latest_job = _latest_project_job(account, project_id=project["id"]) + requested_job_id = str( + requested_payload.get("source_job_id") + or requested_payload.get("sourceJobId") + or requested_payload.get("job_id") + or requested_payload.get("jobId") + or "" + ).strip() + latest_job = _load_owned_job(account, requested_job_id) if requested_job_id else None + if latest_job and str(latest_job.get("project_id") or "") != str(project["id"]): + latest_job = None + if not latest_job: + latest_job = _latest_project_job(account, project_id=project["id"]) if not latest_job: raise HTTPException(status_code=404, detail="No completed job available for review draft") existing = legacy.db.fetch_one( diff --git a/tests/test_main_agent_governance.py b/tests/test_main_agent_governance.py index b1f8967..e923a17 100644 --- a/tests/test_main_agent_governance.py +++ b/tests/test_main_agent_governance.py @@ -969,6 +969,23 @@ class MainAgentGovernanceTests(unittest.TestCase): self.assertEqual(review_payload["recommended_action"]["job_id"], "job_review_source") self.assertTrue(review_payload["recommended_action"]["review_id"]) + explicit_review_response = self.client.post( + "/v2/oneliner/actions/execute", + headers=self.ctx["member_headers"], + json={ + "action_key": "review-draft", + "project_id": self.ctx["project_id"], + "platform": "douyin", + "payload": { + "source_job_id": "job_review_source", + }, + }, + ) + self.assertEqual(explicit_review_response.status_code, 200, explicit_review_response.text) + explicit_review_payload = explicit_review_response.json() + self.assertEqual(explicit_review_payload["recommended_action"]["action"], "open-review-edit") + self.assertEqual(explicit_review_payload["recommended_action"]["job_id"], "job_review_source") + self_check_response = self.client.post( "/v2/oneliner/actions/execute", headers=self.ctx["member_headers"], diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 18e3b80..bf8406d 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -5693,7 +5693,7 @@ function renderDashboardScreen() { return screenShell( "项目总台", "先做最能推进当前项目的一步,再按需看概览。", - `${button("新建项目", "create-project")} ${button("导入主页", "open-import-homepage")} ${button("创建 Agent", "open-create-assistant", "primary")}`, + `${button("新建项目", "create-project")} ${button("导入主页", "open-import-homepage")} ${button("创建 Agent", "direct-create-assistant", "primary")}`, dashboardHomeRenderer?.renderDashboardHome ? `
${dashboardHomeRenderer.renderDashboardHome(homeModel, { escapeHtml })}
` : renderEmptyState("首页模块未加载", "请刷新页面后重试。") @@ -6698,7 +6698,7 @@ function renderPlaybookScreen() { return screenShell( "Agent", "这里接真实 Agent 列表,当前已经支持切换和编辑 Agent。", - `${button("配置 OneLiner", "open-oneliner-profile")} ${button("看配置历史", "open-oneliner-profile-history", "secondary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: playbookHandoffAttrs })} ${button("设主模型", "open-preferred-model")} ${button("新建 Agent", "open-create-assistant")} ${button("去生产", "goto-production", "primary")}`, + `${button("配置 OneLiner", "open-oneliner-profile")} ${button("看配置历史", "open-oneliner-profile-history", "secondary")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: playbookHandoffAttrs })} ${button("设主模型", "open-preferred-model")} ${button("新建 Agent", "direct-create-assistant")} ${button("去生产", "goto-production", "primary")}`, ` ${renderMainAgentLandingNotice("playbook")}
@@ -6736,7 +6736,7 @@ function renderPlaybookScreen() { ? `${actionTag("看平台 Agent", "select-page-tab", `data-page-tab-key="playbookDetailTab" data-page-tab-value="platform_agents"`)} ${actionTag("交给主 Agent", "handoff-to-main-agent", playbookHandoffAttrs)}` : activeTab === "models" ? `${actionTag("设主模型", "open-preferred-model")} ${actionTag("回工作区", "select-page-tab", `data-page-tab-key="playbookDetailTab" data-page-tab-value="workspace"`)}` - : `${actionTag("配置 OneLiner", "open-oneliner-profile")} ${actionTag("看配置历史", "open-oneliner-profile-history")} ${actionTag(currentAssistant ? "去生产" : "新建 Agent", currentAssistant ? "goto-production" : "open-create-assistant")} ${actionTag("交给主 Agent", "handoff-to-main-agent", playbookHandoffAttrs)}` + : `${actionTag("配置 OneLiner", "open-oneliner-profile")} ${actionTag("看配置历史", "open-oneliner-profile-history")} ${actionTag(currentAssistant ? "去生产" : "新建 Agent", currentAssistant ? "goto-production" : "direct-create-assistant")} ${actionTag("交给主 Agent", "handoff-to-main-agent", playbookHandoffAttrs)}` }
@@ -7296,7 +7296,7 @@ function renderReviewScreen() { return screenShell( "发布与复盘", "先看已保存复盘,再把完成任务转成结构化复盘。", - `${button("写复盘", "open-create-review")} ${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: reviewHandoffAttrs })} ${button("去生产", "goto-production", "primary")}`, + `${button("写复盘", "direct-review-draft")} ${button("刷新", "refresh-data")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: reviewHandoffAttrs })} ${button("去生产", "goto-production", "primary")}`, ` ${renderMainAgentLandingNotice("review")}
@@ -7313,7 +7313,7 @@ function renderReviewScreen() {

${escapeHtml(reviewTaskTitle)}

${escapeHtml(reviewTaskSummary)}

- ${actionTag(completed.length ? "写复盘" : reviews.length ? "看复盘" : "去生产", completed.length ? "open-review-from-job" : reviews.length ? "goto-review" : "goto-production", completed[0]?.id ? `data-job-id="${escapeHtml(completed[0].id)}"` : "")} + ${actionTag(completed.length ? "写复盘" : reviews.length ? "看复盘" : "去生产", completed.length ? "direct-review-draft" : reviews.length ? "goto-review" : "goto-production", completed[0]?.id ? `data-job-id="${escapeHtml(completed[0].id)}"` : "")} ${actionTag("交给主 Agent", "handoff-to-main-agent", reviewHandoffAttrs)} ${escapeHtml(`已保存 ${formatNumber(reviews.length)} 条`)} ${escapeHtml(`已发布 ${formatNumber(publishedReviewCount)} 条`)} @@ -7333,7 +7333,7 @@ function renderReviewScreen() { : "当前还没有可用复盘,先回到生产中心跑出一条完成链路。" )}

- ${actionTag(completed.length ? "写复盘" : "去生产", completed.length ? "open-review-from-job" : "goto-production", completed[0]?.id ? `data-job-id="${escapeHtml(completed[0].id)}"` : "")} + ${actionTag(completed.length ? "写复盘" : "去生产", completed.length ? "direct-review-draft" : "goto-production", completed[0]?.id ? `data-job-id="${escapeHtml(completed[0].id)}"` : "")} ${actionTag("刷新", "refresh-data")} ${actionTag("交给主 Agent", "handoff-to-main-agent", reviewHandoffAttrs)}
@@ -7375,7 +7375,7 @@ function renderReviewScreen() {
已完成 ${escapeHtml(job.line_type || "analysis")} - ${actionTag("写复盘", "open-review-from-job", `data-job-id="${escapeHtml(job.id)}"`)} + ${actionTag("写复盘", "direct-review-draft", `data-job-id="${escapeHtml(job.id)}"`)} ${canDeriveAiVideo(job) ? renderPipelineJobTag("aiVideo", job, "做 AI 视频") : ""} ${canDeriveRealCut(job) ? renderPipelineJobTag("realCut", job, "做实拍剪辑") : ""} ${actionTag("看详情", "open-job-detail", `data-job-id="${escapeHtml(job.id)}"`)} @@ -8968,7 +8968,7 @@ function renderLastJobDetailCard() {
${escapeHtml(detail.job.line_type || "-")} ${detail.job.status === "failed" ? `${escapeHtml(recovery.reason)}` : ""} - ${detail.job.status === "completed" ? actionTag("写复盘", "open-review-from-job", `data-job-id="${escapeHtml(detail.job.id)}"`) : ""} + ${detail.job.status === "completed" ? actionTag("写复盘", "direct-review-draft", `data-job-id="${escapeHtml(detail.job.id)}"`) : ""} ${detail.job.status === "failed" ? actionTag( recovery.actionLabel, recovery.recoverable ? "recover-job" : recovery.actionKey, @@ -12361,7 +12361,8 @@ document.addEventListener("click", async (event) => { if (name === "direct-create-ai-video") { await runDirectWorkbenchAction("create-ai-video", { busyLabel: "正在创建 AI 视频任务...", - errorTitle: "创建 AI 视频任务失败" + errorTitle: "创建 AI 视频任务失败", + payload: action.dataset.jobId ? { source_job_id: action.dataset.jobId } : {} }); return; } @@ -12372,7 +12373,8 @@ document.addEventListener("click", async (event) => { if (name === "direct-create-real-cut") { await runDirectWorkbenchAction("create-real-cut", { busyLabel: "正在创建实拍剪辑任务...", - errorTitle: "创建实拍剪辑任务失败" + errorTitle: "创建实拍剪辑任务失败", + payload: action.dataset.jobId ? { source_job_id: action.dataset.jobId } : {} }); return; } @@ -12383,7 +12385,8 @@ document.addEventListener("click", async (event) => { if (name === "direct-review-draft") { await runDirectWorkbenchAction("review-draft", { busyLabel: "正在生成复盘草稿...", - errorTitle: "生成复盘草稿失败" + errorTitle: "生成复盘草稿失败", + payload: action.dataset.jobId ? { source_job_id: action.dataset.jobId } : {} }); return; } diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 5e1234a..87fa8c9 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -998,6 +998,14 @@ test("main agent landing quick actions prefer direct execute flows where executo assert.match(APP, /async function runDirectWorkbenchAction\(executorKey, options = \{\}\)/); }); +test("playbook and review high-frequency actions now reuse direct execute handlers", () => { + const playbook = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()"); + const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()"); + assert.match(playbook, /direct-create-assistant/); + assert.match(review, /direct-review-draft/); + assert.match(APP, /payload: action\.dataset\.jobId \? \{ source_job_id: action\.dataset\.jobId \} : \{\}/); +}); + test("main agent landing notices expose a compact mobile follow-up strip", () => { const landing = extractBetween(APP, "function renderMainAgentLandingNotice(screenKey)", "function renderEmptyState(title, description)"); assert.match(landing, /mobile-only compact-summary-row/);