feat(ui): 浅蓝主题、外观设置与圆桌看板主题适配,优化工作区布局
新增 lightblue 主题与外观设置联动,圆桌 Step3 看板/report 统一配色;工作区侧边栏、输入框与轻应用 iframe 主题参数同步更新。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
30d4f9667b
commit
c702119e66
39 files changed
+2562
-1441
No files matched your search
@@ -21,11 +21,14 @@ import {
|
||||
} from "react";
|
||||
import {
|
||||
ArrowLeft,
|
||||
Boxes,
|
||||
CheckCircle,
|
||||
Code2,
|
||||
ExternalLink,
|
||||
LayoutPanelTop,
|
||||
Loader2,
|
||||
Pause,
|
||||
Pencil,
|
||||
RefreshCw,
|
||||
SendHorizontal,
|
||||
Sparkles,
|
||||
@@ -43,7 +46,7 @@ import { DASHBOARD_VIEW_STORAGE_PREFIX } from "@/roundtable-planning/pages/Dashb
|
||||
import { useAutoScroll } from "@/roundtable-planning/hooks/useAutoScroll";
|
||||
import { useDialogueSpeedMetrics } from "@/roundtable-planning/hooks/useDialogueSpeedMetrics";
|
||||
import type { UseStep3DashboardReturn } from "@/roundtable-planning/hooks/useStep3Dashboard";
|
||||
import { parseReportJson } from "@/roundtable-planning/lib/report-template";
|
||||
import { parseReportJson, stripReportFence } from "@/roundtable-planning/lib/report-template";
|
||||
|
||||
interface Step3DashboardModeProps {
|
||||
selectedModel: string;
|
||||
@@ -65,6 +68,7 @@ function formatNow(): string {
|
||||
}
|
||||
|
||||
type DashView = "chat" | "page";
|
||||
type RenderMode = "app" | "html";
|
||||
|
||||
export function Step3DashboardMode({
|
||||
selectedModel,
|
||||
@@ -101,12 +105,13 @@ export function Step3DashboardMode({
|
||||
// 视图:有产物默认看「大屏页面」,否则进「交互区」看抽取过程。
|
||||
const [view, setView] = useState<DashView>(() => (hasResult ? "page" : "chat"));
|
||||
|
||||
// 完成即自动切到大屏页面(含问答修订后的再次完成)。
|
||||
const prevPhaseRef = useRef(phase);
|
||||
useEffect(() => {
|
||||
if (phase === "done" && prevPhaseRef.current !== "done") setView("page");
|
||||
prevPhaseRef.current = phase;
|
||||
}, [phase]);
|
||||
// 大屏页面的两种渲染:
|
||||
// - "app" 应用渲染:用应用内 React 组件(DashboardApp)渲染,交互更丰富(Tab/动画/钻取)。
|
||||
// - "html" 网页渲染:直接把自包含 HTML 大屏塞进 iframe,与「新窗口打开 / 分享 / 下载」一致。
|
||||
const [renderMode, setRenderMode] = useState<RenderMode>("app");
|
||||
|
||||
// 绘制完成**不再**自动切到大屏页面:用户正在交互区看抽取过程/继续问答时,强行跳走很打断。
|
||||
// 想看成品由用户手动点顶栏「查看大屏」。没有产物时初始即落交互区(见上 useState 初值)。
|
||||
|
||||
// 仅在交互区允许本流自动接管沙箱(并行流防抢);离开/卸载时让出。
|
||||
useEffect(() => {
|
||||
@@ -114,10 +119,22 @@ export function Step3DashboardMode({
|
||||
return () => setPreviewEnabled(false);
|
||||
}, [view, setPreviewEnabled]);
|
||||
|
||||
// 新窗口打开:把当前大屏数据写进 localStorage,在新标签页打开应用内 React 大屏
|
||||
// (独立全屏路由 /roundtable-dashboard,无侧边栏)。无真实数据时 standalone 回落 DEMO。
|
||||
// 新窗口打开:跟随当前渲染方式——
|
||||
// - 网页渲染:把自包含 HTML 大屏(blob)直接在新标签页打开(纯网页,无应用壳);
|
||||
// - 应用渲染:把当前大屏数据写进 localStorage,在新标签页打开应用内 React 大屏
|
||||
// (独立全屏路由 /roundtable-dashboard,无侧边栏)。无真实数据时 standalone 回落 DEMO。
|
||||
const handleOpenNewWindow = () => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
if (renderMode === "html" && html) {
|
||||
const blob = new Blob([html], { type: "text/html;charset=utf-8" });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
window.open(blobUrl, "_blank", "noopener,noreferrer");
|
||||
// 延迟回收,给新标签页足够时间加载(过早 revoke 会打不开)。
|
||||
window.setTimeout(() => URL.revokeObjectURL(blobUrl), 60_000);
|
||||
return;
|
||||
}
|
||||
|
||||
const key = `${DASHBOARD_VIEW_STORAGE_PREFIX}${Date.now()}`;
|
||||
try {
|
||||
// 先清掉历史键,避免 localStorage 堆积;再写本次数据。
|
||||
@@ -135,9 +152,9 @@ export function Step3DashboardMode({
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
};
|
||||
|
||||
const handleRedraw = () => {
|
||||
// 「编辑」:回到交互区接着上次的聊天记录继续问答修改(不丢弃上下文、不重画)。
|
||||
const handleEdit = () => {
|
||||
setView("chat");
|
||||
redraw();
|
||||
};
|
||||
|
||||
const isStreaming =
|
||||
@@ -160,19 +177,57 @@ export function Step3DashboardMode({
|
||||
<div className="flex items-center gap-2">
|
||||
{view === "page" ? (
|
||||
<>
|
||||
{/* 渲染方式切换:应用渲染(React 组件)/ 网页渲染(自包含 HTML iframe) */}
|
||||
<div className="inline-flex items-center gap-0.5 rounded-xl border border-slate-200 bg-slate-50 p-0.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRenderMode("app")}
|
||||
title="用应用内 React 组件渲染(Tab 切换、动画、席位钻取)"
|
||||
className={`flex items-center gap-1 rounded-lg px-2.5 py-1 text-[10.5px] font-bold transition ${
|
||||
renderMode === "app"
|
||||
? "bg-white text-primary shadow-sm border border-slate-200"
|
||||
: "text-slate-500 hover:text-slate-700"
|
||||
}`}
|
||||
>
|
||||
<Boxes className="w-3.5 h-3.5" />
|
||||
应用渲染
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRenderMode("html")}
|
||||
disabled={!html}
|
||||
title={
|
||||
html
|
||||
? "用自包含 HTML 大屏渲染(与新窗口打开 / 分享 / 下载一致)"
|
||||
: "尚无已生成的 HTML 大屏,先完成一次绘制"
|
||||
}
|
||||
className={`flex items-center gap-1 rounded-lg px-2.5 py-1 text-[10.5px] font-bold transition disabled:opacity-40 disabled:cursor-not-allowed ${
|
||||
renderMode === "html"
|
||||
? "bg-white text-primary shadow-sm border border-slate-200"
|
||||
: "text-slate-500 hover:text-slate-700"
|
||||
}`}
|
||||
>
|
||||
<Code2 className="w-3.5 h-3.5" />
|
||||
网页渲染
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleRedraw}
|
||||
title="回到交互区,重新绘制(可在那里通过问答修改大屏)"
|
||||
onClick={handleEdit}
|
||||
title="回到交互区,加载上次聊天记录继续问答修改"
|
||||
className="bg-white border border-slate-200 text-slate-700 hover:bg-slate-50 text-[10.5px] font-bold px-3 py-1.5 rounded-xl flex items-center gap-1 transition"
|
||||
>
|
||||
<RefreshCw className="w-3.5 h-3.5 text-slate-500" />
|
||||
重新绘制
|
||||
<Pencil className="w-3.5 h-3.5 text-slate-500" />
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleOpenNewWindow}
|
||||
title="在新标签页打开 React 大屏页面(全屏,无侧边栏)"
|
||||
title={
|
||||
renderMode === "html"
|
||||
? "在新标签页打开自包含 HTML 大屏(纯网页,可分享 / 下载)"
|
||||
: "在新标签页打开应用内 React 大屏(全屏,无侧边栏)"
|
||||
}
|
||||
className="bg-white border border-slate-200 text-slate-700 hover:bg-slate-50 text-[10.5px] font-bold px-3 py-1.5 rounded-xl flex items-center gap-1 transition"
|
||||
>
|
||||
<ExternalLink className="w-3.5 h-3.5 text-slate-500" />
|
||||
@@ -214,9 +269,18 @@ export function Step3DashboardMode({
|
||||
|
||||
{/* 主体:交互区(聊天 + 抽取沙箱) / 大屏页面(React) */}
|
||||
{view === "page" ? (
|
||||
<div className="flex-1 min-h-0 overflow-auto">
|
||||
<DashboardApp data={dashboardData} />
|
||||
</div>
|
||||
renderMode === "html" && html ? (
|
||||
<iframe
|
||||
title="方案研讨大屏"
|
||||
srcDoc={html}
|
||||
className="flex-1 min-h-0 w-full border-0 bg-white"
|
||||
sandbox="allow-scripts allow-popups allow-downloads allow-same-origin"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex-1 min-h-0 overflow-auto">
|
||||
<DashboardApp data={dashboardData} />
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="flex-1 min-h-0 p-3">
|
||||
<section className="h-full min-h-0 rounded-2xl border border-slate-200/60 bg-white overflow-hidden">
|
||||
@@ -287,6 +351,19 @@ function DashboardChatPanel({
|
||||
const dialogueMetrics = useDialogueSpeedMetrics(dialogues);
|
||||
const [text, setText] = useState("");
|
||||
|
||||
// 消息列表里不展示 report-json 结构化数据本体(它已在右侧沙箱实时渲染),
|
||||
// 把数据块替换成一句「看右侧沙箱」的提示,避免左右两边重复刷整段 JSON。
|
||||
const displayDialogues = useMemo(
|
||||
() =>
|
||||
dialogues.map((d) => {
|
||||
const content = d.content ?? "";
|
||||
if (!content) return d;
|
||||
const stripped = stripReportFence(content, d.streaming === true);
|
||||
return stripped === content ? d : { ...d, content: stripped };
|
||||
}),
|
||||
[dialogues],
|
||||
);
|
||||
|
||||
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const t = text.trim();
|
||||
@@ -368,7 +445,7 @@ function DashboardChatPanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{dialogues.map((msg) => (
|
||||
{displayDialogues.map((msg) => (
|
||||
<Step2Message
|
||||
key={msg.id}
|
||||
msg={msg}
|
||||
|
||||
Reference in new issue
Block a user