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; } // "recap" -> "Recap", "chiarimenti_risolti" -> "Chiarimenti risolti" function humanizeKey(k: string): string { const spaced = k.replace(/[_-]+/g, " ").trim(); return spaced.charAt(0).toUpperCase() + spaced.slice(1); } /** Renders any artifact payload as readable text — NEVER raw JSON. Objects become * labelled sections, arrays become bullet lists, primitives become paragraphs; * strings render as markdown. Nested structures recurse. `depth` drives the * visual hierarchy: top-level sections are split by hairline rules, nested fields * sit tighter, and object-arrays become grouped cards. */ function StructuredValue({ value, depth = 0 }: { value: unknown; depth?: number }): ReactElement { if (value === null || value === undefined || value === "") { return

—

; } if (typeof value === "string") return ; if (typeof value === "number" || typeof value === "boolean") { return

{String(value)}

; } if (Array.isArray(value)) { const allPrimitive = value.every((v) => v === null || typeof v !== "object"); if (allPrimitive) { return (
    {value.map((item, i) =>
  • {String(item)}
  • )}
); } return (
{value.map((item, i) => (
))}
); } const entries = Object.entries(value as Record); return (
{entries.map(([k, v], i) => (

{humanizeKey(k)}

))}
); } 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 ; } // Fallback for any other kind / shape: formatted text, never raw JSON. return ; }