For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt, and this page is available as Markdown at /zh/guide/genui/openui/library-guide.md.
这是开发预览网站。请访问正式文档 lynx-stack.dev

Libraries、内置组件与自定义组件

OpenUI Library 是 Agent 与 client 之间的 contract。它描述 Agent 可以写出的 component calls,提供解析和 prompt 生成所需的 JSON Schema,并把每个允许的名称 映射到可信的 ReactLynx renderer。

这篇指南介绍默认 Library、其中 26 个内置组件,以及新增或替换组件的流程。

Library 包含什么

每个 component definition 有四部分:

  • 稳定的 OpenUI component name;
  • 一个 Zod object schema,字段顺序决定位置参数顺序;
  • 一段用于 generated prompt 的简短 description;
  • 可信的 ReactLynx component 实现。

createOpenUiLibrary() 把这些 definitions 组合成带以下成员的 Library:

成员用途
componentsRenderer 使用的 name-to-definition map。
componentGroupsPrompt 和 tooling 使用的分类及排序提示。
rootroot statement 必须使用的组件类型;默认为 Stack。
toJSONSchema()带位置 prop definitions 的 parser/handshake schema。
toSpec()框架无关的 prompt specification。
prompt(options)面向这个精确 Library 的底层 prompt 生成。Server 侧应优先使用 system prompt 指南介绍的 headless prompt 入口。

默认 Library 应只创建一次,并保持对象 identity 稳定:

const library = useMemo(() => createOpenUiLibrary(), []);

<OpenUiRenderer response={response} library={library} />;

内置组件

默认情况下,createOpenUiLibrary() 包含以下组件。它们既从聚合入口 @lynx-js/genui/openui/catalog 导出,也可以通过 @lynx-js/genui/openui/catalog/Stack 等逐组件子路径导入。

服务端生成 schema 或 prompt 时,可从 @lynx-js/genui/openui/catalog/agent(或 @lynx-js/genui-openui/catalog/agent)导入使用 null renderer 的组件定义。 该入口与 ReactLynx catalog 共享名称、描述、schema 和组件 ref,无需加载 JSX、 CSS、ReactLynx 或 Lynx UI。createOpenUiPromptLibrary() 直接复用这些定义。

两个工厂共用组件顺序、分组和选项处理逻辑。配置和自定义组件定义对应时, toJSONSchema()、toSpec() 和 .prompt() 输出相同。渲染库提供真实的 ReactLynx 组件;Prompt 库提供返回 null 的 renderer,可以在 Node 中加载。

两端都支持用 componentNames 筛选内置组件,并自动保留选定的 root;也支持 includeDefaultComponents: false,仅使用调用方传入的组件和分组。自定义组件始终 追加在内置组件后,同名定义以后者为准。禁用默认组件时,componentNames 不生效, 调用方必须自行提供 root。筛选仅控制 Library 的组件集合;若还需要排除未选组件的 模块依赖,使用 openui/explicit 配合逐组件导入。

// 服务端和客户端使用相同的筛选配置。
const options = { componentNames: ['Stack', 'Text'] };
const promptLibrary = createOpenUiPromptLibrary(options);
const rendererLibrary = createOpenUiLibrary(options);

buildOpenUiSystemPrompt() 会额外添加生成规则和示例,与上述底层 .prompt() 的 一致性约定不同。组件集合受限时,不添加依赖其他默认组件的规则和示例。

按需引入单个定义时,使用 @lynx-js/genui/openui/catalog/agent/Stack(或 @lynx-js/genui-openui/catalog/agent/Stack)。每个子路径导出与 Agent 聚合入口 相同的组件名,只加载该组件定义及其 schema 依赖。

import { Stack, Text } from '@lynx-js/genui/openui/catalog/agent';
import { createLibrary } from '@openuidev/lang-core';

const library = createLibrary({ root: 'Stack', components: [Stack, Text] });
const prompt = library.prompt();

布局

组件用途
Stack支持换行、间距、对齐和排列的通用横/纵向布局;默认 Library root。
Row横向布局容器。
Column纵向布局容器。
List带可选分割线和间距的纵向或横向集合。

内容

