refactor(canvas): 预演台 - 重构工作台与场景交互链路
This commit is contained in:
@@ -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 },
|
||||
Reference in New Issue
Block a user