Skip to content

Commit 9058434

Browse files
committed
fix(ui): backfill onboarding command guidance to master
1 parent 1350760 commit 9058434

6 files changed

Lines changed: 714 additions & 51 deletions

File tree

catalog/ui-onboarding.json

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@
1717
{ "title": "开始提问或输入命令", "body": "可以直接提问,也可以先用常用命令快速查看状态。" }
1818
],
1919
"commands": [
20-
{ "id": "status", "command": "status", "label": "查看系统状态", "description": "检查硬件、部署和资源概况。" },
21-
{ "id": "hardware", "command": "hardware", "label": "查看硬件", "description": "查看 GPU、CPU、内存等基础信息。" },
22-
{ "id": "models", "command": "models", "label": "查看模型", "description": "确认 AIMA 已识别到哪些模型。" },
23-
{ "id": "engines", "command": "engines", "label": "查看引擎", "description": "确认本机可用推理引擎。" },
24-
{ "id": "deployments", "command": "deployments", "label": "查看部署", "description": "检查当前运行中的部署。" }
20+
{ "id": "status", "command": "/cli status", "label": "查看系统状态", "description": "检查硬件、部署和资源概况。" },
21+
{ "id": "help", "command": "/cli help", "label": "查看帮助", "description": "查看命令帮助和可用用法。" },
22+
{ "id": "hardware", "command": "/cli hal detect", "label": "查看硬件", "description": "查看 GPU、CPU、内存等基础信息。" },
23+
{ "id": "models", "command": "/cli model list", "label": "查看模型", "description": "确认 AIMA 已识别到哪些模型。" },
24+
{ "id": "engines", "command": "/cli engine list", "label": "查看引擎", "description": "确认本机可用推理引擎。" },
25+
{ "id": "deployments", "command": "/cli deploy list", "label": "查看部署", "description": "检查当前运行中的部署。" }
2526
]
2627
},
2728
"full_commands": {
@@ -30,35 +31,43 @@
3031
"id": "inspect",
3132
"title": "系统查看",
3233
"items": [
33-
{ "command": "status", "description": "查看系统总体状态。" },
34-
{ "command": "hardware", "description": "查看硬件检测结果。" },
35-
{ "command": "metrics", "description": "查看实时硬件指标。" }
34+
{ "id": "status", "command": "/cli status", "description": "查看系统总体状态。" },
35+
{ "id": "hardware", "command": "/cli hal detect", "description": "查看硬件检测结果。" },
36+
{ "id": "metrics", "command": "/cli hal metrics", "description": "查看实时硬件指标。" }
3637
]
3738
},
3839
{
3940
"id": "inventory",
4041
"title": "资源清单",
4142
"items": [
42-
{ "command": "models", "description": "列出已识别模型。" },
43-
{ "command": "engines", "description": "列出可用引擎。" },
44-
{ "command": "deployments", "description": "列出当前部署。" },
45-
{ "command": "fleet", "description": "列出局域网设备。" }
43+
{ "id": "models", "command": "/cli model list", "description": "列出已识别模型。" },
44+
{ "id": "engines", "command": "/cli engine list", "description": "列出可用引擎。" },
45+
{ "id": "deployments", "command": "/cli deploy list", "description": "列出当前部署。" },
46+
{ "id": "fleet_devices", "command": "/cli fleet devices", "description": "列出局域网设备。" }
4647
]
4748
},
4849
{
49-
"id": "cli",
50-
"title": "CLI 示例",
50+
"id": "top_level_commands",
51+
"title": "顶层命令",
52+
"description": "由服务端动态填充的顶层命令列表。",
53+
"items": []
54+
},
55+
{
56+
"id": "common_examples",
57+
"title": "常用示例",
5158
"items": [
52-
{ "command": "/cli help", "description": "查看 CLI 子命令帮助。" },
53-
{ "command": "/cli model list", "description": "通过 CLI 列出已注册模型。" },
54-
{ "command": "/cli engine list", "description": "通过 CLI 列出已注册引擎。" }
59+
{ "id": "engine_plan", "command": "/cli engine plan", "description": "先看当前硬件推荐使用哪个引擎。" },
60+
{ "id": "engine_pull", "command": "/cli engine pull", "description": "预先拉取默认引擎,减少首次部署等待。" },
61+
{ "id": "model_pull", "command": "/cli model pull {sample_model}", "description": "按模型名下载模型文件,`{sample_model}` 是示例值,可替换为实际模型名。" },
62+
{ "id": "deploy_dry_run", "command": "/cli deploy {sample_model} --dry-run", "description": "先预览部署解析结果和推荐配置,`{sample_model}` 是示例值,可替换为实际模型名。" },
63+
{ "id": "run", "command": "/cli run {sample_model}", "description": "一条命令拉取、部署并等待服务就绪,`{sample_model}` 是示例值,可替换为实际模型名。" }
5564
]
5665
}
5766
]
5867
},
5968
"troubleshooting": {
6069
"items": [
61-
{ "title": "Agent 不可用", "body": "先用 status、hardware、models 等直接命令确认基础状态。" },
70+
{ "title": "Agent 不可用", "body": "先用 /cli status、/cli hal detect、/cli model list 等直接命令确认基础状态。" },
6271
{ "title": "需要配置", "body": "点击右上角设置,检查 LLM endpoint、model 和 API key。" },
6372
{ "title": "需要远程协助", "body": "点击连线灵机云,按引导进入远程协助流程。" }
6473
]
@@ -79,11 +88,12 @@
7988
{ "title": "Ask or run a command", "body": "You can ask AIMA directly or begin with a common command." }
8089
],
8190
"commands": [
82-
{ "id": "status", "command": "status", "label": "System status", "description": "Inspect hardware, deployments, and overall state." },
83-
{ "id": "hardware", "command": "hardware", "label": "Hardware", "description": "Show detected GPU, CPU, and memory information." },
84-
{ "id": "models", "command": "models", "label": "Models", "description": "List models AIMA already knows about." },
85-
{ "id": "engines", "command": "engines", "label": "Engines", "description": "List available inference engines." },
86-
{ "id": "deployments", "command": "deployments", "label": "Deployments", "description": "Show active inference deployments." }
91+
{ "id": "status", "command": "/cli status", "label": "System status", "description": "Inspect hardware, deployments, and overall state." },
92+
{ "id": "help", "command": "/cli help", "label": "Help", "description": "Show command help and available usage." },
93+
{ "id": "hardware", "command": "/cli hal detect", "label": "Hardware", "description": "Show detected GPU, CPU, and memory information." },
94+
{ "id": "models", "command": "/cli model list", "label": "Models", "description": "List models AIMA already knows about." },
95+
{ "id": "engines", "command": "/cli engine list", "label": "Engines", "description": "List available inference engines." },
96+
{ "id": "deployments", "command": "/cli deploy list", "label": "Deployments", "description": "Show active inference deployments." }
8797
]
8898
},
8999
"full_commands": {
@@ -92,35 +102,43 @@
92102
"id": "inspect",
93103
"title": "Inspect",
94104
"items": [
95-
{ "command": "status", "description": "Show the overall system state." },
96-
{ "command": "hardware", "description": "Show hardware detection details." },
97-
{ "command": "metrics", "description": "Show live hardware metrics." }
105+
{ "id": "status", "command": "/cli status", "description": "Show the overall system state." },
106+
{ "id": "hardware", "command": "/cli hal detect", "description": "Show hardware detection details." },
107+
{ "id": "metrics", "command": "/cli hal metrics", "description": "Show live hardware metrics." }
98108
]
99109
},
100110
{
101111
"id": "inventory",
102112
"title": "Inventory",
103113
"items": [
104-
{ "command": "models", "description": "List detected models." },
105-
{ "command": "engines", "description": "List available engines." },
106-
{ "command": "deployments", "description": "List active deployments." },
107-
{ "command": "fleet", "description": "List LAN devices." }
114+
{ "id": "models", "command": "/cli model list", "description": "List detected models." },
115+
{ "id": "engines", "command": "/cli engine list", "description": "List available engines." },
116+
{ "id": "deployments", "command": "/cli deploy list", "description": "List active deployments." },
117+
{ "id": "fleet_devices", "command": "/cli fleet devices", "description": "List LAN devices." }
108118
]
109119
},
110120
{
111-
"id": "cli",
112-
"title": "CLI examples",
121+
"id": "top_level_commands",
122+
"title": "Top-level commands",
123+
"description": "Top-level commands filled dynamically by the server.",
124+
"items": []
125+
},
126+
{
127+
"id": "common_examples",
128+
"title": "Common examples",
113129
"items": [
114-
{ "command": "/cli help", "description": "Show CLI subcommand help." },
115-
{ "command": "/cli model list", "description": "List registered models through the CLI endpoint." },
116-
{ "command": "/cli engine list", "description": "List registered engines through the CLI endpoint." }
130+
{ "id": "engine_plan", "command": "/cli engine plan", "description": "Check which engine the current hardware should use." },
131+
{ "id": "engine_pull", "command": "/cli engine pull", "description": "Pre-pull the default engine to reduce first-run latency." },
132+
{ "id": "model_pull", "command": "/cli model pull {sample_model}", "description": "Download a model by name; `{sample_model}` is an example value and can be replaced with a real model name." },
133+
{ "id": "deploy_dry_run", "command": "/cli deploy {sample_model} --dry-run", "description": "Preview the resolved deployment plan before execution; `{sample_model}` is an example value and can be replaced with a real model name." },
134+
{ "id": "run", "command": "/cli run {sample_model}", "description": "Resolve, pull, deploy, and wait for readiness in one command; `{sample_model}` is an example value and can be replaced with a real model name." }
117135
]
118136
}
119137
]
120138
},
121139
"troubleshooting": {
122140
"items": [
123-
{ "title": "Agent unavailable", "body": "Use direct commands like status, hardware, and models first." },
141+
{ "title": "Agent unavailable", "body": "Use direct commands like /cli status, /cli hal detect, and /cli model list first." },
124142
{ "title": "Need configuration help", "body": "Open Settings and verify the LLM endpoint, model, and API key." },
125143
{ "title": "Need remote help", "body": "Use the support button to connect to the remote assistance flow." }
126144
]

cmd/aima/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,11 +258,11 @@ func run() error {
258258
SupportManifest: supportSvc.GoUXManifestJSON,
259259
OnboardingManifest: func(ctx context.Context) (json.RawMessage, error) {
260260
_ = ctx
261-
raw, err := catalog.FS.ReadFile("ui-onboarding.json")
261+
raw, err := buildOnboardingManifestJSON(cat)
262262
if err != nil {
263-
return nil, fmt.Errorf("read ui onboarding manifest: %w", err)
263+
return nil, fmt.Errorf("build ui onboarding manifest: %w", err)
264264
}
265-
return json.RawMessage(raw), nil
265+
return raw, nil
266266
},
267267
})
268268

