开发自定义 widget(TS + Zod + D3)
VizChat 的 widget 是可插拔的——用 TypeScript 定义 Schema、写渲染逻辑,AI Agent 就能理解并调用你的 widget。
VizChat 的画布 widget 不是硬编码的。custom-widgets/ 目录下每个子目录是一个 widget 模块,包含 Schema(Zod 定义)、配置(可编辑字段)、operations(AI 可调用的动作)、iframe 入口(沙箱渲染)、renderer(实际画布展示)、i18n 文案、prompt(教 Agent 怎么用这个 widget)。脚手架会一次生成 10 个文件,你只需要填业务逻辑。
前置条件
- 克隆仓库并完成本地开发环境搭建
- 熟悉 TypeScript、Zod、基本 D3 概念(如果做可视化)
- 已启动本地前后端:
./scripts/start-backend.sh和./scripts/start-frontend.sh
推荐路径:用 skill 全流程引导
/build-builtin-widget <自然语言描述,例如 "一个显示实时股票走势的 widget">
这会走完"需求澄清 → 脚手架 → 实现 → 注册 → 质量检查"全流程。以下是手动步骤(skill 内部等价执行)。
分步(手动)
-
脚手架(kebab-case 命名):
pnpm --filter vizchat-nextjs run create:widget stock-ticker生成 10 个文件在
apps/vizchat-nextjs/src/custom-widgets/stock-ticker/,包括widget-schema.ts、widget-config.ts、stock-ticker-operations.ts、lib/stock-ticker-renderer.ts、iframe/stock-ticker-iframe-entry.ts、StockTickerWidget.tsx、prompt.md、i18n/{en,zh}.json、index.ts。 -
定义数据 Schema(
widget-schema.ts):用 Zod 描述items和config的形状。每个字段用.describe()标注(LLM 会读),数组类字段用shared/schemas/base-schemas.ts提供的 helper 保持一致性。 -
声明可编辑字段(
widget-config.ts):告诉前端属性面板哪些字段用 ColorPicker、哪些用 NumericField、哪些分到哪个折叠分组。基于shared/components/rjsf/的 JSON Schema → UI 自动化生成。 -
实现 operations(
<type>-operations.ts):这是 Agent 的 API 面。至少实现createWidget/addItems/updateConfig/removeItems。每个 op 接收params并返回新的 snapshot。参考infographic/__tests__/infographic-operations.test.ts写单测。 -
写 renderer(
lib/<type>-renderer.ts):画布上的实际绘制。对于 D3 可视化,继承shared/d3/core/d3-base-renderer.ts并实现update(items, config);优先使用selection.join()模式处理 enter/update/exit。非 D3 场景可以直接写 DOM / React 片段。 -
iframe 入口(
iframe/<type>-iframe-entry.ts):渲染在沙箱 iframe 里,通过 postMessage 和主页面同步状态。通常只需要导入 renderer 并调用。 -
React widget 壳(
<WidgetName>Widget.tsx):React Flow 的节点组件,负责头部(title、菜单)+ 嵌入IframeWidgetContainer。用shared/factories/createWidgetHeader保持风格统一。 -
i18n:在
i18n/en.json、i18n/zh.json填所有属性面板字段的中英文。 -
Agent 手册(
prompt.md):这份 Markdown 会被拼进 Agent 的系统提示词,决定 Agent 什么时候、用什么参数调用你的 widget。必须包含数据形状示例、典型 operation 调用示例、不适用场景。 -
注册 + 种子:
重启后端 —— lifespan 经
run_widget_compiler(mode='builtin')自动编译 BUILTIN widget + reseed 对象存储(Phase 5 后取代旧seed:builtin-widgets)。后端启动时会自动执行此步,开发阶段手动触发更快。重启前端即可在"widget 面板"看到你的 widget。
质量检查
- 单元测试:
pnpm --filter vizchat-nextjs test src/custom-widgets/stock-ticker— 覆盖 operations 和 snapshot。 - 类型:
pnpm --filter vizchat-nextjs run type-check - Lint:在项目根跑
./scripts/lint.sh(含 react-doctor、env 对齐等)
模板与参考实现
- 脚手架模板:
src/custom-widgets/_template/,占位符__WidgetName__/__widgetName__/__WIDGET_TYPE__/__widget-type__/__UPPER_SNAKE__。 - 参考实现:
infographic/(静态信息图)、pdf-viewer/(嵌入 PDF.js)、web-viewer/(嵌入网页)。