| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- import { defineCatalog } from "@json-render/core";
- import { schema } from "@json-render/react/schema";
- import { shadcnComponentDefinitions } from "@json-render/shadcn/catalog";
- import { z } from "zod";
- // =============================================================================
- // Shared 3D schemas
- // =============================================================================
- const vec3 = z.array(z.number());
- const animation3D = z
- .object({
- rotate: vec3.nullable(),
- })
- .nullable();
- const transform3DProps = {
- position: vec3.nullable(),
- rotation: vec3.nullable(),
- scale: vec3.nullable(),
- };
- const material3DProps = {
- color: z.string().nullable(),
- metalness: z.number().nullable(),
- roughness: z.number().nullable(),
- emissive: z.string().nullable(),
- emissiveIntensity: z.number().nullable(),
- wireframe: z.boolean().nullable(),
- opacity: z.number().nullable(),
- };
- const mesh3DProps = {
- ...transform3DProps,
- ...material3DProps,
- args: z.array(z.number()).nullable(),
- animation: animation3D,
- };
- /**
- * json-render + AI SDK Example Catalog
- *
- * Components for rendering data dashboards generated by the ToolLoopAgent.
- * Data flows in through tools (weather, GitHub, crypto, HN), not user actions.
- */
- export const explorerCatalog = defineCatalog(schema, {
- components: {
- // From @json-render/shadcn (used as-is)
- Stack: shadcnComponentDefinitions.Stack,
- Card: shadcnComponentDefinitions.Card,
- Grid: shadcnComponentDefinitions.Grid,
- Heading: shadcnComponentDefinitions.Heading,
- Separator: shadcnComponentDefinitions.Separator,
- Accordion: shadcnComponentDefinitions.Accordion,
- Progress: shadcnComponentDefinitions.Progress,
- Skeleton: shadcnComponentDefinitions.Skeleton,
- Badge: shadcnComponentDefinitions.Badge,
- Alert: shadcnComponentDefinitions.Alert,
- // Chat-specific components (different schemas or fully custom)
- Text: {
- props: z.object({
- content: z.string(),
- muted: z.boolean().nullable(),
- }),
- description: "Text content",
- example: { content: "Here is your data overview." },
- },
- 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",
- },
- // 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.",
- },
- },
- 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. Use for multiple-choice questions, settings, or any single-select input.',
- 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. Use when there are many options and a dropdown is more compact than radio buttons.',
- 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. Use for free-text entry like names, emails, search, etc.',
- 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, pushState, etc. Can be used for quiz submissions, form actions, navigation, and more.",
- example: {
- label: "Submit",
- variant: "default",
- size: "default",
- disabled: null,
- },
- },
- // =========================================================================
- // 3D Scene Components (React Three Fiber)
- // =========================================================================
- // Containers
- Scene3D: {
- props: z.object({
- height: z.string().nullable(),
- background: z.string().nullable(),
- cameraPosition: vec3.nullable(),
- cameraFov: z.number().nullable(),
- autoRotate: z.boolean().nullable(),
- }),
- slots: ["default"],
- description:
- "3D scene container with orbit controls. All 3D components (Sphere, Box, lights, etc.) must be children of a Scene3D. height is a CSS value like '500px'.",
- example: {
- height: "500px",
- background: "#000010",
- cameraPosition: [0, 25, 45],
- cameraFov: null,
- autoRotate: null,
- },
- },
- Group3D: {
- props: z.object({
- ...transform3DProps,
- animation: animation3D,
- }),
- slots: ["default"],
- description:
- "3D group for positioning, rotating, and animating children together. Use to create orbits: position a planet inside a Group3D and animate the group's rotation.",
- example: {
- position: null,
- rotation: null,
- scale: null,
- animation: { rotate: [0, 0.005, 0] },
- },
- },
- // Geometry primitives
- Box: {
- props: z.object(mesh3DProps),
- description:
- "3D box/cube mesh. args: [width, height, depth]. Supports on.press for click interaction.",
- example: {
- position: [0, 0, 0],
- color: "#4488ff",
- args: [1, 1, 1],
- },
- },
- Sphere: {
- props: z.object(mesh3DProps),
- description:
- "3D sphere mesh. args: [radius, widthSegments, heightSegments]. Use higher segment counts (32+) for smooth spheres.",
- example: {
- position: [0, 0, 0],
- color: "#4B7BE5",
- args: [1, 32, 32],
- },
- },
- Cylinder: {
- props: z.object(mesh3DProps),
- description:
- "3D cylinder mesh. args: [radiusTop, radiusBottom, height, radialSegments].",
- example: {
- position: [0, 0, 0],
- color: "#88aa44",
- args: [1, 1, 2, 32],
- },
- },
- Cone: {
- props: z.object(mesh3DProps),
- description: "3D cone mesh. args: [radius, height, radialSegments].",
- example: {
- position: [0, 0, 0],
- color: "#ff8844",
- args: [1, 2, 32],
- },
- },
- Torus: {
- props: z.object(mesh3DProps),
- description:
- "3D torus (donut) mesh. args: [radius, tube, radialSegments, tubularSegments].",
- example: {
- position: [0, 0, 0],
- color: "#aa44ff",
- args: [1, 0.4, 16, 100],
- },
- },
- Plane: {
- props: z.object(mesh3DProps),
- description:
- "3D flat plane mesh. args: [width, height]. Useful for ground planes or flat surfaces.",
- example: {
- position: [0, -1, 0],
- rotation: [-Math.PI / 2, 0, 0],
- color: "#334455",
- args: [10, 10],
- },
- },
- Ring: {
- props: z.object(mesh3DProps),
- description:
- "3D flat ring mesh. args: [innerRadius, outerRadius, thetaSegments]. Great for orbit path indicators.",
- example: {
- position: [0, 0, 0],
- rotation: [-Math.PI / 2, 0, 0],
- color: "#ffffff",
- opacity: 0.2,
- args: [14.8, 15.2, 64],
- },
- },
- // Lights
- AmbientLight: {
- props: z.object({
- color: z.string().nullable(),
- intensity: z.number().nullable(),
- }),
- description:
- "Ambient light that illuminates all objects equally. Use for base scene illumination.",
- example: { color: null, intensity: 0.3 },
- },
- PointLight: {
- props: z.object({
- position: vec3.nullable(),
- color: z.string().nullable(),
- intensity: z.number().nullable(),
- distance: z.number().nullable(),
- }),
- description:
- "Point light that emits from a position in all directions. Use for suns, lamps, etc.",
- example: { position: [0, 0, 0], intensity: 2 },
- },
- DirectionalLight: {
- props: z.object({
- position: vec3.nullable(),
- color: z.string().nullable(),
- intensity: z.number().nullable(),
- }),
- description:
- "Directional light like sunlight. Position sets direction, not location.",
- example: { position: [5, 10, 5], intensity: 1 },
- },
- // Helpers (drei)
- Stars: {
- props: z.object({
- radius: z.number().nullable(),
- depth: z.number().nullable(),
- count: z.number().nullable(),
- factor: z.number().nullable(),
- fade: z.boolean().nullable(),
- speed: z.number().nullable(),
- }),
- description:
- "Starfield background for space scenes. Renders thousands of tiny points around the scene.",
- example: { count: 5000, fade: true },
- },
- Label3D: {
- props: z.object({
- text: z.string(),
- position: vec3.nullable(),
- rotation: vec3.nullable(),
- color: z.string().nullable(),
- fontSize: z.number().nullable(),
- anchorX: z.enum(["left", "center", "right"]).nullable(),
- anchorY: z.enum(["top", "middle", "bottom"]).nullable(),
- }),
- description:
- "Text label rendered in 3D space. Always faces the camera (billboard). Use for labeling objects in a scene.",
- example: {
- text: "Earth",
- position: [15, 2, 0],
- color: "#ffffff",
- fontSize: 0.8,
- },
- },
- },
- actions: {},
- });
|