feat: add API key validation flow for AI provider config
All checks were successful
Beta Release / beta (push) Successful in 37s
All checks were successful
Beta Release / beta (push) Successful in 37s
- Add POST /api/providers/validate backend endpoint that sends a test request to the provider's chat/completions API to verify the key - Add validateProvider to frontend API client - Redesign PanelProviders: show token input inline with Validate button, display valid/invalid badge after validation, Save only appears after successful validation - Add i18n keys (EN/FR) for validation flow 💾 Generated with Crush Assisted-by: GLM-5-Turbo via Crush <crush@charm.land>
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/muyue/muyue/internal/config"
|
||||
"github.com/muyue/muyue/internal/lsp"
|
||||
@@ -529,3 +533,95 @@ func (s *Server) handleRunUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
"updated": len(needsUpdate),
|
||||
})
|
||||
}
|
||||
|
||||
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-Text-01"
|
||||
}
|
||||
|
||||
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"})
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ func (s *Server) routes() {
|
||||
s.mux.HandleFunc("/api/mcp/configure", s.handleMCPConfigure)
|
||||
s.mux.HandleFunc("/api/config/profile", s.handleSaveProfile)
|
||||
s.mux.HandleFunc("/api/config/provider", s.handleSaveProvider)
|
||||
s.mux.HandleFunc("/api/providers/validate", s.handleValidateProvider)
|
||||
s.mux.HandleFunc("/api/update/run", s.handleRunUpdate)
|
||||
s.mux.HandleFunc("/api/chat", s.handleChat)
|
||||
s.mux.HandleFunc("/api/chat/history", s.handleChatHistory)
|
||||
|
||||
Reference in New Issue
Block a user