From 1b1192990928cc1fc7a9e07318943d1e976cd7cc Mon Sep 17 00:00:00 2001 From: kris Date: Mon, 30 Mar 2026 13:33:15 +0800 Subject: [PATCH] feat: simplify mobile discovery and production actions --- web/storyforge-web-v4/assets/app.js | 50 ++++++++++++------- .../tests/workbench-pages.test.mjs | 15 ++++++ 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 779c1b9..e4653ac 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -618,6 +618,10 @@ function hasSessionBackendMismatch(expectedBackendUrl = DEFAULT_BACKEND_URL) { return Boolean(expected && current && expected !== current); } +function isMobileViewport() { + return typeof window !== "undefined" && Boolean(window.matchMedia?.("(max-width: 760px)")?.matches); +} + function formatBackendDisplayLabel(value = DEFAULT_BACKEND_URL) { const normalized = normalizeBackendUrlValue(value || DEFAULT_BACKEND_URL); if (!normalized) return "未配置后端"; @@ -5527,6 +5531,7 @@ function renderDiscoveryScreen() { const selectedProject = getSelectedProject(); const importedSources = getCurrentProjectSourcesForAccount(selected, selectedProject?.id || ""); const tracked = selected?.id ? isTrackedAccount(selected.id) : false; + const isMobileUi = isMobileViewport(); const discoveryHandoffAttrs = buildMainAgentHandoffAttrs({ sourceScreen: "discovery", sourceActionKey: "discovery-handoff", @@ -5587,12 +5592,15 @@ function renderDiscoveryScreen() { } else { detailBodyHtml = renderDiscoveryRelationsSection(linkedAccounts, similarCandidates); } + const discoveryActionsHtml = isMobileUi + ? `${button("导入主页", "open-import-homepage")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: discoveryHandoffAttrs })} ${button("存对标", "open-benchmark-link", "primary", { disabledReason: workbenchReason || "" })}` + : `${button("导入主页", "open-import-homepage")} ${button("导入当前对标", "open-import-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button(tracked ? "已在跟踪" : "加入跟踪", "open-track-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("账号分析", "analyze-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("高分分析", "analyze-top-videos", "secondary", { disabledReason: workbenchReason || "" })} ${button("查相似", "open-similar-search", "secondary", { disabledReason: workbenchReason || "" })} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: discoveryHandoffAttrs })} ${button("存对标", "open-benchmark-link", "primary", { disabledReason: workbenchReason || "" })}`; return screenShell( "找对标", isWorkbenchPlatform(currentPlatform) ? `这里已经接入真实${currentPlatformLabel}账号列表和单账号详情。` : `${workbenchReason}。当前仍可导入内容源、绑定 Agent 和沉淀复盘。`, - `${button("导入主页", "open-import-homepage")} ${button("导入当前对标", "open-import-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button(tracked ? "已在跟踪" : "加入跟踪", "open-track-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("账号分析", "analyze-selected-account", "secondary", { disabledReason: workbenchReason || "" })} ${button("高分分析", "analyze-top-videos", "secondary", { disabledReason: workbenchReason || "" })} ${button("查相似", "open-similar-search", "secondary", { disabledReason: workbenchReason || "" })} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: discoveryHandoffAttrs })} ${button("存对标", "open-benchmark-link", "primary", { disabledReason: workbenchReason || "" })}`, + discoveryActionsHtml, ` ${renderMainAgentLandingNotice("discovery")}
@@ -5702,24 +5710,26 @@ function renderDiscoveryScreen() {
${escapeHtml(getAccountName(selected) || "未选中")} -
-
- 下一步先做 - ${escapeHtml(detailTabs.find((tab) => tab.value === activeTab)?.label || "账号概览")} + ${isMobileUi ? "" : ` +
+
+ 下一步先做 + ${escapeHtml(detailTabs.find((tab) => tab.value === activeTab)?.label || "账号概览")} +
+

${escapeHtml(selected ? `先围绕 ${getAccountName(selected)} 做导入、分析和相似扩展。` : "先从账号池里选一个对象,再继续导入和分析。")}

+
+ ${actionTag("导入当前对标", "open-import-selected-account")} + ${actionTag("账号分析", "analyze-selected-account")} + ${actionTag("查相似", "open-similar-search")} +
-

${escapeHtml(selected ? `先围绕 ${getAccountName(selected)} 做导入、分析和相似扩展。` : "先从账号池里选一个对象,再继续导入和分析。")}

-
- ${actionTag("导入当前对标", "open-import-selected-account")} - ${actionTag("账号分析", "analyze-selected-account")} - ${actionTag("查相似", "open-similar-search")} +
+ 当前对标 ${escapeHtml(getAccountName(selected) || "未选中")} + ${escapeHtml(importedSources.length ? `已接入 ${importedSources.length}` : "未接入项目")} + ${escapeHtml(tracked ? "已加入跟踪" : "未加入跟踪")} + ${escapeHtml(reports.length ? `报告 ${reports.length}` : "暂无报告")}
-
-
- 当前对标 ${escapeHtml(getAccountName(selected) || "未选中")} - ${escapeHtml(importedSources.length ? `已接入 ${importedSources.length}` : "未接入项目")} - ${escapeHtml(tracked ? "已加入跟踪" : "未加入跟踪")} - ${escapeHtml(reports.length ? `报告 ${reports.length}` : "暂无报告")} -
+ `} ${selectedSummaryHtml} ${renderDetailTabs("discoveryDetailTab", detailTabs)} ${detailBodyHtml} @@ -6443,6 +6453,7 @@ function renderProductionScreen() { const recoverableCount = failedJobs.filter((item) => item.recovery.recoverable).length; const recentDocs = appState.documents.slice(0, 3); const works = getProductionWorks(6); + const isMobileUi = isMobileViewport(); const productionHandoffAttrs = buildMainAgentHandoffAttrs({ sourceScreen: "production", sourceActionKey: "production-handoff", @@ -6465,10 +6476,13 @@ function renderProductionScreen() { { value: "outputs", label: "作品与产物" } ]; const activeTab = getActiveDetailTab("productionDetailTab", tabs); + const productionActionsHtml = isMobileUi + ? `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })}` + : `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })} ${button("去复盘", "goto-review", "primary")} ${button("批量恢复", "batch-recover-jobs", "secondary", { disabledReason: recoverableCount ? "" : "当前没有可恢复的失败任务" })}`; return screenShell( "生产中心", "这里已经接上真实任务和知识库文档,后续再继续补任务创建动作。", - `${renderPipelineButton("aiVideo")} ${renderPipelineButton("realCut")} ${button("交给主 Agent", "handoff-to-main-agent", "secondary", { attrs: productionHandoffAttrs })} ${button("去复盘", "goto-review", "primary")} ${button("批量恢复", "batch-recover-jobs", "secondary", { disabledReason: recoverableCount ? "" : "当前没有可恢复的失败任务" })}`, + productionActionsHtml, ` ${renderMainAgentLandingNotice("production")}
diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index a1e5775..f9d20d4 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -271,6 +271,21 @@ test("discovery and production screens expose mobile focus cards with next-step assert.match(cssMobile, /\.production-mobile-task-deck/); }); +test("mobile discovery and production simplify duplicated top-level actions", () => { + const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()"); + const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()"); + + assert.match(APP, /function isMobileViewport\(\)/); + assert.match(discovery, /const isMobileUi = isMobileViewport\(\);/); + assert.match(discovery, /const discoveryActionsHtml = isMobileUi/); + assert.match(discovery, /button\("导入主页", "open-import-homepage"\)/); + assert.match(discovery, /button\("存对标", "open-benchmark-link"/); + assert.match(production, /const isMobileUi = isMobileViewport\(\);/); + assert.match(production, /const productionActionsHtml = isMobileUi/); + assert.match(production, /button\("交给主 Agent", "handoff-to-main-agent"/); + assert.match(production, /button\("去复盘", "goto-review", "primary"\)/); +}); + 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/);