npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@boteteam/a2ui-render

v0.0.39

Published

博特-A2UI 协议渲染引擎

Readme

A2UI 业务接入指南

本文档面向业务方,说明如何使用 @boteteam/a2ui-render 渲染 A2UI 页面,并覆盖常见接入场景:

  • 基础渲染
  • 协议版本切换
  • 消息更新与局部刷新
  • 事件回调与自定义动作
  • 样式覆盖(细则见 styleVars.md
  • 内置文案国际化
  • 支持 customComponents 自定义组件注册

1. 推荐接入方式

1.1 安装

npm install @boteteam/a2ui-render

1.2 基础使用

import React from 'react';
import { BaseRenderer, normalizeToLitProtocolMessages, type A2UIMessage } from '@boteteam/a2ui-render';

const rawMessages: A2UIMessage[] = [
  {
    beginRendering: {
      surfaceId: '@default',
      root: 'root',
    },
  },
  {
    surfaceUpdate: {
      surfaceId: '@default',
      components: [
        {
          id: 'root',
          component: 'Text',
          text: 'Hello A2UI',
          variant: 'h2',
        },
      ],
    },
  },
];

export default function A2uiPage() {
  const messages = normalizeToLitProtocolMessages(rawMessages);
  return <BaseRenderer messages={messages} protocolVersion="0.8" />;
}

2. 消息格式与渲染原则

2.1 运行时标准

BaseRenderer 运行时遵循 @a2ui/lit@a2ui/web_core 消息协议:

  • beginRendering
  • surfaceUpdate
  • dataModelUpdate

2.2 扁平写法兼容

业务常见的扁平结构可以直接传入,先经 normalizeToLitProtocolMessages 转换再渲染。

转换器已内置如下兼容能力:

  • component: "Text"component: { Text: {...} }
  • children: []children: { explicitList: [] }
  • text: "abc"text: { literalString: "abc" }
  • variant 映射为 usageHint
  • align 映射为 alignment
  • justify 映射为 distribution
  • action.event 展平为 action
  • text.call formatDate 执行后转 literalString
  • Icon.icon 自动映射为协议要求的 Icon.nameliteralString

2.3 协议版本选择

BaseRenderer 支持通过 protocolVersion 明确指定运行时分支:

  • protocolVersion="0.8" 使用 0.8 兼容分支
  • protocolVersion="0.9" 使用 0.9 分支
  • 不传时默认使用 0.8

2.4 v0.9 消息与 version 字段

  • v0.9 传输形态(createSurface / updateComponents / updateDataModel / deleteSurface不要求每条消息带 version: "v0.9";由 BaseRendererprotocolVersion 决定如何解析。
  • BaseRenderer 会在内部对传入的 messages 调用 normalizeToLitProtocolMessages(messages, protocolVersion);与 protocolVersion 不一致时会报错并展示解析面板(如「A2UI 消息协议不匹配」)。
  • 业务侧若仍手动调用 normalizeToLitProtocolMessages:建议第二参数与 protocolVersion 一致。不传第二参数时:若整批均为 v0.9 信封则按 0.9 推断,否则按 0.8 推断。
  • v0.9 下 @a2ui/web_core纯字符串DynamicString 不做 ${...} 插值;与官方 Playground 示例一致时,本包在 normalizeToLitProtocolMessages0.9 路径中会把含真实插值片段的字符串自动包成 formatString 调用(已写 \\${ 的转义文案不会误包)。
  • action:协议要求 { "event": { "name", "context?" } }{ "functionCall": … }。若误写为顶层 name / contextSurfaceModel.dispatchAction 不会 emit,按钮点击无回调。normalizeToLitProtocolMessages 会把该扁平形态自动包成 eventButton.primary 会映射为 variant: primary | default,以通过 strict schema。

示例:

<BaseRenderer
  messages={messages}
  protocolVersion="0.9"
/>

注意事项:

  • 单个页面会话内不要来回切换 0.80.9
  • 若需要切换版本,建议刷新页面后再渲染
  • 推荐通过 URL 参数或页面级配置固定当前页面协议版本

3. 如何更新消息与局部刷新

3.1 建议模式

建议拆分为两类消息:

  • 结构消息(beginRenderingsurfaceUpdate)通常固定
  • 数据消息(dataModelUpdate)按业务变化更新

3.2 局部数据刷新示例

import React, { useMemo, useState } from 'react';
import { BaseRenderer, normalizeToLitProtocolMessages, type A2UIMessage } from '@boteteam/a2ui-render';

const baseMessages: A2UIMessage[] = [
  { beginRendering: { surfaceId: '@default', root: 'root' } },
  {
    surfaceUpdate: {
      surfaceId: '@default',
      components: [
        { id: 'root', component: 'Text', text: { path: '/status' }, variant: 'h2' },
      ],
    },
  },
];

export default function A2uiStatusPage() {
  const [status, setStatus] = useState('On Time');

  const rawMessages = useMemo<A2UIMessage[]>(() => ([
    ...baseMessages,
    {
      dataModelUpdate: {
        surfaceId: '@default',
        path: '/',
        contents: [{ key: 'status', valueString: status }],
      },
    },
  ]), [status]);

  const messages = useMemo(() => normalizeToLitProtocolMessages(rawMessages), [rawMessages]);

  return (
    <>
      <button type="button" onClick={() => setStatus('Delayed')}>Set Delayed</button>
      <BaseRenderer messages={messages} />
    </>
  );
}

3.3 注意事项

  • 每次更新 messages 时,需包含完整上下文消息集合
  • 同一页面内保持 surfaceId 一致
  • 数据更新优先使用 dataModelUpdate,避免频繁重建组件树

4. 如何处理自定义事件

4.1 事件定义

在组件里定义 action,例如按钮点击:

{
  "id": "refresh-btn",
  "component": "Button",
  "child": "refresh-text",
  "action": {
    "name": "refresh",
    "context": {
      "source": "flight-card"
    }
  }
}

4.2 事件回调

BaseRenderer 通过 onAction 统一回调业务层:

<BaseRenderer
  messages={messages}
  onAction={(action) => {
    if (action.name === 'refresh') {
      // 业务刷新逻辑
    }
  }}
/>

其中 action 结构:

  • name 动作名
  • context 上下文对象

5. 如何覆盖样式

5.1 推荐入口

当前包提供两种样式入口:

  • className:业务命名空间,便于在 Less 中写局部选择器覆盖。
  • styleVars:CSS 自定义属性字典,写入 渲染根a2ui-surface 宿主,并在各组件 shadow 内注入本包维护的覆盖样式,使 var(--a2ui-*) 能稳定解析。
<BaseRenderer
  messages={messages}
  className="biz-a2ui-theme"
  styleVars={{
    '--a2ui-text-h2-size': '24px',
    'a2ui-color-primary': '#1677ff',
  }}
/>

styleVars 的 key 支持 --tokentoken(会自动补成 --token)。不建议把普通 CSS 属性如 color 塞进 styleVars,非变量样式放在业务 Less 即可。

5.2 生效范围:0.8 与 v0.9 的共同点与差异

共同点

  • DEFAULT_A2UI_LIT_STYLE_VARS 中的键及你在 styleVars 里追加的 a2ui-* 变量名,两种协议下都会参与合并并注入宿主。
  • a2ui-color-primary 会同步到 --p-50,与内置回退链一致。
  • 本包对 Text、Row、Column、Card、Button、TextField、CheckBox、DateTimeInput、Slider、MultipleChoice 等做的 shadow 样式覆盖,在 0.8 与 v0.9 下同一套实现都会执行;选择器同时匹配 a2ui-*a2ui-basic-*,不因协议换 tag 而失效。

仅 v0.9 额外生效的路径

  • a2ui-surface:host 上注入 字号与圆角桥接,把 a2ui-text-h1-size 等映射为官方 Lit 使用的 --a2ui-font-size-2xl--a2ui-font-size-xs--a2ui-border-radius,因此同一套「规范字号变量」会同时影响 本包 shadow官方 basic 组件内部 typography
  • 官方主题大量使用 --a2ui-color-on-* 等语义色,可通过 styleVars 在宿主上覆盖并继承进深层 shadow;0.8 侧 Lit 包中一般不出现这组命名,多为 v0.9 对齐明暗时使用。

不建议依赖

  • 上游生成样式中的 --_a2ui-* 内部槽位,仅作临时调试,勿当作对外契约。

完整清单

  • 哪些变量在两种协议下均由本包消费、按组件列出每个 shadow 里出现的 var(--a2ui-*)、v0.8 与 v0.9 分节说明、以及 未做 shadow 覆盖的内置组件,见同目录 styleVars.md(与 shadow/*.cssdefaultA2uiLitStyleVars.ts 同步维护)。

6. 自定义组件能力

当前版本以官方 @a2ui/lit 标准组件为主,同时支持在渲染入口通过 customComponents 注入业务自定义组件。 自定义组件按当前这套机制,必须是 Web Component 构造器,本质上就是 HTMLElement 子类(或等价的自定义元素类)。

6.1 业务侧约定

  • 将自定义组件名称统一前缀,例如 BizChartBizTag
  • 组件配置保持纯 JSON 可序列化
  • 交互通过 action 回传,不在组件配置内嵌函数

6.2 接入方式

在渲染入口传入 customComponents,key 为协议组件名,value 支持两种写法:

  • 直接传自定义元素构造器
  • 传配置对象:{ elementCtor, tagName, schema }

示例:

import { BaseRenderer, type A2UICustomComponentRegistry } from '@boteteam/a2ui-render';

const customComponents: A2UICustomComponentRegistry = {
  // 写法一:直接传构造器
  BizChart: BizChartElement,
  // 写法二:传完整配置
  BizTag: {
    elementCtor: BizTagElement,
    tagName: 'biz-tag',
    schema: {
      type: 'object',
    },
  },
};

<BaseRenderer
  messages={messages}
  customComponents={customComponents}
/>

6.2.1 接入步骤小结

  1. 实现类:自定义元素类继承 HTMLElement(或等价),在类上完成展示与交互逻辑。
  2. 注册表:构造 A2UICustomComponentRegistrykey 与协议 JSON 里的 component 字符串完全一致(如 "BizTag")。
  3. 挂载渲染<BaseRenderer messages={…} protocolVersion="0.8" | "0.9" customComponents={customComponents} onAction={…} />
  4. 消息体:在 surfaceUpdate / updateComponentscomponents 数组里为对应 id 声明 component 及业务字段;保持可 JSON 序列化。
  5. 动作:业务侧在 onAction 中统一处理;自定义元素内通过 a2uiaction 上报(见下文)。

6.3 标准实现约定

是的,业务侧自定义组件建议按下面约定实现,这也是当前 BaseRenderer 的标准接入方式。

A 参数读取

  • 推荐优先读取 this.componentProps,它是渲染引擎统一归一化后的参数对象
  • 归一化会自动处理 literalString 等协议值包装,减少业务组件的重复转换逻辑
  • 仍可读取同名属性(如 this.subtitle),但建议统一走 componentProps 提升稳定性

B 事件上报

  • 通过 dispatchEvent(new CustomEvent('a2uiaction', ...)) 向引擎抛出业务动作
  • 事件建议设置 bubbles: truecomposed: true,确保能穿过 Shadow DOM 被外层 a2ui-surface 监听
  • detail 推荐扁平结构{ name: string, context?: Record<string, unknown> }(与 README 示例一致)。mergeLitActionPayload 同时兼容 v0.9 官方消息里常见的 detail.action 包裹形态;业务自定义组件优先使用扁平结构即可。

C 最小示例

class BizPromoCardElement extends HTMLElement {
  componentProps: any;

  connectedCallback() {
    this.renderCard();
    this.onclick = (ev: MouseEvent) => {
      const target = ev.target as HTMLElement | null;
      const isTitle = Boolean(target?.closest?.('[data-biz-title-action="1"]'));
      if (!isTitle) return;

      this.dispatchEvent(new CustomEvent('a2uiaction', {
        detail: {
          name: this.componentProps?.actionName || 'biz_promo_click',
          context: {
            source: 'BizPromoCard',
            title: this.componentProps?.title,
            badge: this.componentProps?.badge,
          },
        },
        bubbles: true,
        composed: true,
      }));
    };
  }

  private renderCard() {
    const title = this.componentProps?.title ?? 'Custom Card';
    const subtitle = this.componentProps?.subtitle ?? '';
    this.innerHTML = `
      <div>
        <strong data-biz-title-action="1">${title}</strong>
        <div>${subtitle}</div>
      </div>
    `;
  }
}

6.4 协议 v0.8 与 v0.9 下的注册与属性

| 项目 | v0.8 | v0.9 | |---|---|---| | 注册入口 | 写入官方 ComponentRegistryregister(typeName, ctor, tagName?, schema?) | 将自定义项与 basic catalog 合并为同一 catalogId 再交给 MessageProcessor;仍通过同一 customComponents 触发注册逻辑 | | 消息里组件名 | 常见为嵌套对象 { "BizX": { …props } },引擎侧会归一成 component: "BizX" + 平铺 props | "component": "BizX" 与标准内置组件相同风格 | | tagName 省略时 | 由运行时与 Lit 规则决定 | 默认生成为 a2ui- + 驼峰转短横线(如 BizPromoCarda2ui-biz-promo-card),并 customElements.define(已存在则跳过) | | schema 省略或非 Zod | 按运行时约定 | 使用宽松 Zod 占位(passthrough),便于通过校验;生产环境建议为自定义类型提供明确 Zod schema 与协议字段对齐 | | componentProps 来源 | 来自旧链路中的 component.properties 等 | 主要来自 context.componentModel.properties;引擎在 connectedCallback 与模型 onUpdated 时会再次归一化,复杂 UI 可在收到更新后自行重绘 |

createSurface.catalogId 推荐使用官方 basic catalog 的 URL(即 basicCatalog.id)。为兼容 Playground 等来源,也支持 standard / default / basic(不区分大小写)或空字符串,包内在交给 MessageProcessor 前会解析为 basicCatalog.id;其它值仍按字面匹配已注册目录,未注册则抛出 Catalog not found。自定义类型名只要不与其他内置组件冲突即可。

6.5 onAction 与双通道(v0.9)

  • 官方 Lit 组件:动作多走 @a2ui/web_coreprocessor.model.onAction 流。
  • 业务自定义原生组件:使用 a2uiaction 自定义事件。
  • v0.9BaseRenderer 同时订阅上述通道与 a2uiaction,保证内置表单与自定义元素都能触发同一 onAction 回调。
  • 回调入参经 mergeLitActionPayload 与 dataModel 等合并;自定义组件请使用 detail.name + detail.context,避免被误判为默认的 name: 'action'

6.6 使用 React 编写自定义块

本包 不支持customComponents 里直接注册 React 函数组件;协议与 Lit 渲染树均为 原生自定义元素

若业务必须用 React:

  1. 仍注册一个 HTMLElement 子类
  2. 在元素内部创建 挂载用 div,使用 React 18 的 createRootReact 17 的 ReactDOM.render 将 React 子树挂到该节点。
  3. disconnectedCallbackroot.unmount()unmountComponentAtNode,避免泄漏。
  4. componentProps 或协议更新后:引擎会更新归一化属性,元素内需 主动再次调用 render(例如在归一化完成后、componentModel.onUpdated 回调中),否则 React 仍显示旧 props。

自定义组件如何更新数据(v0.9)

与内置 TextField / CheckBox 一样,推荐把可编辑状态放在 Surface 的 DataModel 上,而不是只放在 React 本地 useState 里(除非你不打算让同 surface 上的 Button action.context 路径绑定mergeLitActionPayload 兜底读到该字段)。

Lit 在渲染自定义节点时会设置 this.context@a2ui/web_coreComponentContext)。其中 this.context.dataContext 提供与官方组件相同的读写能力:

| 能力 | 说明 | |------|------| | 读绑定值 | this.context.dataContext.resolveDynamicValue(dynamicValue)dynamicValue 与协议一致,例如组件 JSON 里的 { path: "/form/memo" }(与 this.componentProps.value 中归一化后的形态一致即可)。 | | 写绑定值 | this.context.dataContext.set(path, value)pathJSON Pointer 风格字符串(与协议里 path 相同,一般以 / 开头);value 为写入模型的标量或对象。相对路径会按当前组件数据作用域解析,表单根路径下通常直接使用 /form/字段名。 | | 何时重绘 React | 模型或其它组件改写同一路径后,订阅 this.context.componentModel.onUpdated,在回调里再次执行 ReactDOM.render / root.render,否则子树仍显示旧 UI。 |

在 React 子树里更新数据:在自定义元素的 renderReact 里把 dataContext.setresolveDynamicValue 封装成闭包,以 props 形式传给 React 组件(例如 onChange 写路径、value 为解析后的当前值),避免在 .tsx 里直接依赖 this 生命周期错位。

与「只上报 a2uiaction」的区别

  • dataContext.set:写入当前 surface 的 客户端数据模型,官方 Buttonaction.event.context 里若带有 path,提交时会在服务端可见的载荷中体现为已解析的字面量mergeLitActionPayload 在部分形态下也会用整段 form 等做兜底合并。适合 表单字段、与内置控件混排 的场景。
  • a2uiaction:不经过上述模型路径,仅在 onAction 中收到 detail.context(经 mergeLitActionPayload 处理)。适合 一次性操作、无需进 DataModel 的场景。

应用侧可参考 monorepo 中 chatBotA2UITest 页面:

  • custElement/BizReactCounterElement.tsx:内嵌 antd、本地计数与 a2uiaction 提交。
  • custElement/BizFormCustomField.tsx:内嵌 antd Input,通过 dataContext.set / resolveDynamicValue/form/customMemo 等路径绑定,并与 componentModel.onUpdated 联动重渲染。

工程提示:若使用 Umi 等构建工具,将实现文件命名为与 BizFormCustomFieldElement 类名不同的模块名(例如 BizFormCustomField.tsx)再 export class BizFormCustomFieldElement,再于 barrel index.tsexport { BizFormCustomFieldElement } from './BizFormCustomField',可避免删除同名 .ts 后解析仍优先命中 .ts 导致的 ENOENT

6.7 注意事项

  • customComponents 在每次 Lit 重渲染流程前会 先清空再按表注册;请保证同一渲染周期内注册表完整。
  • 重复注册同名 tag 时依赖浏览器 customElements.get 跳过;若更换 构造器 实现,需避免与已定义 tag 冲突。
  • 自定义组件名称建议带业务前缀,例如 BizChartBizTag
  • 组件配置保持 纯 JSON 可序列化;回传业务优先用 a2uiaction / 官方 action需要与同 surface 内置表单、提交按钮共享状态时,使用 context.dataContext 写回模型(见 6.6),不在消息里嵌入函数。
  • 使用 innerHTML 拼接协议字符串时,注意 XSS:展示前做转义或仅用 文本节点 / React 默认转义

7. 解析失败可视化

BaseRenderer 在解析失败时会显示错误面板,避免页面空白,支持展示:

  • 错误摘要信息
  • 可展开的堆栈详情

常见触发场景:

  • surfaceId 不一致
  • 消息结构不符合协议
  • customComponents 注册参数不合法

8. 内置文案国际化

渲染引擎内置了部分默认文案,可通过全局字典覆盖:

  • Please enter a value
  • No options found
  • Select items
  • Filter options...

默认已提供中文回退文案。业务可在页面初始化时注入 window.A2UI_I18N 覆盖:

window.A2UI_I18N = {
  'Please enter a value': '请输入内容',
  'No options found': '未找到可选项',
  'Select items': '请选择',
  'Filter options...': '筛选选项...',
};

9. 推荐最佳实践

  • 传入 messages 时可直接交给 BaseRenderer,内部会按 protocolVersionnormalizeToLitProtocolMessages 与校验;若业务侧已预处理,也可继续手动调用(须与 protocolVersion 一致)
  • 页面级固定 protocolVersion,避免运行时混用版本
  • 消息结构与数据更新分离管理
  • dataModelUpdate 做局部刷新
  • onAction 承接所有业务交互
  • classNamestyleVars 管理样式覆盖
  • 自定义组件优先通过 customComponents 统一注册