GEN-UI / 2026  ·  知识介绍 归档于 生成式 UI · 平台工程 内部知识文档 · 中文 15 章
生成式 UI · 平台知识 · Nº 01

让模型描述界面,而不是手写页面.

模型调用平台工具 present_ui,用自然语言说明「要一块什么样的界面」;平台按已发布的组件目录生成界面规格,对话客户端再把组件从 Registry 拉下来画出来。页面不落在模型的上下文里,而是落在你们的组件库里。

07 组成部分合同到平台
04 CLI 子命令create / sync / publish / zip
12 常见坑逐条可查
↳   读法:先看第 I 章对号入座,再跳到你要做的那一节
FIG. 01 / GEN-UI 插图 Nº 01 present_ui · json-render 一次界面的生命周期
链路图 / Lifecycle 01 — 07
01 管理员开启生成式 UI在 Manage 里选 Kernel 与允许使用的领域组件 Manage
02 用户跟 Agent 说话平台把该 Agent 的 gen_ui_runtime 交给客户端 runtime
03 SDK 注册 Remote按 runtime 里的地址去 Registry 注册组件远端 SDK
04 模型调用 present_uidescription 说明界面,data 带原始业务数据 tool
05 平台生成 Spec按 Catalog 生成 json-render Spec,送 Registry 校验 validate
06 流式事件推送不合格让模型重试,合格后推补丁与状态 patch
07 画出 PresentationView加载 Kernel 与领域 Pack,在对话流或侧栏渲染 render
两件正交的事:画在哪 × 等不等用户 → IX
I. 概览 / Orientation • 你在做的事 → 先看哪一节 001 / 015
第一章 · 对号入座

先找到你要解决的那件事.

怎么用这一章 这张表是全文的入口。同一件事可能横跨两节——比如「发布 Pack」既要看第 IV 章的 CLI,也要看第 VII 章的提交模型。

01 做业务组件并发布在自己的业务仓库里写组件、生成目录、传到 Registry IVCLI 怎么用 VPack 怎么创建和发布
02 把自有对话客户端接上不用平台自带的 web-ui,自己接一套前端 VISDK 怎么集成
03 让 Agent 帮你写 Pack / 接客户端两项工作都能让编码助手代劳——把 gen-ui 的 skill 装进你的编码助手,命名、meta.ts、SDK 接线都不用自己记 Vskill 怎么装
04 查看已发布的包、预览、导入导出给人看的 Registry 管理界面 VIIRegistry CMS 怎么查看
05 把 test3 的包搬到 show1 或生产三套环境各自独立,没有自动复制 VIII跨 Registry 同步
06 让某个 Agent 能画界面平台侧的开关、Kernel 选择、组件白名单 VIII在 agents-platform 里配置
Ø
一句话理解全局 这七块都读同一个合同包 @agents-platform/gen-ui-contract。它是彼此之间的共同语言,不是某一块的私有格式。平台 API 只做 HTTP 客户端,不把组件产物存进自己的库——产物在对象存储,元数据在 PostgreSQL 的 gen_ui schema,Registry 不把 zip 本身当数据库。
II. 组成部分 / Anatomy • 各管什么,边界在哪 002 / 015
第二章 · 七块东西

每块东西只管一件事.

读法 业务项目一般不直接依赖合同包;真正要动手的只有 CLI、SDK 和领域 Pack 三块。Kernel 与 Registry 是平台侧的地基。

拓扑 / Topology 模型 → 平台 API → Registry / 客户端
Source 模型

调用 present_ui,给出 description 和 data。不手写 Spec。

Tool present_ui

向 Registry 要 Catalog 和 prompt,生成 Spec 并送去校验。合格后把流式事件直接推给客户端。

Store Registry

存元数据与构建产物,提供 Catalog、prompt 和校验。不调用模型,也不推界面。

Render 对话客户端 + SDK

从 Registry 加载 Kernel 与领域 Pack,吃 present_ui 的事件,画出界面。