组件用途
Card带 Stack 类布局 props 的 card、sunk 或 clear 样式容器。
CardHeaderCard 的标题和可选副标题。
Text带 h1、h2、caption、body 等语义 variants 的普通文本。
TextContent带紧凑字号/字重 variants 的内容文本。
Separator简单视觉分隔符。
Divider横向或纵向分割线。

按钮

组件用途
Button可点击的 primary、secondary 或 tertiary action,支持 destructive 样式。
ButtonsButton 组件组。

数据展示与媒体

组件用途
Tag紧凑标签或状态 tag。
Image带 fit 和语义尺寸 variants 的远程图片。
Icon内置 Material Icons 字形,支持尺寸和颜色 variants。
Video带 URL 和可选标题的视频附件占位。
AudioPlayer带 URL 和描述的音频附件占位。
LoadingInline 或 block skeleton/loading feedback。
Canvas通过字符串或流式代码片段绘制 Web Canvas 2D。

浮层

组件用途
Tabs在带 label 的多个 child view 间切换。
Modal从 trigger 组件打开 generated detail content。

输入

组件用途
CheckBox带可选 action 和 form name 的布尔输入。
RadioGroup带 default、card 或 row 展示方式的单选输入。
ChoicePicker以 chips、list 或 dropdown-like control 展示的选择器。
Slider带可选 step 和 action 的数值范围输入。
TextField支持可选 regex 校验的短文本、数字、密码或多行输入。
DateTimeInput带 label、上下界和日期/时间展示开关的值。

Button、CheckBox、Modal、RadioGroup、Slider 和 TextField 使用 @lynx-js/lynx-ui;generated UI 允许使用这些组件时,需要添加该 peer dependency。

默认视觉层使用 Luna:先引入 @lynx-js/luna-styles/index.css,再引入 @lynx-js/genui/openui/styles/theme.css,并把 renderer 放在 luna-light、luna-dark、lunaris-light 或 lunaris-dark 主题作用域内。 lynx-ui 是 headless 组件库,因此 Luna 提供的是 tokens,不是替代组件 class。

完整、最新的位置参数签名和 live preview 见 OpenUI Catalog。Schema 是唯一事实来源: 可选参数只能从右侧开始省略,也不支持 named-argument syntax。

OpenUI component 语法

组件 Zod object 中的字段顺序,就是 wire-level 位置参数顺序。例如内置 Stack schema 依次以 children、direction、wrap、gap、align 和 justify 开头,因此下面的写法合法:

root = Stack([header, body], "column", false, "m", "stretch", "start")

下面不是合法的 OpenUI Lang:

root = Stack(children: [header, body], gap: "m")

Child components 也是 values。它们既可以声明为具名 statements,也可以在 parent schema 允许时 inline 使用:

root = Card([title, TextContent("Inline content")])
title = CardHeader("Account", "Updated just now")

添加自定义组件

如果应用还没有直接依赖 Zod,先安装:

pnpm add zod

用稳定名称、有序 prop schema、prompt description 和 ReactLynx renderer 定义 组件。样式应放进 CSS class,而不是 inline style object;可见文本必须包在 <text> 中。

import { createOpenUiLibrary, defineComponent } from '@lynx-js/genui/openui';
import { z } from 'zod/v4';

import './Banner.css';

export const Banner = defineComponent({
  name: 'Banner',
  description: 'Compact status banner with a title and tone.',
  props: z.object({
    title: z.string(),
    tone: z.enum(['info', 'success', 'warning']).optional(),
  }),
  component: ({ props }) => (
    <view className={`Banner Banner-${props.tone ?? 'info'}`}>
      <text className='BannerTitle'>{props.title}</text>
    </view>
  ),
});

export const library = createOpenUiLibrary({
  components: [Banner],
  componentGroups: [
    { name: 'Product', components: ['Banner'] },
  ],
});

Agent 现在可以输出:

root = Stack([notice])
notice = Banner("Payment received", "success")

调用方提供的 components 和 groups 会追加在默认值后。如果自定义组件与内置组件 同名,后加入的自定义 definition 会覆盖 components map 中的默认值。这应当被 视为一次有意 override,并且 prompt 侧 schema 必须与它保持一致。

显式选择组件

