From f05a43fee31a61de6e68c55bd92c1e3d5ed42740 Mon Sep 17 00:00:00 2001 From: kris Date: Sat, 28 Mar 2026 08:58:59 +0800 Subject: [PATCH] improve auto-connect loading states --- web/storyforge-web-v4/assets/app.js | 48 +++++++++++++++++++ .../tests/workbench-pages.test.mjs | 20 ++++++++ 2 files changed, 68 insertions(+) diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index ae82120..c9508e5 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -1149,16 +1149,37 @@ async function ensureAutoSession(options = {}) { return Boolean(appState.session); } appState.autoConnectAttempted = true; + renderAll(); try { await loginWithAutoSession(backendUrl); return true; } catch (error) { appState.autoConnectError = formatActionErrorMessage(error, "自动连接失败"); persistSession(null); + renderAll(); return false; } } +function isAutoConnectionPending() { + return !appState.session + && appState.autoConnectAttempted + && !appState.autoConnectSuppressed + && !appState.autoConnectError; +} + +function renderAutoConnectingScreen(screenTitle, nextStepText) { + return screenShell( + screenTitle, + "正在自动连接工作区,成功后会自动替换成真实内容。", + `${button("连接状态", "open-auth", "primary")}`, + renderEmptyState( + "正在自动连接工作区", + nextStepText || "如果超过几秒仍未进入真实页面,可以点右上角查看连接状态。" + ) + ); +} + async function refreshFromAuthModal() { const modal = document.querySelector(".auth-modal-backdrop"); const visible = modal && !modal.classList.contains("hidden"); @@ -3878,6 +3899,9 @@ function renderAdminWorkbenchScreen() { function renderDashboardScreen() { if (!appState.session) { + if (isAutoConnectionPending()) { + return renderAutoConnectingScreen("项目总台", "会话签发成功后,这里会自动切成真实项目总台。"); + } return screenShell( "项目总台", "先自动连接工作区,再加载项目、对标、Agent 和生产状态。", @@ -3907,6 +3931,9 @@ function renderDashboardScreen() { function renderProjectsScreen() { if (!appState.dashboard) { + if (isAutoConnectionPending()) { + return renderAutoConnectingScreen("我的项目", "工作区就绪后,这里会自动加载真实项目和导入队列。"); + } return screenShell("我的项目", "先完成工作区自动连接,再加载项目。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("项目未加载", "自动连接成功后,这里会显示真实项目和导入队列。")); } const projects = safeArray(appState.dashboard.projects); @@ -4109,6 +4136,9 @@ function renderDiscoveryRelationsSection(linkedAccounts, similarCandidates) { function renderDiscoveryScreen() { if (!appState.dashboard) { + if (isAutoConnectionPending()) { + return renderAutoConnectingScreen("找对标", "工作区就绪后,这里会自动显示当前平台的账号列表和详情。"); + } return screenShell("找对标", "完成工作区自动连接后才能加载真实对标账号。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("对标库未加载", "自动连接成功后,这里会显示当前平台的账号列表和详情。")); } const query = appState.discoveryQuery.toLowerCase(); @@ -4282,6 +4312,9 @@ function renderDiscoveryScreen() { function renderTrackingScreen() { if (!appState.dashboard) { + if (isAutoConnectionPending()) { + return renderAutoConnectingScreen("跟踪账号", "工作区就绪后,这里会自动加载真实跟踪对象和日报。"); + } return screenShell("跟踪账号", "完成工作区自动连接后才能生成真实日报。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("日报未加载", "当前还没有可用的对标账号数据。")); } const currentPlatform = getCurrentPlatformValue(); @@ -4428,6 +4461,9 @@ function renderAutomationScreen() { function renderOwnedScreen() { if (!appState.dashboard) { + if (isAutoConnectionPending()) { + return renderAutoConnectingScreen("我的账号", "工作区就绪后,这里会自动显示当前账号和建议动作。"); + } return screenShell("我的账号", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("我的账号未加载", "自动连接成功后,这里会展示当前账号和建议动作。")); } const me = appState.me || appState.session?.account || {}; @@ -4507,6 +4543,9 @@ function renderOwnedScreen() { function renderPlaybookScreen() { if (!appState.dashboard) { + if (isAutoConnectionPending()) { + return renderAutoConnectingScreen("Agent", "工作区就绪后,这里会自动加载真实 Agent 列表和模型。"); + } return screenShell("Agent", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("Agent 未加载", "自动连接成功后,这里会展示真实 Agent 列表和模型。")); } const assistants = safeArray(appState.dashboard.assistants); @@ -4681,6 +4720,9 @@ function renderPlaybookScreen() { function renderProductionScreen() { if (!appState.dashboard) { + if (isAutoConnectionPending()) { + return renderAutoConnectingScreen("生产中心", "工作区就绪后,这里会自动加载真实任务和作品。"); + } return screenShell("生产中心", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("生产中心未加载", "自动连接成功后,这里会展示真实任务和作品。")); } const jobs = safeArray(appState.dashboard.recent_jobs); @@ -4822,6 +4864,9 @@ function renderProductionScreen() { function renderReviewScreen() { if (!appState.dashboard) { + if (isAutoConnectionPending()) { + return renderAutoConnectingScreen("发布与复盘", "工作区就绪后,这里会自动生成复盘入口和最近完成任务。"); + } return screenShell("发布与复盘", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("复盘未加载", "自动连接成功后,这里会先用最近任务生成一版复盘入口。")); } if (!backendSupports("/v2/reviews")) { @@ -4889,6 +4934,9 @@ function renderReviewScreen() { function renderCreditsScreen() { if (!appState.dashboard) { + if (isAutoConnectionPending()) { + return renderAutoConnectingScreen("额度", "工作区就绪后,这里会自动展示真实额度和运营看板。"); + } return screenShell("额度", "先自动连接工作区。", `${button("自动连接", "open-auth", "primary")}`, renderEmptyState("额度未加载", "自动连接成功后,这里会展示真实额度和运营看板。")); } const jobs = safeArray(appState.dashboard.recent_jobs); diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index d4c0373..8ded76f 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -55,3 +55,23 @@ test("projects screen uses an adaptive project grid instead of a fixed three-col assert.match(CSS, /\.project-status-grid\s*\{[\s\S]*repeat\(auto-fit,\s*minmax\(260px,\s*1fr\)\)/); assert.match(CSS, /\.entity-card\.pad\s*\{[\s\S]*padding:\s*15px/); }); + +test("dashboard and project screens distinguish auto-connecting from truly disconnected", () => { + const dashboard = extractBetween(APP, "function renderDashboardScreen()", "function renderProjectsScreen()"); + const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab("); + const helper = extractBetween(APP, "function isAutoConnectionPending()", "async function refreshFromAuthModal()"); + + assert.match(APP, /function isAutoConnectionPending\(\)/); + assert.match(APP, /function renderAutoConnectingScreen\(/); + assert.match(helper, /正在自动连接工作区/); + assert.match(dashboard, /isAutoConnectionPending\(\)/); + assert.match(dashboard, /renderAutoConnectingScreen\("项目总台"/); + assert.match(projects, /isAutoConnectionPending\(\)/); + assert.match(projects, /renderAutoConnectingScreen\("我的项目"/); +}); + +test("ensureAutoSession re-renders immediately when auto-connect starts", () => { + const source = extractBetween(APP, "async function ensureAutoSession(options = {})", "async function refreshFromAuthModal()"); + assert.match(source, /appState\.autoConnectAttempted = true;/); + assert.match(source, /renderAll\(\);/); +});