Registry → Kernel 与领域 Pack 提供可渲染组件 FIG. 02
01 合同@agents-platform/gen-ui-contract Pack 元数据、Catalog、校验的共同形状。 业务项目一般不直接依赖它——它是其余各块之间的共同语言,改它等于改所有人的接口。
02 Registrygen-ui/registry 存 Pack 元数据与构建产物,提供 Catalog 与校验。 它不调用模型。可以理解成一个「知道所有组件长什么样」的服务,被平台和客户端同时查询。
03 CMSgen-ui/registry-cms 给人看的管理页:清单、Schema、预览、导入导出、运行时配置。 打开它需要 Registry 配了 publish token 时输入同一个 token;内测环境通常不设 token,打开即可。
04 SDK@agents-platform/gen-ui-sdk 对话客户端用来激活会话、吃流式事件、把界面挂到消息流或侧栏。 不打包 React,也不依赖 @json-render/shadcn。平台自带的 web-ui 已经接好。
05 CLI@agents-platform/gen-ui-cli · 命令名 gen-ui 在业务仓库里创建 Pack、同步合同、构建发布、打 zip。 需要 Node.js 22+。四个子命令各自都可以加 --help。
06 Kernelkernel-shadcn 平台自带的基础组件(按钮、输入、卡片、表格等)和主题变量。 它是布局底座。关掉「使用 Kernel 组件」时主题仍然在,只是 Catalog 里不再列出 Kernel 组件。
07 领域 Pack业务仓库里用 CLI 创建 你们自己的界面,例如请假单、订单卡片。 一个用户能单独指认的界面,一个目录:src/components/<kebab-case>/。
08 平台src/agents_platform/agent/gen_ui · Manage · web-ui 按 Agent 配置解析 Catalog、生成 Spec、把事件推给客户端。 产物走对象存储,元数据走 PostgreSQL 的 gen_ui schema。
III. 运行链路 / Lifecycle • 从说话到画出来 003 / 015
第三章 · 七步

一次界面是怎么出现的.

关键判断点 第 5 步的校验决定了模型能不能「蒙混过关」。GEN_UI_VALIDATE_RESOLVED_STATE 默认开启,会把能静态求值的状态表达式代入后再校验。

1

开启生成式 UI

管理员在 Manage 里给 Agent 打开生成式 UI,选好 Kernel 和允许使用的领域组件。

Manage
2

拿到 runtime

用户跟这个 Agent 说话。平台把该 Agent 的 gen_ui_runtime 交给客户端,SDK 按里面的地址去 Registry 注册 Remote。

SDK
3

模型描述界面

模型调用内部工具 present_ui,用自然语言 description 说明要什么界面,用 data 带上原始业务数据。

Model
4

生成并校验 Spec

平台按 Catalog 生成 json-render Spec,送到 Registry 校验。不合格就让模型重试。

Registry
5

渲染出来

合格后通过流式事件把补丁和状态推给客户端。SDK 加载组件,画出 PresentationView。

Client
↻
新建 vs 更新 新建界面时模型只写 description,不手写 Spec。更新已有界面时带上 presentationId 和 updateMode:state 只改数据,spec 按描述增量改,replace 按描述重建。

两件正交的事

「画在哪」和「等不等用户」是两个独立维度,组合出四种形态:横轴是 surface(inline / artifact),纵轴是等待用户。

侧栏里的表单就是 artifact 加 interrupt(等待用户)——两个维度都取到,才有「Agent 停住、等你在侧栏填完」这套交互。

理解这四条之后,第 X 章的 SDK 挂载规则和第 XIV 章的 Manage 开关就都能推导出来了——那四个开关正是按这张表切的。

inline · 对话流里 artifact · 侧栏 不等用户 流式卡片最常见的形态。界面跟着回复一起长出来,用户看完就继续对话。 参考资料面板新 artifact 默认自动选中;不抢焦点用 setGenUiAutoOpenArtifact(false)。 等待用户 inline 表单未提交时同样画 PresentationView。提交后有 submittedSummary 只显示这一句,否则 Host 用 useInterruptSubmitted 展开字段。 侧栏表单Agent 停住,等用户提交后再继续。这是提问式交互的主形态。
IV. CLI / Command Line • 四个子命令 004 / 015
第四章 · gen-ui

在自己的仓库里造一个 Pack.

前置条件 安装 @agents-platform/gen-ui-cli 后即可使用命令 gen-ui,需要 Node.js 22+。

i. Scaffold

gen-ui create

在当前目录下生成 Pack 项目。写在当前工作目录,不写进平台仓库。

bash · 创建
gen-ui create leave-request --css tailwind --no-demo
gen-ui create leave-request --css stylex --bridge --react 17 --storybook
ii. Generate

gen-ui sync

用各组件的 meta.ts 生成 pack.json 与 Module Federation exposes。

bash · 同步
gen-ui sync
gen-ui sync ./leave-request
iii. Ship

gen-ui publish

把 dist 或 zip 发到 Registry。

bash · 发布
gen-ui publish ./leave-request --build \
  --registry https://www.djzctest3.gm/gen-ui-registry
iv. Bundle

gen-ui zip

只打 Pack Bundle zip,不访问 Registry。换环境、或先给别人审包时用。

bash · 打包
gen-ui zip ./leave-request --build \
  --out ./leave-request-v1.zip

