🚀 快速安装

复制以下命令并运行,立即安装此 Skill:

npx skills add https://skills.sh/aradotso/trending-skills/json-render-generative-ui

💡 提示:需要 Node.js 和 NPM

json-render 生成式 UI 框架

技能由 ara.so 提供 — Daily 2026 Skills 系列。

json-render 是一个生成式 UI 框架,它允许 AI 根据自然语言提示生成动态界面,并将输出限制在预定义的组件目录中。AI 输出 JSON;json-render 则安全且可预测地在任何平台上渲染它。

安装

# React (核心)
npm install @json-render/core @json-render/react

# React + shadcn/ui (36个预构建组件)
npm install @json-render/shadcn

# React Native
npm install @json-render/core @json-render/react-native

# Vue
npm install @json-render/core @json-render/vue

# Svelte
npm install @json-render/core @json-render/svelte

# SolidJS
npm install @json-render/core @json-render/solid

# 视频 (Remotion)
npm install @json-render/core @json-render/remotion

# 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

# 3D (React Three Fiber)
npm install @json-render/core @json-render/react-three-fiber @react-three/fiber @react-three/drei three

# OG 图像 / SVG / PNG
npm install @json-render/core @json-render/image

# 状态管理适配器
npm install @json-render/zustand   # 或 redux, jotai, xstate

# MCP 集成 (Claude, ChatGPT, Cursor)
npm install @json-render/mcp

# YAML 传输格式
npm install @json-render/yaml

核心概念

概念 描述
目录 (Catalog) 定义允许的组件和操作(AI 的护栏)
规范 (Spec) AI 生成的 JSON,描述要渲染哪些组件及其属性
注册表 (Registry) 将目录组件名称映射到实际渲染实现
渲染器 (Renderer) 特定平台的组件,接收规范 + 注册表并渲染 UI
操作 (Actions) AI 可以触发的命名事件(例如 export_report, refresh_data)

规范格式

扁平规范格式使用根键 + 元素映射:

const spec = {
  root: "card-1",
  elements: {
    "card-1": {
      type: "Card",
      props: { title: "仪表板" },
      children: ["metric-1", "metric-2", "button-1"],
    },
    "metric-1": {
      type: "Metric",
      props: { label: "收入", value: "124000", format: "currency" },
      children: [],
    },
    "metric-2": {
      type: "Metric",
      props: { label: "增长率", value: "0.18", format: "percent" },
      children: [],
    },
    "button-1": {
      type: "Button",
      props: { label: "导出报告", action: "export_report" },
      children: [],
    },
  },
};

步骤 1:定义目录 (Catalog)

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() }),
    },
  },
});

步骤 2:定义注册表 (Registry) (React)

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>
    ),
  },
});

步骤 3:渲染规范

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);
      }}
    />
  );
}

使用 AI 生成规范 (Vercel AI SDK)

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(
  "创建一个销售仪表板,显示收入、转化率和一个导出按钮"
);

流式规范

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); // 渲染器可以优雅地处理部分规范
      }
    }
    stream();
  }, [prompt]);

  if (!spec) return <div>正在生成 UI...</div>;
  return <Renderer spec={spec} registry={registry} />;
}

使用预构建的 shadcn/ui 组件

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 渲染器

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 SFC 中:
// <template>
//   <Renderer :spec="spec" :registry="registry" />
// </template>

React Native 渲染器

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 生成

import { renderToBuffer } from "@json-render/react-pdf";

const invoiceSpec = {
  root: "doc",
  elements: {
    doc: {
      type: "Document",
      props: { title: "发票 #1234" },
      children: ["page-1"],
    },
    "page-1": {
      type: "Page",
      props: { size: "A4" },
      children: ["heading-1", "table-1"],
    },
    "heading-1": {
      type: "Heading",
      props: { text: "发票 #1234", level: "h1" },
      children: [],
    },
    "table-1": {
      type: "Table",
      props: {
        columns: [
          { header: "项目", width: "60%" },
          { header: "金额", width: "40%", align: "right" },
        ],
        rows: [
          ["组件 A", "$10.00"],
          ["组件 B", "$25.00"],
          ["总计", "$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" },
  });
}

邮件生成

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: "欢迎加入!" },
      children: [],
    },
    "text-1": {
      type: "Text",
      props: { text: "感谢您注册。点击下方按钮开始使用。" },
      children: [],
    },
    "button-1": {
      type: "Button",
      props: { text: "开始使用", href: "https://example.com" },
      children: [],
    },
  },
};

