|
|
@@ -0,0 +1,363 @@
|
|
|
+import { schema } from "@json-render/svelte/schema";
|
|
|
+import { z } from "zod";
|
|
|
+
|
|
|
+/**
|
|
|
+ * json-render + AI SDK Example Catalog (Svelte)
|
|
|
+ *
|
|
|
+ * Components for rendering data dashboards generated by the ToolLoopAgent.
|
|
|
+ * Data flows in through tools (weather, GitHub, crypto, HN), not user actions.
|
|
|
+ */
|
|
|
+export const explorerCatalog = schema.createCatalog({
|
|
|
+ components: {
|
|
|
+ // Layout
|
|
|
+ Stack: {
|
|
|
+ props: z.object({
|
|
|
+ direction: z.enum(["horizontal", "vertical"]).nullable(),
|
|
|
+ gap: z.enum(["sm", "md", "lg"]).nullable(),
|
|
|
+ wrap: z.boolean().nullable(),
|
|
|
+ }),
|
|
|
+ slots: ["default"],
|
|
|
+ description: "Flex layout container",
|
|
|
+ example: { direction: "vertical", gap: "md", wrap: null },
|
|
|
+ },
|
|
|
+
|
|
|
+ Card: {
|
|
|
+ props: z.object({
|
|
|
+ title: z.string().nullable(),
|
|
|
+ description: z.string().nullable(),
|
|
|
+ }),
|
|
|
+ slots: ["default"],
|
|
|
+ description: "Card container with optional title and description",
|
|
|
+ example: { title: "Weather", description: "Current conditions" },
|
|
|
+ },
|
|
|
+
|
|
|
+ Grid: {
|
|
|
+ props: z.object({
|
|
|
+ columns: z.enum(["1", "2", "3", "4"]).nullable(),
|
|
|
+ gap: z.enum(["sm", "md", "lg"]).nullable(),
|
|
|
+ }),
|
|
|
+ slots: ["default"],
|
|
|
+ description: "Responsive grid layout container",
|
|
|
+ example: { columns: "3", gap: "md" },
|
|
|
+ },
|
|
|
+
|
|
|
+ // Typography
|
|
|
+ Heading: {
|
|
|
+ props: z.object({
|
|
|
+ text: z.string(),
|
|
|
+ level: z.enum(["h1", "h2", "h3", "h4"]).nullable(),
|
|
|
+ }),
|
|
|
+ description: "Section heading",
|
|
|
+ example: { text: "Data Explorer", level: "h1" },
|
|
|
+ },
|
|
|
+
|
|
|
+ Text: {
|
|
|
+ props: z.object({
|
|
|
+ content: z.string(),
|
|
|
+ muted: z.boolean().nullable(),
|
|
|
+ }),
|
|
|
+ description: "Text content",
|
|
|
+ example: { content: "Here is your data overview." },
|
|
|
+ },
|
|
|
+
|
|
|
+ // Data display
|
|
|
+ Badge: {
|
|
|
+ props: z.object({
|
|
|
+ text: z.string(),
|
|
|
+ variant: z
|
|
|
+ .enum(["default", "secondary", "destructive", "outline"])
|
|
|
+ .nullable(),
|
|
|
+ }),
|
|
|
+ description: "Status badge",
|
|
|
+ example: { text: "Live", variant: "default" },
|
|
|
+ },
|
|
|
+
|
|
|
+ Alert: {
|
|
|
+ props: z.object({
|
|
|
+ variant: z.enum(["default", "destructive"]).nullable(),
|
|
|
+ title: z.string(),
|
|
|
+ description: z.string().nullable(),
|
|
|
+ }),
|
|
|
+ description: "Alert or info message",
|
|
|
+ },
|
|
|
+
|
|
|
+ Separator: {
|
|
|
+ props: z.object({}),
|
|
|
+ description: "Visual divider",
|
|
|
+ },
|
|
|
+
|
|
|
+ Metric: {
|
|
|
+ props: z.object({
|
|
|
+ label: z.string(),
|
|
|
+ value: z.string(),
|
|
|
+ detail: z.string().nullable(),
|
|
|
+ trend: z.enum(["up", "down", "neutral"]).nullable(),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ "Single metric display with label, value, and optional trend indicator",
|
|
|
+ example: {
|
|
|
+ label: "Temperature",
|
|
|
+ value: "72F",
|
|
|
+ detail: "Feels like 68F",
|
|
|
+ trend: "up",
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ Table: {
|
|
|
+ props: z.object({
|
|
|
+ data: z.array(z.record(z.string(), z.unknown())),
|
|
|
+ columns: z.array(
|
|
|
+ z.object({
|
|
|
+ key: z.string(),
|
|
|
+ label: z.string(),
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ emptyMessage: z.string().nullable(),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ 'Data table. Use { "$state": "/path" } to bind read-only data from state.',
|
|
|
+ example: {
|
|
|
+ data: { $state: "/stories" },
|
|
|
+ columns: [
|
|
|
+ { key: "title", label: "Title" },
|
|
|
+ { key: "score", label: "Score" },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ Link: {
|
|
|
+ props: z.object({
|
|
|
+ text: z.string(),
|
|
|
+ href: z.string(),
|
|
|
+ }),
|
|
|
+ description: "External link that opens in a new tab",
|
|
|
+ example: { text: "View on GitHub", href: "https://github.com" },
|
|
|
+ },
|
|
|
+
|
|
|
+ // Charts
|
|
|
+ BarChart: {
|
|
|
+ props: z.object({
|
|
|
+ title: z.string().nullable(),
|
|
|
+ data: z.array(z.record(z.string(), z.unknown())),
|
|
|
+ xKey: z.string(),
|
|
|
+ yKey: z.string(),
|
|
|
+ aggregate: z.enum(["sum", "count", "avg"]).nullable(),
|
|
|
+ color: z.string().nullable(),
|
|
|
+ height: z.number().nullable(),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ 'Bar chart visualization. Use { "$state": "/path" } to bind read-only data. xKey is the category field, yKey is the numeric value field.',
|
|
|
+ },
|
|
|
+
|
|
|
+ LineChart: {
|
|
|
+ props: z.object({
|
|
|
+ title: z.string().nullable(),
|
|
|
+ data: z.array(z.record(z.string(), z.unknown())),
|
|
|
+ xKey: z.string(),
|
|
|
+ yKey: z.string(),
|
|
|
+ aggregate: z.enum(["sum", "count", "avg"]).nullable(),
|
|
|
+ color: z.string().nullable(),
|
|
|
+ height: z.number().nullable(),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ 'Line chart visualization. Use { "$state": "/path" } to bind read-only data. xKey is the x-axis field, yKey is the numeric value field.',
|
|
|
+ },
|
|
|
+
|
|
|
+ // Interactive
|
|
|
+ Tabs: {
|
|
|
+ props: z.object({
|
|
|
+ defaultValue: z.string().nullable(),
|
|
|
+ tabs: z.array(
|
|
|
+ z.object({
|
|
|
+ value: z.string(),
|
|
|
+ label: z.string(),
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ }),
|
|
|
+ slots: ["default"],
|
|
|
+ description: "Tabbed content container",
|
|
|
+ },
|
|
|
+
|
|
|
+ TabContent: {
|
|
|
+ props: z.object({
|
|
|
+ value: z.string(),
|
|
|
+ }),
|
|
|
+ slots: ["default"],
|
|
|
+ description: "Content for a specific tab",
|
|
|
+ },
|
|
|
+
|
|
|
+ Progress: {
|
|
|
+ props: z.object({
|
|
|
+ value: z.number(),
|
|
|
+ max: z.number().nullable(),
|
|
|
+ }),
|
|
|
+ description: "Progress bar",
|
|
|
+ },
|
|
|
+
|
|
|
+ Skeleton: {
|
|
|
+ props: z.object({
|
|
|
+ width: z.string().nullable(),
|
|
|
+ height: z.string().nullable(),
|
|
|
+ }),
|
|
|
+ description: "Loading placeholder",
|
|
|
+ },
|
|
|
+
|
|
|
+ // Educational / Rich content
|
|
|
+ Callout: {
|
|
|
+ props: z.object({
|
|
|
+ type: z.enum(["info", "tip", "warning", "important"]).nullable(),
|
|
|
+ title: z.string().nullable(),
|
|
|
+ content: z.string(),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ "Highlighted callout box for tips, warnings, notes, or key information",
|
|
|
+ example: {
|
|
|
+ type: "tip",
|
|
|
+ title: "Did you know?",
|
|
|
+ content: "The sun is about 93 million miles from Earth.",
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ Accordion: {
|
|
|
+ props: z.object({
|
|
|
+ items: z.array(
|
|
|
+ z.object({
|
|
|
+ title: z.string(),
|
|
|
+ content: z.string(),
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ type: z.enum(["single", "multiple"]).nullable(),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ "Collapsible accordion sections for organizing detailed content",
|
|
|
+ example: {
|
|
|
+ items: [{ title: "Overview", content: "A brief introduction." }],
|
|
|
+ type: "multiple",
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ Timeline: {
|
|
|
+ props: z.object({
|
|
|
+ items: z.array(
|
|
|
+ z.object({
|
|
|
+ title: z.string(),
|
|
|
+ description: z.string().nullable(),
|
|
|
+ date: z.string().nullable(),
|
|
|
+ status: z.enum(["completed", "current", "upcoming"]).nullable(),
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ "Vertical timeline showing ordered events, steps, or historical milestones",
|
|
|
+ example: {
|
|
|
+ items: [
|
|
|
+ {
|
|
|
+ title: "Discovery",
|
|
|
+ description: "Initial breakthrough",
|
|
|
+ date: "1905",
|
|
|
+ status: "completed",
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ PieChart: {
|
|
|
+ props: z.object({
|
|
|
+ title: z.string().nullable(),
|
|
|
+ data: z.array(z.record(z.string(), z.unknown())),
|
|
|
+ nameKey: z.string(),
|
|
|
+ valueKey: z.string(),
|
|
|
+ height: z.number().nullable(),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ 'Pie/donut chart for proportional data. Use { "$state": "/path" } to bind read-only data. nameKey is the label field, valueKey is the numeric value field.',
|
|
|
+ },
|
|
|
+
|
|
|
+ // Interactive / Input
|
|
|
+ RadioGroup: {
|
|
|
+ props: z.object({
|
|
|
+ label: z.string().nullable(),
|
|
|
+ value: z.string().nullable(),
|
|
|
+ options: z.array(
|
|
|
+ z.object({
|
|
|
+ value: z.string(),
|
|
|
+ label: z.string(),
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ 'Radio button group for single selection. Use { "$bindState": "/path" } for two-way binding.',
|
|
|
+ example: {
|
|
|
+ label: "Choose one",
|
|
|
+ value: { $bindState: "/answer" },
|
|
|
+ options: [
|
|
|
+ { value: "a", label: "Option A" },
|
|
|
+ { value: "b", label: "Option B" },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ SelectInput: {
|
|
|
+ props: z.object({
|
|
|
+ label: z.string().nullable(),
|
|
|
+ value: z.string().nullable(),
|
|
|
+ placeholder: z.string().nullable(),
|
|
|
+ options: z.array(
|
|
|
+ z.object({
|
|
|
+ value: z.string(),
|
|
|
+ label: z.string(),
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ 'Dropdown select input. Use { "$bindState": "/path" } for two-way binding.',
|
|
|
+ example: {
|
|
|
+ label: "Country",
|
|
|
+ value: { $bindState: "/selectedCountry" },
|
|
|
+ placeholder: "Select a country",
|
|
|
+ options: [
|
|
|
+ { value: "us", label: "United States" },
|
|
|
+ { value: "uk", label: "United Kingdom" },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ TextInput: {
|
|
|
+ props: z.object({
|
|
|
+ label: z.string().nullable(),
|
|
|
+ value: z.string().nullable(),
|
|
|
+ placeholder: z.string().nullable(),
|
|
|
+ type: z.enum(["text", "email", "number", "password", "url"]).nullable(),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ 'Text input field. Use { "$bindState": "/path" } for two-way binding.',
|
|
|
+ example: {
|
|
|
+ label: "Your name",
|
|
|
+ value: { $bindState: "/userName" },
|
|
|
+ placeholder: "Enter your name",
|
|
|
+ type: "text",
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ Button: {
|
|
|
+ props: z.object({
|
|
|
+ label: z.string(),
|
|
|
+ variant: z
|
|
|
+ .enum(["default", "secondary", "destructive", "outline", "ghost"])
|
|
|
+ .nullable(),
|
|
|
+ size: z.enum(["default", "sm", "lg"]).nullable(),
|
|
|
+ disabled: z.boolean().nullable(),
|
|
|
+ }),
|
|
|
+ description:
|
|
|
+ "Clickable button. Use with on.press to trigger actions like setState.",
|
|
|
+ example: {
|
|
|
+ label: "Submit",
|
|
|
+ variant: "default",
|
|
|
+ size: "default",
|
|
|
+ disabled: null,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+
|
|
|
+ actions: {},
|
|
|
+});
|