修复 embed 跨域并持久化圆桌 Step3 结果

- Gateway 默认 CORS 白名单增加 5174,embed 文档补充跨域配置说明
- 圆桌草稿新增 step3 快照字段与迁移,结果绘制 HTML 可随草稿恢复

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
董宏凯andCursor committed 2026-06-03 16:27:42 +08:00
1 parent 40ba81ee6a
commit aea7724cdb
15 files changed
+527 -89

No files matched your search

@@ -20,11 +20,15 @@ import {
FileText,
ListOrdered,
Loader2,
PanelLeftClose,
PanelLeftOpen,
RefreshCw,
Sparkles,
X,
} from "lucide-react";
import { useArtifacts } from "@/components/workspace/artifacts";
import { cn } from "@/lib/utils";
import {
buildArtifactDownloadUrl,
listSessionArtifacts,
@@ -36,6 +40,7 @@ import { ScrollToBottomButton } from "@/roundtable-planning/components/ScrollToB
import { Step2Message } from "@/roundtable-planning/components/Step2Panel";
import { Step2SandboxLayout } from "@/roundtable-planning/components/Step2SandboxLayout";
import { markdownComponents } from "@/roundtable-planning/components/markdown-components";
import type { DraftStep3Snapshot } from "@/roundtable-planning/api/drafts";
import { useAutoScroll } from "@/roundtable-planning/hooks/useAutoScroll";
import {
useStep3Report,
@@ -65,6 +70,10 @@ interface Step3ReportModeProps {
availableModels: ReadonlyArray<ModelOption>;
modelsLoading: boolean;
setSelectedModel: (model: string) => void;
/** 草稿里已存的上次绘制结果 —— 有则进入时恢复展示,不重新绘制。 */
savedReport?: DraftStep3Snapshot | null;
/** 一次绘制完成时回调,把结果交给上层存进草稿 step3。 */
onReportSaved?: (snapshot: DraftStep3Snapshot) => void;
onBack: () => void;
}
@@ -82,6 +91,8 @@ export function Step3ReportMode({
availableModels,
modelsLoading,
setSelectedModel,
savedReport,
onReportSaved,
onBack,
}: Step3ReportModeProps) {
const reportSeatDeliveries = useMemo(
@@ -97,6 +108,7 @@ export function Step3ReportMode({
seatStubMessages,
activeArtifactThreadId,
start,
hydrateSaved,
redraw,
abort,
openArtifactFromStep,
@@ -106,16 +118,35 @@ export function Step3ReportMode({
seatDeliveries: reportSeatDeliveries,
consensusText,
selectedModel,
savedReport,
onReportSaved,
});
const [modal, setModal] = useState<SourceModal>(null);
// 左栏「研讨原料」收起态:打开沙箱时默认收起成图标条(对齐第 2 步侧栏收起),
// 关闭沙箱时自动展开;中途用户可手动切换(effect 只在沙箱开关变化的边沿触发)。
const { open: artifactsOpen } = useArtifacts();
const [leftCollapsed, setLeftCollapsed] = useState(false);
const prevArtifactsOpenRef = useRef(artifactsOpen);
useEffect(() => {
if (artifactsOpen !== prevArtifactsOpenRef.current) {
prevArtifactsOpenRef.current = artifactsOpen;
setLeftCollapsed(artifactsOpen);
}
}, [artifactsOpen]);
// 进入即自动开跑;卸载(含返回普通视图)中断在途请求。
// 不加"永久闸门"——否则 StrictMode 的 mount→unmount→remount 会在 cleanup 里 abort
// 掉首次 run,remount 又因闸门跳过重启,导致永远不绘制。run() 自带 runningRef 去重,
// remount 重新 start() 即可(开发期至多多建一个空线程,生产无 StrictMode 只跑一次)。
// 若草稿里已存上次绘制结果(savedReport)→ 直接恢复展示,不重新跑模型;用户可点「重新绘制」覆盖。
useEffect(() => {
start();
if (savedReport?.html) {
hydrateSaved(savedReport);
} else {
start();
}
return () => abort();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -178,34 +209,86 @@ export function Step3ReportMode({
{/* 主体:左源卡 + 右沙箱 */}
<div className="flex-1 min-h-0 flex gap-3 p-3">
{/* 左栏:收起的源内容 */}
<aside className="w-[280px] shrink-0 flex flex-col gap-3 overflow-y-auto pr-0.5">
<SourceCard
icon={<CheckCircle className="w-4 h-4 text-emerald-500" />}
title="总控最终结论"
hint={consensusText ? "点击查看结论全文" : "暂无结论"}
badge={hasConsensus ? "已共识" : undefined}
disabled={!consensusText}
onClick={() => setModal("consensus")}
/>
<SourceCard
icon={<FileText className="w-4 h-4 text-primary" />}
title="各席位最终交付"
hint="点击逐席位查看交付"
badge={String(seatDeliveries.length)}
disabled={seatDeliveries.length === 0}
onClick={() => setModal("seats")}
/>
<SourceCard
icon={<Download className="w-4 h-4 text-primary" />}
title="各席位生成文件"
hint="点击查看/预览产出文件"
onClick={() => setModal("files")}
/>
{/* 左栏:研讨原料源卡。打开沙箱时收起成 52px 图标条(对齐第 2 步侧栏收起样式)。 */}
<aside
className={cn(
"shrink-0 flex flex-col gap-3 overflow-y-auto pr-0.5 transition-[width] duration-200 ease-out",
leftCollapsed ? "w-[52px]" : "w-[280px]",
)}
>
{leftCollapsed ? (
<>
<button
type="button"
onClick={() => setLeftCollapsed(false)}
title="展开研讨原料"
aria-expanded={false}
className="w-full shrink-0 flex h-10 items-center justify-center rounded-2xl border border-slate-200/50 bg-white text-slate-500 hover:text-primary hover:border-primary/30 transition-colors shadow-[0_2px_8px_rgba(0,0,0,0.01)]"
>
<PanelLeftOpen className="h-4 w-4" />
</button>
<CollapsedSourceCard
icon={<CheckCircle className="w-4 h-4 text-emerald-500" />}
title="总控最终结论"
disabled={!consensusText}
onClick={() => setModal("consensus")}
/>
<CollapsedSourceCard
icon={<FileText className="w-4 h-4 text-primary" />}
title="各席位最终交付"
disabled={seatDeliveries.length === 0}
onClick={() => setModal("seats")}
/>
<CollapsedSourceCard
icon={<Download className="w-4 h-4 text-primary" />}
title="各席位生成文件"
onClick={() => setModal("files")}
/>
</>
) : (
<>
<div className="shrink-0 flex items-center justify-between gap-2 px-1">
<span className="text-[11px] font-bold text-slate-500 uppercase tracking-widest">
研讨原料
</span>
<button
type="button"
onClick={() => setLeftCollapsed(true)}
title="收起研讨原料"
aria-expanded
className="shrink-0 flex h-7 w-7 items-center justify-center rounded-lg border border-slate-200/50 bg-white text-slate-500 hover:text-primary hover:border-primary/30 transition-colors"
>
<PanelLeftClose className="h-3.5 w-3.5" />
</button>
</div>
<SourceCard
icon={<CheckCircle className="w-4 h-4 text-emerald-500" />}
title="总控最终结论"
hint={consensusText ? "点击查看结论全文" : "暂无结论"}
badge={hasConsensus ? "已共识" : undefined}
disabled={!consensusText}
onClick={() => setModal("consensus")}
/>
<SourceCard
icon={<FileText className="w-4 h-4 text-primary" />}
title="各席位最终交付"
hint="点击逐席位查看交付"
badge={String(seatDeliveries.length)}
disabled={seatDeliveries.length === 0}
onClick={() => setModal("seats")}
/>
<SourceCard
icon={<Download className="w-4 h-4 text-primary" />}
title="各席位生成文件"
hint="点击查看/预览产出文件"
onClick={() => setModal("files")}
/>
<div className="mt-1 rounded-2xl border border-dashed border-slate-200 bg-slate-50/60 p-3 text-[11px] leading-relaxed text-slate-400">
原研讨内容已收起到这里,随时点开查阅。右侧由「方案总结智能体」据此绘制可视化看板。
</div>
<div className="mt-1 rounded-2xl border border-dashed border-slate-200 bg-slate-50/60 p-3 text-[11px] leading-relaxed text-slate-400">
原研讨内容已收起到这里,随时点开查阅。右侧由「方案总结智能体」据此绘制可视化汇报。
</div>
</>
)}
</aside>
{/* 右栏:智能体进度 + 沙箱(复用第 2 步) */}
@@ -300,6 +383,31 @@ function SourceCard({
);
}
// 收起态的源卡:仅图标,居中,点击仍打开对应弹窗(对齐第 2 步 CollapsedSidebarSection 样式)。
function CollapsedSourceCard({
icon,
title,
disabled,
onClick,
}: {
icon: React.ReactNode;
title: string;
disabled?: boolean;
onClick: () => void;
}) {
return (
<button
type="button"
title={title}
disabled={disabled}
onClick={onClick}
className="w-full shrink-0 flex items-center justify-center rounded-2xl border border-slate-200/50 bg-white p-2.5 text-slate-500 shadow-[0_2px_8px_rgba(0,0,0,0.01)] transition hover:text-primary hover:border-primary/30 disabled:opacity-40 disabled:cursor-not-allowed"
>
{icon}
</button>
);
}
// ── 右栏:智能体进度面板(沙箱的 60% 区域)────────────────────────────────
function AgentProgressPanel({