From 794de0133ececf74167a01bc59080e4d5960dcee Mon Sep 17 00:00:00 2001 From: kris Date: Mon, 30 Mar 2026 12:46:04 +0800 Subject: [PATCH] feat: finish mobile task cards for thin pages --- web/storyforge-web-v4/assets/app.js | 79 ++++++++++++++++++- web/storyforge-web-v4/index.html | 8 +- .../tests/workbench-pages.test.mjs | 12 +++ 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 926d6c0..cd12593 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -618,6 +618,17 @@ function hasSessionBackendMismatch(expectedBackendUrl = DEFAULT_BACKEND_URL) { return Boolean(expected && current && expected !== current); } +function formatBackendDisplayLabel(value = DEFAULT_BACKEND_URL) { + const normalized = normalizeBackendUrlValue(value || DEFAULT_BACKEND_URL); + if (!normalized) return "未配置后端"; + try { + const parsed = new URL(normalized); + return parsed.host || normalized; + } catch { + return normalized; + } +} + function compareDateDesc(leftValue, rightValue) { return new Date(rightValue || 0).getTime() - new Date(leftValue || 0).getTime(); } @@ -6930,11 +6941,45 @@ function renderCreditsScreen() { const usage = appState.tenantUsage || quota?.usage || {}; const categories = usage?.categories || {}; const estimatedVideoUsage = (categories.ai_video?.quantity || 0) + (categories.real_cut?.quantity || 0); + const budgetAmount = (quota?.monthly_budget_cents || usage?.total_cost_cents || 0) / 100; + const usedAmount = (usage?.total_cost_cents || 0) / 100; + const quotaProtectionLabel = quota?.enabled === false ? "额度保护关闭" : "额度保护开启"; + const riskLabel = quota?.storage_over_limit ? "存储超限" : "当前风险可控"; return screenShell( "额度", "在接真实计费前,先按任务量给出运营看板。", `${button("刷新", "refresh-data")}`, ` +
+
+ 当前额度任务 + ${escapeHtml(quotaProtectionLabel)} +
+

${escapeHtml( + quota + ? `先确认预算 ${formatNumber(budgetAmount)} 元和已用 ${formatNumber(usedAmount)} 元,再决定是继续放量还是先收紧高成本动作。` + : "先看预算和高成本动作的预估消耗,再决定是否要继续做视频或剪辑任务。" + )}

+
+ ${actionTag("刷新额度", "refresh-data")} + ${actionTag("去生产中心", "goto-production")} + ${actionTag("交给主 Agent", "handoff-to-main-agent", buildMainAgentHandoffAttrs({ + sourceScreen: "credits", + sourceActionKey: "credits-main-agent-handoff", + intentKey: "custom", + title: "评估当前额度和预算风险", + goal: "评估当前额度和预算风险", + summary: "让主 Agent 结合当前预算、已用额度和高成本动作,给出下一步建议。", + planSteps: ["读取当前额度看板", "判断预算与高成本动作风险", "给出下一步控制建议"] + }))} +
+
+
+ 预算 ${escapeHtml(formatNumber(budgetAmount))} 元 + ${escapeHtml(riskLabel)} + ${escapeHtml(formatNumber(categories.copy?.quantity || jobs.filter((item) => item.line_type === "analysis").length))} 条文案 + ${escapeHtml(formatNumber(estimatedVideoUsage || jobs.filter((item) => item.line_type === "ai_video" || item.line_type === "real_cut").length))} 次视频 +
文案消耗预估${escapeHtml(formatNumber(categories.copy?.quantity || jobs.filter((item) => item.line_type === "analysis").length))}
分析 / 生成链路按任务量估算
本周期预算${escapeHtml(formatNumber((quota?.monthly_budget_cents || usage?.total_cost_cents || 0) / 100))}
已用 ${escapeHtml(formatNumber((usage?.total_cost_cents || 0) / 100))} 元
@@ -6992,17 +7037,49 @@ function renderSettingsScreen() { { value: "display", label: "界面与帮助" } ]; const activeTab = getActiveDetailTab("settingsDetailTab", tabs); + const sessionConnected = Boolean(session); + const settingsHandoffAttrs = buildMainAgentHandoffAttrs({ + sourceScreen: "settings", + sourceActionKey: "settings-main-agent-handoff", + intentKey: "custom", + title: "检查当前设置和工作区连接", + goal: "检查当前设置和工作区连接", + summary: "让主 Agent 读取当前连接状态、工作区和常用入口,再给出下一步建议。", + planSteps: ["读取当前连接与项目上下文", "确认当前工作区和入口状态", "生成下一步建议"] + }); return screenShell( "设置", "这里不放系统治理内容,只处理当前用户需要理解的连接、界面和帮助信息。", `${button("连接状态", "open-auth")} ${isSuperAdmin() ? button("管理员配置台", "goto-admin-workbench", "primary") : button("刷新", "refresh-data", "primary")}`, ` -
+

设置与帮助

把连接状态、当前工作区和使用说明放在一起,避免和管理员控制面混在同一页。

当前设置

先看你现在接到了哪里,再决定是否要调整。
+
+
+ 当前设置任务 + ${escapeHtml(sessionConnected ? "已自动连接" : "等待连接")} +
+

${escapeHtml( + sessionConnected + ? `当前已经连到 ${session?.account?.display_name || session?.account?.username || "当前工作区"},先确认工作区和常用入口,再决定是否切项目或回到业务页。` + : "先确认当前站点是否已自动连接到工作区,再决定是重试连接还是回到业务页。" + )}

+
+ ${actionTag("打开连接状态", "open-auth")} + ${project ? actionTag("去我的项目", "goto-intake") : ""} + ${actionTag("交给主 Agent", "handoff-to-main-agent", settingsHandoffAttrs)} +
+
+
+ ${escapeHtml(sessionConnected ? "已自动连接" : "等待连接")} + ${escapeHtml(project?.name || "未选项目")} + ${escapeHtml(formatBackendDisplayLabel(session?.backendUrl || DEFAULT_BACKEND_URL))} + ${escapeHtml(activeTab === "workspace" ? "连接与工作区" : "界面与帮助")} +
${renderDetailTabs("settingsDetailTab", tabs)} ${activeTab === "workspace" ? `
diff --git a/web/storyforge-web-v4/index.html b/web/storyforge-web-v4/index.html index de07518..7f43aa3 100644 --- a/web/storyforge-web-v4/index.html +++ b/web/storyforge-web-v4/index.html @@ -4,7 +4,7 @@ - StoryForge Web V4 Prototype + StoryForge Workbench @@ -1687,7 +1687,7 @@
- +
@@ -1807,7 +1807,7 @@
- +
@@ -1941,7 +1941,7 @@ - +
diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 7c14183..be4e992 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -287,6 +287,8 @@ test("remaining mobile workbench screens expose focus cards and compact summarie const automation = extractBetween(APP, "function renderAutomationScreen()", "function renderOwnedScreen()"); const owned = extractBetween(APP, "function renderOwnedScreen()", "function renderPlaybookScreen()"); const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()"); + const credits = extractBetween(APP, "function renderCreditsScreen()", "function renderSettingsScreen()"); + const settings = extractBetween(APP, "function renderSettingsScreen()", "function renderTopbar()"); assert.match(projects, /mobile-only mobile-flow-focus-card/); assert.match(projects, /当前项目任务/); @@ -312,6 +314,16 @@ test("remaining mobile workbench screens expose focus cards and compact summarie assert.match(review, /当前复盘任务/); assert.match(review, /mobile-only compact-summary-row/); assert.match(review, /已保存/); + + assert.match(credits, /mobile-only mobile-flow-focus-card/); + assert.match(credits, /当前额度任务/); + assert.match(credits, /mobile-only compact-summary-row/); + assert.match(credits, /预算/); + + assert.match(settings, /mobile-only mobile-flow-focus-card/); + assert.match(settings, /当前设置任务/); + assert.match(settings, /mobile-only compact-summary-row/); + assert.match(settings, /已自动连接/); }); test("projects screen uses an adaptive project grid instead of a fixed three-column squeeze", () => {