All checks were successful
Beta Release / beta (push) Successful in 44s
Break down the 627-line handlers.go into specialized modules: - handlers_chat.go: chat and streaming endpoints - handlers_config.go: configuration endpoints - handlers_common.go: shared utilities - handlers_info.go: info and status endpoints - handlers_terminal.go: terminal/shell endpoints - handlers_tools.go: tool-related endpoints Also includes config improvements, orchestrator enhancements, and web component updates. 💘 Generated with Crush Assisted-by: MiniMax-M2.7 via Crush <crush@charm.land>
284 lines
7.1 KiB
Go
284 lines
7.1 KiB
Go
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})
|
|
}
|