cmd/aima/main_test.go

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ import (
1818
state "github.com/jguan/aima/internal"
1919
"github.com/jguan/aima/internal/agent"
2020
benchpkg "github.com/jguan/aima/internal/benchmark"
21+
"github.com/jguan/aima/internal/cli"
2122
"github.com/jguan/aima/internal/engine"
2223
"github.com/jguan/aima/internal/knowledge"
2324
"github.com/jguan/aima/internal/mcp"
2425
aimaRuntime "github.com/jguan/aima/internal/runtime"
26+
"github.com/spf13/cobra"
2527
)
2628

2729
type fakeRuntime struct {
@@ -106,6 +108,186 @@ func TestOnboardingManifestEmbeddedShape(t *testing.T) {
106108
}
107109
}
108110

111+
func TestBuildOnboardingManifestJSON_IncludesAllTopLevelCommands(t *testing.T) {
112+
t.Parallel()
113+
114+
raw, err := buildOnboardingManifestJSON(&knowledge.Catalog{})
115+
if err != nil {
116+
t.Fatalf("build onboarding manifest: %v", err)
117+
}
118+
119+
var manifest onboardingManifest
120+
if err := json.Unmarshal(raw, &manifest); err != nil {
121+
t.Fatalf("unmarshal onboarding manifest: %v", err)
122+
}
123+
124+
zh, ok := manifest.Locales["zh"]
125+
if !ok || zh == nil {
126+
t.Fatalf("zh locale missing: %#v", manifest.Locales)
127+
}
128+
129+
var topLevel onboardingGroup
130+
found := false
131+
for _, group := range zh.FullCommands.Groups {
132+
if group.ID == "top_level_commands" {
133+
topLevel = group
134+
found = true
135+
break
136+
}
137+
}
138+
if !found {
139+
t.Fatalf("top_level_commands group missing: %#v", zh.FullCommands.Groups)
140+
}
141+
142+
root := cli.NewRootCmd(&cli.App{})
143+
root.InitDefaultHelpCmd()
144+
root.InitDefaultCompletionCmd()
145+
146+
want := make(map[string]struct{})
147+
for _, cmd := range root.Commands() {
148+
if cmd == nil || cmd.Hidden {
149+
continue
150+
}
151+
want["/cli "+cmd.Name()] = struct{}{}
152+
}
153+
154+
got := make(map[string]struct{})
155+
for _, item := range topLevel.Items {
156+
got[item.Command] = struct{}{}
157+
}
158+
159+
if len(got) != len(want) {
160+
t.Fatalf("top_level_commands item count = %d, want %d\nitems=%#v", len(got), len(want), topLevel.Items)
161+
}
162+
163+
for command := range want {
164+
if _, ok := got[command]; !ok {
165+
t.Fatalf("top_level_commands missing command %s\nitems=%#v", command, topLevel.Items)
166+
}
167+
}
168+
169+
for command := range got {
170+
if _, ok := want[command]; !ok {
171+
t.Fatalf("top_level_commands has unexpected command %s\nitems=%#v", command, topLevel.Items)
172+
}
173+
}
174+
}
175+
176+
func TestBuildOnboardingManifestJSON_MarksSampleModelExamplesAsReplaceable(t *testing.T) {
177+
t.Parallel()
178+
179+
cat := &knowledge.Catalog{
180+
ModelAssets: []knowledge.ModelAsset{
181+
{
182+
Metadata: knowledge.ModelMetadata{
183+
Name: "demo-llm",
184+
Type: "llm",
185+
Family: "demo",
186+
ParameterCount: "1B",
187+
},
188+
},
189+
},
190+
}
191+
192+
raw, err := buildOnboardingManifestJSON(cat)
193+
if err != nil {
194+
t.Fatalf("build onboarding manifest: %v", err)
195+
}
196+
197+
text := string(raw)
198+
for _, want := range []string{
199+
`"/cli help"`,
200+
`"/cli model pull demo-llm"`,
201+
`"/cli deploy demo-llm --dry-run"`,
202+
`"/cli run demo-llm"`,
203+
"demo-llm 是示例模型名,可替换成你自己的模型名",
204+
"demo-llm is an example model name; replace it with your own model name",
205+
} {
206+
if !strings.Contains(text, want) {
207+
t.Fatalf("generated manifest missing %q\nmanifest=%s", want, text)
208+
}
209+
}
210+
}
211+
212+
func TestBuildOnboardingManifestJSON_LocalizesTopLevelCommandDescriptions(t *testing.T) {
213+
t.Parallel()
214+
215+
raw, err := buildOnboardingManifestJSON(&knowledge.Catalog{})
216+
if err != nil {
217+
t.Fatalf("build onboarding manifest: %v", err)
218+
}
219+
220+
var manifest onboardingManifest
221+
if err := json.Unmarshal(raw, &manifest); err != nil {
222+
t.Fatalf("unmarshal onboarding manifest: %v", err)
223+
}
224+
225+
zh := manifest.Locales["zh"]
226+
en := manifest.Locales["en"]
227+
if zh == nil || en == nil {
228+
t.Fatalf("missing locales: %#v", manifest.Locales)
229+
}
230+
231+
zhHelp := findOnboardingCommandDescription(t, zh.FullCommands.Groups, "top_level_commands", "/cli help")
232+
enHelp := findOnboardingCommandDescription(t, en.FullCommands.Groups, "top_level_commands", "/cli help")
233+
if zhHelp != "查看任意命令帮助。可直接执行,例如 /cli help;查看 model 的帮助可用 /cli help model。" {
234+
t.Fatalf("zh /cli help description = %q, want %q", zhHelp, "查看任意命令帮助。可直接执行,例如 /cli help;查看 model 的帮助可用 /cli help model。")
235+
}
236+
if enHelp != "View help for any command. You can run it directly, for example /cli help; for model help use /cli help model." {
237+
t.Fatalf("en /cli help description = %q, want %q", enHelp, "View help for any command. You can run it directly, for example /cli help; for model help use /cli help model.")
238+
}
239+
240+
zhModel := findOnboardingCommandDescription(t, zh.FullCommands.Groups, "top_level_commands", "/cli model")
241+
if zhModel != "管理模型。需要子命令,例如 /cli model list 或 /cli model pull qwen3-8b。" {
242+
t.Fatalf("zh /cli model description = %q, want %q", zhModel, "管理模型。需要子命令,例如 /cli model list 或 /cli model pull qwen3-8b。")
243+
}
244+
245+
zhDeploy := findOnboardingCommandDescription(t, zh.FullCommands.Groups, "top_level_commands", "/cli deploy")
246+
if zhDeploy != "部署推理服务。通常需要模型名,例如 /cli deploy qwen3-8b --dry-run;查看部署列表可用 /cli deploy list。" {
247+
t.Fatalf("zh /cli deploy description = %q, want %q", zhDeploy, "部署推理服务。通常需要模型名,例如 /cli deploy qwen3-8b --dry-run;查看部署列表可用 /cli deploy list。")
248+
}
249+
}
250+
251+
func TestRewriteOnboardingCommands_DropsKnownCommandWhenCLICommandIsMissing(t *testing.T) {
252+
t.Parallel()
253+
254+
root := &cobra.Command{Use: "aima"}
255+
items := []onboardingCommand{
256+
{ID: "help", Command: "/cli help", Description: "show help"},
257+
{ID: "custom", Command: "/cli custom", Description: "custom command"},
258+
}
259+
260+
got := rewriteOnboardingCommands(items, root, "demo-llm")
261+
if len(got) != 1 {
262+
t.Fatalf("rewriteOnboardingCommands() item count = %d, want 1 (%#v)", len(got), got)
263+
}
264+
if got[0].ID != "custom" {
265+
t.Fatalf("rewriteOnboardingCommands() preserved wrong item: %#v", got)
266+
}
267+
if got[0].Command != "/cli custom" {
268+
t.Fatalf("rewriteOnboardingCommands() command = %q, want /cli custom", got[0].Command)
269+
}
270+
}
271+
272+
func findOnboardingCommandDescription(t *testing.T, groups []onboardingGroup, groupID, command string) string {
273+
t.Helper()
274+
275+
for _, group := range groups {
276+
if group.ID != groupID {
277+
continue
278+
}
279+
for _, item := range group.Items {
280+
if item.Command == command {
281+
return item.Description
282+
}
283+
}
284+
t.Fatalf("command %q not found in group %q", command, groupID)
285+
}
286+
287+
t.Fatalf("group %q not found", groupID)
288+
return ""
289+
}
290+
109291
func TestParseExtraParamsStrict(t *testing.T) {
110292
tests := []struct {
111293
name string

0 commit comments

Comments
 (0)