Skip to content

Commit 272e137

Browse files
committed
feat: Add built-in contribution points for the workbench UI
- Introduced `BuiltinWorkbenchContributionProvider` to manage navigation, context tabs, settings categories, and sender context chips. - Created `WorkbenchContributionCatalog` to aggregate contributions from multiple providers. - Defined contribution models including `NavigationContribution`, `ContextTabContribution`, `SettingsCategoryContribution`, and `SenderContextChipContribution`. - Implemented `TaskPlanApprovalPanel` for displaying task plans, tool calls, approvals, and results with appropriate state management. - Updated documentation to define the contribution points and design boundaries for the IoTCoWork workbench. - Marked several UI features in the roadmap as completed.
1 parent 4845251 commit 272e137

14 files changed

Lines changed: 2723 additions & 270 deletions
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
namespace IoTCoWork.Workbench.Extensibility;
2+
3+
public sealed class BuiltinWorkbenchContributionProvider : IWorkbenchContributionProvider
4+
{
5+
public IEnumerable<WorkbenchContribution> GetContributions(WorkbenchContributionContext context)
6+
{
7+
foreach (var contribution in GetNavigation())
8+
{
9+
yield return contribution;
10+
}
11+
12+
foreach (var contribution in GetContextTabs(context))
13+
{
14+
yield return contribution;
15+
}
16+
17+
foreach (var contribution in GetSettingsCategories())
18+
{
19+
yield return contribution;
20+
}
21+
22+
foreach (var contribution in GetSenderContextChips(context))
23+
{
24+
yield return contribution;
25+
}
26+
}
27+
28+
private static IEnumerable<NavigationContribution> GetNavigation()
29+
{
30+
yield return new("sessions", "会话", "本地任务会话列表。", "message", 10)
31+
{
32+
Group = "workspace",
33+
IsPrimary = true,
34+
};
35+
yield return new("capabilities", "能力中心", "本地智能体、技能、MCP 与插件占位。", "appstore", 20)
36+
{
37+
Group = "workspace",
38+
ActionId = "open-capabilities",
39+
};
40+
}
41+
42+
private static IEnumerable<ContextTabContribution> GetContextTabs(WorkbenchContributionContext context)
43+
{
44+
yield return new("tree", "工程树", "本地 workspace 结构。", "folder-open", 10)
45+
{
46+
Kind = "builtin",
47+
};
48+
yield return new("artifacts", "产物", "当前会话产物摘要。", "file-done", 20)
49+
{
50+
Badge = context.ArtifactCount > 0 ? context.ArtifactCount.ToString() : null,
51+
Kind = "builtin",
52+
};
53+
yield return new("tools", "工具运行", "本地任务计划与工具运行。", "tool", 30)
54+
{
55+
Badge = context.IsRunning ? "运行" : null,
56+
Kind = "builtin",
57+
};
58+
yield return new("logs", "日志", "本地工作台日志。", "profile", 40)
59+
{
60+
Kind = "builtin",
61+
};
62+
yield return new("risk", "风险", "边界与安全风险。", "safety", 50)
63+
{
64+
Badge = "2",
65+
Kind = "builtin",
66+
};
67+
}
68+
69+
private static IEnumerable<SettingsCategoryContribution> GetSettingsCategories()
70+
{
71+
yield return new("appearance", "外观", "主题与界面显示。", "skin", 10)
72+
{
73+
Keywords = ["theme", "appearance", "外观", "主题"],
74+
};
75+
yield return new("shortcuts", "快捷键", "本地键盘入口。", "keyboard", 20)
76+
{
77+
Keywords = ["shortcut", "hotkey", "快捷键", "键盘"],
78+
};
79+
yield return new("model", "模型", "作图模型与请求参数。", "experiment", 30)
80+
{
81+
Keywords = ["model", "image", "模型", "质量", "格式"],
82+
};
83+
yield return new("network", "本地网络", "本机代理与连接状态。", "global", 40)
84+
{
85+
Keywords = ["network", "proxy", "网络", "代理"],
86+
};
87+
yield return new("updates", "更新", "应用版本检查。", "cloud-sync", 50)
88+
{
89+
Keywords = ["update", "release", "更新", "版本"],
90+
};
91+
yield return new("capabilities", "能力中心", "本地扩展点入口。", "appstore", 60)
92+
{
93+
Keywords = ["capability", "plugin", "mcp", "能力", "插件"],
94+
};
95+
}
96+
97+
private static IEnumerable<SenderContextChipContribution> GetSenderContextChips(WorkbenchContributionContext context)
98+
{
99+
yield return new("workspace", "工作区", "当前本地 workspace。", "folder-open", 10)
100+
{
101+
Value = context.WorkspaceName,
102+
IsStatic = true,
103+
};
104+
yield return new("edge-target", "边缘", "边缘目标端。", "thunderbolt", 20)
105+
{
106+
Value = "C# AOT",
107+
ActionId = "cycle-edge-target",
108+
};
109+
yield return new("model", "模型", "当前模型。", "experiment", 30)
110+
{
111+
Value = context.Model,
112+
ActionId = "cycle-model",
113+
};
114+
yield return new("approval", "审批", "本地审批模式。", "safety", 40)
115+
{
116+
Value = "每次审批",
117+
ActionId = "cycle-approval",
118+
};
119+
yield return new("output", "输出", "本地输出位置。", "file-done", 50)
120+
{
121+
Value = "当前会话",
122+
ActionId = "cycle-output",
123+
};
124+
}
125+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
namespace IoTCoWork.Workbench.Extensibility;
2+
3+
public sealed class WorkbenchContributionCatalog
4+
{
5+
private readonly IEnumerable<IWorkbenchContributionProvider> _providers;
6+
7+
public WorkbenchContributionCatalog(IEnumerable<IWorkbenchContributionProvider> providers)
8+
{
9+
_providers = providers;
10+
}
11+
12+
public IReadOnlyList<NavigationContribution> GetNavigation(WorkbenchContributionContext context) =>
13+
GetContributions<NavigationContribution>(context);
14+
15+
public IReadOnlyList<ContextTabContribution> GetContextTabs(WorkbenchContributionContext context) =>
16+
GetContributions<ContextTabContribution>(context);
17+
18+
public IReadOnlyList<SettingsCategoryContribution> GetSettingsCategories(WorkbenchContributionContext context) =>
19+
GetContributions<SettingsCategoryContribution>(context);
20+
21+
public IReadOnlyList<SenderContextChipContribution> GetSenderContextChips(WorkbenchContributionContext context) =>
22+
GetContributions<SenderContextChipContribution>(context);
23+
24+
private IReadOnlyList<TContribution> GetContributions<TContribution>(WorkbenchContributionContext context)
25+
where TContribution : WorkbenchContribution
26+
{
27+
return _providers
28+
.SelectMany(provider => provider.GetContributions(context))
29+
.OfType<TContribution>()
30+
.OrderBy(contribution => contribution.Order)
31+
.ThenBy(contribution => contribution.Id, StringComparer.Ordinal)
32+
.ToArray();
33+
}
34+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
namespace IoTCoWork.Workbench.Extensibility;
2+
3+
public interface IWorkbenchContributionProvider
4+
{
5+
IEnumerable<WorkbenchContribution> GetContributions(WorkbenchContributionContext context);
6+
}
7+
8+
public sealed record WorkbenchContributionContext(
9+
string WorkspaceName,
10+
string ActiveSessionTitle,
11+
string Mode,
12+
string WorkspaceStatus,
13+
string Model,
14+
string TargetSize,
15+
int ArtifactCount,
16+
bool IsRunning);
17+
18+
public abstract record WorkbenchContribution(
19+
string Id,
20+
string Title,
21+
string Description,
22+
string Icon,
23+
int Order = 0)
24+
{
25+
public string Source { get; init; } = "builtin";
26+
public string? Group { get; init; }
27+
}
28+
29+
public sealed record NavigationContribution(
30+
string Id,
31+
string Title,
32+
string Description,
33+
string Icon,
34+
int Order = 0)
35+
: WorkbenchContribution(Id, Title, Description, Icon, Order)
36+
{
37+
public string? ActionId { get; init; }
38+
public bool IsPrimary { get; init; }
39+
}
40+
41+
public sealed record ContextTabContribution(
42+
string Id,
43+
string Title,
44+
string Description,
45+
string Icon,
46+
int Order = 0)
47+
: WorkbenchContribution(Id, Title, Description, Icon, Order)
48+
{
49+
public string? Badge { get; init; }
50+
public string Kind { get; init; } = "summary";
51+
}
52+
53+
public sealed record SettingsCategoryContribution(
54+
string Id,
55+
string Title,
56+
string Description,
57+
string Icon,
58+
int Order = 0)
59+
: WorkbenchContribution(Id, Title, Description, Icon, Order)
60+
{
61+
public IReadOnlyList<string> Keywords { get; init; } = [];
62+
public string Status { get; init; } = string.Empty;
63+
public string Kind { get; init; } = "builtin";
64+
65+
public bool Matches(string query)
66+
{
67+
return Title.Contains(query, StringComparison.OrdinalIgnoreCase)
68+
|| Description.Contains(query, StringComparison.OrdinalIgnoreCase)
69+
|| Keywords.Any(keyword => keyword.Contains(query, StringComparison.OrdinalIgnoreCase));
70+
}
71+
}
72+
73+
public sealed record SenderContextChipContribution(
74+
string Id,
75+
string Title,
76+
string Description,
77+
string Icon,
78+
int Order = 0)
79+
: WorkbenchContribution(Id, Title, Description, Icon, Order)
80+
{
81+
public string Value { get; init; } = string.Empty;
82+
public string? ActionId { get; init; }
83+
public bool IsStatic { get; init; }
84+
}

0 commit comments

Comments
 (0)