W
AI-Wiki
CONCEPT

组件目录(Catalog)

组件目录(Catalog)

定义

组件目录(Catalog)是 json-render 架构中由开发者预先声明的“AI 可用界面能力白名单”。它规定大模型在生成 生成式 UI 时可以使用哪些组件、每类组件有哪些字段、Spec 应该采用什么结构、以及组件可以触发哪些动作。

Catalog 不是最终渲染出来的 UI,也不是某个 React、Vue 或移动端组件实现本身。它更接近一份 schema 契约:AI 只负责在这份契约内产出结构化的 JSON Spec,真正把界面画出来的是后续的渲染器和开发者注册的真实组件。

在来源文章对 json-render 的描述中,核心设计可以概括为:AI 不写 UI,AI 只生成受 Catalog 约束的 JSON Spec;真正渲染像素的是开发者定义和注册的组件。Catalog 因此是 json-render “护栏式生成”的关键边界。

在 json-render 架构中的语境

json-render 的流水线中,用户 Prompt 会交给 AI;AI 结合 Catalog 生成 JSON Spec;Renderer 再把 Spec 渲染成真实 UI。这个过程可写成:

  • 用户 Prompt → AI + Catalog → JSON Spec → Renderer。

Catalog 位于 AI 生成环节之前,用来告诉模型:

  • 允许生成哪些组件类型;
  • 每种组件的字段名和字段类型是什么;
  • Spec 的根结构、元素集合和子节点引用关系应该如何组织;
  • 哪些数据绑定和表达式可以使用;
  • 哪些动作可以被组件触发。

这使 json-render 和“直接让 LLM 生成 JSX/HTML/前端代码”的路线不同。直接生成代码时,模型可能发明不存在的组件、输出不可预测结构,甚至带来注入脚本等安全风险;使用 Catalog 时,模型的自由度被限制在开发者定义的 schema 内,不确定性留在模型侧,确定性由开发者掌握。

Catalog 声明的内容

Catalog 通常会声明一组可由 AI 生成的组件类型。例如来源文章中的示例包含:

  • Card:卡片类组件;
  • Metric:指标类组件;
  • Button:按钮类组件。

它还会声明组件字段。例如:

  • Card 可带 title 字段;
  • Metric 可带 value 字段;
  • Button 可带 label 字段。

Catalog 还会定义 JSON Spec 的组织结构。来源示例中出现了 rootchildrenelements 等关键结构:

  • root 表示界面根节点配置;
  • root.children 是一个字符串数组,用于引用根节点下的子元素;
  • elements 是元素表,用于保存各个具体元素的定义;
  • 每个元素通过 type 字段声明自身组件类型,例如 CardMetricButton

动作边界也属于 Catalog 的声明范围。来源示例把 setState 放入动作白名单,表示 AI 生成的界面可以触发该动作,但不能任意调用未登记的动作。

一个简化示例如下:

import { defineCatalog } from '@json-render/core';
import { z } from 'zod';

const catalog = defineCatalog(
  z.object({
    root: z.object({ children: z.array(z.string()) }),
    elements: z.record(z.union([
      z.object({ type: z.literal('Card'), title: z.string() }),
      z.object({ type: z.literal('Metric'), value: z.number() }),
      z.object({ type: z.literal('Button'), label: z.string() }),
    ])),
  }),
  { components: ['Card', 'Metric', 'Button'], actions: ['setState'] }
);

这个示例表达了几个边界:AI 可以输出 CardMetricButton 三类元素;Card.title 必须是字符串;Metric.value 必须是数字;Button.label 必须是字符串;根节点只能通过 children 引用子元素;动作白名单中只有 setState

defineCatalog 与 zod 的作用

zod 在 Catalog 中用于固化 JSON Spec 的结构和字段类型。开发者通过 zod schema 明确规定 Spec 应满足的形状,例如对象层级、数组字段、记录表、联合类型、字面量组件类型,以及字段的字符串或数字类型。

defineCatalog 则把这份 zod schema 与组件、动作等元信息组织成模型可用的契约。换句话说:

  • zod 负责“结构与类型校验”:规定什么 JSON Spec 合法;
  • defineCatalog 负责“目录化与契约化”:把 schema、组件白名单、动作白名单组织成 json-render 能使用、也能提示给模型的 Catalog。

因此,Catalog 并不是随口写给模型看的提示词,而是可被程序校验的契约。模型生成的 JSON 必须严格匹配这个契约,才能继续交给渲染器处理。

安全机制与边界

Catalog 的安全性来自“白名单 + schema 校验”。所有 AI 可生成的组件、动作、数据绑定都必须先在 Catalog 中登记。模型如果越界,应该被 schema 校验挡回,而不是直接进入渲染阶段。