三条命名约束

这三条在 create 时就该定下来,改起来牵连 Registry 上的存量。

  • 项目 id = 目录名 = package.json 的 name = Registry 包 id。同一个字符串:小写字母开头,只含 a-z、0-9、-,例如 leave-request。
  • typeName 必须带包 id 的 Pascal 前缀。领域组件的 typeName 不以包前缀开头时,Registry 直接拒收。
  • 产物路径只允许字母、数字、._~- 和 /。越界的文件名会在发布阶段被挡下。
text · 命名推导
包 id          leave-request
本地组件名     Card
────────────────────────────────
typeName      LeaveRequestCard
              ↑ 包 id 的 Pascal 前缀 + 本地名

package.json  "version": "1"
Registry      记成 v1
!
示例目录会进发布产物 --demo 默认生成示例 card/,而且会进发布产物。正式包用 --no-demo,或发布前删掉该目录再 sync。
create 选项 · 交互终端会追问没传的选项;非交互必须带上项目 id,其余用默认值
选项 含义
--css tailwind|stylex 样式方案,默认 Tailwind。前缀都是 gui,颜色跟 Host 的主题变量走。
--bridge 组件自带 React,挂在 ShadowRoot 里,与 Host 的 React 隔离。
--react 17|19 只在 --bridge 下有效,默认 17,方便旧项目迁入。不加 --bridge 时与 Host 共享 React 19。
--storybook 同时生成 Storybook。
--demo / --no-demo 是否生成示例 card/。默认生成,而且会进发布产物。正式包用 --no-demo。
publish 选项 · 直接发 zip 时主线已经钉在包里,不能再加 --mode / --major / --build
选项 含义
--registry Registry 根地址。没传则用环境变量 GEN_UI_REGISTRY_URL。
--token 发布凭证。没传则用 GEN_UI_REGISTRY_PUBLISH_TOKEN。Registry 没配 token 时可以不传。
--mode overwrite|new-major 覆盖当前主线,或新开一条主线。交互终端会问;非交互默认 overwrite。
--major vN 覆盖指定主线,默认用 package.json 的主线数字。
--build 发布前先 pnpm run build。不加就上传现有 dist。
§
发布后自检 Registry 上该包的主线应与 package.json 的 version 一致——"1" 对应 v1。create 结束、pnpm build、publish、zip 都会跑 sync,所以平时改完 meta.ts 或入口,手动跑一次 gen-ui sync 就够了。
V. Skill / Agent 侧 • 让 Agent 自己会写 Pack 005 / 015
第五章 · 装一次,之后交给 Agent

把 gen-ui 的 skill 装给你的 Agent.

这一步是可选的 上一章的四个子命令,你自己敲也完全能走完。装 skill 的意义是让 Agent 知道 meta.ts 该怎么写、typeName 该怎么起名、哪些约束越界会被 Registry 拒收——把这份文档里的规则变成它手上的工具。

0
第 0 步:先确认能连上 gitlab.gm Skill 源在内网 GitLab 上(gitlab.gm)。装之前先确认这台机器能访问该域名,并且已经配好 GitLab 的访问凭证——skills 会去拉仓库,拉不动就会在这一步直接失败,而不是等到装完才报错。
bash · 安装 skill
npx skills add https://gitlab.gm/GovProEleTrade/resource-data/data-resource-and-intelligent-procurement/agents-platform/-/tree/master/gen-ui/skills/gen-ui

一条命令,装完即用。skills add 接受的是仓库里那个目录的 URL,不是包名——所以升级要重跑这条命令,它不会自动跟着平台版本走。

↳
装完之后 Agent 多知道什么 meta.ts 三个必填字段该怎么填 · expose 与 typeName 的命名约束 · 什么时候该标 self · 本地怎么用 preview.tsx 验 · 发布前该跑哪几条命令。
源地址内网

gitlab.gm/GovProEleTrade/…/agents-platform 下的 gen-ui/skills/gen-ui 目录。换分支就换 URL 里的 master。

装在客户端不是平台

装在你写代码的那台机器的 Agent 里,和 Registry、Kernel 无关。团队成员各装各的。

!
skill 不是发布链路的一环 它只影响写的时候。装没装、装的是哪个版本,都不会改变 gen-ui sync 生成什么、Registry 收不收——那两件事只认 meta.ts 和校验规则。
VI. Pack / 组件目录 • meta.ts 与提交模型 006 / 015
第六章 · 一个目录一个界面

写给模型看的那份说明.

心智模型 meta.ts 不是给人看的文档,而是模型的选择依据。description 写得好不好,直接决定模型会不会在正确的场景挑中你的组件。

