feat(admin): add model config CRUD and platform bridge pages

- Backend: ModelConfigStore with Provider/Model/Routing CRUD
- Backend: /api/v1/admin/models/config endpoints
- Frontend: ModelsPage with 3-tab editor (providers/models/routing)
- Frontend: BridgePage showing adapter status + group chat rules
- Admin nav expanded to 9 items
This commit is contained in:
Aska committed 2026-08-12 17:13:29 +08:00
1 parent 1a80ac2a00
commit 8420691438
7 files changed
+422 -1

No files matched your search

+92 -1
View File
@@ -6,6 +6,7 @@ import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
@@ -73,6 +74,7 @@ type Dependencies struct {
Orchestrator OrchestratorPipeline
WSHub WebSocketHub
LLMLogger LLMCallLogger
ModelConfig *ModelConfigStore
PluginList []map[string]string
}
@@ -114,6 +116,8 @@ func (r *Router) setupRoutes() {
r.mux.HandleFunc("/api/v1/admin/system", r.authMW(r.adminMW(r.systemInfoHandler)))
r.mux.HandleFunc("/api/v1/admin/plugins", r.authMW(r.adminMW(r.pluginListHandler)))
r.mux.HandleFunc("/api/v1/admin/sessions", r.authMW(r.adminMW(r.adminSessionsHandler)))
r.mux.HandleFunc("/api/v1/admin/models/config", r.authMW(r.adminMW(r.modelConfigHandler)))
r.mux.HandleFunc("/api/v1/admin/models/config/", r.authMW(r.adminMW(r.modelConfigItemHandler)))
}
// ServeHTTP implements http.Handler.
@@ -392,7 +396,94 @@ func (r *Router) pluginListHandler(w http.ResponseWriter, _ *http.Request) {
}
func (r *Router) adminSessionsHandler(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, 200, M{"sessions": []any{}, "total": r.deps.WSHub.Count()})
writeJSON(w, 200, M{"sessions": []any{}, "total": 0})
}
// --- Model Config ---
func (r *Router) modelConfigHandler(w http.ResponseWriter, req *http.Request) {
if r.deps.ModelConfig == nil {
writeJSON(w, 503, M{"error": "model config unavailable"})
return
}
switch req.Method {
case "GET":
writeJSON(w, 200, M{
"providers": r.deps.ModelConfig.GetProviders(),
"models": r.deps.ModelConfig.GetModels(),
"routing": r.deps.ModelConfig.GetRouting(),
})
default:
writeJSON(w, 405, M{"error": "method not allowed"})
}
}
func (r *Router) modelConfigItemHandler(w http.ResponseWriter, req *http.Request) {
if r.deps.ModelConfig == nil {
writeJSON(w, 503, M{"error": "model config unavailable"})
return
}
// Path: /api/v1/admin/models/config/{type}/{name}
path := strings.TrimPrefix(req.URL.Path, "/api/v1/admin/models/config/")
parts := strings.SplitN(path, "/", 2)
if len(parts) < 2 {
writeJSON(w, 400, M{"error": "invalid path"})
return
}
kind, name := parts[0], parts[1]
switch req.Method {
case "POST", "PUT":
var body map[string]any
json.NewDecoder(req.Body).Decode(&body)
switch kind {
case "providers":
r.deps.ModelConfig.SaveProvider(&ModelProvider{
Name: name, BaseURL: str(body, "base_url"),
APIKey: str(body, "api_key"), Timeout: num(body, "timeout_sec"),
Retries: num(body, "max_retries"),
})
case "models":
var tags []string
if t, ok := body["tags"].([]any); ok {
for _, v := range t { tags = append(tags, fmt.Sprint(v)) }
}
r.deps.ModelConfig.SaveModel(&ModelEntry{
ID: name, Name: str(body, "name"), Provider: str(body, "provider"),
Description: str(body, "description"), Tags: tags,
Params: body["params"].(map[string]any), Enabled: true,
})
case "routing":
var chain []string
if c, ok := body["fallback_chain"].([]any); ok {
for _, v := range c { chain = append(chain, fmt.Sprint(v)) }
}
r.deps.ModelConfig.SaveRouting(&RoutingRule{
Purpose: name, FallbackChain: chain, Required: body["required"].(bool),
})
}
writeJSON(w, 200, M{"ok": true})
case "DELETE":
switch kind {
case "providers": r.deps.ModelConfig.DeleteProvider(name)
case "models": r.deps.ModelConfig.DeleteModel(name)
case "routing": r.deps.ModelConfig.DeleteRouting(name)
}
writeJSON(w, 204, nil)
default:
writeJSON(w, 405, M{"error": "method not allowed"})
}
}
func str(m map[string]any, key string) string {
if v, ok := m[key].(string); ok { return v }
return ""
}
func num(m map[string]any, key string) int {
if v, ok := m[key].(float64); ok { return int(v) }
return 0
}
// --- Helpers ---