Add the avatar vocabulary to UINode (UI capability platform)
Why: plugins and the host need one shared avatar primitive for member/ owner displays in cards, messages, and admin surfaces (user decision 2026-09-30: avatars, cards, settings become base capabilities plugins reuse directly). Evidence: go test green with regenerated golden (single avatar with sub/tone plus a stacked group); omitempty keeps existing wire samples byte-stable. Impact: additive optional UINode field + type; unknown-type consumers ignore it (forward compat). Renderers gain 'avatar' via the ui repo. Refs: Agent-System-Documents proposal 2026-09-30-ui-redesign-and-auth.md
This commit is contained in:
@@ -125,6 +125,12 @@ func TestGoldenCardEvents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGoldenUIVocabularies(t *testing.T) {
|
func TestGoldenUIVocabularies(t *testing.T) {
|
||||||
|
golden(t, "ui_avatar", []UINode{
|
||||||
|
{Type: UIAvatar, Avatar: &UIAvatarSpec{Name: "weichen", Sub: "OWNER"}},
|
||||||
|
{Type: UIAvatar, Avatar: &UIAvatarSpec{
|
||||||
|
Stack: []UIAvatarSpec{{Name: "甲", Tone: "ok"}, {Name: "乙", Tone: "warn"}, {Name: "丙"}},
|
||||||
|
}},
|
||||||
|
})
|
||||||
golden(t, "ui_vocabularies", []UINode{
|
golden(t, "ui_vocabularies", []UINode{
|
||||||
{Type: UIProgress, Progress: &UIProgressSpec{Label: "市场可行性 78%", Value: 0.78, Tone: "ok"}},
|
{Type: UIProgress, Progress: &UIProgressSpec{Label: "市场可行性 78%", Value: 0.78, Tone: "ok"}},
|
||||||
{Type: UICompare, Compare: &UICompareSpec{
|
{Type: UICompare, Compare: &UICompareSpec{
|
||||||
|
|||||||
Vendored
+27
@@ -0,0 +1,27 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "avatar",
|
||||||
|
"avatar": {
|
||||||
|
"name": "weichen",
|
||||||
|
"sub": "OWNER"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "avatar",
|
||||||
|
"avatar": {
|
||||||
|
"stack": [
|
||||||
|
{
|
||||||
|
"name": "甲",
|
||||||
|
"tone": "ok"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "乙",
|
||||||
|
"tone": "warn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "丙"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
+2
-1
@@ -152,7 +152,7 @@ export interface HITLResponse {
|
|||||||
|
|
||||||
export type UINodeType =
|
export type UINodeType =
|
||||||
| 'section' | 'list' | 'kv' | 'form' | 'markdown' | 'doc-ref' | 'actions' | 'chart-lite'
|
| 'section' | 'list' | 'kv' | 'form' | 'markdown' | 'doc-ref' | 'actions' | 'chart-lite'
|
||||||
| 'progress' | 'compare' | 'tree' | 'table' | 'steps' | 'tabs' | 'timeline';
|
| 'avatar' | 'progress' | 'compare' | 'tree' | 'table' | 'steps' | 'tabs' | 'timeline';
|
||||||
|
|
||||||
export interface UINode {
|
export interface UINode {
|
||||||
type: UINodeType;
|
type: UINodeType;
|
||||||
@@ -166,6 +166,7 @@ export interface UINode {
|
|||||||
chart?: { kind: 'bar' | 'line'; labels: string[]; series: number[] };
|
chart?: { kind: 'bar' | 'line'; labels: string[]; series: number[] };
|
||||||
children?: UINode[];
|
children?: UINode[];
|
||||||
|
|
||||||
|
avatar?: { name?: string; sub?: string; tone?: string; stack?: { name?: string; sub?: string; tone?: string }[] };
|
||||||
progress?: { label?: string; value: number; tone?: string };
|
progress?: { label?: string; value: number; tone?: string };
|
||||||
compare?: {
|
compare?: {
|
||||||
left: { title?: string; tone?: string; nodes?: UINode[] };
|
left: { title?: string; tone?: string; nodes?: UINode[] };
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const (
|
|||||||
UIChartLite UINodeType = "chart-lite"
|
UIChartLite UINodeType = "chart-lite"
|
||||||
// —— 收件箱/工作区扩展词汇(提案 D28;graph 远期,追溯视图按"对象为中心 +
|
// —— 收件箱/工作区扩展词汇(提案 D28;graph 远期,追溯视图按"对象为中心 +
|
||||||
// 分组泳道 + 逐层展开"约束设计,禁止力导向裸图)——
|
// 分组泳道 + 逐层展开"约束设计,禁止力导向裸图)——
|
||||||
|
UIAvatar UINodeType = "avatar" // 头像(成员/责任人展示;UI 能力平台,插件可用)
|
||||||
UIProgress UINodeType = "progress" // 进度条(摄取/生成/填充度)
|
UIProgress UINodeType = "progress" // 进度条(摄取/生成/填充度)
|
||||||
UICompare UINodeType = "compare" // 左右对比(基线 vs 新内容、diff 确认)
|
UICompare UINodeType = "compare" // 左右对比(基线 vs 新内容、diff 确认)
|
||||||
UITree UINodeType = "tree" // 树(文档树/笔记树骨架;节点带状态灯/徽标/计数)
|
UITree UINodeType = "tree" // 树(文档树/笔记树骨架;节点带状态灯/徽标/计数)
|
||||||
@@ -27,24 +28,33 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UINode struct {
|
type UINode struct {
|
||||||
Type UINodeType `json:"type"`
|
Type UINodeType `json:"type"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Items []string `json:"items,omitempty"` // list
|
Items []string `json:"items,omitempty"` // list
|
||||||
KV [][2]string `json:"kv,omitempty"` // kv
|
KV [][2]string `json:"kv,omitempty"` // kv
|
||||||
Fields []UIField `json:"fields,omitempty"` // form
|
Fields []UIField `json:"fields,omitempty"` // form
|
||||||
Markdown string `json:"markdown,omitempty"`
|
Markdown string `json:"markdown,omitempty"`
|
||||||
DocID ID `json:"docId,omitempty"` // doc-ref
|
DocID ID `json:"docId,omitempty"` // doc-ref
|
||||||
Actions []UIAction `json:"actions,omitempty"`
|
Actions []UIAction `json:"actions,omitempty"`
|
||||||
Chart *UIChart `json:"chart,omitempty"`
|
Chart *UIChart `json:"chart,omitempty"`
|
||||||
Children []UINode `json:"children,omitempty"` // section
|
Children []UINode `json:"children,omitempty"` // section
|
||||||
|
|
||||||
Progress *UIProgressSpec `json:"progress,omitempty"` // progress
|
Avatar *UIAvatarSpec `json:"avatar,omitempty"` // avatar
|
||||||
Compare *UICompareSpec `json:"compare,omitempty"` // compare
|
Progress *UIProgressSpec `json:"progress,omitempty"` // progress
|
||||||
Tree *UITreeSpec `json:"tree,omitempty"` // tree
|
Compare *UICompareSpec `json:"compare,omitempty"` // compare
|
||||||
Table *UITableSpec `json:"table,omitempty"` // table
|
Tree *UITreeSpec `json:"tree,omitempty"` // tree
|
||||||
Steps *UIStepsSpec `json:"steps,omitempty"` // steps
|
Table *UITableSpec `json:"table,omitempty"` // table
|
||||||
Tabs *UITabsSpec `json:"tabs,omitempty"` // tabs
|
Steps *UIStepsSpec `json:"steps,omitempty"` // steps
|
||||||
Timeline *UITimelineSpec `json:"timeline,omitempty"` // timeline
|
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 判定)。
|
// UIProgress:0–1 归一进度或 n/m 计数(Value=Done/Total 任一形式,前端按 <1 判定)。
|
||||||
@@ -88,9 +98,9 @@ type UITableColumn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UITableRow struct {
|
type UITableRow struct {
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
Cells [][2]string `json:"cells"` // 与 Columns.Key 对应的有序单元格
|
Cells [][2]string `json:"cells"` // 与 Columns.Key 对应的有序单元格
|
||||||
Actions []UIAction `json:"actions,omitempty"` // 行级操作(走命令总线)
|
Actions []UIAction `json:"actions,omitempty"` // 行级操作(走命令总线)
|
||||||
}
|
}
|
||||||
|
|
||||||
type UITableSpec struct {
|
type UITableSpec struct {
|
||||||
@@ -146,7 +156,7 @@ type UIField struct {
|
|||||||
type UIAction struct {
|
type UIAction struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Style string `json:"style,omitempty"` // primary | default | danger
|
Style string `json:"style,omitempty"` // primary | default | danger
|
||||||
Command string `json:"command,omitempty"` // 绑定命令总线命令 id;空则降级为发消息
|
Command string `json:"command,omitempty"` // 绑定命令总线命令 id;空则降级为发消息
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user