meta.ts · 手写来源

组件目录的唯一真相。扫描路径 src/components/*/meta.ts。改这个文件,不要改产物。

expose 必需
./ 加一个大写开头的名字,例如 ./LeaveForm。
description 必需
写给模型。说明组件做什么、什么需求下选用、用户怎么操作。这三件事缺一件,模型就会误选或漏选。
props 必需
z.object(...),每个字段 .describe()。只放要由模型填写的数据,组件内部状态留在组件里。
interaction
不写就是 bound。组件自己带提交按钮时写 "self"。

两种入口模式

component 模式与 Host 共享 React 19;bridge 模式自带 React,挂在 ShadowRoot 里。

tsx · component 模式 — LeaveForm.tsx
// 入口是同目录的 LeaveForm.tsx
// 具名组件再 export default
// Module Federation 靠默认导出装载

export function LeaveForm(props: LeaveFormProps) { … }
export default LeaveForm;
tsx · bridge 模式 — bridge.tsx
// Remote 在影子树里自己创建 React root
export function mount(shadowRoot: ShadowRoot, props: Props) {
  return { update?, unmount };
}
↳
本地怎么验 写完在 src/preview.tsx 挂上组件,pnpm dev 看字段、样式和提交。确认后再 gen-ui sync → gen-ui publish . --build --registry "$GEN_UI_REGISTRY_URL"。
VII. 提交模型 / Who Submits • bound 与 self 007 / 015
第七章 · 唯一出口

谁按下那个提交按钮.

为什么重要 这条边界决定了生成器会不会给你多造一条提交链。选错会让界面上出现两个提交入口,或者一个都没有。

bound默认

Spec 负责绑定状态和提交控件,通常是 Kernel 的输入框和按钮。

你只描述数据形状,提交壳由平台生成。大多数表单用这个。

self组件自己提交

提交权在组件里。Catalog 会标 [self submit],生成器不再为它造另一条 Input/Button 提交链。

  • Registry 拒绝「self 组件在场时还出现 submitInterrupt」
  • Registry 拒绝「self 组件 props 里出现 $bindState」
  • 只读 $state 仍然合法
  • Kernel 不能声明 self
!
不能在 Pack 里直接调 useGenUiCapability 这个 hook 只给 Host 侧的 PresentationView 用——它读的是 Host 那份 React Context,构造出能力对象,再作为保留 prop genUi 注入组件。

Pack 与 Host 各带一份 SDK,两边的 Context 互相看不见。所以在 Pack 里再调一次,拿到的是空能力。Pack 只使用注入进来的 genUi;引入 SDK 时只作类型导入(import type),不要进运行时依赖。

tsx · self 提交 — 唯一出口 submit()
import type { GenUiCapability } from "@agents-platform/gen-ui-sdk";

type LeaveState = { reason: string; days: number };

<button
  type="button"
  disabled={!genUi?.canSubmit || !reason.trim()}
  onClick={() =>
    void genUi!.submit({ reason, days }, { summary: `请假 ${days} 天` })
  }
>
  {genUi?.submitting ? "提交中…" : "提交"}
</button>
submit(state, { summary })
唯一出口

等待用户的块走 Host 的 resume:表单数据仍是快照盖上显式 state;非空 summary 放进保留信封 $genUi,平台剥成 submittedSummary,不进 submittedData。inline / artifact 走 submitMessage,summary 就是那条气泡。

canSubmit
直接绑 disabled

Host 没接桥、已经提交过、或这一笔还在途,它都是 false。不需要自己维护这套状态。

×
提交之后不能反悔 等待用户的块提交过后不能改走发消息。这是单向的:一旦用 resume 交了,就沿着等待用户那条路走完。组件里 interruptStatus === "submitted" 的分支只用于重新打开 artifact 时回显表单值,不承担消息流里的记录。
VIII. 样式 / Theming • --gui-* 与 Host 主题 008 / 015
第八章 · 不写死颜色

暗色与换肤跟着 Host 走.

一条规则 Pack 不定义 --gui-* 变量,组件里不写死颜色。这套变量由 Kernel 在运行时注入。

Tailwind

类名经 src/cn.ts 的 cn() 处理,自动补上前缀。

tsx · cn() 前缀
cn("border-border")
// → "gui:border-border"

StyleX

用 src/theme.stylex.ts 的令牌,不写裸值。

tsx · theme tokens
t.border
t.radiusLg
--gui-*
颜色与圆角

只用脚手架接好的 --gui-*,作用域是 .gui-scope。暗色和换肤会跟着 Host。

preview-theme.css
本地预览

本地预览页的 .gui-scope 在 src/preview-theme.css,只服务 pnpm dev 和 Storybook。真正上线后,主题由 Kernel 注入。

treeshakeCompensation
别动这两个开关

StyleX 项目保留 rsbuild 的 treeshakeCompensation: true,以及 Storybook 的 lazyCompilation: false,否则开发预览会没有样式。

.dark
暗色怎么生效

PresentationView 在 Kernel remote 可用时自己注入 --gui-*。暗色跟祖先或自身上的 dark 走(.dark .gui-scope 或 .gui-scope.dark)。

IX. SDK / Integration • 四条会话入口 009 / 015
第九章 · 接自己的客户端

四条入口,一个 sessionKey.

适用范围 平台自带的 web-ui 已经接好。这一章讲的是把另一套对话客户端接上时要做什么。客户端需要能拿到本平台的 Agent runtime、LangGraph 形状的消息,以及流式 custom。

0
第 0 步:先初始化 Module Federation 在调用 SDK 之前,用宿主自己的 Rsbuild、Vite 或 Webpack Federation 插件创建全局 runtime。remotes 留空,Remote 由 SDK 按会话注册;shareStrategy 用 version-first。SDK 只调用全局的 registerRemotes / loadRemote,不会自己再 createInstance。runtime 没初始化就注册 Remote,会直接失败。
共享依赖 · 全部 singleton: true、eager: true,让 component 模式的 Pack 与 Host 共用一份 React 19 和 json-render
包 requiredVersion
react、react/jsx-runtime、react/jsx-dev-runtime ^19.1.0
react-dom、react-dom/client ^19.1.0
@json-render/core、@json-render/react ^0.20.0,并写 version: "0.20.0"

bridge 模式的 Pack 自带 React,不走这张表。同一会话可以并存多条主线,Remote 名里带主线(id__vN)。

i.

激活

activateGenUiSession

Agent runtime 响应里的 gen_ui_runtime 交给 activateGenUiSession。切会话后只处理当前 key 的事件。

registryBaseUrl 必须是带协议的绝对地址。以 / 开头的值会被拼到当前页面的 origin 上;业务站点的域名不是平台网关时,不能写 /gen-ui-registry。
runtime 可以晚到先用空 runtime init,配置到了再补写。日常路径不要为了补配置再调一次会清空已画块的 init。
ts · 激活
await activateGenUiSession(sessionKey, runtime, {
  registryBaseUrl: "https://www.djzctest3.gm/gen-ui-registry",
});
ii.

流式

dispatchGenUiEvent

custom 里 type 为 gen-ui-runtime、gen-ui-patch、gen-ui-state 的整条事件交给 dispatchGenUiEvent(sessionKey, event)。

其余 custom 留给 Host 自己处理——SDK 只认这三个 type。
↳
三个事件类型 gen-ui-runtime 补配置 · gen-ui-patch 改界面结构 · gen-ui-state 改数据
iii.

历史

hydrateGenUiRuntimes / hydrateGenUiPresentation

runtime 绑定完成后再水合,避免块先到、Remote 还没注册。

历史块只来自工具 present_ui 的结果,以及正文为晋升信封(type: "agents-platform.gen-ui-promotion")的 task 消息。
ts · 水合顺序
await hydrateGenUiRuntimes(
  sessionKey,
  resolvedRuntimesFromMessages(messages),
);
for (const presentation of presentationsFromMessages(messages)) {
  await hydrateGenUiPresentation(sessionKey, presentation);
}
iv.

提交桥

setGenUiHostBridge

这是 Pack 里 genUi.submit 的出口。切会话或卸载时传 null。

submitMessage 收到{ text, state?, presentationId, surface }。正在流式输出时拒绝它,避免并发 run。
等待用户只走 resume。载荷可能带保留字段 $genUi(组件的 summary),必须原样转发给 LangGraph。要替换 continue 路径默认的键值气泡,在桥上提供 renderSubmitMessage。
ts · 提交桥
setGenUiHostBridge({
  resume: (payload) => resumeInterrupt(payload),
  submitMessage: async (input) => {
    if (streaming) return;
    await sendMessage({ text: input.text });
  },
});
X. 挂载 / Mounting • tool_calls 里找 present_ui 010 / 015
第十章 · 谁认领哪一块

把块挂到它该在的位置.

边距与对齐 留在 Host 的 className。只有要换成自己的提交壳时才传 onInterruptSubmit 和 renderSubmit,提交前 markInterruptSubmitted。

tsx · 遍历 tool_calls
const snapshots = usePresentations();
const target = resolvePresentUiTarget(
  call, laterMessages, messages, snapshots,
);

target 给出 presentationId、surface、interrupt(是否等待用户)。同一 id 后一次成功调用盖过旧位置。工具结果 status === "error" 时不认领这块,错误文案由 Host 自己画。

↳
消息尾部补漏 还没被工具占位的 inline 流式块,从 usePresentations("inline") 里取,去掉等待用户的、去掉历史已恢复的 id、去掉 claimedPresentUiIds,剩下的就是要补的。
inline对话流

消息流里 <PresentationView presentationId={id} className="..." />。

若同时等待用户:未提交时同样画 PresentationView。提交后由 Host 画只读卡片:工具结果或 useInterruptSubmittedSummary(id) 有 submittedSummary 就只显示这一句,否则用 useInterruptSubmitted(id) 展开字段。

artifact侧栏

消息流入口用 ArtifactRef(className / children 画卡片)。点击会 selectArtifact。

侧栏用 useSelectedArtifact() 的 id 再画 PresentationView。新 artifact 默认自动选中;不抢焦点就 setGenUiAutoOpenArtifact(false);收起用 closeArtifact()。

!
块挂上了但组件失败 原因在 PresentationSnapshot.error。默认用 SDK 内置提示;renderError={(message) => …} 换成自己的样式;renderError={false} 则不渲染。
XI. CMS / Registry 管理页 • 清单 · Schema · 预览 · 运行时配置 011 / 015
第十一章 · 给人看的界面

浏览器里查看已发布的包.

凭证 Registry 配置了 GEN_UI_REGISTRY_PUBLISH_TOKEN 时,打开 CMS 要输入同一个 token。它写入浏览器,供导出、导入、改运行时配置使用。内测环境通常不设 token,打开即可。

CMS 入口 · 浏览器通过平台域名打开
环境 CMS 地址
test3 https://www.djzctest3.gm/gen-ui-registry/cms/
show1 https://www.djzcshow1.gm/gen-ui-registry/cms/
生产 只在内网 http://<内网IP>:30033/gen-ui-registry/cms/,不对公网开放
Runtime Config 入口 · 打开就是运行时配置页,按环境换域名即可
环境 Runtime Config 地址
test3 https://www.djzctest3.gm/gen-ui-registry/cms/runtime-config
show1 https://www.djzcshow1.gm/gen-ui-registry/cms/runtime-config
生产 只在内网 http://<内网IP>:30033/gen-ui-registry/cms/runtime-config
01 打开运行时配置页用上面的地址,或从 CMS 顶栏进去 要先用 publish token 登录,内测环境通常不设 token
02 点 新增配置项一行一个键:键名 / 值 / 说明 键名建议带前缀,例如 leave.apiBase,避免不同 Pack 撞名
03 保存即生效平台写进 gen_ui_runtime.runtime_config 下一次该 Agent 的会话带上解析好的配置,已开的会话不会自己刷新
04 Pack 里用 getRuntimeConfig() 读Host 激活会话时把它注给 SDK 读不到时返回空对象,组件要自己兜底

Registry 视图

左侧是包列表,可按 Kernel / 领域包筛选和搜索。点进一个包,有四个页签。

页签 看什么
Exposes 暴露了哪些组件、typeName、说明
Schemas 每个组件的 props 合同
Majors 已发布的主线
Playground 用 Storybook 预览(发布时带了 Storybook 才会有)

顶栏 API 文档 打开这份 Registry 的 OpenAPI。Manage 里 Agent 的「生成式 UI」页也有「打开 Registry CMS」。

Runtime Config 视图

浏览器可以读到的键值,例如领域组件要用的 API 根地址。平台把它放进 gen_ui_runtime.runtime_config,Host 激活会话时注入 SDK,Pack 用 getRuntimeConfig() 读取。

页面分两块:上面是键值表(键名 / 值 / 说明三列),右上角 新增配置项 加一行;下面是 SDK 使用方式,把注入和读取两条路写成代码给你抄。空配置时表里显示「暂无配置项」——不报错,就是读不到。

ts · 写入这一侧 — 平台塞进 gen_ui_runtime
// 平台按 Agent 解析配置,随会话下发
gen_ui_runtime.runtime_config = {
  "leave.apiBase": "https://www.djzctest3.gm/leave-api",
  "leave.env":     "test3",
};
tsx · Host 这一侧 — 激活会话时注入
await activateGenUiSession(sessionKey, runtime, {
  registryBaseUrl: "https://www.djzctest3.gm/gen-ui-registry",
  // 想覆盖 Registry 下发的值,在这里合并后再传
  runtimeConfig: runtime.runtime_config ?? {},
});
tsx · Pack 这一侧 — 领域组件里读
const { apiBase, env } = getRuntimeConfig();

// 配置缺失时给一个能跑通的兜底,别让组件白屏
const base = apiBase ?? "/fallback-api";
↳
换环境要重填 配置跟着 Registry 走,不跟着包走。同一个包搬到 show1 之后,两边的键名要对齐,值各填各的。跳过这一步的典型症状,就是组件里 getRuntimeConfig() 拿到空对象。
!
不要放密钥 这份配置会下发到浏览器。写进去的秘密等于公开。换环境后 Runtime Config 要在目标 CMS 里另行填写——它不在 Pack Bundle zip 里。
↳
读写权限 读接口公开,并带 Access-Control-Allow-Origin: *,所以 Manage 可以跨域拉包清单。写接口(发布、导入导出、改运行时配置)与 publish token 共用。

常用只读接口

把根换成上面的 Registry 根即可。types 是领域组件白名单——不传则该 Pack 的组件都在。

Kernel 组件默认保留,因为它是布局底座;关掉「使用 Kernel 组件」时主题仍然在,只是 Catalog 里不再列出 Kernel 组件。

http · 只读地址
GET /catalog?kernel=kernel-shadcn@v1&packRefs=leave-request@latest
GET /catalog/prompt?kernel=kernel-shadcn@v1&packRefs=leave-request@v1&types=LeaveRequestForm
GET /health
XII. 同步 / Portability • Pack Bundle 与两条路径 012 / 015
第十二章 · 搬包

把包从一处搬到另一处.

前提 test3、show1、生产各自有一份 Registry:元数据在各自的库,产物在各自的桶 agents-platform-gui。没有自动复制。

↻
搬的是什么 Pack Bundle 是一个 zip,里面是若干主线槽的清单和构建文件。运行时配置不在这个 zip 里——换环境后,Runtime Config 要在目标 CMS 里另行填写。

路径 A · 用 CMS

推荐。有可视化勾选,能看到写入/跳过结果。

1

源 Registry 的 CMS,顶栏点「导出」

勾选要搬走的包和主线(可按 Kernel / 领域、关键字筛选)。默认会选中当前列表里的主线。

源 CMS
2

下载 zip

包详情里的「导出此包」只导出当前这一个包。目标 Registry 若要求 token,导入导出都会带上你登录 CMS 时填的凭证。

源 CMS
3

目标 Registry 的 CMS,点「导入」

看结果:写入几条、跳过几条。内容 digest 与目标上同一主线相同的槽会跳过;内容不同则写入(覆盖该主线槽)。

目标 CMS

路径 B · 用 CLI

适合自动化或源环境不可达 CMS 时。

bash · 打包 + 推送
gen-ui zip . --build --out ./leave-request-v1.zip

gen-ui publish ./leave-request-v1.zip \
  --registry https://www.djzcshow1.gm/gen-ui-registry \
  --token "$GEN_UI_REGISTRY_PUBLISH_TOKEN"
!
搬字节 vs 重新构建 zip 源不会重新 sync、也不会重新 build,主线以包内为准。直接对源目录 gen-ui publish . --registry <目标> --build 也可以,那是重新构建再上传,不是搬已发布的字节——要保证两边产物一致时,优先搬 zip。
↳
限制 上传上限约 200MB。导出依赖 Registry 能读到对象存储;没配 MinIO 时导出会失败。源环境若已有 CMS,也可以从源 CMS 导出 zip,再用 gen-ui publish <file.zip> --registry <目标> 推过去。
XIII. 平台配置 / Configuration • 环境变量与 Manage 开关 013 / 015
第十三章 · 让某个 Agent 能画界面

从环境变量到四个开关.

三类变量,三种用途 API 用集群内地址,Manage / web-ui 用构建期变量,对象桶单独一套。混用会拼出两层前缀。

环境变量 · API 侧
变量 含义
GEN_UI_REGISTRY_URL 这是集群内的根,不带浏览器前缀 /gen-ui-registry。例如测试环境容器内是 http://agents-platform-gen-ui-registry-service:30033。没配这个变量,Catalog 和校验都不会工作。
GEN_UI_VALIDATE_RESOLVED_STATE 默认开启。校验前把 Spec 里能静态求值的 $state / $bindState 换成 State 的实际值,这样「路径对、取值不符合 props」也会被打回让模型改。设为 false 则这些表达式整棵跳过取值校验。
GEN_UI_MINIO_BUCKET 对象桶用 agents-platform-gui,不要复用平台自己的 MINIO_BUCKET。

构建期变量

Manage 与 web-ui 在构建期用 VITE_GEN_UI_REGISTRY_URL。部署到平台域名时写成 /gen-ui-registry,浏览器会把它拼到当前页面的 origin。

§
部署细节 见仓库 docs/operations/gen-ui-registry-deployment.md。

Manage 里四个开关

编辑 Agent,页签「生成式 UI」,按序做完这四步。

  • 打开「启用生成式 UI」。关闭时 Agent 不会生成界面,会话也不加载 Pack。
  • 选 Kernel。从 Registry 选 Kernel Pack 和主线。默认 kernel-shadcn@latest;日常可钉成 kernel-shadcn@v1 这类明确主线。
  • 四类开关。按「是否等待用户」和「inline / artifact」组合。它们决定这类界面能不能使用 Kernel 的输入框、按钮等组件。
  • Pack 与 typeName。每个领域包一条,引用写成 <id>@vN 或 <id>@latest,并至少勾选一个 typeName。
!
等待用户时的空提交口 需要等用户提交时如果关掉 Kernel,又没有 self 组件,用户会没有提交入口。interaction: "self" 的组件始终自己提交,不受这些开关影响。关掉 Kernel 后只用领域 Pack 的组件,主题样式仍然生效。
↳
名单即权限 这里勾选的 typeName 就是该 Agent 允许模型使用的组件;没勾的领域组件既生成不出来,也校验不过。保存后,下一次该 Agent 的会话会带上解析好的 gen_ui_runtime。同一套配置对子 Agent 也生效:主 Agent 与子 Agent 使用的 Kernel 包 id 必须一致。
XIV. 术语 / Glossary • Pack 引用 · 主线 · typeName 014 / 015
第十四章 · 三个常用词

把三个词说清楚.

为什么单列一章 第 XII 章的稳定性和第 XIII 章的权限控制,全都建立在这三个词上面。混用它们是最容易出线上事故的地方。

Pack 引用Reference

形如 leave-request@v1 或 leave-request@latest。

@latest 解析该包当前最新主线;钉死 v1 则不受后续新主线影响。

主线Major

来自 Pack 项目 package.json 的 version。写成 "1",Registry 记成 v1。

同一主线可以覆盖发布;要保留旧产物就开新主线。

typeNameSpec 里的组件名

模型在 Spec 里使用的组件名。gen-ui sync 会给本地组件名加上包 id 的 Pascal 前缀。

包 leave-request、本地 Card,全名是 LeaveRequestCard。Manage 里勾选的就是这个全名。

XV. 踩坑 / Pitfalls • 十二条,逐条可查 015 / 015
第十五章 · 容易踩的地方

这些地方已经有人踩过.

怎么用 左边一栏偏「配置与集成」,右边一栏偏「Pack 与产物」。上线前对着扫一遍,比事后查日志便宜。

P01
registryBaseUrl 写成了 /gen-ui-registry

该路径只适用于挂在平台域名下的 web-ui / Manage。独立站点要写 test3 或 show1 的绝对地址,以 / 开头的值会被拼到当前页面的 origin 上。

P07
用 @latest 上了线

随后有人覆盖或新开主线,线上界面跟着变。要稳定就钉 vN。

P02
忘了初始化全局 Module Federation

SDK 注册 Remote 直接失败。SDK 不会自己 createInstance,宿主必须先建好全局 runtime。

P08
把密钥写进 Runtime Config

这份配置会下发到浏览器。值会进 gen_ui_runtime.runtime_config,谁都能读。

P03
create 时留着示例 card/

发布后 Catalog 里多出一个不该给模型用的组件。它进了发布产物,模型就可能挑中它。

P09
换 Registry 只导入了 Pack zip

忘了在目标环境补 Runtime Config,组件里 getRuntimeConfig() 为空。Runtime Config 不在 Pack Bundle zip 里。

P04
手改了 pack.json 或 src/generated/exposes.ts

下次 sync 会被覆盖。改 meta.ts。那两份是产物,不是来源。

P10
StyleX 项目改掉了两个开关

改了 treeshakeCompensation 或 Storybook 的 lazyCompilation: false,本地预览没有样式。

P05
typeName 没用包前缀

领域组件的 typeName 必须以包 id 的 Pascal 前缀开头,否则 Registry 拒收。

P11
self 组件与 Kernel 提交链冲突

Registry 会拒绝「self 组件在场时还出现 submitInterrupt」以及「self 组件 props 里出现 $bindState」。只读 $state 仍然合法。

P06
只调了 GEN_UI_REGISTRY_URL 却写成了浏览器前缀

API 侧要的是集群内根,不带 /gen-ui-registry。没配对,Catalog 和校验都不会工作。

P12
等待用户时关掉 Kernel 又没 self 组件

用户没有提交入口,Agent 停在那里等一个永远不来的提交。