feat: add workflow demo presentation stage

This commit is contained in:
lda
2026-07-05 06:29:52 +07:00 Verified
parent c95d71fe5d
commit a8402a4d1d
19 changed files with 1944 additions and 233 deletions
@@ -0,0 +1,105 @@
import type { DemoEvent } from "../demo/timeline/models.js";
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
import {
graphExecutionForBeat,
projectInterruptContract,
} from "./demo-workflow-model.js";
import { InterruptContractPreview } from "./InterruptContractPreview.js";
import { NodeSpotlight } from "./NodeSpotlight.js";
import { OperationBlock } from "./OperationBlock.js";
import { StageCaption } from "./StageCaption.js";
import type { SceneBeatDefinition, SceneDefinition } from "./storyboard.js";
import { WorkflowGraphStage } from "./WorkflowGraphStage.js";
type DemoWorkflowSceneProps = {
readonly scene: SceneDefinition;
readonly beat: SceneBeatDefinition;
readonly demo: DemoTimelineController;
readonly selectedNodeId: string | null;
readonly selectNode: (nodeId: string) => void;
readonly openEvidence: () => void;
};
const operationStageByBeat: Readonly<Record<string, DemoEvent["stage"] | undefined>> = {
operation: "run_start",
resume: "run_resume",
trace: "trace_read",
};
const findEvent = (
demo: DemoTimelineController,
stage: DemoEvent["stage"],
): DemoEvent | null => demo.state.events.find((event) => event.stage === stage) ?? null;
export const DemoWorkflowScene = ({
scene,
beat,
demo,
selectedNodeId,
selectNode,
openEvidence,
}: DemoWorkflowSceneProps) => {
const runStart = findEvent(demo, "run_start");
const currentStage = operationStageByBeat[beat.id];
const currentEvent = currentStage ? findEvent(demo, currentStage) : null;
const contract = runStart ? projectInterruptContract(runStart) : null;
const execution = graphExecutionForBeat(beat.id);
const showExpandedOperation = beat.id === "operation" || beat.id === "resume" || beat.id === "trace";
const showGraph = beat.id === "graph" || beat.id === "interrupt" || beat.id === "approval" || beat.id === "output";
const showReceipt = showGraph;
const contractMode = beat.id === "approval"
? "approval"
: beat.id === "interrupt"
? "preview"
: null;
return (
<>
<StageCaption eyebrow="Live system walkthrough" title={scene.title}>
<p>{beat.caption}</p>
</StageCaption>
<div className="demo-workflow-stage" data-beat={beat.id}>
{showExpandedOperation && currentEvent && (
<OperationBlock
event={currentEvent}
variant="expanded"
openEvidence={openEvidence}
/>
)}
{showReceipt && runStart && (
<OperationBlock
event={runStart}
variant="receipt"
openEvidence={openEvidence}
/>
)}
{showGraph && (
<div className="demo-workflow-stage__graph">
<WorkflowGraphStage
execution={execution}
selectedNodeId={selectedNodeId}
selectNode={selectNode}
/>
{contractMode && contract && (
<InterruptContractPreview contract={contract} mode={contractMode} />
)}
</div>
)}
{showExpandedOperation && !currentEvent && (
<div className="demo-workflow-stage__pending" role="status">
Replay operation is not available yet.
</div>
)}
</div>
{selectedNodeId && (
<NodeSpotlight nodeId={selectedNodeId} close={() => selectNode("")} />
)}
</>
);
};