import type { ReactElement } from "react"; import { SqlViewer, type SqlBlock } from "./SqlViewer"; import { SchemaLinkingViewer, type SchemaLinking } from "./SchemaLinkingViewer"; import { MarkdownView } from "./MarkdownView"; type ArtifactData = { kind: string; data?: unknown; content?: unknown; [k: string]: unknown }; function asRecord(v: unknown): Record | null { return v && typeof v === "object" && !Array.isArray(v) ? (v as Record) : null; } // The payload lives in `data` (harness contract); fall back to `content`, then the // artifact object itself, so older/other producers still render something. function payload(artifact: ArtifactData): unknown { if (artifact.data !== undefined) return artifact.data; if (artifact.content !== undefined) return artifact.content; return artifact; } function toSchemaLinking(raw: unknown): SchemaLinking | null { let obj: unknown = raw; if (typeof raw === "string") { try { obj = JSON.parse(raw); } catch { return null; } } const rec = asRecord(obj); if (!rec || !Array.isArray(rec.candidates)) return null; return { candidates: rec.candidates as SchemaLinking["candidates"], joins: Array.isArray(rec.joins) ? (rec.joins as SchemaLinking["joins"]) : [], excluded: Array.isArray(rec.excluded) ? (rec.excluded as SchemaLinking["excluded"]) : [], open_questions: Array.isArray(rec.open_questions) ? (rec.open_questions as string[]) : [], question: typeof rec.question === "string" ? rec.question : undefined, }; } function toSqlBlocks(raw: unknown): SqlBlock[] | null { if (typeof raw === "string") return [{ name: "SQL", sql: raw }]; const rec = asRecord(raw); if (!rec) return null; if (typeof rec.sql === "string") return [{ name: "SQL", sql: rec.sql }]; if (typeof rec.content === "string") return [{ name: "SQL", sql: rec.content }]; if (Array.isArray(rec.ctes)) { const blocks = rec.ctes .map((c) => asRecord(c)) .filter((c): c is Record => !!c && typeof c.sql === "string") .map((c) => ({ name: typeof c.name === "string" ? c.name : "cte", sql: c.sql as string })); if (typeof rec.final === "string") blocks.push({ name: "final", sql: rec.final }); return blocks.length ? blocks : null; } return null; } function toMarkdown(raw: unknown): string | null { if (typeof raw === "string") return raw; const rec = asRecord(raw); if (!rec) return null; for (const k of ["markdown", "question", "text"]) { if (typeof rec[k] === "string") return rec[k] as string; } return null; } function cteNames(raw: unknown): string[] | null { if (Array.isArray(raw) && raw.every((x) => typeof x === "string")) return raw as string[]; const rec = asRecord(raw); if (rec && Array.isArray(rec.names) && rec.names.every((x) => typeof x === "string")) { return rec.names as string[]; } return null; } function JsonFallback({ value }: { value: unknown }): ReactElement { const text = typeof value === "string" ? value : JSON.stringify(value, null, 2); return (
{text}
); } export function ArtifactView({ artifact }: { artifact: ArtifactData }): ReactElement { const kind = artifact.kind ?? ""; const data = payload(artifact); if (kind === "schema_linking") { const linking = toSchemaLinking(data); if (linking) return ; } if (kind === "sql" || kind === "cte_result") { const blocks = toSqlBlocks(data); if (blocks) return ; } if (kind === "cte_plan") { const names = cteNames(data); if (names) { return (
    {names.map((n, i) => (
  1. {n}
  2. ))}
); } } if (kind === "question" || kind === "phase") { const md = toMarkdown(data); if (md !== null) return ; } return ; }