package api import ( "bytes" "encoding/json" "io" "net/http" "strings" "time" "github.com/muyue/muyue/internal/config" ) func (s *Server) handleUpdatePreferences(w http.ResponseWriter, r *http.Request) { if r.Method != "PUT" { writeError(w, "PUT only", http.StatusMethodNotAllowed) return } if s.config == nil { writeError(w, "no config", http.StatusNotFound) return } var body struct { Language string `json:"language"` KeyboardLayout string `json:"keyboard_layout"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } if body.Language != "" { s.config.Profile.Preferences.Language = body.Language } if body.KeyboardLayout != "" { s.config.Profile.Preferences.KeyboardLayout = body.KeyboardLayout } if err := config.Save(s.config); err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } writeJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleSaveProfile(w http.ResponseWriter, r *http.Request) { if r.Method != "PUT" { writeError(w, "PUT only", http.StatusMethodNotAllowed) return } if s.config == nil { writeError(w, "no config", http.StatusNotFound) return } var body struct { Name string `json:"name"` Pseudo string `json:"pseudo"` Email string `json:"email"` Editor string `json:"editor"` Shell string `json:"shell"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } if body.Name != "" { s.config.Profile.Name = body.Name } if body.Pseudo != "" { s.config.Profile.Pseudo = body.Pseudo } if body.Email != "" { s.config.Profile.Email = body.Email } if body.Editor != "" { s.config.Profile.Preferences.Editor = body.Editor } if body.Shell != "" { s.config.Profile.Preferences.Shell = body.Shell } if err := config.Save(s.config); err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } writeJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleSaveProvider(w http.ResponseWriter, r *http.Request) { if r.Method != "PUT" { writeError(w, "PUT only", http.StatusMethodNotAllowed) return } if s.config == nil { writeError(w, "no config", http.StatusNotFound) return } var body struct { Name string `json:"name"` APIKey string `json:"api_key"` Model string `json:"model"` BaseURL string `json:"base_url"` Active *bool `json:"active"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } if body.Name == "" { writeError(w, "name required", http.StatusBadRequest) return } found := false for i := range s.config.AI.Providers { if s.config.AI.Providers[i].Name == body.Name { if body.APIKey != "" { s.config.AI.Providers[i].APIKey = body.APIKey } if body.Model != "" { s.config.AI.Providers[i].Model = body.Model } if body.BaseURL != "" { s.config.AI.Providers[i].BaseURL = body.BaseURL } if body.Active != nil { if *body.Active { for j := range s.config.AI.Providers { s.config.AI.Providers[j].Active = false } } s.config.AI.Providers[i].Active = *body.Active } found = true break } } if !found { writeError(w, "provider not found", http.StatusNotFound) return } if err := config.Save(s.config); err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } writeJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleValidateProvider(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { writeError(w, "POST only", http.StatusMethodNotAllowed) return } var body struct { Name string `json:"name"` APIKey string `json:"api_key"` Model string `json:"model"` BaseURL string `json:"base_url"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } if body.APIKey == "" { writeError(w, "api_key required", http.StatusBadRequest) return } baseURL := body.BaseURL if baseURL == "" { for _, p := range s.config.AI.Providers { if p.Name == body.Name { baseURL = p.BaseURL break } } } if baseURL == "" { switch body.Name { case "minimax": baseURL = "https://api.minimax.io/v1" case "openai": baseURL = "https://api.openai.com/v1" case "anthropic": baseURL = "https://api.anthropic.com/v1" default: baseURL = "https://api.minimax.io/v1" } } model := body.Model if model == "" { for _, p := range s.config.AI.Providers { if p.Name == body.Name { model = p.Model break } } } if model == "" { model = "MiniMax-M2.7" } reqBody, _ := json.Marshal(map[string]interface{}{ "model": model, "messages": []map[string]string{{"role": "user", "content": "Hi"}}, "max_tokens": 5, "stream": false, }) url := strings.TrimRight(baseURL, "/") + "/chat/completions" req, err := http.NewRequest("POST", url, bytes.NewReader(reqBody)) if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+body.APIKey) client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Do(req) if err != nil { writeError(w, "connection failed: "+err.Error(), http.StatusBadGateway) return } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { writeError(w, "invalid_api_key", http.StatusUnauthorized) return } if resp.StatusCode != http.StatusOK { writeError(w, "api_error: "+string(respBody), http.StatusBadGateway) return } writeJSON(w, map[string]interface{}{"status": "valid"}) } func (s *Server) handleSaveTerminalSettings(w http.ResponseWriter, r *http.Request) { if r.Method != "PUT" { writeError(w, "PUT only", http.StatusMethodNotAllowed) return } if s.config == nil { writeError(w, "no config", http.StatusNotFound) return } var body struct { FontSize int `json:"font_size"` FontFamily string `json:"font_family"` Theme string `json:"theme"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, err.Error(), http.StatusBadRequest) return } if body.FontSize > 0 { s.config.Terminal.FontSize = body.FontSize } if body.FontFamily != "" { s.config.Terminal.FontFamily = body.FontFamily } if body.Theme != "" { s.config.Terminal.Theme = body.Theme } if err := config.Save(s.config); err != nil { writeError(w, err.Error(), http.StatusInternalServerError) return } writeJSON(w, map[string]interface{}{ "status": "ok", "theme": config.GetTerminalTheme(s.config.Terminal.Theme), }) } func (s *Server) handleGetTerminalThemes(w http.ResponseWriter, r *http.Request) { themes := make([]map[string]string, 0, len(config.DEFAULT_TERMINAL_THEMES)) for id, theme := range config.DEFAULT_TERMINAL_THEMES { themes = append(themes, map[string]string{ "id": id, "name": theme.Name, }) } writeJSON(w, map[string]interface{}{"themes": themes}) }