feat(frontend): v2 artifact viewers for cte_plan/cte_result/phase
Add CtePlanViewer, CteResultViewer, and PhaseSummaryViewer to render the structured v2 payloads (schema_version: 2) the harness now emits for artifact-gate widgets, per contracts.md. Extract PreviewGrid from ResultsPanel as a reusable AG Grid component shared by CteResultViewer. ArtifactView dispatches to the new viewers on a schema_version/shape guard, falling back to the existing legacy renderers unchanged for older sessions and replay fixtures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,10 @@ import type { ReactElement } from "react";
|
||||
import { SqlViewer, type SqlBlock } from "./SqlViewer";
|
||||
import { SchemaLinkingViewer, type SchemaLinking } from "./SchemaLinkingViewer";
|
||||
import { MarkdownView } from "./MarkdownView";
|
||||
import { CtePlanViewer } from "./CtePlanViewer";
|
||||
import { CteResultViewer } from "./CteResultViewer";
|
||||
import { PhaseSummaryViewer } from "./PhaseSummaryViewer";
|
||||
import type { CtePlanV2, CteResultV2, PhaseSummaryV2 } from "./artifactV2";
|
||||
|
||||
type ArtifactData = { kind: string; data?: unknown; content?: unknown; [k: string]: unknown };
|
||||
|
||||
@@ -60,6 +64,26 @@ function toMarkdown(raw: unknown): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function toCtePlanV2(raw: unknown): CtePlanV2 | null {
|
||||
const rec = asRecord(raw);
|
||||
if (!rec || rec.schema_version !== 2 || !Array.isArray(rec.ctes)) return null;
|
||||
return rec as unknown as CtePlanV2;
|
||||
}
|
||||
|
||||
function toCteResultV2(raw: unknown): CteResultV2 | null {
|
||||
const rec = asRecord(raw);
|
||||
if (!rec || rec.schema_version !== 2 || typeof rec.sql !== "string" || typeof rec.name !== "string") {
|
||||
return null;
|
||||
}
|
||||
return rec as unknown as CteResultV2;
|
||||
}
|
||||
|
||||
function toPhaseSummaryV2(raw: unknown): PhaseSummaryV2 | null {
|
||||
const rec = asRecord(raw);
|
||||
if (!rec || rec.schema_version !== 2) return null;
|
||||
return rec as unknown as PhaseSummaryV2;
|
||||
}
|
||||
|
||||
function cteNames(raw: unknown): string[] | null {
|
||||
if (Array.isArray(raw) && raw.every((x) => typeof x === "string")) return raw as string[];
|
||||
const rec = asRecord(raw);
|
||||
@@ -140,11 +164,17 @@ export function ArtifactView({ artifact }: { artifact: ArtifactData }): ReactEle
|
||||
const linking = toSchemaLinking(data);
|
||||
if (linking) return <SchemaLinkingViewer linking={linking} />;
|
||||
}
|
||||
if (kind === "cte_result") {
|
||||
const resultV2 = toCteResultV2(data);
|
||||
if (resultV2) return <CteResultViewer result={resultV2} />;
|
||||
}
|
||||
if (kind === "sql" || kind === "cte_result") {
|
||||
const blocks = toSqlBlocks(data);
|
||||
if (blocks) return <SqlViewer blocks={blocks} />;
|
||||
}
|
||||
if (kind === "cte_plan") {
|
||||
const planV2 = toCtePlanV2(data);
|
||||
if (planV2) return <CtePlanViewer plan={planV2} />;
|
||||
const names = cteNames(data);
|
||||
if (names) {
|
||||
return (
|
||||
@@ -156,6 +186,10 @@ export function ArtifactView({ artifact }: { artifact: ArtifactData }): ReactEle
|
||||
);
|
||||
}
|
||||
}
|
||||
if (kind === "phase") {
|
||||
const phaseV2 = toPhaseSummaryV2(data);
|
||||
if (phaseV2) return <PhaseSummaryViewer phase={phaseV2} />;
|
||||
}
|
||||
if (kind === "question" || kind === "phase") {
|
||||
const md = toMarkdown(data);
|
||||
if (md !== null) return <MarkdownView source={md} />;
|
||||
|
||||
Reference in New Issue
Block a user