refactor(canvas): 预演台 - 重构工作台与场景交互链路

This commit is contained in:
ddcat666
2026-10-06 09:06:43 +08:00
parent 1dfc8ce7ac
commit 112916587c
128 changed files with 42372 additions and 5011 deletions

View File

@@ -2,18 +2,18 @@ import { useCallback, useMemo, useState } from "react";
import { Button, Table } from "antd";
import { Switch } from "@/components/ui/base/switch";
import { CanvasDirectorWorkbench } from "@/components/canvas/director/canvas-director-workbench";
import { DIRECTOR_REPRO_MATRIX, createDirectorReproScene, directorReproSceneIsOffline, injectDirectorReproModel, type DirectorReproModelVariant } from "@/lib/canvas/director/director-repro-fixture";
import { readDirectorReproSnapshot, type DirectorReproSnapshot } from "@/lib/canvas/director/director-repro-runtime";
import { resetDirectorDiagnosticDedupe } from "@/lib/canvas/director/director-diagnostics-recorder";
import { CanvasPrevisWorkbench } from "@/components/canvas/previs/canvas-previs-workbench";
import { PREVIS_REPRO_MATRIX, createPrevisReproScene, previsReproSceneIsOffline, injectPrevisReproModel, type PrevisReproModelVariant } from "@/lib/canvas/previs/previs-repro-fixture";
import { readPrevisReproSnapshot, type PrevisReproSnapshot } from "@/lib/canvas/previs/previs-repro-runtime";
import { resetPrevisDiagnosticDedupe } from "@/lib/canvas/previs/previs-diagnostics-recorder";
import { getClientDiagnosticEvents } from "@/services/diagnostics/client-diagnostics";
import { StatusBadge } from "@/components/ui/base/badges";
import type { DirectorScene } from "@/types/director";
import type { PrevisScene } from "@/types/previs";
/**
* P0 手工复现入口(仅 DEV 注册)。
*
* 渲染真实 CanvasDirectorWorkbench + 确定性 fixture,不是静态 mock。
* 渲染真实 CanvasPrevisWorkbench + 确定性 fixture,不是静态 mock。
* 本页自己的 callback 都是本地确定性实现,不调用任何 API:
* onChange 只写本页 state,onFlush 只在「强制失败」开关打开时抛错,
* onApply 只累加本地计数。
@@ -22,21 +22,21 @@ import type { DirectorScene } from "@/types/director";
* 真实工作台自带的新增控件(默认演员 GLB、上传模型、画布图片立牌)
* 仍可能联网,这是刻意保留的真实行为 —— 不要据此宣称整个工作台永不发网。
*/
export default function DirectorReproLab() {
const [scene, setScene] = useState<DirectorScene>(() => createDirectorReproScene());
export default function PrevisReproLab() {
const [scene, setScene] = useState<PrevisScene>(() => createPrevisReproScene());
// workbench 是 fixed inset-0 全屏浮层,会完全遮住本页。默认关闭,
// 让环境快照 / 事件列表 / 复现矩阵先可读,再由用户显式打开进入复现。
const [workbenchOpen, setWorkbenchOpen] = useState(false);
const [forceSaveFailure, setForceSaveFailure] = useState(false);
const [appliedCount, setAppliedCount] = useState(0);
const [flushCount, setFlushCount] = useState(0);
const [events, setEvents] = useState(() => readDirectorEvents());
const snapshot = useMemo(() => readDirectorReproSnapshot(), []);
const [events, setEvents] = useState(() => readPrevisEvents());
const snapshot = useMemo(() => readPrevisReproSnapshot(), []);
const refreshEvents = useCallback(() => setEvents(readDirectorEvents()), []);
const refreshEvents = useCallback(() => setEvents(readPrevisEvents()), []);
/** 本地确定性保存:只写本页 state,不触碰项目 store,也不发请求。 */
const onChange = useCallback((next: DirectorScene) => setScene(next), []);
const onChange = useCallback((next: PrevisScene) => setScene(next), []);
/** 强制失败开关用于复现「保存失败 / 关闭恢复」,抛错后由 coordinator 走真实 error 语义。 */
const onFlush = useCallback(() => {
@@ -49,8 +49,8 @@ export default function DirectorReproLab() {
}, []);
const reset = useCallback(() => {
resetDirectorDiagnosticDedupe();
setScene(createDirectorReproScene());
resetPrevisDiagnosticDedupe();
setScene(createPrevisReproScene());
setForceSaveFailure(false);
setAppliedCount(0);
setFlushCount(0);
@@ -59,15 +59,15 @@ export default function DirectorReproLab() {
}, [refreshEvents]);
/** 显式注入模型对象:固定 id,重复点击替换同一对象而不是堆叠。 */
const injectModel = useCallback((variant: DirectorReproModelVariant) => {
setScene((current) => injectDirectorReproModel(current, variant));
const injectModel = useCallback((variant: PrevisReproModelVariant) => {
setScene((current) => injectPrevisReproModel(current, variant));
}, []);
return (
<div className="min-h-dvh overflow-y-auto p-5" style={{ background: "var(--bg)", color: "var(--fg)" }}>
<header className="mb-4 flex flex-wrap items-center gap-3">
<h1 className="text-lg font-semibold">导演台 P0 复现台</h1>
<StatusBadge tone={directorReproSceneIsOffline(scene) ? "error" : "success"} data-testid="offline-tag" label={directorReproSceneIsOffline(scene) ? "fixture 无网络资产" : "已注入模型资产"} />
<h1 className="text-lg font-semibold">预演台 P0 复现台</h1>
<StatusBadge tone={previsReproSceneIsOffline(scene) ? "error" : "success"} data-testid="offline-tag" label={previsReproSceneIsOffline(scene) ? "fixture 无网络资产" : "已注入模型资产"} />
<span className="text-[var(--fs-tiny)] opacity-70" data-testid="object-count">
对象数 {scene.objects.length}
</span>
@@ -81,7 +81,7 @@ export default function DirectorReproLab() {
<span className="text-[var(--fs-tiny)] opacity-70">强制保存失败</span>
<Switch checked={forceSaveFailure} onChange={setForceSaveFailure} data-testid="force-save-failure" />
<Button size="small" data-testid="toggle-workbench" onClick={() => setWorkbenchOpen((open) => !open)}>
{workbenchOpen ? "关闭导演台" : "打开导演台"}
{workbenchOpen ? "关闭预演台" : "打开预演台"}
</Button>
<Button size="small" data-testid="refresh-events" onClick={refreshEvents}>
刷新事件
@@ -97,23 +97,23 @@ export default function DirectorReproLab() {
<ReproMatrix />
{workbenchOpen ? (
<CanvasDirectorWorkbench open scene={scene} imageNodes={[]} onboardingScope="director-repro-lab" onClose={() => setWorkbenchOpen(false)} onChange={onChange} onApply={onApply} onDeleteImageNode={() => undefined} onFlush={onFlush} />
<CanvasPrevisWorkbench canvasId="previs-repro-lab" open scene={scene} imageNodes={[]} onboardingScope="previs-repro-lab" onClose={() => setWorkbenchOpen(false)} onChange={onChange} onApply={onApply} onDeleteImageNode={() => undefined} onFlush={onFlush} />
) : null}
</div>
);
}
type DirectorEventRow = { id: string; timestamp: string; level: string; code: string; message: string };
type PrevisEventRow = { id: string; timestamp: string; level: string; code: string; message: string };
/** 只显示导演台自己的稳定码事件,避免整条诊断流噪声淹没复现现场。 */
function readDirectorEvents(): DirectorEventRow[] {
/** 只显示预演台自己的稳定码事件,避免整条诊断流噪声淹没复现现场。 */
function readPrevisEvents(): PrevisEventRow[] {
return getClientDiagnosticEvents()
.filter((event) => (event.code || "").startsWith("DIRECTOR_"))
.filter((event) => (event.code || "").startsWith("PREVIS_"))
.map((event) => ({ id: event.id, timestamp: event.timestamp, level: event.level, code: event.code || "", message: event.message }))
.reverse();
}
function EnvironmentSnapshot({ snapshot, appliedCount, flushCount, sceneRevisionHint }: { snapshot: DirectorReproSnapshot; appliedCount: number; flushCount: number; sceneRevisionHint: string }) {
function EnvironmentSnapshot({ snapshot, appliedCount, flushCount, sceneRevisionHint }: { snapshot: PrevisReproSnapshot; appliedCount: number; flushCount: number; sceneRevisionHint: string }) {
const { runtime, webgl } = snapshot;
const rows: Array<[string, string]> = [
["应用版本", runtime.appVersion],
@@ -158,11 +158,11 @@ function EnvironmentSnapshot({ snapshot, appliedCount, flushCount, sceneRevision
);
}
function DiagnosticEventList({ events }: { events: DirectorEventRow[] }) {
function DiagnosticEventList({ events }: { events: PrevisEventRow[] }) {
return (
<section className="mb-4">
<h2 className="mb-2 text-[var(--fs-label)] font-semibold opacity-75">结构化事件({events.length})</h2>
<p className="mb-2 text-[var(--fs-tiny)] opacity-60">来自统一 client-diagnostics 缓冲区,只筛选 DIRECTOR_ 稳定码;不含 stack、URL 与业务正文。</p>
<p className="mb-2 text-[var(--fs-tiny)] opacity-60">来自统一 client-diagnostics 缓冲区,只筛选 PREVIS_ 稳定码;不含 stack、URL 与业务正文。</p>
{events.length ? (
<Table
size="small"
@@ -178,7 +178,7 @@ function DiagnosticEventList({ events }: { events: DirectorEventRow[] }) {
]}
/>
) : (
<p className="text-[var(--fs-tiny)] opacity-55">暂无导演台事件。触发下方矩阵中的失败场景后点击「刷新事件」。</p>
<p className="text-[var(--fs-tiny)] opacity-55">暂无预演台事件。触发下方矩阵中的失败场景后点击「刷新事件」。</p>
)}
</section>
);
@@ -187,11 +187,11 @@ function DiagnosticEventList({ events }: { events: DirectorEventRow[] }) {
function ReproMatrix() {
return (
<section className="mb-4">
<h2 className="mb-2 text-[var(--fs-label)] font-semibold opacity-75">P0 手工复现矩阵({DIRECTOR_REPRO_MATRIX.length})</h2>
<h2 className="mb-2 text-[var(--fs-label)] font-semibold opacity-75">P0 手工复现矩阵({PREVIS_REPRO_MATRIX.length})</h2>
<Table
size="small"
rowKey="id"
dataSource={[...DIRECTOR_REPRO_MATRIX]}
dataSource={[...PREVIS_REPRO_MATRIX]}
pagination={false}
columns={[
{ title: "分组", dataIndex: "group", width: 108 },