返回 DeepSeek-Reasonix
slash_args.go
根目录 / desktop / slash_args.go
1 package main
2
3 import (
4 "strings"
5
6 "reasonix/internal/config"
7 "reasonix/internal/control"
8 "reasonix/internal/pluginpkg"
9 )
10
11 // SlashArgItem is one sub-command / argument suggestion for the composer's slash
12 // menu (the part after the command word). Mirrors the CLI's arg completion via
13 // the shared control.SlashArgItems, so desktop and CLI offer the same hints.
14 type SlashArgItem struct {
15 Label string `json:"label"`
16 Insert string `json:"insert"`
17 Hint string `json:"hint"`
18 Descend bool `json:"descend"`
19 }
20
21 // SlashArgsResult carries the suggestions plus the byte offset in the input where
22 // the current token begins, so the composer replaces just that token.
23 type SlashArgsResult struct {
24 Items []SlashArgItem `json:"items"`
25 From int `json:"from"`
26 }
27
28 // SlashArgs completes the arguments of a management slash command (/mcp, /model,
29 // /skill, /hooks) for the composer — the same logic the chat TUI uses. Empty
30 // Items means the input has no structured arguments to complete.
31 func (a *App) SlashArgs(input string) SlashArgsResult {
32 a.mu.RLock()
33 ctrl := a.activeCtrlLocked()
34 model := ""
35 tabID := ""
36 if tab := a.activeTabLocked(); tab != nil {
37 model = tab.model
38 tabID = tab.ID
39 }
40 a.mu.RUnlock()
41 if ctrl == nil {
42 return SlashArgsResult{Items: []SlashArgItem{}}
43 }
44 data := control.ArgData{
45 Skills: ctrl.Skills(),
46 DisabledSkills: ctrl.DisabledSkills(),
47 ConfiguredMCP: ctrl.ConfiguredMCPNames(),
48 DisconnectedMCP: ctrl.DisconnectedMCPNames(),
49 CurrentModel: model,
50 }
51 if fields := strings.Fields(input); len(fields) > 0 && fields[0] == "/effort" {
52 if effort := a.EffortForTab(tabID); effort.Supported {
53 data.EffortLevels = append([]string(nil), effort.Levels...)
54 }
55 }
56 if names, err := pluginpkg.InstalledNames(config.ReasonixHomeDir()); err == nil {
57 data.PluginNames = names
58 }
59 seen := map[string]bool{}
60 for _, m := range a.Models() {
61 data.ModelRefs = append(data.ModelRefs, m.Ref)
62 if m.Provider != "" && !seen[m.Provider] {
63 seen[m.Provider] = true
64 data.ProviderNames = append(data.ProviderNames, m.Provider)
65 }
66 if m.Current {
67 data.CurrentProvider = m.Provider
68 }
69 }
70 if h := ctrl.Host(); h != nil {
71 data.ServerNames = h.ServerNames()
72 }
73 data.MemoryRefs, data.MemoryArchives = control.MemoryCompletionData(ctrl.Memory())
74 items, from := control.SlashArgItems(input, data)
75 // Non-nil so it serializes as a JSON array, never null — the frontend filters
76 // over it directly.
77 out := SlashArgsResult{Items: []SlashArgItem{}, From: from}
78 for _, it := range items {
79 out.Items = append(out.Items, SlashArgItem{Label: it.Label, Insert: it.Insert, Hint: it.Hint, Descend: it.Descend})
80 }
81 return out
82 }
83
83 lines GO