Merge pull request 'Add activity card, card events, HITL anchoring and UI vocabulary extension' (#2) from feature/core-cards-commands-r1 into main

This commit was merged in pull request #2.
This commit is contained in:
2026-09-30 06:22:01 +00:00
13 changed files with 624 additions and 7 deletions
+56
View File
@@ -0,0 +1,56 @@
package core
import "time"
// ActivityCard:异步任务/待确认事项的统一状态对象——右栏收件箱的条目。
// 与一次性消息相对:卡片是有生命周期的状态(进行中→完成/待办→已决/失败/弃置),
// 由服务端权威维护,经 card.* 事件驱动前端投影;确认动作回写对话时间线并留审计。
//
// 生命周期:active → done | action_required → resolved | failed | dismissed。
// Kind 决定卡身的默认内容组合(progress 流式区 / compare 对比 / review-list 裁决清单…),
// Body 用 UI 有限词汇渲染;插件可扩展 Kind(前端按未知 kind 走通用卡渲染)。
type ActivityCard struct {
ID ID `json:"id"`
// ProjectID/ConversationID 定位归属;ConversationID 为空表示项目级卡片(远期跨会话收件箱)。
ProjectID ID `json:"projectId"`
ConversationID ID `json:"conversationId,omitempty"`
SourceRunID ID `json:"sourceRunId,omitempty"` // 溯源:产生卡片的 run
AnchorMessageID ID `json:"anchorMessageId,omitempty"` // 互定位:出生消息(↙ 定位到对话)
Kind CardKind `json:"kind"`
Title string `json:"title"`
Status CardStatus `json:"status"`
Tone string `json:"tone,omitempty"` // ok | warn | danger | info
Body []UINode `json:"body,omitempty"`
Actions []UIAction `json:"actions,omitempty"` // 走命令总线;未绑定 command 降级为发消息
Pinned bool `json:"pinned,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
type CardKind string
const (
CardKindProgress CardKind = "progress" // 后台任务进度(流式区 + 进度行)
CardKindConfirm CardKind = "confirm" // 待确认(相关性/冲突/门禁/投影)
CardKindCompare CardKind = "compare" // 左右对比(基线 vs 新内容)
CardKindReviewList CardKind = "review-list" // 逐条裁决清单(候选边/缺项)
CardKindResult CardKind = "result" // 结果与产物(结论 + 来源 + 打开入口)
CardKindError CardKind = "error" // 失败显式呈现(可重试/可取回)
CardKindStatus CardKind = "status" // 常态状态卡(文档进度等)
)
type CardStatus string
const (
CardActive CardStatus = "active" // 进行中
CardActionRequired CardStatus = "action_required" // 待用户处理
CardDone CardStatus = "done" // 完成(未读结果)
CardResolved CardStatus = "resolved" // 已确认/已处理(盖章留痕)
CardFailed CardStatus = "failed"
CardDismissed CardStatus = "dismissed" // 用户关闭(非确认语义)
)
// Terminal 报告卡片是否处于终态(终态卡进入"已完成"可收纳区)。
func (s CardStatus) Terminal() bool {
return s == CardResolved || s == CardFailed || s == CardDismissed
}
+72 -1
View File
@@ -83,6 +83,77 @@ func TestGoldenHITL(t *testing.T) {
},
AllowFreeInput: true, FreeInputHint: "也可以直接输入其他格式要求",
})
// 阻断态 + 锚定(内联确认卡:anchorMessageId 内联于对话流;blocked 禁用确认并给出出路)。
golden(t, "hitl_card_blocked", HITLCard{
ID: "hitl_def456abc789", RunID: "run_1", ConversationID: "cnv_1",
AnchorMessageID: "msg_abc123def456",
Question: "批准变更 CO-2026-007?", Kind: HITLKindConfirm,
Choices: []HITLChoice{
{ID: "approve", Label: "确认批准", Description: "变更单进入执行态", Impact: "立即生效并留痕,不可裸文本撤回"},
{ID: "reject", Label: "驳回", Description: "关闭变更单并记录理由", Impact: "留痕可审计"},
},
Blocked: true, BlockReason: "存在未闭环高风险变更单,可先处理后重新检查",
})
}
func TestGoldenActivityCard(t *testing.T) {
golden(t, "activity_card", ActivityCard{
ID: "card_abc123def456", ProjectID: "prj_abc123def456", ConversationID: "cnv_abc123def456",
SourceRunID: "run_abc123def456", AnchorMessageID: "msg_abc123def456",
Kind: CardKindCompare, Title: "项目执行周期不一致 · 目标已基线",
Status: CardActionRequired, Tone: "danger",
Body: []UINode{
{Type: UICompare, Compare: &UICompareSpec{
Left: UICompareSide{Title: "基线 V1.2 · 已评审", Tone: "danger", Nodes: []UINode{{Type: UIMarkdown, Markdown: "12 个月(立项到注册)"}}},
Right: UICompareSide{Title: "新资料 · 待确认", Tone: "ok", Nodes: []UINode{{Type: UIMarkdown, Markdown: "18 个月(含临床试验)"}}},
}},
{Type: UIProgress, Title: "切片进度", Progress: &UIProgressSpec{Label: "切片 24/24", Value: 1, Tone: "ok"}},
},
Actions: []UIAction{
{ID: "escalate", Label: "发起变更流程", Style: "primary", Command: "change.escalate"},
{ID: "keep", Label: "维持原样", Style: "danger"},
},
Pinned: true,
})
}
func TestGoldenCardEvents(t *testing.T) {
golden(t, "event_card_created", StreamEvent{Seq: 45, RunID: "run_1", ConversationID: "cnv_1", TS: "2026-09-30T10:00:00Z", V: 1, Type: EvCardCreated,
Payload: json.RawMessage(`{"card":{"id":"card_abc123def456","projectId":"prj_1","kind":"progress","title":"竞品分析 PPTX 入库","status":"active","createdAt":"2026-09-30T10:00:00Z","updatedAt":"2026-09-30T10:00:00Z"}}`)})
golden(t, "event_card_resolved", StreamEvent{Seq: 46, RunID: "run_1", ConversationID: "cnv_1", TS: "2026-09-30T10:05:00Z", V: 1, Type: EvCardResolved,
Payload: json.RawMessage(`{"cardId":"card_abc123def456","actionId":"escalate"}`)})
}
func TestGoldenUIVocabularies(t *testing.T) {
golden(t, "ui_vocabularies", []UINode{
{Type: UIProgress, Progress: &UIProgressSpec{Label: "市场可行性 78%", Value: 0.78, Tone: "ok"}},
{Type: UICompare, Compare: &UICompareSpec{
Left: UICompareSide{Title: "基线", Tone: "danger"},
Right: UICompareSide{Title: "新内容", Tone: "ok", Nodes: []UINode{{Type: UIMarkdown, Markdown: "18 个月"}}},
}},
{Type: UITree, Tree: &UITreeSpec{Nodes: []UITreeNode{
{ID: "stage-1", Label: "商务与策划", Count: 24, Children: []UITreeNode{
{ID: "topic-1", Label: "市场调研", Tone: "ok", Badge: "多挂载", Count: 12, Selected: true},
}},
}}},
{Type: UITable, Table: &UITableSpec{
Columns: []UITableColumn{{Key: "id", Label: "编号"}, {Key: "title", Label: "标题"}},
Rows: []UITableRow{
{ID: "ur-005", Cells: [][2]string{{"id", "UR-005"}, {"title", "项目执行周期"}}, Actions: []UIAction{{ID: "trace", Label: "追溯"}}},
},
}},
{Type: UISteps, Steps: &UIStepsSpec{Current: 2, Steps: []UIStep{
{Label: "申请", Status: "done"}, {Label: "影响分析", Status: "done"}, {Label: "评审", Status: "active"}, {Label: "批准", Status: "pending"},
}}},
{Type: UITabs, Tabs: &UITabsSpec{Active: "view", Tabs: []UITab{
{ID: "view", Label: "查看", Nodes: []UINode{{Type: UIMarkdown, Markdown: "正文"}}},
{ID: "history", Label: "历史版本"},
}}},
{Type: UITimeline, Timeline: &UITimelineSpec{Items: []UITimelineItem{
{Time: "10:42", Title: "v0.6 填入注册分类界定", Tone: "ok", Markdown: "来源 S-121", Ref: "S-121"},
{Time: "09-28", Title: "v0.5 更新竞品定价段落", Ref: "陈磊确认"},
}}},
})
}
func TestGoldenProfileAndTools(t *testing.T) {
@@ -119,7 +190,7 @@ func TestGoldenPlugin(t *testing.T) {
golden(t, "plugin_manifest", PluginManifest{
ID: "example", Name: "示例插件", Version: "0.1.0", APIVersion: PluginAPIVersion,
Requires: PluginRequires{Network: []string{"api.example.com"}},
Provides: PluginProvides{Tools: []string{"http_echo"}, Intents: []string{"demo"}, Panels: []string{"demo"}, Cards: []string{"note"}, Settings: []string{"greeting"}},
Provides: PluginProvides{Tools: []string{"http_echo"}, Intents: []string{"demo"}, Panels: []string{"demo"}, Cards: []string{"note"}, Settings: []string{"greeting"}, Commands: []string{"demo.refresh"}, Workspaces: []string{"demo-main"}},
})
}
+26
View File
@@ -34,6 +34,11 @@ const (
EvRunCompleted StreamEventType = "run.completed"
EvRunFailed StreamEventType = "run.failed"
EvError StreamEventType = "error"
// 收件箱卡片(activity.go):前端右栏收件箱的唯一数据源(投影语义,服务端权威)。
EvCardCreated StreamEventType = "card.created"
EvCardUpdated StreamEventType = "card.updated"
EvCardStatus StreamEventType = "card.status"
EvCardResolved StreamEventType = "card.resolved"
)
// ProtocolVersion 是事件信封与 payload 词汇的当前版本。
@@ -131,3 +136,24 @@ type ErrorPayload struct {
Message string `json:"message"`
RunID ID `json:"runId,omitempty"`
}
// CardPayload:card.created / card.updated 携带整卡投影(服务端权威,前端整体替换)。
type CardPayload struct {
Card ActivityCard `json:"card"`
}
// CardStatusPayload:card.status 携带状态迁移(含 Tone,便于轻量更新状态灯)。
type CardStatusPayload struct {
CardID ID `json:"cardId"`
Status CardStatus `json:"status"`
Tone string `json:"tone,omitempty"`
Note string `json:"note,omitempty"`
}
// CardResolvedPayload:card.resolved 携带所点动作语义——确认结论回写对话时间线
// (AI 回执)并写入审计;Answer 为自由输入文本(如有)。
type CardResolvedPayload struct {
CardID ID `json:"cardId"`
ActionID string `json:"actionId,omitempty"`
Answer string `json:"answer,omitempty"`
}
+6
View File
@@ -17,6 +17,12 @@ type HITLCard struct {
Form []UINode `json:"form,omitempty"` // form kind 的字段(UI 词汇)
CreatedAt string `json:"createdAt"`
Resolved bool `json:"resolved"`
// AnchorMessageID:锚定的发起消息——未决卡内联渲染于该消息之后(对话流组成部分),
// 并在右栏收件箱生成投影卡(双现;任一侧响应即双侧销项)。
AnchorMessageID ID `json:"anchorMessageId,omitempty"`
// Blocked:阻断态(门禁不通过等)——确认入口禁用并给出出路;阻断本身进审计。
Blocked bool `json:"blocked,omitempty"`
BlockReason string `json:"blockReason,omitempty"`
}
type HITLKind string
+27 -1
View File
@@ -28,6 +28,8 @@ type PluginProvides struct {
Panels []string `json:"panels,omitempty"`
Cards []string `json:"cards,omitempty"`
Settings []string `json:"settings,omitempty"`
Commands []string `json:"commands,omitempty"` // commands/ 资产名(spec 权威在 Agent-Command 库)
Workspaces []string `json:"workspaces,omitempty"` // workspaces/ 资产名(工作区弹层)
}
// PluginPanel:右栏面板声明(UI schema 静态骨架 + 可选数据提供者)。
@@ -52,12 +54,36 @@ type WorkflowDef struct {
type WorkflowStep struct {
ID string `json:"id"`
Title string `json:"title"`
Kind string `json:"kind"` // prompt | tool | hitl | artifact
Kind string `json:"kind"` // prompt | tool | hitl | artifact | card
Prompt string `json:"prompt,omitempty"` // prompt: 发给模型的指令模板
Tool string `json:"tool,omitempty"` // tool: 工具名
Args json.RawMessage `json:"args,omitempty"` // tool: 参数模板
Hitl *HITLCardTemplate `json:"hitl,omitempty"` // hitl: 卡片模板
Artifact string `json:"artifact,omitempty"` // artifact: 产出工件名模板
Card *ActivityCardTemplate `json:"card,omitempty"` // card: 产收件箱任务卡
}
// ActivityCardTemplate:工作流步骤产卡模板(kind=card)——步骤执行时生成
// ActivityCard(右栏收件箱),Body/Actions 走 UI 有限词汇;Actions 建议绑定命令。
type ActivityCardTemplate struct {
Kind CardKind `json:"kind"`
Title string `json:"title"`
Tone string `json:"tone,omitempty"`
Body []UINode `json:"body,omitempty"`
Actions []UIAction `json:"actions,omitempty"`
}
// PluginWorkspace:工作区弹层声明(深读与治理视图;Esc/遮罩关闭回对话现场,
// 不承载任何唯一步骤——每个操作必须另有命令路径,提案 D31)。
type PluginWorkspace struct {
ID string `json:"id"`
Title string `json:"title"`
Icon string `json:"icon,omitempty"`
Shortcut string `json:"shortcut,omitempty"` // "1"–"9"(⌘N / Ctrl+N 召出)
BadgeTool string `json:"badgeTool,omitempty"` // 角标提供者工具:返回 {count,tone}
Body []UINode `json:"body"`
// RefreshTool:挂载时调用刷新 Body 动态区(与 Panel 同机制,v1 整体刷新)。
RefreshTool string `json:"refreshTool,omitempty"`
}
type HITLCardTemplate struct {
+63
View File
@@ -0,0 +1,63 @@
{
"id": "card_abc123def456",
"projectId": "prj_abc123def456",
"conversationId": "cnv_abc123def456",
"sourceRunId": "run_abc123def456",
"anchorMessageId": "msg_abc123def456",
"kind": "compare",
"title": "项目执行周期不一致 · 目标已基线",
"status": "action_required",
"tone": "danger",
"body": [
{
"type": "compare",
"compare": {
"left": {
"title": "基线 V1.2 · 已评审",
"tone": "danger",
"nodes": [
{
"type": "markdown",
"markdown": "12 个月(立项到注册)"
}
]
},
"right": {
"title": "新资料 · 待确认",
"tone": "ok",
"nodes": [
{
"type": "markdown",
"markdown": "18 个月(含临床试验)"
}
]
}
}
},
{
"type": "progress",
"title": "切片进度",
"progress": {
"label": "切片 24/24",
"value": 1,
"tone": "ok"
}
}
],
"actions": [
{
"id": "escalate",
"label": "发起变更流程",
"style": "primary",
"command": "change.escalate"
},
{
"id": "keep",
"label": "维持原样",
"style": "danger"
}
],
"pinned": true,
"createdAt": "0001-01-01T00:00:00Z",
"updatedAt": "0001-01-01T00:00:00Z"
}
+19
View File
@@ -0,0 +1,19 @@
{
"seq": 45,
"runId": "run_1",
"conversationId": "cnv_1",
"ts": "2026-09-30T10:00:00Z",
"v": 1,
"type": "card.created",
"payload": {
"card": {
"id": "card_abc123def456",
"projectId": "prj_1",
"kind": "progress",
"title": "竞品分析 PPTX 入库",
"status": "active",
"createdAt": "2026-09-30T10:00:00Z",
"updatedAt": "2026-09-30T10:00:00Z"
}
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"seq": 46,
"runId": "run_1",
"conversationId": "cnv_1",
"ts": "2026-09-30T10:05:00Z",
"v": 1,
"type": "card.resolved",
"payload": {
"cardId": "card_abc123def456",
"actionId": "escalate"
}
}
+27
View File
@@ -0,0 +1,27 @@
{
"id": "hitl_def456abc789",
"runId": "run_1",
"conversationId": "cnv_1",
"question": "批准变更 CO-2026-007?",
"kind": "confirm",
"choices": [
{
"id": "approve",
"label": "确认批准",
"description": "变更单进入执行态",
"impact": "立即生效并留痕,不可裸文本撤回"
},
{
"id": "reject",
"label": "驳回",
"description": "关闭变更单并记录理由",
"impact": "留痕可审计"
}
],
"allowFreeInput": false,
"createdAt": "",
"resolved": false,
"anchorMessageId": "msg_abc123def456",
"blocked": true,
"blockReason": "存在未闭环高风险变更单,可先处理后重新检查"
}
+6
View File
@@ -23,6 +23,12 @@
],
"settings": [
"greeting"
],
"commands": [
"demo.refresh"
],
"workspaces": [
"demo-main"
]
}
}
+152
View File
@@ -0,0 +1,152 @@
[
{
"type": "progress",
"progress": {
"label": "市场可行性 78%",
"value": 0.78,
"tone": "ok"
}
},
{
"type": "compare",
"compare": {
"left": {
"title": "基线",
"tone": "danger"
},
"right": {
"title": "新内容",
"tone": "ok",
"nodes": [
{
"type": "markdown",
"markdown": "18 个月"
}
]
}
}
},
{
"type": "tree",
"tree": {
"nodes": [
{
"id": "stage-1",
"label": "商务与策划",
"count": 24,
"children": [
{
"id": "topic-1",
"label": "市场调研",
"tone": "ok",
"badge": "多挂载",
"count": 12,
"selected": true
}
]
}
]
}
},
{
"type": "table",
"table": {
"columns": [
{
"key": "id",
"label": "编号"
},
{
"key": "title",
"label": "标题"
}
],
"rows": [
{
"id": "ur-005",
"cells": [
[
"id",
"UR-005"
],
[
"title",
"项目执行周期"
]
],
"actions": [
{
"id": "trace",
"label": "追溯"
}
]
}
]
}
},
{
"type": "steps",
"steps": {
"current": 2,
"steps": [
{
"label": "申请",
"status": "done"
},
{
"label": "影响分析",
"status": "done"
},
{
"label": "评审",
"status": "active"
},
{
"label": "批准",
"status": "pending"
}
]
}
},
{
"type": "tabs",
"tabs": {
"active": "view",
"tabs": [
{
"id": "view",
"label": "查看",
"nodes": [
{
"type": "markdown",
"markdown": "正文"
}
]
},
{
"id": "history",
"label": "历史版本"
}
]
}
},
{
"type": "timeline",
"timeline": {
"items": [
{
"time": "10:42",
"title": "v0.6 填入注册分类界定",
"tone": "ok",
"markdown": "来源 S-121",
"ref": "S-121"
},
{
"time": "09-28",
"title": "v0.5 更新竞品定价段落",
"ref": "陈磊确认"
}
]
}
}
]
+52 -2
View File
@@ -80,7 +80,8 @@ export type StreamEventType =
| 'run.phase' | 'hitl.request' | 'hitl.resolved' | 'plan.update' | 'todo.update'
| 'agent.spawn' | 'agent.progress' | 'agent.result'
| 'artifact.created' | 'artifact.updated'
| 'run.completed' | 'run.failed' | 'error';
| 'run.completed' | 'run.failed' | 'error'
| 'card.created' | 'card.updated' | 'card.status' | 'card.resolved';
export interface StreamEvent<P = unknown> {
seq: number; runId?: ID; conversationId: ID; ts: string; v: number;
@@ -105,6 +106,28 @@ export interface ContextObservation {
}
export interface RunFailedPayload { code: string; message: string }
// ---- 收件箱卡片 ----
export type CardKind =
| 'progress' | 'confirm' | 'compare' | 'review-list'
| 'result' | 'error' | 'status';
export type CardStatus =
| 'active' | 'action_required' | 'done'
| 'resolved' | 'failed' | 'dismissed';
export interface ActivityCard {
id: ID; projectId: ID; conversationId?: ID;
sourceRunId?: ID; anchorMessageId?: ID;
kind: CardKind; title: string; status: CardStatus; tone?: string;
body?: UINode[]; actions?: { id: string; label: string; style?: 'primary' | 'default' | 'danger'; command?: string }[];
pinned?: boolean; createdAt: string; updatedAt: string;
}
export interface CardPayload { card: ActivityCard }
export interface CardStatusPayload { cardId: ID; status: CardStatus; tone?: string; note?: string }
export interface CardResolvedPayload { cardId: ID; actionId?: string; answer?: string }
// ---- HITL ----
export type HITLKind = 'choice' | 'input' | 'confirm' | 'form';
@@ -117,6 +140,7 @@ export interface HITLCard {
id: ID; runId: ID; conversationId: ID; question: string; kind: HITLKind;
choices?: HITLChoice[]; allowFreeInput: boolean; freeInputHint?: string;
timeoutSec?: number | null; form?: UINode[]; createdAt: string; resolved: boolean;
anchorMessageId?: ID; blocked?: boolean; blockReason?: string;
}
export interface HITLResponse {
@@ -126,7 +150,8 @@ export interface HITLResponse {
// ---- UI 词汇 ----
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';
export interface UINode {
type: UINodeType;
@@ -139,6 +164,25 @@ export interface UINode {
actions?: { id: string; label: string; style?: 'primary' | 'default' | 'danger' }[];
chart?: { kind: 'bar' | 'line'; labels: string[]; series: number[] };
children?: UINode[];
progress?: { label?: string; value: number; tone?: string };
compare?: {
left: { title?: string; tone?: string; nodes?: UINode[] };
right: { title?: string; tone?: string; nodes?: UINode[] };
};
tree?: {
nodes: {
id: string; label: string; tone?: string; badge?: string; count?: number;
selected?: boolean; children?: unknown[];
}[];
};
table?: {
columns: { key: string; label: string }[];
rows: { id?: string; cells: [string, string][]; actions?: UINode['actions'] }[];
};
steps?: { current: number; steps: { label: string; status?: 'pending' | 'active' | 'done' | 'failed'; tone?: string }[] };
tabs?: { active?: string; tabs: { id: string; label: string; nodes?: UINode[] }[] };
timeline?: { items: { time?: string; title: string; tone?: string; markdown?: string; ref?: string }[] };
}
export interface UIField {
@@ -178,6 +222,7 @@ export interface PluginManifest {
provides: {
tools?: string[]; workflows?: string[]; intents?: string[];
prompts?: string[]; panels?: string[]; cards?: string[]; settings?: string[];
commands?: string[]; workspaces?: string[];
};
}
@@ -185,6 +230,11 @@ export interface PluginPanel {
id: string; title: string; icon?: string; body: UINode[]; refreshTool?: string;
}
export interface PluginWorkspace {
id: string; title: string; icon?: string; shortcut?: string;
badgeTool?: string; body: UINode[]; refreshTool?: string;
}
// ---- 摄取 / 检索 ----
export interface ParsedDocument {
+106 -3
View File
@@ -15,6 +15,15 @@ const (
UIDocRef UINodeType = "doc-ref" // 工件引用(右栏查看器打开)
UIActions UINodeType = "actions" // 可点动作(发命令/发消息)
UIChartLite UINodeType = "chart-lite"
// —— 收件箱/工作区扩展词汇(提案 D28;graph 远期,追溯视图按"对象为中心 +
// 分组泳道 + 逐层展开"约束设计,禁止力导向裸图)——
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 {
@@ -28,6 +37,99 @@ type UINode struct {
Actions []UIAction `json:"actions,omitempty"`
Chart *UIChart `json:"chart,omitempty"`
Children []UINode `json:"children,omitempty"` // section
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
}
// 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 {
@@ -42,9 +144,10 @@ type UIField struct {
}
type UIAction struct {
ID string `json:"id"`
Label string `json:"label"`
Style string `json:"style,omitempty"` // primary | default | danger
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 {