mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
0549f5cafc
Make slash commands easier to navigate in chat by splitting help into levels, compacting list output, and surfacing current selections for model, search, memory, and browser settings. Also route /status to the active conversation session and add an access inspector so users can understand their current command and ACL context.
186 lines
4.9 KiB
Go
186 lines
4.9 KiB
Go
package command
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/memohai/memoh/internal/settings"
|
|
)
|
|
|
|
func (h *Handler) buildSettingsGroup() *CommandGroup {
|
|
g := newCommandGroup("settings", "View and update bot settings")
|
|
g.DefaultAction = "get"
|
|
g.Register(SubCommand{
|
|
Name: "get",
|
|
Usage: "get - View current settings",
|
|
Handler: func(cc CommandContext) (string, error) {
|
|
s, err := h.settingsService.GetBot(cc.Ctx, cc.BotID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return formatKV([]kv{
|
|
{"Language", s.Language},
|
|
{"ACL Default Effect", s.AclDefaultEffect},
|
|
{"Reasoning Enabled", boolStr(s.ReasoningEnabled)},
|
|
{"Reasoning Effort", s.ReasoningEffort},
|
|
{"Heartbeat Enabled", boolStr(s.HeartbeatEnabled)},
|
|
{"Heartbeat Interval", fmt.Sprintf("%d min", s.HeartbeatInterval)},
|
|
{"Chat Model", h.resolveModelName(cc, s.ChatModelID)},
|
|
{"Heartbeat Model", h.resolveModelName(cc, s.HeartbeatModelID)},
|
|
{"Search Provider", h.resolveSearchProviderName(cc, s.SearchProviderID)},
|
|
{"Memory Provider", h.resolveMemoryProviderName(cc, s.MemoryProviderID)},
|
|
{"Browser Context", h.resolveBrowserContextName(cc, s.BrowserContextID)},
|
|
}), nil
|
|
},
|
|
})
|
|
g.Register(SubCommand{
|
|
Name: "update",
|
|
Usage: "update [--language L] [--acl_default_effect allow|deny] ... - Update settings",
|
|
IsWrite: true,
|
|
Handler: func(cc CommandContext) (string, error) {
|
|
if len(cc.Args) == 0 {
|
|
return settingsUpdateUsage(), nil
|
|
}
|
|
req := settings.UpsertRequest{}
|
|
args := cc.Args
|
|
for i := 0; i < len(args); i++ {
|
|
if i+1 >= len(args) {
|
|
return fmt.Sprintf("Missing value for %s.\n\n%s", args[i], settingsUpdateUsage()), nil
|
|
}
|
|
switch args[i] {
|
|
case "--language":
|
|
i++
|
|
req.Language = args[i]
|
|
case "--acl_default_effect":
|
|
i++
|
|
req.AclDefaultEffect = args[i]
|
|
case "--reasoning_enabled":
|
|
i++
|
|
v := strings.ToLower(args[i]) == "true"
|
|
req.ReasoningEnabled = &v
|
|
case "--reasoning_effort":
|
|
i++
|
|
req.ReasoningEffort = &args[i]
|
|
case "--heartbeat_enabled":
|
|
i++
|
|
v := strings.ToLower(args[i]) == "true"
|
|
req.HeartbeatEnabled = &v
|
|
case "--heartbeat_interval":
|
|
i++
|
|
val, err := strconv.Atoi(args[i])
|
|
if err != nil {
|
|
return fmt.Sprintf("Invalid heartbeat_interval: %s", args[i]), nil
|
|
}
|
|
req.HeartbeatInterval = &val
|
|
case "--chat_model_id":
|
|
i++
|
|
req.ChatModelID = args[i]
|
|
case "--heartbeat_model_id":
|
|
i++
|
|
req.HeartbeatModelID = args[i]
|
|
default:
|
|
return fmt.Sprintf("Unknown option: %s\n\n%s", args[i], settingsUpdateUsage()), nil
|
|
}
|
|
}
|
|
_, err := h.settingsService.UpsertBot(cc.Ctx, cc.BotID, req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return "Settings updated.", nil
|
|
},
|
|
})
|
|
return g
|
|
}
|
|
|
|
func settingsUpdateUsage() string {
|
|
return "Usage: /settings update [options]\n\n" +
|
|
"Options:\n" +
|
|
"- --language <value>\n" +
|
|
"- --acl_default_effect <allow|deny>\n" +
|
|
"- --reasoning_enabled <true|false>\n" +
|
|
"- --reasoning_effort <low|medium|high>\n" +
|
|
"- --heartbeat_enabled <true|false>\n" +
|
|
"- --heartbeat_interval <minutes>\n" +
|
|
"- --chat_model_id <id>\n" +
|
|
"- --heartbeat_model_id <id>"
|
|
}
|
|
|
|
func (h *Handler) getBotSettings(cc CommandContext) (settings.Settings, error) {
|
|
if h.settingsService == nil {
|
|
return settings.Settings{}, errors.New("settings service is not available")
|
|
}
|
|
return h.settingsService.GetBot(cc.Ctx, cc.BotID)
|
|
}
|
|
|
|
// resolveModelName resolves a model UUID to "model_name (provider_name)".
|
|
func (h *Handler) resolveModelName(cc CommandContext, modelID string) string {
|
|
if modelID == "" {
|
|
return "(none)"
|
|
}
|
|
if h.modelsService == nil {
|
|
return modelID
|
|
}
|
|
m, err := h.modelsService.GetByID(cc.Ctx, modelID)
|
|
if err != nil {
|
|
return modelID
|
|
}
|
|
provName := ""
|
|
if h.providersService != nil {
|
|
p, err := h.providersService.Get(cc.Ctx, m.ProviderID)
|
|
if err == nil {
|
|
provName = p.Name
|
|
}
|
|
}
|
|
if provName != "" {
|
|
return fmt.Sprintf("%s (%s)", m.Name, provName)
|
|
}
|
|
return m.Name
|
|
}
|
|
|
|
// resolveSearchProviderName resolves a search provider UUID to its name.
|
|
func (h *Handler) resolveSearchProviderName(cc CommandContext, id string) string {
|
|
if id == "" {
|
|
return "(none)"
|
|
}
|
|
if h.searchProvService == nil {
|
|
return id
|
|
}
|
|
p, err := h.searchProvService.Get(cc.Ctx, id)
|
|
if err != nil {
|
|
return id
|
|
}
|
|
return p.Name
|
|
}
|
|
|
|
// resolveMemoryProviderName resolves a memory provider UUID to its name.
|
|
func (h *Handler) resolveMemoryProviderName(cc CommandContext, id string) string {
|
|
if id == "" {
|
|
return "(none)"
|
|
}
|
|
if h.memProvService == nil {
|
|
return id
|
|
}
|
|
p, err := h.memProvService.Get(cc.Ctx, id)
|
|
if err != nil {
|
|
return id
|
|
}
|
|
return p.Name
|
|
}
|
|
|
|
// resolveBrowserContextName resolves a browser context UUID to its name.
|
|
func (h *Handler) resolveBrowserContextName(cc CommandContext, id string) string {
|
|
if id == "" {
|
|
return "(none)"
|
|
}
|
|
if h.browserCtxService == nil {
|
|
return id
|
|
}
|
|
p, err := h.browserCtxService.GetByID(cc.Ctx, id)
|
|
if err != nil {
|
|
return id
|
|
}
|
|
return p.Name
|
|
}
|