Skip to content

Commit 0e2dd47

Browse files
authored
Merge pull request #35 from Approaching-AI/hotfix/v0.3.2
hotfix(v0.3.2): simplify onboarding manifest generation
2 parents 1350760 + 3bf8eb2 commit 0e2dd47

6 files changed

Lines changed: 641 additions & 51 deletions

File tree

catalog/ui-onboarding.json

Lines changed: 112 additions & 36 deletions
Large diffs are not rendered by default.

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: 254 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 {
@@ -50,6 +52,29 @@ type mockCommandRunner struct {
5052
pipe func(context.Context, []string, []string) error
5153
}
5254

55+
type onboardingManifestDoc struct {
56+
Locales map[string]*onboardingLocaleDoc `json:"locales"`
57+
}
58+
59+
type onboardingLocaleDoc struct {
60+
FullCommands onboardingFullCommandsDoc `json:"full_commands"`
61+
}
62+
63+
type onboardingFullCommandsDoc struct {
64+
Groups []onboardingGroupDoc `json:"groups"`
65+
}
66+
67+
type onboardingGroupDoc struct {
68+
ID string `json:"id"`
69+
Items []onboardingCommandDoc `json:"items"`
70+
}
71+
72+
type onboardingCommandDoc struct {
73+
ID string `json:"id"`
74+
Command string `json:"command"`
75+
Description string `json:"description"`
76+
}
77+
5378
func (m *mockCommandRunner) Run(ctx context.Context, name string, args ...string) ([]byte, error) {
5479
if m.run != nil {
5580
return m.run(ctx, name, args...)
@@ -106,6 +131,235 @@ func TestOnboardingManifestEmbeddedShape(t *testing.T) {
106131
}
107132
}
108133

134+
func TestBuildOnboardingManifestJSON_IncludesAllTopLevelCommands(t *testing.T) {
135+
t.Parallel()
136+
137+
raw, err := buildOnboardingManifestJSON(&knowledge.Catalog{})
138+
if err != nil {
139+
t.Fatalf("build onboarding manifest: %v", err)
140+
}
141+
142+
manifest := decodeOnboardingManifest(t, raw)
143+
zh, ok := manifest.Locales["zh"]
144+
if !ok || zh == nil {
145+
t.Fatalf("zh locale missing: %#v", manifest.Locales)
146+
}
147+
148+
topLevel := findOnboardingGroup(t, zh.FullCommands.Groups, "top_level_commands")
149+
150+
root := cli.NewRootCmd(&cli.App{})
151+
root.InitDefaultHelpCmd()
152+
root.InitDefaultCompletionCmd()
153+
154+
want := make(map[string]struct{})
155+
for _, cmd := range root.Commands() {
156+
if cmd == nil || cmd.Hidden {
157+
continue
158+
}
159+
want["/cli "+cmd.Name()] = struct{}{}
160+
}
161+
162+
got := make(map[string]struct{})
163+
for _, item := range topLevel.Items {
164+
got[item.Command] = struct{}{}
165+
}
166+
167+
if len(got) != len(want) {
168+
t.Fatalf("top_level_commands item count = %d, want %d\nitems=%#v", len(got), len(want), topLevel.Items)
169+
}
170+
171+
for command := range want {
172+
if _, ok := got[command]; !ok {
173+
t.Fatalf("top_level_commands missing command %s\nitems=%#v", command, topLevel.Items)
174+
}
175+
}
176+
177+
for command := range got {
178+
if _, ok := want[command]; !ok {
179+
t.Fatalf("top_level_commands has unexpected command %s\nitems=%#v", command, topLevel.Items)
180+
}
181+
}
182+
}
183+
184+
func TestBuildOnboardingManifestJSON_MarksSampleModelExamplesAsReplaceable(t *testing.T) {
185+
t.Parallel()
186+
187+
cat := &knowledge.Catalog{
188+
ModelAssets: []knowledge.ModelAsset{
189+
{
190+
Metadata: knowledge.ModelMetadata{
191+
Name: "demo-llm",
192+
Type: "llm",
193+
Family: "demo",
194+
ParameterCount: "1B",
195+
},
196+
},
197+
},
198+
}
199+
200+
raw, err := buildOnboardingManifestJSON(cat)
201+
if err != nil {
202+
t.Fatalf("build onboarding manifest: %v", err)
203+
}
204+
205+
text := string(raw)
206+
for _, want := range []string{
207+
`"/cli help"`,
208+
`"/cli model pull demo-llm"`,
209+
`"/cli deploy demo-llm --dry-run"`,
210+
`"/cli run demo-llm"`,
211+
"demo-llm 是示例模型名,可替换成你自己的模型名",
212+
"demo-llm is an example model name; replace it with your own model name",
213+
} {
214+
if !strings.Contains(text, want) {
215+
t.Fatalf("generated manifest missing %q\nmanifest=%s", want, text)
216+
}
217+
}
218+
}
219+
220+
func TestBuildOnboardingManifestJSON_UsesLocalizedTemplates(t *testing.T) {
221+
t.Parallel()
222+
223+
sourceRaw, err := catalog.FS.ReadFile("ui-onboarding.json")
224+
if err != nil {
225+
t.Fatalf("read embedded onboarding manifest: %v", err)
226+
}
227+
228+
source := decodeOnboardingManifest(t, sourceRaw)
229+
zhSource, ok := source.Locales["zh"]
230+
if !ok || zhSource == nil {
231+
t.Fatalf("source zh locale missing: %#v", source.Locales)
232+
}
233+
enSource, ok := source.Locales["en"]
234+
if !ok || enSource == nil {
235+
t.Fatalf("source en locale missing: %#v", source.Locales)
236+
}
237+
238+
zhTemplates := findOnboardingGroup(t, zhSource.FullCommands.Groups, "top_level_commands")
239+
enTemplates := findOnboardingGroup(t, enSource.FullCommands.Groups, "top_level_commands")
240+
zhHelpTemplate := findOnboardingCommandByID(t, zhTemplates.Items, "help")
241+
zhModelTemplate := findOnboardingCommandByID(t, zhTemplates.Items, "model")
242+
zhDeployTemplate := findOnboardingCommandByID(t, zhTemplates.Items, "deploy")
243+
enHelpTemplate := findOnboardingCommandByID(t, enTemplates.Items, "help")
244+
245+
for _, template := range []onboardingCommandDoc{zhHelpTemplate, zhModelTemplate, zhDeployTemplate, enHelpTemplate} {
246+
if strings.TrimSpace(template.Description) == "" {
247+
t.Fatalf("top_level_commands template missing description: %#v", template)
248+
}
249+
}
250+
251+
raw, err := buildOnboardingManifestJSON(&knowledge.Catalog{
252+
ModelAssets: []knowledge.ModelAsset{
253+
{
254+
Metadata: knowledge.ModelMetadata{
255+
Name: "demo-llm",
256+
Type: "llm",
257+
Family: "demo",
258+
ParameterCount: "1B",
259+
},
260+
},
261+
},
262+
})
263+
if err != nil {
264+
t.Fatalf("build onboarding manifest: %v", err)
265+
}
266+
267+
manifest := decodeOnboardingManifest(t, raw)
268+
zh := manifest.Locales["zh"]
269+
en := manifest.Locales["en"]
270+
if zh == nil || en == nil {
271+
t.Fatalf("missing locales: %#v", manifest.Locales)
272+
}
273+
274+
zhHelp := findOnboardingCommandDescription(t, zh.FullCommands.Groups, "top_level_commands", "/cli help")
275+
if want := replaceSampleModelPlaceholder(zhHelpTemplate.Description, "demo-llm"); zhHelp != want {
276+
t.Fatalf("zh /cli help description = %q, want %q", zhHelp, want)
277+
}
278+
279+
enHelp := findOnboardingCommandDescription(t, en.FullCommands.Groups, "top_level_commands", "/cli help")
280+
if want := replaceSampleModelPlaceholder(enHelpTemplate.Description, "demo-llm"); enHelp != want {
281+
t.Fatalf("en /cli help description = %q, want %q", enHelp, want)
282+
}
283+
284+
zhModel := findOnboardingCommandDescription(t, zh.FullCommands.Groups, "top_level_commands", "/cli model")
285+
if want := replaceSampleModelPlaceholder(zhModelTemplate.Description, "demo-llm"); zhModel != want {
286+
t.Fatalf("zh /cli model description = %q, want %q", zhModel, want)
287+
}
288+
289+
zhDeploy := findOnboardingCommandDescription(t, zh.FullCommands.Groups, "top_level_commands", "/cli deploy")
290+
if want := replaceSampleModelPlaceholder(zhDeployTemplate.Description, "demo-llm"); zhDeploy != want {
291+
t.Fatalf("zh /cli deploy description = %q, want %q", zhDeploy, want)
292+
}
293+
}
294+
295+
func TestBuildTopLevelOnboardingItems_FallsBackToCommandShort(t *testing.T) {
296+
t.Parallel()
297+
298+
root := &cobra.Command{Use: "aima"}
299+
root.AddCommand(&cobra.Command{
300+
Use: "version",
301+
Short: "Show version information",
302+
})
303+
304+
items := buildTopLevelOnboardingItems(root, map[string]string{})
305+
if len(items) != 1 {
306+
t.Fatalf("buildTopLevelOnboardingItems() item count = %d, want 1", len(items))
307+
}
308+
if got := items[0]["description"]; got != "Show version information" {
309+
t.Fatalf("buildTopLevelOnboardingItems() description = %#v, want %q", got, "Show version information")
310+
}
311+
}
312+
313+
func decodeOnboardingManifest(t *testing.T, raw []byte) onboardingManifestDoc {
314+
t.Helper()
315+
316+
var manifest onboardingManifestDoc
317+
if err := json.Unmarshal(raw, &manifest); err != nil {
318+
t.Fatalf("unmarshal onboarding manifest: %v", err)
319+
}
320+
return manifest
321+
}
322+
323+
func findOnboardingGroup(t *testing.T, groups []onboardingGroupDoc, groupID string) onboardingGroupDoc {
324+
t.Helper()
325+
326+
for _, group := range groups {
327+
if group.ID == groupID {
328+
return group
329+
}
330+
}
331+
332+
t.Fatalf("group %q not found", groupID)
333+
return onboardingGroupDoc{}
334+
}
335+
336+
func findOnboardingCommandByID(t *testing.T, items []onboardingCommandDoc, id string) onboardingCommandDoc {
337+
t.Helper()
338+
339+
for _, item := range items {
340+
if item.ID == id {
341+
return item
342+
}
343+
}
344+
345+
t.Fatalf("command id %q not found", id)
346+
return onboardingCommandDoc{}
347+
}
348+
349+
func findOnboardingCommandDescription(t *testing.T, groups []onboardingGroupDoc, groupID, command string) string {
350+
t.Helper()
351+
352+
group := findOnboardingGroup(t, groups, groupID)
353+
for _, item := range group.Items {
354+
if item.Command == command {
355+
return item.Description
356+
}
357+
}
358+
359+
t.Fatalf("command %q not found in group %q", command, groupID)
360+
return ""
361+
}
362+
109363
func TestParseExtraParamsStrict(t *testing.T) {
110364
tests := []struct {
111365
name string

0 commit comments

Comments
 (0)