如果 Library 只应包含调用方提供的 definitions,请把 includeDefaultComponents 设为 false。仍要使用的每个内置组件都需要从 catalog 导入,并通过 components 传入:

import { createOpenUiLibrary } from '@lynx-js/genui/openui/explicit';
import { Stack } from '@lynx-js/genui/openui/catalog/Stack';
import { TextContent } from '@lynx-js/genui/openui/catalog/TextContent';

export const library = createOpenUiLibrary({
  includeDefaultComponents: false,
  components: [Stack, TextContent],
  componentGroups: [
    { name: 'Selected', components: ['Stack', 'TextContent'] },
  ],
});

关闭 defaults 后,26 个内置 component definitions 及其 groups 都不会加入。 root 仍默认是 Stack;如果所选 components 不包含 root,创建 Library 时会报错。 可以像示例一样导入并传入 Stack,也可以把 root 设为另一个由调用方提供的组件。 Agent prompt 和客户端 Library 必须限制为相同的组件名称。

includeDefaultComponents 控制的是 Library vocabulary。从 openui 主入口导入 factory 时,该入口仍会静态引用默认 catalog。如果未选择的 catalog modules 不应 进入静态依赖图,请像上面的示例一样使用 openui/explicit 和逐组件子路径。

渲染嵌套 component values

如果自定义 prop 接受 child components,请使用 component render contract 中的 renderNode。它会通过 active Library 递归渲染 elements、arrays 和 primitive values。

export const Panel = defineComponent({
  name: 'Panel',
  description: 'A titled container for generated child content.',
  props: z.object({
    title: z.string(),
    children: z.array(z.any()),
  }),
  component: ({ props, renderNode }) => (
    <view className='Panel'>
      <text className='PanelTitle'>{props.title}</text>
      <view className='PanelBody'>{renderNode(props.children)}</view>
    </view>
  ),
});

不要在自定义组件里调用 generated functions,也不要执行 generated strings。让 OpenUI parser/runtime 在 props 到达 renderer 前完成表达式求值。

交互组件使用的 runtime hooks

自定义组件可以使用 @lynx-js/genui/openui 导出的 hooks:

Hook用途
useRenderNode()渲染嵌套 generated values。
useTriggerAction()执行 ActionPlan 或发出宿主 action。
useIsStreaming()Generated text 不完整时禁用交互。
useIsQueryLoading()读取是否有 Query 正在执行。
useGetFieldValue()从 runtime Store 读取 $variables 或 form values。
useSetFieldValue()更新 form state,并按需触发持久化。
useSetDefaultValue()Streaming 结束后初始化字段,同时避免覆盖用户输入。
useOpenUI()高级接入时访问完整 runtime context。
useFormValidation() / useCreateFormValidation()读取或创建自定义 form validation boundary。

这些 hooks 都必须运行在 <OpenUiRenderer> 下方。交互组件应遵守 isStreaming,防止用户操作尚未完成的模型 response。

JSON Schema 与 parser utilities

在 renderer 外解析或检查 OpenUI 时,可以使用 Library schema:

import { createOpenUiLibrary, createParser } from '@lynx-js/genui/openui';

const library = createOpenUiLibrary();
const parser = createParser(library.toJSONSchema(), library.root);
const result = parser.parse('root = Stack([TextContent("Hello")])');

在 renderer 外解析流式文本时,使用 createStreamingParser。如果 UI 保存累计 response,可以调用 set(fullText);如果只传新增 delta,则调用 push(chunk)。

渲染模型输出时,传入累计 response 和 isStreaming;renderer 已经管理自己的 streaming parser,无需另行解析同一份文本。接入方式见 流式模型输出。

如果同一 response 已经交给 renderer,优先使用它的 onParseResult callback,避免 为了检查 root、stateDeclarations、data statements 或 meta diagnostics 而 创建第二个 parser。

保持 Agent contract 一致

只把组件加入 ReactLynx Library 还不够:Agent 必须收到相同的名称、位置 schema 和描述。默认 prompt 入口刻意保持 headless,使 server code 不需要导入 ReactLynx 或组件 CSS。使用自定义组件时,需要定义匹配的 headless prompt entries,并传给 buildOpenUiSystemPrompt。

完整 CLI 与程序化流程见 System Prompts。