From bd5623ebc5c08f5b03f2597f0ec3618df3c36c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=AE=8F=E5=87=AF?= Date: Thu, 18 Jun 2026 18:01:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(dashboard):=20=E5=A4=A7=E5=B1=8F=E7=BB=98?= =?UTF-8?q?=E5=88=B6=E7=8B=AC=E7=AB=8B=E9=A1=B5=E9=A1=B6=E6=A0=8F=E6=B7=B1?= =?UTF-8?q?=E8=89=B2=E9=80=82=E9=85=8D=20+=20=E4=BF=AE=E5=A4=8D=E5=8D=A1?= =?UTF-8?q?=20loading/=E9=87=8D=E7=BB=98=E6=97=A0=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 顶栏(dashboard-agent-topbar)深色下改深蓝(《深蓝色 UI 规范》卡片#122347/ 页面#0C1836/边框#1A2F5A/文字#C5CFE0),不再白底;仅作用于顶栏。 - init effect 始终并行拉输入 JSON 写入 inputRef,恢复历史后「重新绘制」/续问 才有源数据,否则回退空 prompt 生成不出数据。 - 修复卡在「正在加载任务大屏…」:init effect 改为只依赖 [taskId]、start/ hydrateTranscript 经 ref 读取、finally 无条件 setLoading(false),避免模型异步 加载触发 cleanup 把首跑标记 cancelled 后 loading 永不置 false。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../pages/DashboardAgentPage.tsx | 46 +++++++++++-------- frontend-web/src/styles/globals.css | 30 ++++++++++++ 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/frontend-web/src/roundtable-planning/pages/DashboardAgentPage.tsx b/frontend-web/src/roundtable-planning/pages/DashboardAgentPage.tsx index 897bf4e3..6ce2ccb1 100644 --- a/frontend-web/src/roundtable-planning/pages/DashboardAgentPage.tsx +++ b/frontend-web/src/roundtable-planning/pages/DashboardAgentPage.tsx @@ -122,6 +122,15 @@ export default function DashboardAgentPage() { } }, [availableModels, selectedModel]); + // init 只跑一次:start / hydrateTranscript 用 ref 持有,effect 只依赖 [taskId]。 + // 否则它们的 identity 会随 selectedModel/themeMode 变化重建(模型挂载后异步加载即触发), + // 让 effect 在 await 期间 cleanup → 二次进入被 didInitRef 挡掉 → 首跑的 finally 因 cancelled + // 跳过 setLoading(false),loading 永远停在 true(白转圈,卡在「正在加载任务大屏…」)。 + const startRef = useRef(start); + startRef.current = start; + const hydrateTranscriptRef = useRef(hydrateTranscript); + hydrateTranscriptRef.current = hydrateTranscript; + // ── 挂载:按 taskId 取会话 → 决定直显大屏 / 看生成过程(只跑一次) ── const didInitRef = useRef(false); useEffect(() => { @@ -131,37 +140,38 @@ export default function DashboardAgentPage() { return; } didInitRef.current = true; - let cancelled = false; void (async () => { try { - const session = await getDashboardSessionByTask(taskId).catch(() => null); - if (cancelled) return; + // 会话 + 输入 JSON 并行取。输入**始终**备好并写进 inputRef —— 否则恢复历史 + // 后用户点「重新绘制」/续问时 buildInitialPrompt 拿不到源数据,会回退到空的 + // 圆桌 prompt,智能体没东西可分析 → 生成不出数据(即「模拟数据不生效」)。 + const [session, input] = await Promise.all([ + getDashboardSessionByTask(taskId).catch(() => null), + fetchDashboardInputJson(taskId).catch(() => null), + ]); + inputRef.current = input; const savedDialogues = (session?.transcript as Step2Dialogue[] | undefined) ?? []; const validReport = session?.reportJson && parseReportJson(session.reportJson); if (validReport) { // 有有效结果 → 恢复并直接展示大屏页面。 - hydrateTranscript(savedDialogues, session!.reportJson); + hydrateTranscriptRef.current(savedDialogues, session!.reportJson); setView("page"); } else if (savedDialogues.length > 0) { - // 有历史聊天但无有效结果 → 进生成过程展示历史,不自动重跑。 - hydrateTranscript(savedDialogues, session?.reportJson ?? null); + // 有历史聊天但无有效结果 → 进生成过程展示历史,不自动重跑(点「重新绘制」可重生成)。 + hydrateTranscriptRef.current(savedDialogues, session?.reportJson ?? null); setView("chat"); } else { - // 无任何记录 → 拉输入 JSON 自动开跑,让用户看生成过程(不自动跳)。 - const input = await fetchDashboardInputJson(taskId).catch(() => null); - if (cancelled) return; - inputRef.current = input; + // 无任何记录 → 自动开跑,让用户看生成过程(不自动跳)。 setView("chat"); - if (input) start(); + if (input) startRef.current(); } } finally { - if (!cancelled) setLoading(false); + setLoading(false); } })(); - return () => { - cancelled = true; - }; - }, [taskId, hydrateTranscript, start]); + // 只依赖 taskId:start/hydrateTranscript 经 ref 读取,避免其 identity 变化重跑本 effect。 + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [taskId]); // ── 防抖回存聊天记录 + 最新 report-json(按 taskId 共享) ── useEffect(() => { @@ -245,9 +255,9 @@ export default function DashboardAgentPage() { } return ( -
+
{/* 顶栏 */} -
+

大屏绘制智能体

diff --git a/frontend-web/src/styles/globals.css b/frontend-web/src/styles/globals.css index bec6856c..5a563267 100644 --- a/frontend-web/src/styles/globals.css +++ b/frontend-web/src/styles/globals.css @@ -626,6 +626,36 @@ background-attachment: fixed, fixed; } + /* 「大屏绘制智能体」独立页顶栏(DashboardAgentPage,class=dashboard-agent-topbar)深色 + * 适配:顶栏只为浅色写死了 slate/white 渐变,深色(.dark)下统一改深蓝(《深蓝色 UI 规范》 + * 卡片底 #122347 / 页面底 #0C1836 / 边框 #1A2F5A / 字体 #C5CFE0),仅作用于顶栏内, + * 不影响下方大屏/聊天区。浅色不命中,原样式不变。 */ + .dark .dashboard-agent-topbar { + background-image: linear-gradient(to right, #122347, #0c1836) !important; + border-color: #1a2f5a !important; + } + .dark .dashboard-agent-topbar .text-slate-900 { + color: #e8edf5 !important; + } + .dark .dashboard-agent-topbar + :is(.text-slate-700, .hover\:text-slate-700:hover) { + color: #c5cfe0 !important; + } + .dark .dashboard-agent-topbar :is(.text-slate-500, .text-slate-400) { + color: #6e88b0 !important; + } + .dark .dashboard-agent-topbar .bg-white { + background-color: #1a2f5a !important; + } + .dark .dashboard-agent-topbar + :is(.bg-slate-50, .hover\:bg-slate-50:hover) { + background-color: #0c1836 !important; + } + .dark .dashboard-agent-topbar + :is(.border-slate-100, .border-slate-200) { + border-color: #1a2f5a !important; + } + .workspace-panel { border: 1px solid #f0d7d0; background: #ffffff;