const html = await renderToHtml(emailSpec);

MCP 集成 (Claude, ChatGPT, Cursor)

import { createMCPServer } from "@json-render/mcp";

const server = createMCPServer({
  catalog,
  name: "my-ui-server",
  version: "1.0.0",
});

server.start();

状态管理集成

import { create } from "zustand";
import { createZustandAdapter } from "@json-render/zustand";

const useStore = create((set) => ({
  data: {},
  setData: (data) => set({ data }),
}));

const stateStore = createZustandAdapter(useStore);

// 传递给渲染器,用于带状态的操作处理
<Renderer spec={spec} registry={registry} stateStore={stateStore} />;

YAML 传输格式

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);

完整的 Next.js 应用路由器示例

// 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} />;
}
// 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;
        }
      }}
    />
  );
}

常见模式

条件性组件可用性

// 基于用户角色限制目录
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 : {},
  });
}

带有运行时数据的动态属性

// 组件可以自行获取数据
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>
      );
    },
  },
});

类型安全的操作处理

import { type ActionHandler } from "@json-render/core";

const handleAction: ActionHandler<typeof catalog> = (action, payload) => {
  // 操作和载荷根据你的目录定义具有完整类型
  if (action === "navigate") {
    router.push(payload.path); // payload.path 的类型是 string
  }
};

故障排除

问题 原因 修复方法
AI 生成了未知的组件类型 组件不在目录中 将组件添加到 defineCatalog 或更新 AI 提示词
属性验证错误 AI 生成了不存在的属性 收紧 Zod schema,添加 .strict().describe() 提示
渲染器无显示内容 root 键与 elements 中的键不匹配 检查规范结构;root 必须引用一个有效的元素 ID
部分规范渲染不正确 未处理流式传输 使用 parseSpecStream 工具或在渲染前检查空元素
操作未触发 未向 Renderer 传递 onAction onAction 属性传递给 <Renderer>
shadcn 组件无样式 缺少 Tailwind 配置 确保 @json-render/shadcn 路径在 tailwind.config.js 的内容数组中
注册表中的 TypeScript 错误 目录/注册表不匹配 确保 defineRegistry(catalog, ...) 使用的是同一个 catalog 实例

环境变量

# 用于 AI 生成(使用你偏好的供应商)
OPENAI_API_KEY=your_key_here
ANTHROPIC_API_KEY=your_key_here

# 用于 MCP 服务器
MCP_SERVER_PORT=3001

关键 API 参考

// 核心
defineCatalog(schema, { components, actions })  // 定义护栏
getCatalogSchema(catalog)                        // 获取用于 AI 的 Zod schema
getCatalogPrompt(catalog)                        // 获取用于 AI 的系统提示词

// React
defineRegistry(catalog, { components })          // 创建类型安全的注册表
<Renderer spec={spec} registry={registry} onAction={fn} />

// 核心工具
parseSpecStream(stream)    // 解析流式部分规范
toYAML(spec)              // 将规范转换为 YAML
parseYAML(yaml)           // 将 YAML 规范解析为 JSON

// PDF
renderToBuffer(spec)       // → Buffer
renderToStream(spec)       // → ReadableStream

// 邮件
renderToHtml(spec)         // → HTML 字符串
renderToText(spec)         // → 纯文本字符串

📄 原始文档

完整文档(英文):

https://skills.sh/aradotso/trending-skills/json-render-generative-ui

💡 提示:点击上方链接查看 skills.sh 原始英文文档,方便对照翻译。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。