流式渲染 Streaming
@cloudflare/kumo

概述

Kumo catalog 模块支持从 JSON 结构渲染 UI,专为 AI 生成的界面而设计。它为基于 JSON 的 UI 树提供运行时校验、数据绑定、条件渲染和动作处理。

从代码库派生的 Schema

与需要维护独立 schema 定义的方案不同,Kumo 会从组件实际的 TypeScript 类型自动派生校验 schema。当你更新组件的属性时,校验 schema 会通过组件注册表 codegen 流程自动更新。无需手动同步——你的 schema 始终与组件保持一致。

Schema 由组件的 TypeScript 类型自动生成到 @cloudflare/kumo/ai/schemas 中。 修改组件属性后运行 pnpm codegen:registry 以重新生成。

工作原理

catalog 模块使用一条流水线,从你的 TypeScript 源码中提取组件元数据:

Component TSX → TypeScript Types → Codegen Script → Zod Schemas

ai/schemas.ts 中生成的 schema 包括:

  • 每个组件的属性 schema(如 ButtonPropsSchema)
  • 变体属性的枚举值
  • UI 元素与树结构的 schema
  • 动态值、可见性和动作的 schema

安装

import {
  createKumoCatalog,
  initCatalog,
  resolveProps,
  evaluateVisibility,
} from "@cloudflare/kumo/catalog";

创建 Catalog

创建一个 catalog 实例,用自动生成的 schema 校验 AI 生成的 JSON:

import { createKumoCatalog, initCatalog } from "@cloudflare/kumo/catalog";

// 创建带可选动作的 catalog
const catalog = createKumoCatalog({
  actions: {
    submit_form: { description: "Submit the current form" },
    delete_item: { description: "Delete the selected item" },
  },
});

// 初始化 schema(同步校验前必须调用)
await initCatalog(catalog);

// 校验 AI 生成的 JSON
const result = catalog.validateTree(aiGeneratedJson);
if (result.success) {
  // 渲染校验通过的树
  renderTree(result.data);
}

UI 树格式

UI 树采用为 LLM 生成与流式传输优化的扁平结构。元素之间通过 key 相互引用而不是嵌套,从而支持在元素陆续到达时渐进渲染。

{
  "root": "card-1",
  "elements": {
    "card-1": {
      "key": "card-1",
      "type": "Surface",
      "props": { "className": "p-4" },
      "children": ["heading-1", "text-1", "button-1"]
    },
    "heading-1": {
      "key": "heading-1",
      "type": "Text",
      "props": {
        "variant": "heading2",
        "children": "Welcome"
      },
      "parentKey": "card-1"
    },
    "text-1": {
      "key": "text-1",
      "type": "Text",
      "props": {
        "children": { "path": "/user/name" }
      },
      "parentKey": "card-1"
    },
    "button-1": {
      "key": "button-1",
      "type": "Button",
      "props": {
        "variant": "primary",
        "children": "Get Started"
      },
      "parentKey": "card-1",
      "action": {
        "name": "submit_form"
      }
    }
  }
}

为什么采用扁平结构?

  • 元素一到达即可渲染 (流式)
  • 无需深度遍历树即可轻松更新
  • 简单的序列化/反序列化
  • 与 LLM 逐 token 生成的方式天然契合

动态值 (数据绑定)

属性可以通过 JSON Pointer 路径引用数据模型中的值。这让 AI 能够声明数据绑定,由你的应用在渲染时解析。

import { resolveProps, resolveDynamicValue } from "@cloudflare/kumo/catalog";

// 支撑 UI 的数据模型
const dataModel = {
  user: {
    name: "Alice",
    isAdmin: true,
  },
  items: [
    { id: 1, title: "First Item" },
    { id: 2, title: "Second Item" },
  ],
};

// 带动态引用的 AI 生成属性
const props = {
  children: { path: "/user/name" },
  disabled: false,
};

// 解析所有动态值
const resolved = resolveProps(props, dataModel);
// { children: "Alice", disabled: false }

// 或解析单个值
const name = resolveDynamicValue({ path: "/user/name" }, dataModel);
// "Alice"

可见性条件

元素可以根据数据值、认证状态或复杂的逻辑表达式进行条件渲染。

import {
  evaluateVisibility,
  createVisibilityContext,
} from "@cloudflare/kumo/catalog";

const ctx = createVisibilityContext(
  // 数据模型
  { user: { isAdmin: true, role: "editor" } },
  // 认证状态
  { isSignedIn: true },
);

// 简单布尔值
evaluateVisibility(true, ctx); // true

// 路径检查 (真值测试)
evaluateVisibility({ path: "/user/isAdmin" }, ctx); // true

// 认证检查
evaluateVisibility({ auth: "signedIn" }, ctx); // true
evaluateVisibility({ auth: "signedOut" }, ctx); // false

// 相等性检查
evaluateVisibility(
  {
    eq: [{ path: "/user/role" }, "editor"],
  },
  ctx,
); // true

// 复杂逻辑
evaluateVisibility(
  {
    and: [
      { path: "/user/isAdmin" },
      { auth: "signedIn" },
      { gt: [{ path: "/items/length" }, 0] },
    ],
  },
  ctx,
);

可用操作符

操作符说明
path对数据路径做真值检查
auth”signedIn” 或 “signedOut”
eq / neq相等 / 不相等比较
gt / gte大于 / 大于等于
lt / lte小于 / 小于等于
and / or / not布尔逻辑组合符

