Skip to content

Commit ed589dc

Browse files
committed
feat(swiftui): add SwiftUI renderer with Apple full-platform support
Implements a complete native SwiftUI renderer covering the full A2UI protocol: JSONL stream parsing, surface management, data binding with path resolution, template expansion, action handling, and all standard component types. Includes: - ChecksEvaluator with validation engine (required, email, regex, etc.) - CatalogFunctionEvaluator (formatString, formatNumber, formatCurrency, etc.) - Custom component support with CustomComponentRegistry - A2A networking client with SSE streaming - Icon SVG path support, accessibility attributes - RizzCharts demo (Swift Charts + MapKit) - Demo app with catalog/samples/live-agent modes - Full Apple platform support (iOS, macOS, tvOS, watchOS, visionOS) - 71+ unit tests
1 parent 51dc6a6 commit ed589dc

68 files changed

Lines changed: 14414 additions & 39 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# SwiftUI 标准组件校验与修复流程
2+
3+
## 第一步:读 v0.9 Spec(唯一的属性真相来源)
4+
5+
`specification/v0_9/json/basic_catalog.json`,确认组件有哪些属性、哪些是 `required`、每个属性的类型和枚举值。
6+
7+
**只有 spec 里写的属性才需要响应,不要自己发明。**
8+
9+
## 第二步:读官方 Lit 实现(行为和默认值的真相来源)
10+
11+
必须读 **两个文件**
12+
13+
1. **`renderers/lit/src/0.8/ui/<component>.ts`** — 组件自身的渲染逻辑、CSS 样式(间距、布局、默认值全在这里)
14+
2. **`renderers/lit/src/0.8/ui/root.ts`** — 看组件是怎么被实例化的,哪些属性实际传了、哪些没传(spec 里有但 root.ts 没传的 = 官方也没实现)
15+
16+
**不要猜默认值。** 比如 `gap: 8px` 不是 List 组件的间距,是 Root 的 `:host` 样式。必须看清代码归属。
17+
18+
## 第三步:读 web_core 的 Theme 类型(判断用户重写)
19+
20+
`renderers/web_core/src/v0_8/types/types.ts`,确认官方为该组件提供了什么层级的自定义。**用户重写的范围和粒度必须严格对齐官方 Theme 定义,不可自行发明。**
21+
22+
### 判断规则
23+
24+
`types.ts``Theme` 接口中找到组件对应的字段:
25+
26+
- **`Record<string, boolean>`(纯 classMap)** → 官方只提供 CSS class 级别的重写,无结构化自定义。SwiftUI 侧 **不要** 发明 Style struct。
27+
- 例:`List: Record<string, boolean>` → 不加 `ListComponentStyle`
28+
- **嵌套结构(如 `container` / `element` / `label`** → 官方认为组件有多个可独立自定义的部分。SwiftUI 侧对应创建 Style struct,每个嵌套键对应一组属性。
29+
- 例:`Slider: { container, element, label }` → 创建 `SliderComponentStyle`,包含 label 字体/颜色、轨道颜色等
30+
- 例:`TextField: { container, element, label }` → 创建 `TextFieldComponentStyle`
31+
- **`additionalStyles?.<Component>`(inline styleMap)** → 官方允许自由注入样式,说明组件的视觉重写是预期行为。SwiftUI 侧应提供对应的 ViewModifier。
32+
33+
### SwiftUI 侧实现模式
34+
35+
当确认需要用户重写时,遵循已建立的模式:
36+
37+
1.`A2UIStyle` 中添加 `public var xxxStyle: XxxComponentStyle` 属性
38+
2. 定义 `XxxComponentStyle` struct,属性与官方 Theme 嵌套结构对齐(不要多加也不要少加)
39+
3.`View` extension 中添加 `.a2uiXxxStyle(...)` ViewModifier,用 `transformEnvironment(\.a2uiStyle)` 实现
40+
4. 在组件的 render 方法中读取 `style.xxxStyle` 并应用,原生控件属性优先、自定义样式作为补充
41+
42+
### 不要重写的情况
43+
44+
- 组件在 Theme 中只有 `Record<string, boolean>` → 不加自定义
45+
- 组件是纯原生控件映射(如 SwiftUI.Slider、Toggle)且官方无嵌套 Theme → 只用原生控件,不包装自定义样式
46+
47+
## 第四步:逐项对比 SwiftUI 实现
48+
49+
拿着 spec 的属性列表和 Lit 的行为,逐一检查:
50+
51+
| 检查项 | 方法 |
52+
|---|---|
53+
| 属性是否声明 |`ComponentTypes.swift` 的 struct |
54+
| 属性是否在渲染中响应 |`A2UIComponentView.swift` 对应的 render 方法 |
55+
| 默认值是否正确 | 对比 Lit 的 CSS / JS 默认值 |
56+
| 是否有多余的自定义 | 对比 Lit 的 theme 类型,官方没有的不要加 |
57+
| 是否需要自定义 | 对比 Lit 的 theme 类型,官方有的要加 |
58+
| HIG 对齐 | 用 SwiftUI 原生控件(Slider、Toggle、DatePicker 等)就自动跟系统走 |
59+
60+
## 第五步:Demo 验证
61+
62+
对照 spec 的属性和 Lit 实际使用的属性,确保 demo 覆盖了组件的主要用法(如 direction 的两种值),但不要展示 spec 有、官方未实现的属性。
63+
64+
## 核心原则
65+
66+
1. **Spec 定义"有什么",Lit 定义"怎么做"** — 不要只看 spec 就开始写,必须看官方怎么实现的
67+
2. **不要猜,不要发明** — 间距、颜色、自定义入口,全部从官方代码里找依据
68+
3. **官方没实现的属性不要抢跑** — 比如 `align` 在 spec 里有,但 root.ts 没传,说明官方也没做,不要自作聪明
69+
4. **原生控件优先** — SwiftUI 原生控件(Slider、Toggle、TextField 等)自动跟随系统 HIG,不要套自定义样式覆盖它
70+
71+
## 涉及文件速查
72+
73+
| 用途 | 路径 |
74+
|---|---|
75+
| v0.9 组件属性定义 | `specification/v0_9/json/basic_catalog.json` |
76+
| v0.9 公共类型定义 | `specification/v0_9/json/common_types.json` |
77+
| 官方 Lit 组件实现 | `renderers/lit/src/0.8/ui/<component>.ts` |
78+
| 官方 Lit 组件实例化 | `renderers/lit/src/0.8/ui/root.ts` |
79+
| 官方 Theme 类型定义 | `renderers/web_core/src/v0_8/types/types.ts` |
80+
| SwiftUI 属性模型 | `renderers/swiftui/Sources/A2UISwiftUI/Models/ComponentTypes.swift` |
81+
| SwiftUI 渲染逻辑 | `renderers/swiftui/Sources/A2UISwiftUI/Views/A2UIComponentView.swift` |
82+
| SwiftUI 样式/自定义 | `renderers/swiftui/Sources/A2UISwiftUI/Styling/A2UIStyle.swift` |
83+
| Demo 数据 | `renderers/swiftui/Examples/A2UIDemoApp/A2UIDemoApp/Pages/CatalogPage.swift` |
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
# 完整掌握 A2UI:官方 Demo 运行指南
2+
3+
> 除 SwiftUI Demo App 外,你还需要运行哪些官方 Demo 才能完整理解 A2UI 协议的全部能力。
4+
> 所有 Demo 均来自仓库自带代码,无需额外下载。
5+
6+
---
7+
8+
## 当前 SwiftUI Demo 的覆盖范围(~25%)
9+
10+
| Demo | 覆盖的功能 |
11+
|------|-----------|
12+
| **Catalog**(18 组件) | 全部标准组件的静态展示;基础数据绑定(TextField/CheckBox/Slider 绑 path);v0.8 JSONL 格式 |
13+
| **Samples**(3 个) | 静态 JSON 一次性加载渲染;Card + 布局组合;restaurant_list 使用了 template 展开 |
14+
15+
**未覆盖的关键能力**:A2A Agent 通信、Action 回传、checks/validation 函数体系、formatString 插值、多 Surface、增量更新、流式渲染、自定义组件、Accessibility、Agent 身份展示、多 Agent 编排。
16+
17+
---
18+
19+
## 推荐运行的 6 个官方 Demo
20+
21+
### Demo 1: Lit Component Gallery + Agent(最高优先级)
22+
23+
**你能学到什么**: 全组件行为参考、Action 交互闭环、Agent 动态响应模式
24+
25+
**路径**:
26+
- Agent: `samples/agent/adk/component_gallery`
27+
- Client: `samples/client/lit/component_gallery`
28+
29+
**运行方式**:
30+
31+
```bash
32+
# Terminal 1 — Agent
33+
cd samples/agent/adk/component_gallery
34+
export GEMINI_API_KEY="your_key"
35+
uv run .
36+
# 运行在 http://localhost:10005
37+
38+
# Terminal 2 — Client
39+
cd samples/client/lit/component_gallery
40+
npm install && npm run dev
41+
# 浏览器打开 http://localhost:5173
42+
```
43+
44+
**观察重点**:
45+
- 这是 A2UI 的 "Kitchen Sink",所有 18 个标准组件 + 交互 + Action 回传 + Agent 动态响应都能看到
46+
- 它是官方的**参考实现** —— SwiftUI Catalog 应该对标它的行为
47+
- Agent 响应是流式到达还是一次性到达
48+
- 用户点击 Button 后 Action 如何回传、Agent 如何用新 UI 响应
49+
- TextField/CheckBox/Slider 等输入组件的数据绑定行为
50+
- 多个 Surface 同时存在的效果
51+
52+
---
53+
54+
### Demo 2: Angular Gallery(客户端独立,无需 Agent)
55+
56+
**你能学到什么**: 组合布局模式、编程式 Surface 构造、Theme 自定义
57+
58+
**路径**: `samples/client/angular/projects/gallery`
59+
60+
**运行方式**:
61+
62+
```bash
63+
cd samples/client/angular
64+
npm install
65+
npm run build
66+
npm start -- gallery
67+
```
68+
69+
**观察重点**:
70+
- 这是**纯客户端**的组件展示,不需要 Agent 后端
71+
- 用 TypeScript 代码直接构造 `Surface` 对象来渲染,对比 SwiftUI 中用 JSONL 硬编码的方式
72+
- 组合布局模式:Row 嵌套 Column、Card 包裹复杂内容
73+
- Theme 自定义的视觉效果
74+
- 它分为 Library(单组件展示)和 Gallery(复合场景)两个视图
75+
76+
---
77+
78+
### Demo 3: Restaurant Finder 全链路(最接近真实产品)
79+
80+
**你能学到什么**: template 数组展开、表单交互闭环、Action context 数据绑定
81+
82+
**路径**:
83+
- Agent: `samples/agent/adk/restaurant_finder`
84+
- Client: `samples/client/lit/shell`
85+
86+
**运行方式**:
87+
88+
```bash
89+
# Terminal 1 — 先构建渲染器依赖
90+
cd renderers/web_core && npm install && npm run build
91+
cd ../../renderers/lit && npm install && npm run build
92+
93+
# Terminal 2 — Agent
94+
cd samples/agent/adk/restaurant_finder
95+
export GEMINI_API_KEY="your_key"
96+
uv run .
97+
98+
# Terminal 3 — Client (Shell)
99+
cd samples/client/lit/shell
100+
npm install && npm run dev
101+
# 浏览器打开 http://localhost:5173
102+
```
103+
104+
**观察重点**:
105+
- 用户提问 → AI 生成餐厅列表 UI(`template` 展开数组数据)
106+
- 用户点击餐厅 → AI 生成预约表单(TextField + DateTimeInput + Button with action context)
107+
- 用户填写提交 → Action 带着数据模型回传 Agent → Agent 返回确认 UI
108+
- 理解 **template 数组展开、Action context 数据绑定、完整交互闭环** 的最佳场景
109+
110+
**官方 JSON 参考**(可直接读取理解数据结构):
111+
- `samples/agent/adk/restaurant_finder/examples/single_column_list.json` — 餐厅列表
112+
- `samples/agent/adk/restaurant_finder/examples/booking_form.json` — 预约表单
113+
- `samples/agent/adk/restaurant_finder/examples/confirmation.json` — 提交确认
114+
115+
---
116+
117+
### Demo 4: Contact Multiple Surfaces(多 Surface 演示)
118+
119+
**你能学到什么**: 多 Surface 同时渲染、自定义组件注册、复杂数据模型
120+
121+
**路径**:
122+
- Agent: `samples/agent/adk/contact_multiple_surfaces`
123+
- Client: `samples/client/lit/contact`
124+
125+
**运行方式**:
126+
127+
```bash
128+
# Terminal 1 — Agent
129+
cd samples/agent/adk/contact_multiple_surfaces
130+
export GEMINI_API_KEY="your_key"
131+
uv run . --port=10004
132+
133+
# Terminal 2 — Client
134+
cd samples/client/lit/contact
135+
npm install && npm run dev
136+
```
137+
138+
**观察重点**:
139+
- 仓库中唯一演示 **多 Surface 同时渲染** 的 Demo
140+
- 一次响应中同时 `beginRendering` 两个 Surface(`contact-card` + `org-chart-view`
141+
- 客户端如何布局多个 Surface(分屏/侧栏)
142+
- 自定义组件(`OrgChart``WebFrame`)的注册和渲染方式
143+
- Client-first 扩展模型:客户端告知 Agent 自己支持哪些自定义组件
144+
145+
**官方 JSON 参考**:
146+
- `samples/agent/adk/contact_multiple_surfaces/examples/multi_surface.json` — 多 Surface 响应结构
147+
- `samples/agent/adk/contact_multiple_surfaces/examples/org_chart.json` — 自定义组件
148+
- `samples/agent/adk/contact_multiple_surfaces/examples/contact_card.json` — 联系人卡片
149+
150+
---
151+
152+
### Demo 5: Orchestrator 多 Agent 编排(最复杂场景)
153+
154+
**你能学到什么**: 多 Agent 路由、同一客户端接收不同 Agent 的 UI
155+
156+
**路径**:
157+
- 子 Agent: `restaurant_finder` / `contact_lookup` / `rizzcharts`
158+
- 编排 Agent: `samples/agent/adk/orchestrator`
159+
- Client: `samples/client/angular/projects/orchestrator`
160+
161+
**运行方式**:
162+
163+
```bash
164+
# Terminal 1 — Restaurant Agent
165+
cd samples/agent/adk/restaurant_finder
166+
export GEMINI_API_KEY="your_key"
167+
uv run . --port=10003
168+
169+
# Terminal 2 — Contact Agent
170+
cd samples/agent/adk/contact_lookup
171+
export GEMINI_API_KEY="your_key"
172+
uv run . --port=10004
173+
174+
# Terminal 3 — Rizzcharts Agent
175+
cd samples/agent/adk/rizzcharts
176+
export GEMINI_API_KEY="your_key"
177+
uv run . --port=10005
178+
179+
# Terminal 4 — Orchestrator Agent
180+
cd samples/agent/adk/orchestrator
181+
uv run . --port=10002 \
182+
--subagent_urls=http://localhost:10003 \
183+
--subagent_urls=http://localhost:10004 \
184+
--subagent_urls=http://localhost:10005
185+
186+
# Terminal 5 — Angular Client
187+
cd samples/client/angular
188+
npm install && npm run build
189+
npm start -- orchestrator
190+
```
191+
192+
**观察重点**:
193+
- Orchestrator Agent 根据用户意图将请求路由到不同的子 Agent
194+
- 同一个客户端接收来自不同 Agent 的 UI 响应
195+
- 理解 A2UI 在多 Agent 协作场景下的工作方式
196+
- 注意:rizzcharts 需要 Google Maps API key,没有也能跑,只是地图部分不显示
197+
198+
---
199+
200+
### Demo 6: v0.9 Spec 官方测试用例(读 JSON,不需运行)
201+
202+
**你能学到什么**: checks/validation 函数体系的完整语法、协议边界条件
203+
204+
**路径**: `specification/v0_9/test/cases/`
205+
206+
| 文件 | 覆盖的功能 |
207+
|------|-----------|
208+
| `contact_form_example.jsonl` | **完整表单**:checks + required/email/regex 函数 + formatDate + ChoicePicker + Button action with context |
209+
| `checkable_components.json` | **所有可校验组件**的 checks 用法:TextField(required/email/regex/length) + ChoicePicker(length) + Slider(numeric) + CheckBox(required) + DateTimeInput(required) + **and/or/not 嵌套逻辑** |
210+
| `button_checks.json` | Button checks 的 **and/or 嵌套条件**、variant 属性、废弃属性检测 |
211+
| `text_variants.json` | Text 组件所有 variant 的验证 |
212+
| `theme_validation.json` | Theme(primaryColor)的合法/非法格式 |
213+
| `function_catalog_validation.json` | 函数目录的 schema 验证 |
214+
| `client_messages.json` | 客户端消息格式(UserAction 等) |
215+
216+
**重点阅读**: `contact_form_example.jsonl``checkable_components.json`,它们定义了 checks/validation 函数体系的权威用法,直接决定你 SwiftUI 渲染器需要实现的函数引擎行为。
217+
218+
---
219+
220+
## 推荐运行顺序与收益
221+
222+
| 顺序 | Demo | 运行难度 | 学到什么 | 累计覆盖率 |
223+
|:----:|------|:--------:|---------|:----------:|
224+
| 1 | **Lit Component Gallery** | 中(Agent + Client) | 全组件行为参考、Action 交互、Agent 响应模式 | ~45% |
225+
| 2 | **Angular Gallery** | 低(纯客户端) | 组合布局、编程式 Surface 构造、Theme | ~50% |
226+
| 3 | **Restaurant Finder** | 中(Agent + Client) | template 展开、表单闭环、Action context | ~60% |
227+
| 4 | **Contact Multi-Surface** | 中(Agent + Client) | 多 Surface、自定义组件、复杂数据模型 | ~70% |
228+
| 5 | **Orchestrator** | 高(4 Agent + Client) | 多 Agent 编排、最复杂场景 | ~85% |
229+
| 6 | **读 v0.9 test cases** | 零(读 JSON) | checks/validation 函数体系、协议边界条件 | ~100% |
230+
231+
---
232+
233+
## 每个 Demo 对 SwiftUI 渲染器开发的反哺
234+
235+
| 观察到的行为 | 反哺到 SwiftUI 渲染器 |
236+
|-------------|---------------------|
237+
| Lit Component Gallery 中的 Action 回传流程 | SwiftUI Demo 目前无 Agent 通信,需新建 Live Agent 页面实现 Action 回传 |
238+
| Restaurant Finder 中 template 展开数组 | 验证 SwiftUI restaurant_list.json 的 template 行为是否正确 |
239+
| Contact Multi-Surface 的多 Surface 布局 | SwiftUI `SurfaceManager` 已实现但无 Demo,需补充演示 |
240+
| Contact Multi-Surface 的自定义组件注册 | SwiftUI 需实现自定义组件注册机制 |
241+
| checkable_components.json 中 and/or/not 嵌套 | SwiftUI 需实现 checks 引擎 + 客户端函数注册表 |
242+
| contact_form_example.jsonl 中 formatDate | SwiftUI 需实现 formatDate 函数 |
243+
| Orchestrator 中不同 Agent 的 UI 路由 | SwiftUI 需实现 Agent 连接页面,支持切换/管理多 Agent |
244+
245+
---
246+
247+
## 前置依赖
248+
249+
### 运行 Agent 需要
250+
251+
- Python 3.10+ 和 `uv`(Python 包管理器)
252+
- `GEMINI_API_KEY` 环境变量(从 Google AI Studio 获取)
253+
254+
### 运行 Lit Client 需要
255+
256+
- Node.js 18+ 和 `npm`
257+
- 先构建渲染器:`cd renderers/web_core && npm install && npm run build && cd ../lit && npm install && npm run build`
258+
259+
### 运行 Angular Client 需要
260+
261+
- Node.js 18+ 和 `npm`
262+
- `cd samples/client/angular && npm install && npm run build`

renderers/swiftui/.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Xcode
2+
DerivedData/
3+
*.xcuserstate
4+
xcuserdata/
5+
6+
# Swift Package Manager
7+
.build/
8+
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9+
.swiftpm/xcode/xcuserdata/
10+
11+
# macOS
12+
.DS_Store
13+
14+
# IDE
15+
*.xcworkspace/xcuserdata/
16+
17+
# Development notes (not part of the library)
18+
DEVELOPMENT_PLAN.md
19+
PR_REVIEW.md

0 commit comments

Comments
 (0)