128 lines
8.6 KiB
TypeScript
128 lines
8.6 KiB
TypeScript
import { createElement as el, type ReactNode } from "react";
|
|
import { defineComponent, createLibrary } from "@openuidev/react-lang";
|
|
import { z } from "zod/v4";
|
|
|
|
// No JSX in this file on purpose: `node --test` imports it, and Node strips types but not JSX.
|
|
// el(type, props, ...children) is createElement with the noise removed.
|
|
const p = (style: Record<string, string>, ...kids: ReactNode[]) => el("p", { style }, ...kids);
|
|
const muted = { color: "var(--qai-ink-muted)" } as const;
|
|
const small = { fontSize: "var(--qai-text-xs)", color: "var(--qai-ink-muted)" } as const;
|
|
const disp = { fontFamily: "var(--qai-font-display)" } as const;
|
|
|
|
const Headline = defineComponent({ name: "Headline",
|
|
description: "Report title: the one-line finding, with severity.",
|
|
props: z.object({ title: z.string(), subtitle: z.string().optional(),
|
|
severity: z.enum(["critical", "warning", "info"]).optional() }),
|
|
component: ({ props }) => el("header", null,
|
|
el("p", { className: "label" }, props.severity === "critical" ? "Critical" : props.severity === "warning" ? "Warning" : "Note"),
|
|
el("h2", { style: { ...disp, fontSize: "var(--qai-text-2xl)", margin: "var(--qai-space-s) 0" } }, props.title),
|
|
props.subtitle ? p(muted, props.subtitle) : null) });
|
|
|
|
const Metric = defineComponent({ name: "Metric", description: "One plan metric.",
|
|
props: z.object({ label: z.string(), value: z.string(), note: z.string().optional() }),
|
|
component: ({ props }) => el("div", { className: "card" },
|
|
el("p", { className: "label" }, props.label),
|
|
p({ ...disp, fontSize: "var(--qai-text-xl)" }, props.value),
|
|
props.note ? p(small, props.note) : null) });
|
|
|
|
const MetricGrid = defineComponent({ name: "MetricGrid", description: "Row of plan metrics.",
|
|
props: z.object({ items: z.array(Metric.ref) }),
|
|
component: ({ props, renderNode }) => el("div", { className: "grid-metrics" }, renderNode(props.items)) });
|
|
|
|
const Finding = defineComponent({ name: "Finding", description: "One problem, the plan evidence for it, its impact.",
|
|
props: z.object({ where: z.string(), issue: z.string(), evidence: z.string(), impact: z.string() }),
|
|
component: ({ props }) => el("div", { className: "card" },
|
|
el("p", { className: "label" }, props.where),
|
|
p({ margin: "var(--qai-space-s) 0" }, el("strong", null, props.issue)),
|
|
el("p", { className: "sql" }, props.evidence),
|
|
p(muted, props.impact)) });
|
|
|
|
const Findings = defineComponent({ name: "Findings", description: "Everything wrong with the plan.",
|
|
props: z.object({ items: z.array(Finding.ref) }),
|
|
component: ({ props, renderNode }) => el("section", { className: "report" }, renderNode(props.items)) });
|
|
|
|
const Step = defineComponent({ name: "Step", description: "One action step.",
|
|
props: z.object({ text: z.string(), effort: z.enum(["low", "medium", "high"]).optional(),
|
|
owner: z.enum(["dba", "developer", "both"]).optional(), changeControl: z.boolean().optional() }),
|
|
component: ({ props }) => el("li", { className: "card", style: { display: "flex", gap: "var(--qai-space-md)",
|
|
alignItems: "center", flexWrap: "wrap" } },
|
|
el("span", { style: { flex: "1 1 200px", minWidth: 0 } }, props.text),
|
|
props.effort ? el("span", { className: "pill" }, `effort ${props.effort}`) : null,
|
|
props.owner ? el("span", { className: "pill" }, props.owner) : null,
|
|
props.changeControl ? el("span", { className: "pill" }, "change control") : null) });
|
|
|
|
const StepList = defineComponent({ name: "StepList", description: "Ordered action plan for one option.",
|
|
props: z.object({ title: z.string(), steps: z.array(Step.ref) }),
|
|
component: ({ props, renderNode }) => el("section", null,
|
|
el("h3", { style: disp }, props.title),
|
|
el("ol", { style: { listStyle: "none", padding: 0, margin: 0, display: "grid", gap: "var(--qai-space-md)" } },
|
|
renderNode(props.steps))) });
|
|
|
|
const SqlBlock = defineComponent({ name: "SqlBlock", description: "A SQL snippet the user can copy.",
|
|
props: z.object({ title: z.string(), sql: z.string(), caption: z.string().optional() }),
|
|
component: ({ props }) => el("figure", { className: "card", style: { margin: 0 } },
|
|
el("figcaption", { className: "label" }, props.title),
|
|
el("pre", { className: "sql" }, props.sql),
|
|
el("button", { className: "btn no-print", onClick: () => navigator.clipboard.writeText(props.sql) }, "Copy SQL"),
|
|
props.caption ? el("p", { style: small }, props.caption) : null) });
|
|
|
|
const ScoreBar = defineComponent({ name: "ScoreBar", description: "One Jev dimension: normalised value plus confidence.",
|
|
props: z.object({ label: z.string(), value: z.number(), confidence: z.number().optional(), note: z.string().optional() }),
|
|
component: ({ props }) => el("div", null,
|
|
el("p", { className: "label" }, props.label + (props.confidence !== undefined
|
|
? ` · confidence ${props.confidence.toFixed(2)}` : " · no confidence reported")),
|
|
el("div", { className: "bar", style: { width: `${Math.round(props.value * 100)}%` } }),
|
|
props.note ? el("p", { style: small }, props.note) : null) });
|
|
|
|
const OptionRow = defineComponent({ name: "OptionRow", description: "One ranked option.",
|
|
props: z.object({ rank: z.number(), title: z.string(), composite: z.number(), headline: z.boolean(),
|
|
flags: z.string().optional() }),
|
|
component: ({ props }) => el("tr", { "data-headline": props.headline || undefined },
|
|
el("td", null, String(props.rank)), el("td", null, props.title),
|
|
el("td", { className: "sql" }, props.composite.toFixed(2)),
|
|
el("td", null, props.flags || "—")) });
|
|
|
|
const OptionTable = defineComponent({ name: "OptionTable", description: "All options ranked by composite score.",
|
|
props: z.object({ title: z.string(), weights: z.string(), rows: z.array(OptionRow.ref) }),
|
|
component: ({ props, renderNode }) => el("section", { className: "card" },
|
|
el("h3", { style: disp }, props.title),
|
|
el("p", { style: small }, props.weights),
|
|
el("table", { style: { width: "100%", borderCollapse: "collapse" } }, el("tbody", null, renderNode(props.rows)))) });
|
|
|
|
const VerdictBlock = defineComponent({ name: "VerdictBlock", description: "What Jev decided, and how sure it was.",
|
|
props: z.object({ headline: z.string(), why: z.string(), source: z.enum(["jev", "composite"]), confidence: z.number() }),
|
|
component: ({ props }) => el("section", { className: "card", "data-verdict": "" },
|
|
el("p", { className: "label" }, "First move · " + (props.source === "jev"
|
|
? `Jev, confidence ${props.confidence.toFixed(2)}` : "composite order — Jev did not decide")),
|
|
p({ ...disp, fontSize: "var(--qai-text-lg)" }, props.headline),
|
|
p({}, props.why)) });
|
|
|
|
const Callout = defineComponent({ name: "Callout", description: "A warning or note the user must not miss.",
|
|
props: z.object({ level: z.enum(["info", "warn", "danger", "success"]), title: z.string(), text: z.string() }),
|
|
component: ({ props }) => el("div", { className: "card", "data-level": props.level,
|
|
role: props.level === "danger" ? "alert" : "note" },
|
|
el("p", null, el("strong", null, props.title)), el("p", null, props.text)) });
|
|
|
|
const Report = defineComponent({ name: "Report", description: "Root. A query-plan report in reading order.",
|
|
props: z.object({ blocks: z.array(z.union([Headline.ref, VerdictBlock.ref, MetricGrid.ref, Findings.ref,
|
|
OptionTable.ref, StepList.ref, SqlBlock.ref, ScoreBar.ref, Callout.ref])) }),
|
|
component: ({ props, renderNode }) => el("div", { className: "report", id: "report" }, renderNode(props.blocks)) });
|
|
|
|
export const library = createLibrary({ root: "Report", components: [Headline, Metric, MetricGrid, Finding, Findings,
|
|
Step, StepList, SqlBlock, ScoreBar, OptionRow, OptionTable, VerdictBlock, Callout, Report] });
|
|
|
|
export const reportPromptOptions = {
|
|
preamble: "You output only OpenUI Lang. No prose, no markdown fences, no commentary before or after.",
|
|
additionalRules: [
|
|
"root = Report([...]) and it must list every block you define.",
|
|
"Block order: Headline, VerdictBlock, MetricGrid, Findings, OptionTable, then per option its StepList and SqlBlock, then ScoreBars, then Callout.",
|
|
"Use ONLY numbers present in the input. Never invent a cost, row count, impact, or confidence.",
|
|
"One SqlBlock per rewrite, titled with that option's key. T-SQL only, no markdown.",
|
|
"Every option flagged verify_semantics gets a Callout level danger: compare result rows before deploying.",
|
|
"If verdict.source is composite, say plainly that Jev did not decide.",
|
|
],
|
|
examples: [`root = Report([head, verdict])
|
|
head = Headline("Clustered Index Scan on Sales.Orders is 84% of the plan", "No usable key on CustomerId", "critical")
|
|
verdict = VerdictBlock("Create the covering index", "bottleneck_fit 1.00, semantic_safety 1.00, ease 0.50.", "jev", 0.82)`],
|
|
};
|