动作

元素可以声明由你的应用处理的动作。AI 描述意图,由你的处理程序执行逻辑。

// 在你的 UI 树元素中
{
  "key": "delete-btn",
  "type": "Button",
  "props": {
    "variant": "destructive",
    "children": "Delete"
  },
  "action": {
    "name": "delete_item",
    "params": {
      "itemId": { "path": "/selected/id" }
    },
    "confirm": {
      "title": "Delete Item",
      "message": "Are you sure you want to delete this item?",
      "variant": "danger",
      "confirmLabel": "Delete",
      "cancelLabel": "Cancel"
    },
    "onSuccess": {
      "set": { "/selected": null }
    }
  }
}

// 创建 catalog 时注册动作
const catalog = createKumoCatalog({
  actions: {
    delete_item: {
      description: "Delete an item by ID",
      params: {
        itemId: { type: "string", description: "Item ID to delete" }
      }
    }
  }
});

校验

catalog 使用由组件 TypeScript 类型派生的自动生成 Zod schema,校验 AI 生成的 JSON。

// 校验完整的一棵树
const result = catalog.validateTree(aiJson);

if (result.success) {
  console.log("Valid tree:", result.data);
} else {
  console.error("Validation errors:", result.error);
  // [{ message: "Invalid enum value", path: ["elements", "btn-1", "props", "variant"] }]
}

// 校验单个元素
const elementResult = catalog.validateElement({
  key: "btn-1",
  type: "Button",
  props: { variant: "primary" },
});

// 检查可用组件
catalog.hasComponent("Button"); // true
catalog.hasComponent("Foobar"); // false

// 列出所有组件名
console.log(catalog.componentNames);
// ["Badge", "Banner", "Button", ...]

AI 提示词生成

为 AI 模型生成描述 catalog 的提示词:

const prompt = catalog.generatePrompt();

// 返回描述以下内容的 markdown:
// - 可用组件
// - 可用动作 (如有)
// - 输出格式 (UITree schema)
// - 动态值语法

// 用在你的 LLM 提示词中
const systemPrompt = `
You are a UI generation assistant.

${catalog.generatePrompt()}

Generate UI based on the user's request.
`;

类型导出

所有类型均已导出,便于与 TypeScript 集成:

import type {
  // 核心类型
  UIElement,
  UITree,
  DynamicValue,
  DynamicString,
  DynamicNumber,
  DynamicBoolean,

  // 可见性
  VisibilityCondition,
  LogicExpression,

  // 动作
  Action,
  ActionConfirm,
  ActionHandler,
  ActionHandlers,
  ActionDefinition,

  // 认证与数据
  AuthState,
  DataModel,

  // catalog
  KumoCatalog,
  CatalogConfig,
  ValidationResult,
} from "@cloudflare/kumo/catalog";

完整示例

一个展示 catalog 创建、校验与渲染的完整示例:

import {
  createKumoCatalog,
  initCatalog,
  resolveProps,
  evaluateVisibility,
  createVisibilityContext,
} from "@cloudflare/kumo/catalog";
import { Button, Text, Surface } from "@cloudflare/kumo";

// 1. 创建并初始化 catalog
const catalog = createKumoCatalog({
  actions: {
    greet: { description: "Show a greeting" },
  },
});
await initCatalog(catalog);

// 2. 校验 AI 生成的 JSON
const aiJson = {
  root: "container",
  elements: {
    container: {
      key: "container",
      type: "Surface",
      props: { className: "p-4 space-y-4" },
      children: ["greeting", "action-btn"],
    },
    greeting: {
      key: "greeting",
      type: "Text",
      props: {
        variant: "heading2",
        children: { path: "/user/name" },
      },
      parentKey: "container",
      visible: { auth: "signedIn" },
    },
    "action-btn": {
      key: "action-btn",
      type: "Button",
      props: {
        variant: "primary",
        children: "Say Hello",
      },
      parentKey: "container",
      action: { name: "greet" },
    },
  },
};

const result = catalog.validateTree(aiJson);
if (!result.success) {
  throw new Error("Invalid UI tree");
}

// 3. 设置渲染上下文
const dataModel = {
  user: { name: "Alice", preferences: { theme: "dark" } },
};
const visibilityCtx = createVisibilityContext(dataModel, { isSignedIn: true });

// 4. 渲染函数
function renderElement(element, elements) {
  // 检查可见性
  if (!evaluateVisibility(element.visible, visibilityCtx)) {
    return null;
  }

  // 解析动态属性
  const props = resolveProps(element.props, dataModel);

  // 渲染子元素
  const children = element.children?.map((key) =>
    renderElement(elements[key], elements),
  );

  // 映射到组件
  const Component = { Surface, Text, Button }[element.type];
  return <Component {...props}>{children}</Component>;
}

// 5. 渲染这棵树
const tree = result.data;
const ui = renderElement(tree.elements[tree.root], tree.elements);

核心优势

自动生成的 Schema —— 校验 schema 直接由组件的 TypeScript 类型派生,无需维护独立的 schema 定义。

始终同步 —— 当你更新组件属性时,schema 会通过组件注册表 codegen 流程自动更新。

流式友好 —— 扁平的树结构支持随着 LLM 响应逐 token 到达而渐进渲染。

类型安全 —— 完整的 TypeScript 支持,导出了 UIElement、UITree、DynamicValue 等类型。