diff --git a/cmd/picoclaw-launcher-tui/config/config.go b/cmd/picoclaw-launcher-tui/config/config.go index 28bee27cd..64d479285 100644 --- a/cmd/picoclaw-launcher-tui/config/config.go +++ b/cmd/picoclaw-launcher-tui/config/config.go @@ -8,6 +8,7 @@ package config import ( "bytes" + "encoding/json" "fmt" "os" "path/filepath" @@ -149,6 +150,78 @@ func (p *Provider) UsersForScheme(schemeName string) []User { return out } +// SyncSelectedModelToMainConfig syncs the currently selected model to ~/.picoclaw/config.json +// Adds/replaces a "tui-prefer" model entry and sets it as the default model. +// Preserves all other existing fields in the config file unchanged. +func SyncSelectedModelToMainConfig(scheme Scheme, user User, modelID string) error { + home, err := os.UserHomeDir() + if err != nil { + home = "." + } + mainConfigPath := filepath.Join(home, ".picoclaw", "config.json") + + var cfg map[string]interface{} + if data, err := os.ReadFile(mainConfigPath); err == nil { + if err := json.Unmarshal(data, &cfg); err != nil { + cfg = make(map[string]interface{}) + } + } else { + cfg = make(map[string]interface{}) + } + + if _, ok := cfg["agents"]; !ok { + cfg["agents"] = make(map[string]interface{}) + } + agents, ok := cfg["agents"].(map[string]interface{}) + if ok { + if _, ok := agents["defaults"]; !ok { + agents["defaults"] = make(map[string]interface{}) + } + defaults, ok := agents["defaults"].(map[string]interface{}) + if ok { + defaults["model"] = "tui-prefer" + } + } + + tuiModel := map[string]interface{}{ + "model_name": "tui-prefer", + "model": modelID, + "api_key": user.Key, + "api_base": scheme.BaseURL, + } + + modelList := []interface{}{} + if ml, ok := cfg["model_list"].([]interface{}); ok { + modelList = ml + } + + found := false + for i, m := range modelList { + if entry, ok := m.(map[string]interface{}); ok { + if name, ok := entry["model_name"].(string); ok && name == "tui-prefer" { + modelList[i] = tuiModel + found = true + break + } + } + } + if !found { + modelList = append(modelList, tuiModel) + } + cfg["model_list"] = modelList + + data, err := json.MarshalIndent(cfg, "", " ") + if err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(mainConfigPath), 0o700); err != nil { + return err + } + + return os.WriteFile(mainConfigPath, data, 0o600) +} + func (cfg *TUIConfig) CurrentModelLabel() string { cur := cfg.Provider.Current if cur.Model == "" { diff --git a/cmd/picoclaw-launcher-tui/main.go b/cmd/picoclaw-launcher-tui/main.go index 3d7e62b08..057206ab1 100644 --- a/cmd/picoclaw-launcher-tui/main.go +++ b/cmd/picoclaw-launcher-tui/main.go @@ -26,6 +26,10 @@ func main() { } app := ui.New(cfg, configPath) + // Bind model selection hook to sync to main config + app.OnModelSelected = func(scheme tuicfg.Scheme, user tuicfg.User, modelID string) { + _ = tuicfg.SyncSelectedModelToMainConfig(scheme, user, modelID) + } if err := app.Run(); err != nil { fmt.Fprintf(os.Stderr, "picoclaw-launcher-tui: %v\n", err) os.Exit(1) diff --git a/cmd/picoclaw-launcher-tui/ui/app.go b/cmd/picoclaw-launcher-tui/ui/app.go index 53d1cf8cd..4978935d9 100644 --- a/cmd/picoclaw-launcher-tui/ui/app.go +++ b/cmd/picoclaw-launcher-tui/ui/app.go @@ -25,6 +25,10 @@ type App struct { headerModelTV *tview.TextView modalOpen map[string]bool + // OnModelSelected is called when a model is selected in the UI. + // Can be nil to disable. + OnModelSelected func(scheme tuicfg.Scheme, user tuicfg.User, modelID string) + modelCache map[string][]modelEntry modelCacheMu sync.RWMutex refreshMu sync.Mutex diff --git a/cmd/picoclaw-launcher-tui/ui/models.go b/cmd/picoclaw-launcher-tui/ui/models.go index 46daaeb3e..1f9484b26 100644 --- a/cmd/picoclaw-launcher-tui/ui/models.go +++ b/cmd/picoclaw-launcher-tui/ui/models.go @@ -116,6 +116,24 @@ func (a *App) newModelsPage(schemeName, userName, baseURL string) tview.Primitiv Model: modelIDs[row], } a.save() + + // Trigger model selected callback if set + if a.OnModelSelected != nil && a.cfg.Model.Type == "provider" { + scheme := a.cfg.Provider.SchemeByName(schemeName) + if scheme == nil { + a.goBack() + return + } + var user tuicfg.User + for _, u := range a.cfg.Provider.Users { + if u.Scheme == schemeName && u.Name == userName { + user = u + break + } + } + a.OnModelSelected(*scheme, user, modelIDs[row]) + } + a.goBack() })