Why: The Generative UI proposal U2 needs a versioned, host-validated envelope for agent-authored pages before any backend surface exists: identity/compat/version envelopes, stable per-node ids with source annotations, catalog-driven spec validation, and the surface.updated projection event. Evidence: go vet + go test in this repo (table tests cover a realistic valid spec from the U1 trial shape, violations, limits, and old-payload backward compatibility). Impact: Additive only - UINode gains optional id/source (omitempty), old card/form paths unaffected; no existing symbol changes. Refs: Documents proposal 2026-10-10-generative-ui-plugin.md (r2, U2/U1 sections); Dev task feature/generative-ui-u2
173 lines
6.8 KiB
Go
173 lines
6.8 KiB
Go
package core
|
||
|
||
// UI schema 词汇(4.6 原则 3):设置界面、插件面板、HITL 表单、插件卡片共用
|
||
// 这套有限词汇,由前端通用渲染器渲染。插件不注入 React 组件;表达不了的高级
|
||
// 交互走 Core 契约扩展流程新增词汇,而不是开洞。
|
||
|
||
type UINodeType string
|
||
|
||
const (
|
||
UISection UINodeType = "section" // 标题 + 子节点
|
||
UIList UINodeType = "list" // 字符串列表
|
||
UIKV UINodeType = "kv" // 键值对列表
|
||
UIForm UINodeType = "form" // 字段表单(HITL form / 设置分区复用)
|
||
UIMarkdown UINodeType = "markdown"
|
||
UIDocRef UINodeType = "doc-ref" // 工件引用(右栏查看器打开)
|
||
UIActions UINodeType = "actions" // 可点动作(发命令/发消息)
|
||
UIChartLite UINodeType = "chart-lite"
|
||
// —— 收件箱/工作区扩展词汇(提案 D28;graph 远期,追溯视图按"对象为中心 +
|
||
// 分组泳道 + 逐层展开"约束设计,禁止力导向裸图)——
|
||
UIAvatar UINodeType = "avatar" // 头像(成员/责任人展示;UI 能力平台,插件可用)
|
||
UIProgress UINodeType = "progress" // 进度条(摄取/生成/填充度)
|
||
UICompare UINodeType = "compare" // 左右对比(基线 vs 新内容、diff 确认)
|
||
UITree UINodeType = "tree" // 树(文档树/笔记树骨架;节点带状态灯/徽标/计数)
|
||
UITable UINodeType = "table" // 表格(大表/切片表;行级操作)
|
||
UISteps UINodeType = "steps" // 流程步骤条(变更七步等)
|
||
UITabs UINodeType = "tabs" // 页签容器(工作区多页签)
|
||
UITimeline UINodeType = "timeline" // 时间线(版本链/批注流/事件史)
|
||
)
|
||
|
||
type UINode struct {
|
||
Type UINodeType `json:"type"`
|
||
// ID / Source 是 Generative UI Surface 层的节点级标注(提案 U2):ID 整页
|
||
// 唯一且稳定(交互与修订对位);Source 为支撑该节点事实内容的来源标识,
|
||
// 授权校验在数据绑定层(U3)。均为可选:旧卡片/表单路径不填不受影响。
|
||
ID string `json:"id,omitempty"`
|
||
Source []string `json:"source,omitempty"`
|
||
Title string `json:"title,omitempty"`
|
||
Items []string `json:"items,omitempty"` // list
|
||
KV [][2]string `json:"kv,omitempty"` // kv
|
||
Fields []UIField `json:"fields,omitempty"` // form
|
||
Markdown string `json:"markdown,omitempty"`
|
||
DocID ID `json:"docId,omitempty"` // doc-ref
|
||
Actions []UIAction `json:"actions,omitempty"`
|
||
Chart *UIChart `json:"chart,omitempty"`
|
||
Children []UINode `json:"children,omitempty"` // section
|
||
|
||
Avatar *UIAvatarSpec `json:"avatar,omitempty"` // avatar
|
||
Progress *UIProgressSpec `json:"progress,omitempty"` // progress
|
||
Compare *UICompareSpec `json:"compare,omitempty"` // compare
|
||
Tree *UITreeSpec `json:"tree,omitempty"` // tree
|
||
Table *UITableSpec `json:"table,omitempty"` // table
|
||
Steps *UIStepsSpec `json:"steps,omitempty"` // steps
|
||
Tabs *UITabsSpec `json:"tabs,omitempty"` // tabs
|
||
Timeline *UITimelineSpec `json:"timeline,omitempty"` // timeline
|
||
}
|
||
|
||
// UIAvatar:头像(成员/责任人;Name 取首字,Tone 走语义色;Stack 为叠放组)。
|
||
type UIAvatarSpec struct {
|
||
Name string `json:"name,omitempty"`
|
||
Sub string `json:"sub,omitempty"` // 名字旁的小注(角色/状态)
|
||
Tone string `json:"tone,omitempty"` // ok/warn/danger/info/留空=品牌色
|
||
Stack []UIAvatarSpec `json:"stack,omitempty"` // 叠放头像组(多成员)
|
||
}
|
||
|
||
// UIProgress:0–1 归一进度或 n/m 计数(Value=Done/Total 任一形式,前端按 <1 判定)。
|
||
type UIProgressSpec struct {
|
||
Label string `json:"label,omitempty"`
|
||
Value float64 `json:"value"`
|
||
Tone string `json:"tone,omitempty"`
|
||
}
|
||
|
||
// UICompare:左右对比布局;两侧各带语义色调(如左=基线 danger、右=新内容 ok)。
|
||
type UICompareSide struct {
|
||
Title string `json:"title,omitempty"`
|
||
Tone string `json:"tone,omitempty"`
|
||
Nodes []UINode `json:"nodes,omitempty"`
|
||
}
|
||
|
||
type UICompareSpec struct {
|
||
Left UICompareSide `json:"left"`
|
||
Right UICompareSide `json:"right"`
|
||
}
|
||
|
||
// UITreeNode:树节点;Badge 为角标文本(如"多挂载"),Count 为子孙计数,
|
||
// onSelect 语义由宿主按 Tree 用途路由(命令或工作区定位)。
|
||
type UITreeNode struct {
|
||
ID string `json:"id"`
|
||
Label string `json:"label"`
|
||
Tone string `json:"tone,omitempty"` // 状态灯
|
||
Badge string `json:"badge,omitempty"`
|
||
Count int `json:"count,omitempty"`
|
||
Selected bool `json:"selected,omitempty"`
|
||
Children []UITreeNode `json:"children,omitempty"`
|
||
}
|
||
|
||
type UITreeSpec struct {
|
||
Nodes []UITreeNode `json:"nodes"`
|
||
}
|
||
|
||
type UITableColumn struct {
|
||
Key string `json:"key"`
|
||
Label string `json:"label"`
|
||
}
|
||
|
||
type UITableRow struct {
|
||
ID string `json:"id,omitempty"`
|
||
Cells [][2]string `json:"cells"` // 与 Columns.Key 对应的有序单元格
|
||
Actions []UIAction `json:"actions,omitempty"` // 行级操作(走命令总线)
|
||
}
|
||
|
||
type UITableSpec struct {
|
||
Columns []UITableColumn `json:"columns"`
|
||
Rows []UITableRow `json:"rows"`
|
||
}
|
||
|
||
type UIStep struct {
|
||
Label string `json:"label"`
|
||
Status string `json:"status,omitempty"` // pending | active | done | failed
|
||
Tone string `json:"tone,omitempty"`
|
||
}
|
||
|
||
type UIStepsSpec struct {
|
||
Current int `json:"current"` // 当前步下标(0 起)
|
||
Steps []UIStep `json:"steps"`
|
||
}
|
||
|
||
type UITab struct {
|
||
ID string `json:"id"`
|
||
Label string `json:"label"`
|
||
Nodes []UINode `json:"nodes,omitempty"`
|
||
}
|
||
|
||
type UITabsSpec struct {
|
||
Active string `json:"active,omitempty"` // 默认选中页签 ID
|
||
Tabs []UITab `json:"tabs"`
|
||
}
|
||
|
||
type UITimelineItem struct {
|
||
Time string `json:"time,omitempty"` // 展示用时间文本
|
||
Title string `json:"title"`
|
||
Tone string `json:"tone,omitempty"`
|
||
Markdown string `json:"markdown,omitempty"` // 条目正文
|
||
Ref string `json:"ref,omitempty"` // 引用标识(切片/版本/操作者)
|
||
}
|
||
|
||
type UITimelineSpec struct {
|
||
Items []UITimelineItem `json:"items"`
|
||
}
|
||
|
||
type UIField struct {
|
||
Key string `json:"key"`
|
||
Label string `json:"label"`
|
||
Type string `json:"type"` // text | textarea | number | boolean | select
|
||
Options []string `json:"options,omitempty"`
|
||
Required bool `json:"required,omitempty"`
|
||
Placeholder string `json:"placeholder,omitempty"`
|
||
Help string `json:"help,omitempty"`
|
||
Default string `json:"default,omitempty"`
|
||
}
|
||
|
||
type UIAction struct {
|
||
ID string `json:"id"`
|
||
Label string `json:"label"`
|
||
Style string `json:"style,omitempty"` // primary | default | danger
|
||
Command string `json:"command,omitempty"` // 绑定命令总线命令 id;空则降级为发消息
|
||
}
|
||
|
||
type UIChart struct {
|
||
Kind string `json:"kind"` // bar | line
|
||
Labels []string `json:"labels"`
|
||
Series []float64 `json:"series"`
|
||
}
|