diff --git a/web/storyforge-web-v4/assets/app.js b/web/storyforge-web-v4/assets/app.js index 4ac0060..7884547 100644 --- a/web/storyforge-web-v4/assets/app.js +++ b/web/storyforge-web-v4/assets/app.js @@ -87,6 +87,7 @@ const appState = { }; let PLATFORM_RUNTIME = null; +let onelinerRunPollTimer = null; const API_CLIENT = StoryForgeApiClient.create({ getSession: () => appState.session, @@ -1281,15 +1282,48 @@ function renderOneLinerUi() { if (input && !input.value && !safeArray(appState.onelinerMessages).length) { input.value = ""; } + syncOneLinerRunPolling(); +} + +function clearOneLinerRunPollTimer() { + if (onelinerRunPollTimer) { + clearTimeout(onelinerRunPollTimer); + onelinerRunPollTimer = null; + } +} + +function syncOneLinerRunPolling() { + clearOneLinerRunPollTimer(); + const panel = document.querySelector(".oneliner-backdrop"); + const isPanelVisible = Boolean(panel && !panel.classList.contains("hidden")); + const currentRun = getCurrentOneLinerRun(); + const shouldPoll = Boolean( + isPanelVisible + && currentRun?.id + && ["queued", "running", "blocked"].includes(currentRun.run_status) + ); + if (!shouldPoll) { + return; + } + onelinerRunPollTimer = setTimeout(async () => { + try { + await hydrateSelectedOneLinerRun(); + renderAll(); + } catch { + clearOneLinerRunPollTimer(); + } + }, 1500); } function openOneLinerPanel() { ensureOneLinerUi(); document.querySelector(".oneliner-backdrop")?.classList.remove("hidden"); + syncOneLinerRunPolling(); } function closeOneLinerPanel() { document.querySelector(".oneliner-backdrop")?.classList.add("hidden"); + clearOneLinerRunPollTimer(); } function readActionForm() { diff --git a/web/storyforge-web-v4/tests/workbench-pages.test.mjs b/web/storyforge-web-v4/tests/workbench-pages.test.mjs index 88e4efe..7804866 100644 --- a/web/storyforge-web-v4/tests/workbench-pages.test.mjs +++ b/web/storyforge-web-v4/tests/workbench-pages.test.mjs @@ -290,3 +290,18 @@ test("oneliner runtime shows grouped run health summary above the current run ca assert.match(runtime, /已完成/); assert.match(runtime, /safeArray\(runs\)\.filter\(\(item\) => item\.run_status === "needs_confirmation"\)\.length/); }); + +test("oneliner panel auto-polls active runs while the floating panel stays open", () => { + const render = extractBetween(APP, "function renderOneLinerUi()", "function openOneLinerPanel()"); + const open = extractBetween(APP, "function openOneLinerPanel()", "function closeOneLinerPanel()"); + const close = extractBetween(APP, "function closeOneLinerPanel()", "function readActionForm()"); + + assert.match(APP, /let onelinerRunPollTimer = null;/); + assert.match(APP, /function clearOneLinerRunPollTimer\(\)/); + assert.match(APP, /function syncOneLinerRunPolling\(\)/); + assert.match(render, /syncOneLinerRunPolling\(\);/); + assert.match(open, /syncOneLinerRunPolling\(\);/); + assert.match(close, /clearOneLinerRunPollTimer\(\);/); + assert.match(APP, /setTimeout\(async \(\) => \{/); + assert.match(APP, /await hydrateSelectedOneLinerRun\(\);/); +});