01c36eb32e
- 删除 mock_api_client、mock_calendar_service、mock_history_service - 新增 fixed_length_code_input、link_button、message_composer 共享组件 - 优化登录/注册/密码重置页面使用新组件 - 简化 injection.dart 移除 mock 分支 - 更新 env.dart 配置(BACKEND_URL 替换 API_URL) - 后端 agentscope 工具和测试更新 - 重构 AGENTS.md 文档结构 - 新增 deploy/ 目录和 protocol 文档
467 lines
11 KiB
Markdown
467 lines
11 KiB
Markdown
# UI Schema Protocol
|
|
|
|
> **NOTE**: This document is the single source of truth. All implementations must follow this specification.
|
|
|
|
## Overview
|
|
|
|
A generic UI schema for rendering tool/agent execution results. Designed for AI Agent / Tool ecosystem with extensibility.
|
|
|
|
## Version
|
|
|
|
- **Current**: `1.0`
|
|
- **Status**: Frozen (no new node types)
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ UiSchemaDocument (root) │
|
|
│ - version / schemaType / status / docId │
|
|
│ - meta (protocol-level metadata) │
|
|
│ - nodes (array of UiNode) │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ Field Layers: │
|
|
│ 1. Public fields (all renderers must handle) │
|
|
│ id / type / title / description / icon / status / │
|
|
│ timestamp / actions │
|
|
│ 2. meta (protocol-level, not rendered) │
|
|
│ requestId / toolId / traceId / userId │
|
|
│ 3. extensions (tool私有扩展, renderer透传) │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Data Types
|
|
|
|
### SchemaType
|
|
|
|
```typescript
|
|
type SchemaType = 'tool_result' | 'agent_response' | 'notification';
|
|
```
|
|
|
|
### UiStatus
|
|
|
|
```typescript
|
|
type UiStatus = 'info' | 'success' | 'warning' | 'error' | 'pending';
|
|
```
|
|
|
|
### IconSource
|
|
|
|
```typescript
|
|
type IconSource = 'icon' | 'emoji' | 'url';
|
|
```
|
|
|
|
### ActionType
|
|
|
|
```typescript
|
|
type ActionType = 'navigation' | 'url' | 'event' | 'tool' | 'copy' | 'payload';
|
|
```
|
|
|
|
---
|
|
|
|
## Root Structure
|
|
|
|
```typescript
|
|
interface UiSchemaDocument {
|
|
// Protocol identifier
|
|
version: string; // "1.0"
|
|
schemaType: SchemaType; // tool_result | agent_response | notification
|
|
|
|
// Document metadata
|
|
docId?: string; // For local refresh / diff / analytics
|
|
timestamp?: string; // ISO 8601
|
|
locale?: string; // "zh-CN"
|
|
|
|
// Unified status
|
|
status: UiStatus;
|
|
|
|
// Render control
|
|
renderer?: {
|
|
renderer?: string; // Dedicated renderer name
|
|
theme?: 'default' | 'dark' | 'light';
|
|
};
|
|
|
|
// Protocol-level metadata (not rendered)
|
|
meta?: {
|
|
requestId?: string;
|
|
toolId?: string;
|
|
traceId?: string;
|
|
userId?: string;
|
|
[key: string]: any;
|
|
};
|
|
|
|
// Root nodes
|
|
nodes: UiNode[];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Node Types (v1 Whitelist)
|
|
|
|
```
|
|
✅ Supported in v1:
|
|
- card 卡片
|
|
- list 列表
|
|
- table 表格
|
|
- text 文本/Markdown
|
|
- kv 键值对
|
|
- operation 操作结果
|
|
- error 错误提示
|
|
- container 容器
|
|
|
|
❌ Not supported in v1 (reserved for v2):
|
|
- chart / metric / image / video / tabs / accordion / form
|
|
```
|
|
|
|
---
|
|
|
|
## Common Node Fields
|
|
|
|
All nodes share these fields:
|
|
|
|
```typescript
|
|
interface UiBaseNode {
|
|
id?: string; // For local refresh / action targeting
|
|
type: string; // Node type identifier
|
|
}
|
|
|
|
interface UiTitledNode extends UiBaseNode {
|
|
title?: string;
|
|
description?: string;
|
|
icon?: UiIcon;
|
|
status?: UiStatus;
|
|
timestamp?: string;
|
|
}
|
|
|
|
interface UiActionableNode extends UiTitledNode {
|
|
actions?: UiAction[];
|
|
}
|
|
|
|
interface UiExtendableNode extends UiActionableNode {
|
|
extensions?: Record<string, any>; // Tool私有扩展, 通用renderer透传
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Node Definitions
|
|
|
|
### 1. Card Node
|
|
|
|
```typescript
|
|
interface UiCardNode extends UiExtendableNode {
|
|
type: 'card';
|
|
children?: UiNode[]; // Nested nodes
|
|
footer?: UiTextNode;
|
|
}
|
|
```
|
|
|
|
### 2. List Node
|
|
|
|
```typescript
|
|
interface UiListNode extends UiExtendableNode {
|
|
type: 'list';
|
|
items: ListItem[];
|
|
pagination?: {
|
|
page: number;
|
|
pageSize: number;
|
|
total: number;
|
|
hasMore: boolean;
|
|
};
|
|
emptyText?: string;
|
|
}
|
|
|
|
interface ListItem {
|
|
id: string;
|
|
title: string;
|
|
subtitle?: string;
|
|
description?: string;
|
|
icon?: UiIcon;
|
|
badge?: { label: string; variant?: 'default' | 'success' | 'warning' | 'error' | 'info' };
|
|
metadata?: Record<string, any>;
|
|
actions?: UiAction[];
|
|
}
|
|
```
|
|
|
|
### 3. Table Node
|
|
|
|
```typescript
|
|
interface UiTableNode extends UiExtendableNode {
|
|
type: 'table';
|
|
columns: TableColumn[];
|
|
rows: TableRow[];
|
|
pagination?: Pagination;
|
|
}
|
|
|
|
interface TableColumn {
|
|
key: string;
|
|
label: string;
|
|
width?: string;
|
|
align?: 'left' | 'center' | 'right';
|
|
}
|
|
|
|
interface TableRow {
|
|
id: string;
|
|
cells: Record<string, any>;
|
|
metadata?: Record<string, any>;
|
|
actions?: UiAction[];
|
|
}
|
|
```
|
|
|
|
### 4. Text Node
|
|
|
|
```typescript
|
|
interface UiTextNode extends UiBaseNode {
|
|
type: 'text';
|
|
content: string;
|
|
format?: 'plain' | 'markdown';
|
|
icon?: UiIcon;
|
|
actions?: UiAction[];
|
|
}
|
|
```
|
|
|
|
### 5. Key-Value Node
|
|
|
|
```typescript
|
|
interface UiKvNode extends UiExtendableNode {
|
|
type: 'kv';
|
|
pairs: KeyValuePair[];
|
|
layout?: 'vertical' | 'horizontal' | 'grid';
|
|
}
|
|
|
|
interface KeyValuePair {
|
|
key: string;
|
|
label?: string;
|
|
value: string | number | boolean;
|
|
copyable?: boolean;
|
|
}
|
|
```
|
|
|
|
### 6. Operation Node
|
|
|
|
```typescript
|
|
interface UiOperationNode extends UiExtendableNode {
|
|
type: 'operation';
|
|
operation: 'create' | 'update' | 'delete' | 'execute';
|
|
result: 'success' | 'failure' | 'partial';
|
|
message?: string;
|
|
affectedCount?: number;
|
|
details?: UiNode;
|
|
rollback?: UiAction;
|
|
}
|
|
```
|
|
|
|
### 7. Error Node
|
|
|
|
```typescript
|
|
interface UiErrorNode extends UiBaseNode {
|
|
type: 'error';
|
|
title?: string;
|
|
icon?: UiIcon;
|
|
errorCode: string;
|
|
message: string;
|
|
details?: string;
|
|
stack?: string;
|
|
retryable: boolean;
|
|
suggestions?: string[];
|
|
retry?: UiAction;
|
|
support?: UiAction;
|
|
actions?: UiAction[];
|
|
}
|
|
```
|
|
|
|
### 8. Container Node
|
|
|
|
```typescript
|
|
interface UiContainerNode extends UiBaseNode {
|
|
type: 'container';
|
|
direction: 'vertical' | 'horizontal';
|
|
gap?: number;
|
|
children: UiNode[];
|
|
actions?: UiAction[];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Action Structure
|
|
|
|
```typescript
|
|
interface UiAction {
|
|
id: string;
|
|
label: string;
|
|
icon?: UiIcon;
|
|
style?: 'primary' | 'secondary' | 'ghost' | 'danger';
|
|
disabled?: boolean;
|
|
action: ActionSpec;
|
|
confirm?: {
|
|
title?: string;
|
|
message?: string;
|
|
confirmLabel?: string;
|
|
cancelLabel?: string;
|
|
};
|
|
}
|
|
|
|
type ActionSpec =
|
|
| { type: 'navigation'; path: string; params?: Record<string, any> }
|
|
| { type: 'url'; url: string; target?: '_self' | '_blank' }
|
|
| { type: 'event'; event: string; payload?: Record<string, any> }
|
|
| { type: 'tool'; toolId: string; params?: Record<string, any> }
|
|
| { type: 'copy'; content: string; successMessage?: string }
|
|
| { type: 'payload'; payload: Record<string, any>; submitTo?: string };
|
|
```
|
|
|
|
---
|
|
|
|
## Icon Structure
|
|
|
|
```typescript
|
|
interface UiIcon {
|
|
source: IconSource; // 'icon' | 'emoji' | 'url'
|
|
value: string; // icon name / emoji / URL
|
|
color?: string; // "#FF0000"
|
|
size?: number; // pixels
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Usage Rules
|
|
|
|
### operation vs error
|
|
|
|
| Scenario | Node Type |
|
|
|----------|-----------|
|
|
| Tool execution failed, system exception | `UiErrorNode` |
|
|
| Business operation result (CRUD) | `UiOperationNode` |
|
|
| Network error / permission denied | `UiErrorNode` |
|
|
|
|
### extensions Usage Constraints
|
|
|
|
1. ❌ NO: Any rendering-related style / text / layout
|
|
2. ❌ NO: Any fields other renderers need to read
|
|
3. ✅ YES: Business identifiers (eventId, orderId)
|
|
4. ✅ YES: Dedicated renderer private config
|
|
5. ✅ YES: Analytics / logging context data
|
|
6. ✅ YES: Data that generic renderer doesn't care about
|
|
|
|
---
|
|
|
|
## JSON Examples
|
|
|
|
### Example 1: Success Card
|
|
|
|
```json
|
|
{
|
|
"version": "1.0",
|
|
"schemaType": "tool_result",
|
|
"docId": "doc_evt_001",
|
|
"timestamp": "2026-03-12T10:30:00Z",
|
|
"status": "success",
|
|
"renderer": { "renderer": "calendar" },
|
|
"meta": { "requestId": "req_abc", "toolId": "calendar.create_event" },
|
|
"nodes": [
|
|
{
|
|
"id": "node_card_1",
|
|
"type": "card",
|
|
"title": "日程已创建",
|
|
"description": "会议日程创建成功",
|
|
"icon": { "source": "icon", "value": "event_available" },
|
|
"extensions": { "eventId": "evt_abc123", "color": "#3B82F6" },
|
|
"children": [
|
|
{
|
|
"type": "kv",
|
|
"pairs": [
|
|
{ "key": "title", "label": "主题", "value": "Q1 规划会议" },
|
|
{ "key": "time", "label": "时间", "value": "2026-03-15 14:00" }
|
|
]
|
|
}
|
|
],
|
|
"actions": [
|
|
{
|
|
"id": "action_view",
|
|
"label": "查看详情",
|
|
"style": "primary",
|
|
"action": { "type": "navigation", "path": "/calendar/evt_abc123" }
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Example 2: List Result
|
|
|
|
```json
|
|
{
|
|
"version": "1.0",
|
|
"schemaType": "tool_result",
|
|
"status": "success",
|
|
"meta": { "toolId": "search.documents" },
|
|
"nodes": [
|
|
{ "type": "text", "content": "找到 **3** 个相关文档" },
|
|
{
|
|
"id": "node_list_1",
|
|
"type": "list",
|
|
"items": [
|
|
{
|
|
"id": "item_1",
|
|
"title": "API 设计规范",
|
|
"subtitle": "v2.1",
|
|
"icon": { "source": "emoji", "value": "📄" },
|
|
"actions": [
|
|
{ "id": "a1", "label": "查看", "action": { "type": "url", "url": "/docs/api" } }
|
|
]
|
|
}
|
|
],
|
|
"pagination": { "page": 1, "pageSize": 10, "total": 3, "hasMore": false }
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Example 3: Error Result
|
|
|
|
```json
|
|
{
|
|
"version": "1.0",
|
|
"schemaType": "tool_result",
|
|
"status": "error",
|
|
"meta": { "toolId": "user.delete" },
|
|
"nodes": [
|
|
{
|
|
"id": "node_error_1",
|
|
"type": "error",
|
|
"title": "删除用户失败",
|
|
"icon": { "source": "icon", "value": "error_outline" },
|
|
"errorCode": "PERMISSION_DENIED",
|
|
"message": "您没有权限执行此操作",
|
|
"details": "需要管理员权限",
|
|
"retryable": true,
|
|
"suggestions": ["联系管理员", "联系技术支持"],
|
|
"retry": {
|
|
"id": "action_retry",
|
|
"label": "重试",
|
|
"style": "primary",
|
|
"action": { "type": "tool", "toolId": "user.delete", "params": { "userId": "u1" } }
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Evolution (v2)
|
|
|
|
Reserved for future:
|
|
|
|
- Nested containers: `tabs`, `accordion`, `carousel`
|
|
- Data visualization: `chart`, `metric`, `progress`
|
|
- Rich media: `image`, `video`, `audio`, `file`
|
|
- Internationalization: `i18nKey` field
|
|
- Version migration: `deprecated` flag
|
|
- Offline support: `offline` flag
|