json-render-generative-ui
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesejson-render Generative UI Framework
json-render 生成式UI框架
Skill by ara.so — Daily 2026 Skills collection.
json-render is a Generative UI framework that lets AI generate dynamic interfaces from natural language prompts, constrained to a predefined component catalog. AI outputs JSON; json-render renders it safely and predictably across any platform.
由ara.so提供的Skill — 属于Daily 2026 Skills合集。
json-render是一款生成式UI框架,允许AI根据自然语言提示生成动态界面,并受限于预定义的组件目录。AI输出JSON;json-render可在任意平台安全、可预测地渲染这些JSON。
Installation
安装
bash
undefinedbash
undefinedReact (core)
React(核心)
npm install @json-render/core @json-render/react
npm install @json-render/core @json-render/react
React + shadcn/ui (36 pre-built components)
React + shadcn/ui(包含36个预构建组件)
npm install @json-render/shadcn
npm install @json-render/shadcn
React Native
React Native
npm install @json-render/core @json-render/react-native
npm install @json-render/core @json-render/react-native
Vue
Vue
npm install @json-render/core @json-render/vue
npm install @json-render/core @json-render/vue
Svelte
Svelte
npm install @json-render/core @json-render/svelte
npm install @json-render/core @json-render/svelte
SolidJS
SolidJS
npm install @json-render/core @json-render/solid
npm install @json-render/core @json-render/solid
Video (Remotion)
视频(Remotion)
npm install @json-render/core @json-render/remotion
npm install @json-render/core @json-render/remotion
npm install @json-render/core @json-render/react-pdf
npm install @json-render/core @json-render/react-pdf
邮件
npm install @json-render/core @json-render/react-email @react-email/components @react-email/render
npm install @json-render/core @json-render/react-email @react-email/components @react-email/render
3D (React Three Fiber)
3D(React Three Fiber)
npm install @json-render/core @json-render/react-three-fiber @react-three/fiber @react-three/drei three
npm install @json-render/core @json-render/react-three-fiber @react-three/fiber @react-three/drei three
OG Images / SVG / PNG
OG图片/SVG/PNG
npm install @json-render/core @json-render/image
npm install @json-render/core @json-render/image
State management adapters
状态管理适配器
npm install @json-render/zustand # or redux, jotai, xstate
npm install @json-render/zustand # 或 redux, jotai, xstate
MCP integration (Claude, ChatGPT, Cursor)
MCP集成(Claude、ChatGPT、Cursor)
npm install @json-render/mcp
npm install @json-render/mcp
YAML wire format
YAML传输格式
npm install @json-render/yaml
undefinednpm install @json-render/yaml
undefinedCore Concepts
核心概念
| Concept | Description |
|---|---|
| Catalog | Defines allowed components and actions (the guardrails for AI) |
| Spec | AI-generated JSON describing which components to render and with what props |
| Registry | Maps catalog component names to actual render implementations |
| Renderer | Platform-specific component that takes a spec + registry and renders UI |
| Actions | Named events AI can trigger (e.g. |
| 概念 | 描述 |
|---|---|
| Catalog(组件目录) | 定义允许使用的组件和操作(为AI设置的约束规则) |
| Spec(规格) | AI生成的JSON,描述要渲染的组件及其属性 |
| Registry(注册表) | 将目录中的组件名称映射到实际的渲染实现 |
| Renderer(渲染器) | 平台专属组件,接收规格和注册表并渲染UI |
| Actions(操作) | AI可触发的命名事件(例如 |
Spec Format
规格格式
The flat spec format uses a root key + elements map:
typescript
const spec = {
root: "card-1",
elements: {
"card-1": {
type: "Card",
props: { title: "Dashboard" },
children: ["metric-1", "metric-2", "button-1"],
},
"metric-1": {
type: "Metric",
props: { label: "Revenue", value: "124000", format: "currency" },
children: [],
},
"metric-2": {
type: "Metric",
props: { label: "Growth", value: "0.18", format: "percent" },
children: [],
},
"button-1": {
type: "Button",
props: { label: "Export Report", action: "export_report" },
children: [],
},
},
};扁平化规格格式使用根键+元素映射:
typescript
const spec = {
root: "card-1",
elements: {
"card-1": {
type: "Card",
props: { title: "Dashboard" },
children: ["metric-1", "metric-2", "button-1"],
},
"metric-1": {
type: "Metric",
props: { label: "Revenue", value: "124000", format: "currency" },
children: [],
},
"metric-2": {
type: "Metric",
props: { label: "Growth", value: "0.18", format: "percent" },
children: [],
},
"button-1": {
type: "Button",
props: { label: "Export Report", action: "export_report" },
children: [],
},
},
};Step 1: Define a Catalog
步骤1:定义组件目录
typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { z } from "zod";
const catalog = defineCatalog(schema, {
components: {
Card: {
props: z.object({ title: z.string() }),
description: "A card container with a title",
},
Metric: {
props: z.object({
label: z.string(),
value: z.string(),
format: z.enum(["currency", "percent", "number"]).nullable(),
}),
description: "Displays a single metric value with optional formatting",
},
Button: {
props: z.object({
label: z.string(),
action: z.string(),
}),
description: "Clickable button that triggers an action",
},
Stack: {
props: z.object({
direction: z.enum(["row", "column"]).default("column"),
gap: z.number().optional(),
}),
description: "Layout container that stacks children",
},
},
actions: {
export_report: { description: "Export the current dashboard to PDF" },
refresh_data: { description: "Refresh all metric data" },
navigate: {
description: "Navigate to a page",
payload: z.object({ path: z.string() }),
},
},
});typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { z } from "zod";
const catalog = defineCatalog(schema, {
components: {
Card: {
props: z.object({ title: z.string() }),
description: "带标题的卡片容器",
},
Metric: {
props: z.object({
label: z.string(),
value: z.string(),
format: z.enum(["currency", "percent", "number"]).nullable(),
}),
description: "显示单个指标值,支持可选格式化",
},
Button: {
props: z.object({
label: z.string(),
action: z.string(),
}),
description: "可点击按钮,触发指定操作",
},
Stack: {
props: z.object({
direction: z.enum(["row", "column"]).default("column"),
gap: z.number().optional(),
}),
description: "用于堆叠子元素的布局容器",
},
},
actions: {
export_report: { description: "将当前仪表板导出为PDF" },
refresh_data: { description: "刷新所有指标数据" },
navigate: {
description: "跳转到指定页面",
payload: z.object({ path: z.string() }),
},
},
});Step 2: Define a Registry (React)
步骤2:定义注册表(React)
tsx
import { defineRegistry, Renderer } from "@json-render/react";
function format(value: string, fmt: string | null): string {
if (fmt === "currency") return `$${Number(value).toLocaleString()}`;
if (fmt === "percent") return `${(Number(value) * 100).toFixed(1)}%`;
return value;
}
const { registry } = defineRegistry(catalog, {
components: {
Card: ({ props, children }) => (
<div className="rounded-lg border p-4 shadow-sm">
<h3 className="text-lg font-semibold mb-3">{props.title}</h3>
{children}
</div>
),
Metric: ({ props }) => (
<div className="flex flex-col">
<span className="text-sm text-gray-500">{props.label}</span>
<span className="text-2xl font-bold">
{format(props.value, props.format)}
</span>
</div>
),
Button: ({ props, emit }) => (
<button
className="px-4 py-2 bg-blue-600 text-white rounded"
onClick={() => emit("press")}
>
{props.label}
</button>
),
Stack: ({ props, children }) => (
<div
style={{
display: "flex",
flexDirection: props.direction ?? "column",
gap: props.gap ?? 8,
}}
>
{children}
</div>
),
},
});tsx
import { defineRegistry, Renderer } from "@json-render/react";
function format(value: string, fmt: string | null): string {
if (fmt === "currency") return `$${Number(value).toLocaleString()}`;
if (fmt === "percent") return `${(Number(value) * 100).toFixed(1)}%`;
return value;
}
const { registry } = defineRegistry(catalog, {
components: {
Card: ({ props, children }) => (
<div className="rounded-lg border p-4 shadow-sm">
<h3 className="text-lg font-semibold mb-3">{props.title}</h3>
{children}
</div>
),
Metric: ({ props }) => (
<div className="flex flex-col">
<span className="text-sm text-gray-500">{props.label}</span>
<span className="text-2xl font-bold">
{format(props.value, props.format)}
</span>
</div>
),
Button: ({ props, emit }) => (
<button
className="px-4 py-2 bg-blue-600 text-white rounded"
onClick={() => emit("press")}
>
{props.label}
</button>
),
Stack: ({ props, children }) => (
<div
style={{
display: "flex",
flexDirection: props.direction ?? "column",
gap: props.gap ?? 8,
}}
>
{children}
</div>
),
},
});Step 3: Render the Spec
步骤3:渲染规格
tsx
import { Renderer } from "@json-render/react";
function Dashboard({ spec, onAction }) {
return (
<Renderer
spec={spec}
registry={registry}
onAction={(action, payload) => {
console.log("Action triggered:", action, payload);
onAction?.(action, payload);
}}
/>
);
}tsx
import { Renderer } from "@json-render/react";
function Dashboard({ spec, onAction }) {
return (
<Renderer
spec={spec}
registry={registry}
onAction={(action, payload) => {
console.log("触发操作:", action, payload);
onAction?.(action, payload);
}}
/>
);
}Generating Specs with AI (Vercel AI SDK)
使用AI生成规格(Vercel AI SDK)
typescript
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { getCatalogSchema, getCatalogPrompt } from "@json-render/core";
async function generateDashboard(userPrompt: string) {
const { object: spec } = await generateObject({
model: openai("gpt-4o"),
schema: getCatalogSchema(catalog),
system: getCatalogPrompt(catalog),
prompt: userPrompt,
});
return spec;
}
// Usage
const spec = await generateDashboard(
"Create a sales dashboard showing revenue, conversion rate, and an export button"
);typescript
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { getCatalogSchema, getCatalogPrompt } from "@json-render/core";
async function generateDashboard(userPrompt: string) {
const { object: spec } = await generateObject({
model: openai("gpt-4o"),
schema: getCatalogSchema(catalog),
system: getCatalogPrompt(catalog),
prompt: userPrompt,
});
return spec;
}
// 使用示例
const spec = await generateDashboard(
"创建一个显示收入、转化率和导出按钮的销售仪表板"
);Streaming Specs
流式传输规格
tsx
import { streamObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { getCatalogSchema, getCatalogPrompt, parseSpecStream } from "@json-render/core";
import { Renderer } from "@json-render/react";
import { useState, useEffect } from "react";
function StreamingDashboard({ prompt }: { prompt: string }) {
const [spec, setSpec] = useState(null);
useEffect(() => {
async function stream() {
const { partialObjectStream } = await streamObject({
model: openai("gpt-4o"),
schema: getCatalogSchema(catalog),
system: getCatalogPrompt(catalog),
prompt,
});
for await (const partial of partialObjectStream) {
setSpec(partial); // Renderer handles partial specs gracefully
}
}
stream();
}, [prompt]);
if (!spec) return <div>Generating UI...</div>;
return <Renderer spec={spec} registry={registry} />;
}tsx
import { streamObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { getCatalogSchema, getCatalogPrompt, parseSpecStream } from "@json-render/core";
import { Renderer } from "@json-render/react";
import { useState, useEffect } from "react";
function StreamingDashboard({ prompt }: { prompt: string }) {
const [spec, setSpec] = useState(null);
useEffect(() => {
async function stream() {
const { partialObjectStream } = await streamObject({
model: openai("gpt-4o"),
schema: getCatalogSchema(catalog),
system: getCatalogPrompt(catalog),
prompt,
});
for await (const partial of partialObjectStream) {
setSpec(partial); // Renderer可优雅处理部分规格
}
}
stream();
}, [prompt]);
if (!spec) return <div>正在生成UI...</div>;
return <Renderer spec={spec} registry={registry} />;
}Using Pre-built shadcn/ui Components
使用预构建的shadcn/ui组件
tsx
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { defineRegistry, Renderer } from "@json-render/react";
import { shadcnComponentDefinitions } from "@json-render/shadcn/catalog";
import { shadcnComponents } from "@json-render/shadcn";
// Pick any of the 36 available shadcn components
const catalog = defineCatalog(schema, {
components: {
Card: shadcnComponentDefinitions.Card,
Stack: shadcnComponentDefinitions.Stack,
Heading: shadcnComponentDefinitions.Heading,
Text: shadcnComponentDefinitions.Text,
Button: shadcnComponentDefinitions.Button,
Badge: shadcnComponentDefinitions.Badge,
Table: shadcnComponentDefinitions.Table,
Chart: shadcnComponentDefinitions.Chart,
Input: shadcnComponentDefinitions.Input,
Select: shadcnComponentDefinitions.Select,
},
actions: {
submit: { description: "Submit a form" },
export: { description: "Export data" },
},
});
const { registry } = defineRegistry(catalog, {
components: {
Card: shadcnComponents.Card,
Stack: shadcnComponents.Stack,
Heading: shadcnComponents.Heading,
Text: shadcnComponents.Text,
Button: shadcnComponents.Button,
Badge: shadcnComponents.Badge,
Table: shadcnComponents.Table,
Chart: shadcnComponents.Chart,
Input: shadcnComponents.Input,
Select: shadcnComponents.Select,
},
});
function AIPage({ spec }) {
return <Renderer spec={spec} registry={registry} />;
}tsx
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react/schema";
import { defineRegistry, Renderer } from "@json-render/react";
import { shadcnComponentDefinitions } from "@json-render/shadcn/catalog";
import { shadcnComponents } from "@json-render/shadcn";
// 从36个可用的shadcn组件中选择
const catalog = defineCatalog(schema, {
components: {
Card: shadcnComponentDefinitions.Card,
Stack: shadcnComponentDefinitions.Stack,
Heading: shadcnComponentDefinitions.Heading,
Text: shadcnComponentDefinitions.Text,
Button: shadcnComponentDefinitions.Button,
Badge: shadcnComponentDefinitions.Badge,
Table: shadcnComponentDefinitions.Table,
Chart: shadcnComponentDefinitions.Chart,
Input: shadcnComponentDefinitions.Input,
Select: shadcnComponentDefinitions.Select,
},
actions: {
submit: { description: "提交表单" },
export: { description: "导出数据" },
},
});
const { registry } = defineRegistry(catalog, {
components: {
Card: shadcnComponents.Card,
Stack: shadcnComponents.Stack,
Heading: shadcnComponents.Heading,
Text: shadcnComponents.Text,
Button: shadcnComponents.Button,
Badge: shadcnComponents.Badge,
Table: shadcnComponents.Table,
Chart: shadcnComponents.Chart,
Input: shadcnComponents.Input,
Select: shadcnComponents.Select,
},
});
function AIPage({ spec }) {
return <Renderer spec={spec} registry={registry} />;
}Vue Renderer
Vue渲染器
typescript
import { h, defineComponent } from "vue";
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/vue/schema";
import { defineRegistry, Renderer } from "@json-render/vue";
import { z } from "zod";
const catalog = defineCatalog(schema, {
components: {
Card: {
props: z.object({ title: z.string() }),
description: "Card container",
},
Button: {
props: z.object({ label: z.string() }),
description: "Button",
},
},
actions: {
click: { description: "Button clicked" },
},
});
const { registry } = defineRegistry(catalog, {
components: {
Card: ({ props, children }) =>
h("div", { class: "card" }, [
h("h3", null, props.title),
children,
]),
Button: ({ props, emit }) =>
h("button", { onClick: () => emit("click") }, props.label),
},
});
// In your Vue SFC:
// <template>
// <Renderer :spec="spec" :registry="registry" />
// </template>typescript
import { h, defineComponent } from "vue";
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/vue/schema";
import { defineRegistry, Renderer } from "@json-render/vue";
import { z } from "zod";
const catalog = defineCatalog(schema, {
components: {
Card: {
props: z.object({ title: z.string() }),
description: "卡片容器",
},
Button: {
props: z.object({ label: z.string() }),
description: "按钮",
},
},
actions: {
click: { description: "按钮被点击" },
},
});
const { registry } = defineRegistry(catalog, {
components: {
Card: ({ props, children }) =>
h("div", { class: "card" }, [
h("h3", null, props.title),
children,
]),
Button: ({ props, emit }) =>
h("button", { onClick: () => emit("click") }, props.label),
},
});
// 在Vue单文件组件中使用:
// <template>
// <Renderer :spec="spec" :registry="registry" />
// </template>React Native Renderer
React Native渲染器
tsx
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react-native/schema";
import {
standardComponentDefinitions,
standardActionDefinitions,
} from "@json-render/react-native/catalog";
import { defineRegistry, Renderer } from "@json-render/react-native";
// 25+ standard mobile components out of the box
const catalog = defineCatalog(schema, {
components: { ...standardComponentDefinitions },
actions: standardActionDefinitions,
});
const { registry } = defineRegistry(catalog, {
components: {}, // use all standard implementations
});
export function AIScreen({ spec }) {
return <Renderer spec={spec} registry={registry} />;
}tsx
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/react-native/schema";
import {
standardComponentDefinitions,
standardActionDefinitions,
} from "@json-render/react-native/catalog";
import { defineRegistry, Renderer } from "@json-render/react-native";
// 开箱即用的25+标准移动端组件
const catalog = defineCatalog(schema, {
components: { ...standardComponentDefinitions },
actions: standardActionDefinitions,
});
const { registry } = defineRegistry(catalog, {
components: {}, // 使用所有标准实现
});
export function AIScreen({ spec }) {
return <Renderer spec={spec} registry={registry} />;
}PDF Generation
PDF生成
typescript
import { renderToBuffer } from "@json-render/react-pdf";
const invoiceSpec = {
root: "doc",
elements: {
doc: {
type: "Document",
props: { title: "Invoice #1234" },
children: ["page-1"],
},
"page-1": {
type: "Page",
props: { size: "A4" },
children: ["heading-1", "table-1"],
},
"heading-1": {
type: "Heading",
props: { text: "Invoice #1234", level: "h1" },
children: [],
},
"table-1": {
type: "Table",
props: {
columns: [
{ header: "Item", width: "60%" },
{ header: "Amount", width: "40%", align: "right" },
],
rows: [
["Widget A", "$10.00"],
["Widget B", "$25.00"],
["Total", "$35.00"],
],
},
children: [],
},
},
};
// Returns a Buffer you can send as a response
const buffer = await renderToBuffer(invoiceSpec);
// In a Next.js route handler:
export async function GET() {
const buffer = await renderToBuffer(invoiceSpec);
return new Response(buffer, {
headers: { "Content-Type": "application/pdf" },
});
}typescript
import { renderToBuffer } from "@json-render/react-pdf";
const invoiceSpec = {
root: "doc",
elements: {
doc: {
type: "Document",
props: { title: "Invoice #1234" },
children: ["page-1"],
},
"page-1": {
type: "Page",
props: { size: "A4" },
children: ["heading-1", "table-1"],
},
"heading-1": {
type: "Heading",
props: { text: "Invoice #1234", level: "h1" },
children: [],
},
"table-1": {
type: "Table",
props: {
columns: [
{ header: "Item", width: "60%" },
{ header: "Amount", width: "40%", align: "right" },
],
rows: [
["Widget A", "$10.00"],
["Widget B", "$25.00"],
["Total", "$35.00"],
],
},
children: [],
},
},
};
// 返回可作为响应发送的Buffer
const buffer = await renderToBuffer(invoiceSpec);
// 在Next.js路由处理器中使用:
export async function GET() {
const buffer = await renderToBuffer(invoiceSpec);
return new Response(buffer, {
headers: { "Content-Type": "application/pdf" },
});
}Email Generation
邮件生成
typescript
import { renderToHtml } from "@json-render/react-email";
import { schema, standardComponentDefinitions } from "@json-render/react-email";
import { defineCatalog } from "@json-render/core";
const catalog = defineCatalog(schema, {
components: standardComponentDefinitions,
});
const emailSpec = {
root: "html-1",
elements: {
"html-1": {
type: "Html",
props: { lang: "en" },
children: ["head-1", "body-1"],
},
"head-1": { type: "Head", props: {}, children: [] },
"body-1": {
type: "Body",
props: { style: { backgroundColor: "#f6f9fc" } },
children: ["container-1"],
},
"container-1": {
type: "Container",
props: { style: { maxWidth: "600px", margin: "0 auto" } },
children: ["heading-1", "text-1", "button-1"],
},
"heading-1": {
type: "Heading",
props: { text: "Welcome aboard!" },
children: [],
},
"text-1": {
type: "Text",
props: { text: "Thanks for signing up. Click below to get started." },
children: [],
},
"button-1": {
type: "Button",
props: { text: "Get Started", href: "https://example.com" },
children: [],
},
},
};
const html = await renderToHtml(emailSpec);typescript
import { renderToHtml } from "@json-render/react-email";
import { schema, standardComponentDefinitions } from "@json-render/react-email";
import { defineCatalog } from "@json-render/core";
const catalog = defineCatalog(schema, {
components: standardComponentDefinitions,
});
const emailSpec = {
root: "html-1",
elements: {
"html-1": {
type: "Html",
props: { lang: "en" },
children: ["head-1", "body-1"],
},
"head-1": { type: "Head", props: {}, children: [] },
"body-1": {
type: "Body",
props: { style: { backgroundColor: "#f6f9fc" } },
children: ["container-1"],
},
"container-1": {
type: "Container",
props: { style: { maxWidth: "600px", margin: "0 auto" } },
children: ["heading-1", "text-1", "button-1"],
},
"heading-1": {
type: "Heading",
props: { text: "Welcome aboard!" },
children: [],
},
"text-1": {
type: "Text",
props: { text: "Thanks for signing up. Click below to get started." },
children: [],
},
"button-1": {
type: "Button",
props: { text: "Get Started", href: "https://example.com" },
children: [],
},
},
};
const html = await renderToHtml(emailSpec);MCP Integration (Claude, ChatGPT, Cursor)
MCP集成(Claude、ChatGPT、Cursor)
typescript
import { createMCPServer } from "@json-render/mcp";
const server = createMCPServer({
catalog,
name: "my-ui-server",
version: "1.0.0",
});
server.start();typescript
import { createMCPServer } from "@json-render/mcp";
const server = createMCPServer({
catalog,
name: "my-ui-server",
version: "1.0.0",
});
server.start();State Management Integration
状态管理集成
typescript
import { create } from "zustand";
import { createZustandAdapter } from "@json-render/zustand";
const useStore = create((set) => ({
data: {},
setData: (data) => set({ data }),
}));
const stateStore = createZustandAdapter(useStore);
// Pass to Renderer for action handling with state
<Renderer spec={spec} registry={registry} stateStore={stateStore} />;typescript
import { create } from "zustand";
import { createZustandAdapter } from "@json-render/zustand";
const useStore = create((set) => ({
data: {},
setData: (data) => set({ data }),
}));
const stateStore = createZustandAdapter(useStore);
// 传递给Renderer以结合状态处理操作
<Renderer spec={spec} registry={registry} stateStore={stateStore} />;YAML Wire Format
YAML传输格式
typescript
import { parseYAML, toYAML } from "@json-render/yaml";
// AI can output YAML instead of JSON (often more token-efficient)
const yamlSpec = `
root: card-1
elements:
card-1:
type: Card
props:
title: Hello World
children: [button-1]
button-1:
type: Button
props:
label: Click Me
children: []
`;
const spec = parseYAML(yamlSpec);typescript
import { parseYAML, toYAML } from "@json-render/yaml";
// AI可以输出YAML而非JSON(通常更节省token)
const yamlSpec = `
root: card-1
elements:
card-1:
type: Card
props:
title: Hello World
children: [button-1]
button-1:
type: Button
props:
label: Click Me
children: []
`;
const spec = parseYAML(yamlSpec);Full Next.js App Router Example
完整Next.js App Router示例
tsx
// app/dashboard/page.tsx
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { getCatalogSchema, getCatalogPrompt } from "@json-render/core";
import { DashboardRenderer } from "./DashboardRenderer";
import { catalog } from "@/lib/catalog";
export default async function DashboardPage({
searchParams,
}: {
searchParams: { q?: string };
}) {
const prompt = searchParams.q ?? "Show me a sales overview dashboard";
const { object: spec } = await generateObject({
model: openai("gpt-4o"),
schema: getCatalogSchema(catalog),
system: getCatalogPrompt(catalog),
prompt,
});
return <DashboardRenderer spec={spec} />;
}tsx
// app/dashboard/DashboardRenderer.tsx
"use client";
import { Renderer } from "@json-render/react";
import { registry } from "@/lib/registry";
import { useRouter } from "next/navigation";
export function DashboardRenderer({ spec }) {
const router = useRouter();
return (
<Renderer
spec={spec}
registry={registry}
onAction={(action, payload) => {
switch (action) {
case "navigate":
router.push(payload.path);
break;
case "export_report":
window.open("/api/export", "_blank");
break;
case "refresh_data":
router.refresh();
break;
}
}}
/>
);
}tsx
// app/dashboard/page.tsx
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { getCatalogSchema, getCatalogPrompt } from "@json-render/core";
import { DashboardRenderer } from "./DashboardRenderer";
import { catalog } from "@/lib/catalog";
export default async function DashboardPage({
searchParams,
}: {
searchParams: { q?: string };
}) {
const prompt = searchParams.q ?? "展示销售概览仪表板";
const { object: spec } = await generateObject({
model: openai("gpt-4o"),
schema: getCatalogSchema(catalog),
system: getCatalogPrompt(catalog),
prompt,
});
return <DashboardRenderer spec={spec} />;
}tsx
// app/dashboard/DashboardRenderer.tsx
"use client";
import { Renderer } from "@json-render/react";
import { registry } from "@/lib/registry";
import { useRouter } from "next/navigation";
export function DashboardRenderer({ spec }) {
const router = useRouter();
return (
<Renderer
spec={spec}
registry={registry}
onAction={(action, payload) => {
switch (action) {
case "navigate":
router.push(payload.path);
break;
case "export_report":
window.open("/api/export", "_blank");
break;
case "refresh_data":
router.refresh();
break;
}
}}
/>
);
}Common Patterns
常见模式
Conditional Component Availability
组件的条件可用性
typescript
// Restrict catalog based on user role
function getCatalogForRole(role: "admin" | "viewer") {
const base = { Card, Stack, Heading, Text, Metric };
const adminOnly = role === "admin" ? { Button, Form, Table } : {};
const adminActions = role === "admin"
? { export: { description: "Export data" } }
: {};
return defineCatalog(schema, {
components: { ...base, ...adminOnly },
actions: adminOnly ? adminActions : {},
});
}typescript
// 根据用户角色限制组件目录
function getCatalogForRole(role: "admin" | "viewer") {
const base = { Card, Stack, Heading, Text, Metric };
const adminOnly = role === "admin" ? { Button, Form, Table } : {};
const adminActions = role === "admin"
? { export: { description: "导出数据" } }
: {};
return defineCatalog(schema, {
components: { ...base, ...adminOnly },
actions: adminOnly ? adminActions : {},
});
}Dynamic Props with Runtime Data
结合运行时数据的动态属性
tsx
// Components can fetch their own data
const { registry } = defineRegistry(catalog, {
components: {
LiveMetric: ({ props }) => {
const { data } = useSWR(`/api/metrics/${props.metricId}`);
return (
<div>
<span>{props.label}</span>
<span>{data?.value ?? "..."}</span>
</div>
);
},
},
});tsx
// 组件可自行获取数据
const { registry } = defineRegistry(catalog, {
components: {
LiveMetric: ({ props }) => {
const { data } = useSWR(`/api/metrics/${props.metricId}`);
return (
<div>
<span>{props.label}</span>
<span>{data?.value ?? "..."}</span>
</div>
);
},
},
});Type-Safe Action Handling
类型安全的操作处理
typescript
import { type ActionHandler } from "@json-render/core";
const handleAction: ActionHandler<typeof catalog> = (action, payload) => {
// action and payload are fully typed based on your catalog definition
if (action === "navigate") {
router.push(payload.path); // payload.path is typed as string
}
};typescript
import { type ActionHandler } from "@json-render/core";
const handleAction: ActionHandler<typeof catalog> = (action, payload) => {
// action和payload会根据你的目录定义自动推导类型
if (action === "navigate") {
router.push(payload.path); // payload.path被推导为string类型
}
};Troubleshooting
故障排除
| Problem | Cause | Fix |
|---|---|---|
| AI generates unknown component type | Component not in catalog | Add component to |
| Props validation error | AI hallucinated a prop | Tighten Zod schema, add |
| Renderer shows nothing | | Check spec structure; root must reference a valid element ID |
| Partial spec renders incorrectly | Streaming not handled | Use |
| Actions not firing | | Pass |
| shadcn components unstyled | Missing Tailwind config | Ensure |
| TypeScript errors in registry | Catalog/registry mismatch | Ensure |
| 问题 | 原因 | 解决方法 |
|---|---|---|
| AI生成了未知的组件类型 | 组件未在目录中定义 | 将组件添加到 |
| 属性验证错误 | AI生成了不存在的属性 | 收紧Zod schema,添加 |
| 渲染器无内容显示 | | 检查规格结构;root必须引用有效的元素ID |
| 部分规格渲染异常 | 未处理流式传输 | 使用 |
| 操作未触发 | 未向 | 为 |
| shadcn组件无样式 | 缺少Tailwind配置 | 确保 |
| 注册表出现TypeScript错误 | 目录与注册表不匹配 | 确保 |
Environment Variables
环境变量
bash
undefinedbash
undefinedFor AI generation (use your preferred provider)
AI生成相关(使用你偏好的服务商)
OPENAI_API_KEY=your_key_here
ANTHROPIC_API_KEY=your_key_here
OPENAI_API_KEY=你的密钥
ANTHROPIC_API_KEY=你的密钥
For MCP server
MCP服务器相关
MCP_SERVER_PORT=3001
undefinedMCP_SERVER_PORT=3001
undefinedKey API Reference
核心API参考
typescript
// Core
defineCatalog(schema, { components, actions }) // Define guardrails
getCatalogSchema(catalog) // Get Zod schema for AI
getCatalogPrompt(catalog) // Get system prompt for AI
// React
defineRegistry(catalog, { components }) // Create typed registry
<Renderer spec={spec} registry={registry} onAction={fn} />
// Core utilities
parseSpecStream(stream) // Parse streaming partial specs
toYAML(spec) // Convert spec to YAML
parseYAML(yaml) // Parse YAML spec to JSON
// PDF
renderToBuffer(spec) // → Buffer
renderToStream(spec) // → ReadableStream
// Email
renderToHtml(spec) // → HTML string
renderToText(spec) // → plain text stringtypescript
// 核心API
defineCatalog(schema, { components, actions }) // 定义约束规则
getCatalogSchema(catalog) // 获取用于AI的Zod schema
getCatalogPrompt(catalog) // 获取用于AI的系统提示词
// React相关API
defineRegistry(catalog, { components }) // 创建类型化注册表
<Renderer spec={spec} registry={registry} onAction={fn} />
// 核心工具函数
parseSpecStream(stream) // 解析流式传输的部分规格
toYAML(spec) // 将规格转换为YAML
parseYAML(yaml) // 将YAML规格解析为JSON
// PDF相关API
renderToBuffer(spec) // → Buffer
renderToStream(spec) // → ReadableStream
// 邮件相关API
renderToHtml(spec) // → HTML字符串
renderToText(spec) // → 纯文本字符串