典型应被拦截的情况包括:

  • 使用未登记组件:例如 Catalog 只登记了 CardMetricButton,模型却输出 ChartScriptBlock
  • 字段类型错误:例如 Metric.value 需要数字,模型却输出字符串;
  • 字段结构错误:例如缺少必要的 type,或 root.children 不是字符串数组;
  • 越权动作:例如 Catalog 只允许 setState,模型却生成删除数据、发起未授权请求或其他未登记动作;
  • 不符合整体 JSON Spec 结构:例如 elements 不是合法记录表,或元素定义不属于 schema 中允许的联合类型。

这种机制降低了几类风险:

  • 幻觉组件风险:模型不能随意编造前端组件并假定系统支持;
  • 注入风险:模型不能绕过组件白名单直接输出任意脚本或危险结构;
  • 不可预测输出风险:渲染器面对的是已校验 Spec,而不是任意自然语言或任意代码;
  • 工程维护风险:界面能力沉淀为可审计、可复用的契约,而不是散落在提示词中的隐性规则。

需要注意的是,Catalog 只定义 AI 能输出什么 Spec,并不自动保证每个真实组件实现本身没有漏洞。真实组件如何处理文本、数据、样式和副作用,仍需要开发者在组件实现和 registry 层面负责。

catalog.prompt() 的用途

catalog.prompt() 用于从 Catalog 自动生成系统提示词,提供给任意 LLM。它的作用是把 Catalog 中可用的组件、字段、动作和输出格式说明转化为模型可理解的提示,让模型知道自己应当输出怎样的 JSON Spec。

来源文章强调,接入自己的大模型时,可以调用 catalog.prompt() 拿到系统提示词,再把任意 LLM 的流式输出喂给 SpecStream。这意味着 Catalog 不绑定某一家模型服务,也不要求必须使用 Vercel 自家的模型能力;开发者可以接本地或私有化模型,例如文章提到的 Ollama / vLLM 场景。

与手写提示词相比,catalog.prompt() 的价值在于:提示词从同一份 Catalog 契约自动生成,减少“schema 已变但提示词没改”的漂移问题。模型看到的输出规则和程序校验使用的规则来自同一处定义。

与 registry 的区别

Catalog 和 registry 经常一起出现,但职责不同。

Catalog 定义 AI 能输出什么 Spec。它回答的问题是:

  • AI 可以使用哪些组件类型?
  • 每种组件可以有哪些字段?
  • 字段类型是什么?
  • Spec 的结构是什么?
  • 可触发哪些动作?

registry 则把 Spec 中的组件类型映射到真实组件实现。它回答的问题是:

  • Card 这个 Spec 类型最终由哪个真实组件渲染?
  • Metric 对应 MyMetric 还是另一个指标组件?
  • Button 对应哪个按钮实现?

来源文章中的 React 示例展示了这种分工:

import { Renderer } from '@json-render/react';
import { defineRegistry } from '@json-render/core';

const registry = defineRegistry(catalog, {
  components: { Card: MyCard, Metric: MyMetric, Button: MyButton },
});

<Renderer spec={spec} registry={registry} />;

在这个例子中,Catalog 允许 AI 生成 CardMetricButton 类型的 JSON Spec;registry 则把这些类型分别映射为真实组件 MyCardMyMetricMyButton。如果没有 Catalog,AI 的输出边界不清晰;如果没有 registry,合法 Spec 也无法落到具体界面实现。

与流式渲染和跨平台的关系

Catalog 也服务于 json-render 的流式渲染能力。来源文章提到,SpecStream 可以在模型逐块吐出 JSON 时逐步解析和渲染;而 Catalog 的 schema 约束使这些逐步到来的片段最终仍要收敛到合法 Spec。

Catalog 本身不等于某个前端平台的组件库。json-render 的设计是“一套 Catalog,多端渲染”:同一份 JSON Spec 可以通过不同渲染器落到 React / Next.js、Vue 3、Svelte 5、SolidJS、React Native、Ink 终端 TUI 等目标,也可以扩展到 Remotion 视频、React PDF 文档、React Email 邮件、Satori SVG/PNG 社交卡片、React Three Fiber 3D 场景等非传统 UI 输出。

这种跨平台能力的前提是:AI 输出的不是平台代码,而是受 Catalog 约束的中间 Spec;不同平台通过各自 registry 和 renderer 把同一类 Spec 映射到本端实现。

细节与边界

  • Catalog 是 AI 可用能力的白名单,不是最终 UI,也不是组件实现源码。
  • Catalog 约束的是 JSON Spec 的结构、类型和动作边界;真实组件渲染逻辑仍由开发者实现和维护。
  • defineCatalog 组织 schema、组件元信息和动作元信息;zod 负责把 JSON Spec 的结构与字段类型变成可校验规则。
  • 模型输出不应因为“看起来合理”就直接渲染,必须经过 schema 校验。
  • 未登记组件、错误字段类型、越权动作、不符合 root / children / elements 等结构约定的 Spec,都应被挡回。
  • catalog.prompt() 是把 Catalog 自动转成系统提示词的工具,用于指导任意 LLM 生成合法 Spec。
  • registry 不是 Catalog:registry 负责把 Spec 类型映射到真实组件,例如 Card → MyCardMetric → MyMetricButton → MyButton

相关条目