76ba500417
CI / docker (push) Successful in 2m4s
OpenAI-compatible AI gateway with Vue admin UI, multi-provider egress, ingress key governance, monitoring, and security controls. Co-authored-by: Cursor <cursoragent@cursor.com>
197 lines
6.2 KiB
Go
197 lines
6.2 KiB
Go
package admin
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/rose_cat707/luminary/internal/model"
|
|
"github.com/rose_cat707/luminary/internal/provider"
|
|
"github.com/rose_cat707/luminary/internal/store/cache"
|
|
)
|
|
|
|
func (h *Handler) ListEndpointMeta(c *gin.Context) {
|
|
category := model.ProviderCategory(c.Query("category"))
|
|
if category == "" {
|
|
category = model.CategoryLLM
|
|
}
|
|
providerType := model.ProviderType(c.Query("type"))
|
|
if providerType == "" {
|
|
providerType = provider.DefaultProviderType(category)
|
|
}
|
|
endpoints := provider.EndpointsForCategory(category)
|
|
providerTypes := provider.TypesForCategory(category)
|
|
type item struct {
|
|
provider.EndpointMeta
|
|
DefaultPath string `json:"default_path"`
|
|
}
|
|
out := make([]item, 0, len(endpoints))
|
|
for _, e := range endpoints {
|
|
out = append(out, item{
|
|
EndpointMeta: e,
|
|
DefaultPath: provider.DefaultPath(providerType, e.Key),
|
|
})
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"categories": []gin.H{
|
|
{"key": model.CategoryLLM, "label": provider.CategoryLabel(model.CategoryLLM), "supported": true},
|
|
{"key": model.CategoryEmbedding, "label": provider.CategoryLabel(model.CategoryEmbedding), "supported": true},
|
|
{"key": model.CategoryRerank, "label": provider.CategoryLabel(model.CategoryRerank), "supported": true},
|
|
{"key": model.CategorySpeech, "label": provider.CategoryLabel(model.CategorySpeech), "supported": false},
|
|
{"key": model.CategoryImage, "label": provider.CategoryLabel(model.CategoryImage), "supported": true},
|
|
{"key": model.CategoryAudio, "label": provider.CategoryLabel(model.CategoryAudio), "supported": false},
|
|
},
|
|
"provider_types": providerTypes,
|
|
"all_provider_types": provider.AllProviderTypes(),
|
|
"endpoints": out,
|
|
})
|
|
}
|
|
|
|
func (h *Handler) ListProviders(c *gin.Context) {
|
|
category := c.Query("category")
|
|
providers := h.cache.ListProviders()
|
|
if category != "" {
|
|
filtered := providers[:0]
|
|
for _, p := range providers {
|
|
if string(p.Category) == category {
|
|
filtered = append(filtered, p)
|
|
}
|
|
}
|
|
providers = filtered
|
|
}
|
|
out := make([]model.Provider, 0, len(providers))
|
|
for _, p := range providers {
|
|
p.Models = h.cache.GetModels(p.ID)
|
|
out = append(out, p)
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": out})
|
|
}
|
|
|
|
func (h *Handler) CreateProvider(c *gin.Context) {
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
Type model.ProviderType `json:"type"`
|
|
Category model.ProviderCategory `json:"category"`
|
|
BaseURL string `json:"base_url"`
|
|
AllowedEndpoints []string `json:"allowed_endpoints"`
|
|
EndpointPaths map[string]string `json:"endpoint_paths"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil || req.Name == "" || req.Type == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
return
|
|
}
|
|
if req.Category == "" {
|
|
req.Category = model.CategoryLLM
|
|
}
|
|
if !model.IsCategorySupported(req.Category) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "category not supported"})
|
|
return
|
|
}
|
|
if !provider.IsTypeValidForCategory(req.Category, req.Type) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "protocol type not supported for this category"})
|
|
return
|
|
}
|
|
if h.providerNameTaken(req.Name, 0) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "provider name already exists"})
|
|
return
|
|
}
|
|
allowed := req.AllowedEndpoints
|
|
if len(allowed) == 0 {
|
|
allowed = provider.DefaultEndpointsForCategory(req.Category)
|
|
}
|
|
if err := provider.ValidateCategoryEndpoints(req.Category, allowed); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
pathsJSON, _ := json.Marshal(req.EndpointPaths)
|
|
if req.EndpointPaths == nil {
|
|
pathsJSON = []byte("{}")
|
|
}
|
|
p := model.Provider{
|
|
Name: req.Name,
|
|
Type: req.Type,
|
|
Category: req.Category,
|
|
BaseURL: req.BaseURL,
|
|
AllowedEndpointsJSON: cache.ModelsToJSON(allowed),
|
|
EndpointPathsJSON: string(pathsJSON),
|
|
Status: model.ProviderUnknown,
|
|
}
|
|
if err := h.db.DB().Create(&p).Error; err != nil {
|
|
if h.providerNameTaken(req.Name, 0) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "provider name already exists"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
h.cache.SetProvider(p)
|
|
c.JSON(http.StatusCreated, p)
|
|
}
|
|
|
|
func (h *Handler) GetProvider(c *gin.Context) {
|
|
id := parseUint(c.Param("id"))
|
|
p, ok := h.cache.GetProvider(id)
|
|
if !ok {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
|
return
|
|
}
|
|
p.Keys = h.cache.ListProviderKeys(id)
|
|
p.Models = h.cache.GetModels(id)
|
|
c.JSON(http.StatusOK, p)
|
|
}
|
|
|
|
func (h *Handler) UpdateProvider(c *gin.Context) {
|
|
id := parseUint(c.Param("id"))
|
|
p, ok := h.cache.GetProvider(id)
|
|
if !ok {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
|
return
|
|
}
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
BaseURL string `json:"base_url"`
|
|
Category model.ProviderCategory `json:"category"`
|
|
AllowedEndpoints []string `json:"allowed_endpoints"`
|
|
EndpointPaths map[string]string `json:"endpoint_paths"`
|
|
Enabled *bool `json:"enabled"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
|
return
|
|
}
|
|
if req.Name != "" && req.Name != p.Name {
|
|
if h.providerNameTaken(req.Name, p.ID) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "provider name already exists"})
|
|
return
|
|
}
|
|
p.Name = req.Name
|
|
}
|
|
if req.BaseURL != "" {
|
|
p.BaseURL = req.BaseURL
|
|
}
|
|
if req.Category != "" {
|
|
if !model.IsCategorySupported(req.Category) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "category not supported yet"})
|
|
return
|
|
}
|
|
p.Category = req.Category
|
|
}
|
|
if req.AllowedEndpoints != nil {
|
|
if err := provider.ValidateCategoryEndpoints(p.Category, req.AllowedEndpoints); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
p.AllowedEndpointsJSON = cache.ModelsToJSON(req.AllowedEndpoints)
|
|
}
|
|
if req.EndpointPaths != nil {
|
|
b, _ := json.Marshal(req.EndpointPaths)
|
|
p.EndpointPathsJSON = string(b)
|
|
}
|
|
if req.Enabled != nil {
|
|
p.Enabled = *req.Enabled
|
|
}
|
|
h.db.DB().Save(&p)
|
|
h.cache.SetProvider(p)
|
|
c.JSON(http.StatusOK, p)
|
|
}
|