Files
social-app/docs/plans/2026-02-28-ag-ui-chat-design.md
T
qzl c3192a2431 feat(chat): add ChatBubble widget and mock data for home screen
- Add ChatBubble reusable widget for chat messages
- Add HomeMockData for chat list mock data
- Add HomeScreen widget tests
- Add AG-UI chat design and implementation plan docs
- Add friendship design docs
- Ignore backend/logs directory
2026-02-28 14:47:33 +08:00

568 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AG-UI 聊天功能设计文档
## 1. 概述
本文档描述如何使用 AG-UI 协议实现 AI 聊天功能,包括:
- 消息的发送与接收(通过 AG-UI 事件流)
- AI 工具调用(Tool Call)机制
- 日历卡片作为 Tool Result 渲染
- 前端工具注册与执行
- 本地持久化
## 2. 架构设计
### 2.1 整体流程
```
用户输入消息
AgUiService.sendMessage()
[Mock Mode] 规则引擎决策 → 事件流模拟
[Real Mode] POST /api/chat → SSE 监听
┌─────────────────────────────────────────────────────────────┐
│ AG-UI Event Stream (按序处理) │
├─────────────────────────────────────────────────────────────┤
│ TEXT_MESSAGE_START → TEXT_MESSAGE_CONTENT* → TEXT_MESSAGE_END │
│ TOOL_CALL_START → TOOL_CALL_ARGS* → TOOL_CALL_END │
│ TOOL_CALL_RESULT │
│ RUN_STARTED → ... → RUN_FINISHED │
└─────────────────────────────────────────────────────────────┘
ChatListItem 渲染
```
### 2.2 核心组件
| 组件 | 职责 |
|------|------|
| `AgUiEvent` | AG-UI 事件数据模型 |
| `AgUiService` | 事件流处理:发送消息、解析事件 |
| `ToolRegistry` | 前端工具注册表:定义工具 + handler |
| `AiDecisionEngine` | Mock 模式:规则引擎决定是否调用工具 |
| `UiSchemaParser` | 解析 tool result 中的 UI Schema |
| `UiSchemaRenderer` | 根据 schema 渲染对应组件 |
| `ChatHistoryRepository` | 本地持久化:IndexedDB/localStorage |
### 2.3 状态管理
```
ChatState {
messages: ChatListItem[] // 渲染列表
pendingToolCalls: Map<call_id, ToolCallState>
isLoading: bool
runId: string | null
}
```
## 3. 数据模型
### 3.1 AG-UI 事件模型
```dart
// 基类
abstract class AgUiEvent {
final String type;
final String? timestamp;
}
// 生命周期事件
class RunStartedEvent extends AgUiEvent {
final String threadId;
final String runId;
final String? parentRunId;
}
class RunFinishedEvent extends AgUiEvent {
final String threadId;
final String runId;
final dynamic result;
}
// 文本消息事件
class TextMessageStartEvent extends AgUiEvent {
final String messageId;
final String role; // "user" | "assistant" | "system"
}
class TextMessageContentEvent extends AgUiEvent {
final String messageId;
final String delta;
}
class TextMessageEndEvent extends AgUiEvent {
final String messageId;
}
// 工具调用事件
class ToolCallStartEvent extends AgUiEvent {
final String toolCallId;
final String toolCallName;
final String? parentMessageId;
}
class ToolCallArgsEvent extends AgUiEvent {
final String toolCallId;
final String delta; // JSON fragment
}
class ToolCallEndEvent extends AgUiEvent {
final String toolCallId;
}
class ToolCallResultEvent extends AgUiEvent {
final String messageId;
final String toolCallId;
final ToolResult result; // 给 AI 的原始结果
final UiCard? ui; // 给 UI 的渲染数据
}
class ToolCallErrorEvent extends AgUiEvent {
final String toolCallId;
final String error;
final String? code;
}
```
### 3.2 Tool Result Schemav1
```json
{
"type": "tool_result",
"version": "v1",
"call_id": "call_abc123",
"tool_name": "create_calendar_event",
"result": {
"eventId": "evt_xxx",
"ok": true,
"message": "日程已创建"
},
"ui": {
"type": "card",
"cardType": "calendar_card.v1",
"data": {
"id": "evt_xxx",
"title": "产品评审会议",
"description": "讨论Q2路线图",
"startAt": "2026-03-01T10:00:00+08:00",
"endAt": "2026-03-01T11:00:00+08:00",
"timezone": "Asia/Shanghai",
"location": "会议室A",
"color": "#4F46E5",
"sourceType": "agentGenerated"
},
"actions": [
{"type": "open", "label": "打开", "target": "calendar/evt_xxx"},
{"type": "edit", "label": "编辑", "action": "edit_event"},
{"type": "delete", "label": "删除", "action": "delete_event"}
]
}
}
```
### 3.3 工具定义(前端 Tool Registry
```dart
// 工具定义
class ToolDefinition {
final String name;
final String description;
final Map<String, dynamic> parameters;
final ToolHandler handler;
}
// create_calendar_event 工具
{
"name": "create_calendar_event",
"description": "创建一个日历事件或待办事项",
"parameters": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "事件标题",
"minLength": 1,
"maxLength": 100
},
"description": {
"type": "string",
"description": "事件描述"
},
"startAt": {
"type": "string",
"format": "date-time",
"description": "开始时间 (ISO8601)"
},
"endAt": {
"type": "string",
"format": "date-time",
"description": "结束时间 (ISO8601)"
},
"timezone": {
"type": "string",
"default": "Asia/Shanghai"
},
"location": {
"type": "string"
},
"notes": {
"type": "string"
}
},
"required": ["title", "startAt"]
}
}
```
### 3.4 ChatListItem 模型
```dart
enum ChatItemType {
message, // 纯文本消息
toolCall, // 工具调用中
toolResult, // 工具结果卡片
schedule // 日历事件(兼容旧数据)
}
abstract class ChatListItem {
String get id;
DateTime get timestamp;
ChatItemType get type;
MessageSender get sender;
}
class TextMessageItem extends ChatListItem {
final String id;
final String content;
final DateTime timestamp;
final MessageSender sender;
final bool isStreaming; // 是否正在流式输出
}
class ToolCallItem extends ChatListItem {
final String id;
final String callId;
final String toolName;
final Map<String, dynamic> args; // 解析后的参数
final ToolCallStatus status; // pending | executing | completed | error
final ToolResult? result;
final UiCard? uiCard;
}
class CalendarCardItem extends ChatListItem {
final String id;
final String callId; // 关联的 tool call
final CalendarCardData data;
final List<CardAction> actions;
}
```
## 4. 核心流程
### 4.1 发送消息
```dart
Future<void> sendMessage(String content) async {
// 1. 添加用户消息到列表
final userMessage = TextMessageItem(
id: generateId(),
content: content,
timestamp: DateTime.now(),
sender: MessageSender.user,
);
_chatItems.add(userMessage);
// 2. 发起请求
if (Env.isMockApi) {
await _mockEventStream(content);
} else {
await _realEventStream(content);
}
}
```
### 4.2 Mock 事件流(规则引擎)
```dart
class AiDecisionEngine {
// 意图关键词映射
static final Map<Intent, List<Pattern>> _intentPatterns = {
Intent.createEvent: [
RegExp(r'提醒|开会|预约|日程|安排'),
RegExp(r'明天|今天|后天|下周'),
RegExp(r'\d{1,2}点|\d{1,2}:\d{2}'),
],
Intent.searchEvent: [
RegExp(r'查看|有什么|今天.*日程|明天.*安排'),
],
};
Intent? matchIntent(String text) {
for (final entry in _intentPatterns.entries) {
for (final pattern in entry.value) {
if (pattern.hasMatch(text)) {
return entry.key;
}
}
}
return null;
}
// 支持强制触发:#tool:create_calendar_event {"title": "test"}
bool tryForceTrigger(String text) {...}
}
```
### 4.3 事件解析与处理
```dart
Future<void> _processEvent(AgUiEvent event) async {
switch (event.type) {
case 'TEXT_MESSAGE_START':
_handleTextMessageStart(event);
break;
case 'TEXT_MESSAGE_CONTENT':
_handleTextMessageContent(event);
break;
case 'TEXT_MESSAGE_END':
_handleTextMessageEnd(event);
break;
case 'TOOL_CALL_START':
_handleToolCallStart(event);
break;
case 'TOOL_CALL_ARGS':
_handleToolCallArgs(event);
break;
case 'TOOL_CALL_END':
await _handleToolCallEnd(event);
break;
case 'TOOL_CALL_RESULT':
_handleToolCallResult(event);
break;
case 'TOOL_CALL_ERROR':
_handleToolCallError(event);
break;
}
}
void _handleToolCallStart(ToolCallStartEvent event) {
// 创建 pending 状态的 tool call item
final item = ToolCallItem(
id: event.toolCallId,
callId: event.toolCallId,
toolName: event.toolCallName,
args: {},
status: ToolCallStatus.pending,
);
_chatItems.add(item);
}
Future<void> _handleToolCallEnd(ToolCallEndEvent event) async {
// 1. 找到对应的 pending tool call
final toolCall = _findPendingToolCall(event.toolCallId);
if (toolCall == null) return;
// 2. 校验参数 JSON Schema
final validation = validateToolArgs(toolCall.toolName, toolCall.args);
if (!validation.ok) {
_emitToolCallError(event.toolCallId, validation.error);
return;
}
// 3. 执行工具 handler
toolCall.status = ToolCallStatus.executing;
final result = await ToolRegistry.execute(
toolCall.toolName,
toolCall.args,
);
// 4. 构建 tool result(包含 result + ui
final toolResult = ToolResult(
eventId: result['eventId'],
ok: result['ok'] ?? true,
message: result['message'],
);
final uiCard = _buildUiCard(toolCall.toolName, result);
// 5. 发送 TOOL_CALL_RESULT 事件
_emitToolCallResult(event.toolCallId, toolResult, uiCard);
}
```
### 4.4 UI Schema 渲染
```dart
class UiSchemaRenderer {
static final Map<String, Widget Function(UiCard)> _renderers = {
'calendar_card.v1': (card) => CalendarCardWidget(
data: CalendarCardData.fromJson(card.data),
actions: card.actions,
),
};
static Widget render(UiCard card) {
final renderer = _renderers[card.cardType];
if (renderer != null) {
return renderer(card);
}
// Unknown card type fallback
return _renderUnknownCard(card);
}
static Widget _renderUnknownCard(UiCard card) {
return GenericCardWidget(
rawJson: jsonEncode(card.toJson()),
cardType: card.cardType,
);
}
}
```
### 4.5 日历卡片组件
```dart
class CalendarCardWidget extends StatelessWidget {
final CalendarCardData data;
final List<CardAction> actions;
@override
Widget build(BuildContext context) {
final color = ColorExt.parse(data.color ?? '#4F46E5');
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [...],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 颜色条
Container(
height: 4,
color: color,
),
// 内容
Padding(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(data.title, style: ...),
if (data.description != null) ...,
_buildTimeRow(),
if (data.location != null) ...,
],
),
),
// Actions
if (actions.isNotEmpty) _buildActions(actions),
],
),
);
}
}
```
## 5. 持久化设计
### 5.1 存储结构
```dart
// localStorage / IndexedDB
{
"chat_sessions": {
"current_thread_id": {
"messages": [...], // ChatListItem JSON
"lastRunId": "run_xxx",
"updatedAt": "2026-02-28T12:00:00Z"
}
},
"calendar_events": {
"evt_xxx": {...} // 独立存储的日历事件
}
}
```
### 5.2 恢复逻辑
```dart
Future<void> restoreSession() async {
final session = await ChatHistoryRepository.load('current_thread_id');
if (session != null) {
_chatItems.clear();
_chatItems.addAll(session.messages);
_runId = session.lastRunId;
}
}
```
## 6. 错误处理
### 6.1 Tool Call 错误
```dart
void _emitToolCallError(String callId, String error) {
// 1. 更新 item 状态
final item = _findToolCallItem(callId);
item?.status = ToolCallStatus.error;
item?.errorMessage = error;
// 2. 渲染错误卡片
final errorCard = UiCard(
cardType: 'error_card.v1',
data: {'message': error},
);
// 3. 触发 UI 更新
notifyListeners();
}
```
### 6.2 事件流重连
```dart
// 断线重连时从 snapshot 恢复
Future<void> reconnect() async {
final snapshot = await _fetchMessagesSnapshot();
_chatItems.clear();
_chatItems.addAll(snapshot.messages);
// 重新订阅事件流
_subscribeToEvents();
}
```
## 7. 实施计划
### Phase 1: 基础框架
- [ ] 定义 AG-UI 事件模型
- [ ] 实现 AgUiService 基础结构
- [ ] 实现 ToolRegistry
### Phase 2: Mock 实现
- [ ] 实现 AiDecisionEngine 规则引擎
- [ ] 实现 Mock 事件流
- [ ] 集成现有 HomeScreen
### Phase 3: UI 渲染
- [ ] 实现 UiSchemaParser
- [ ] 实现 CalendarCardWidget
- [ ] 实现 ToolPending / ToolError 状态卡片
### Phase 4: 持久化
- [ ] 实现 ChatHistoryRepository
- [ ] 实现会话恢复
### Phase 5: 真实后端对接
- [ ] 实现 SSE 客户端
- [ ] 实现事件流解析器
## 8. 版本历史
| 版本 | 日期 | 变更 |
|------|------|------|
| v1.0 | 2026-02-28 | 初始版本 |