5301557ad2
CI / docker (push) Has been cancelled
引入用户端对话/生图与管理端 Provider 配置,统一 invoke 编排、Gateway、请求日志与模型管理;前端按设计规范拆分布局,Go module 更名为 github.com/rose_cat707/Atlas;生图长任务 SSE 推送;管理端登录防爆破;协议与服务类型联动;Replicate 模型同步修复;用户端创作台 UI 重构;管理端创作历史替代请求日志。 Co-authored-by: Cursor <cursoragent@cursor.com>
200 lines
6.6 KiB
Go
200 lines
6.6 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/rose_cat707/Atlas/internal/store"
|
|
)
|
|
|
|
func (s *Server) registerAdminRoutes(r gin.IRoutes) {
|
|
r.GET("/admin/providers", s.adminListProviders)
|
|
r.POST("/admin/providers", s.adminCreateProvider)
|
|
r.GET("/admin/providers/:id", s.adminGetProvider)
|
|
r.PUT("/admin/providers/:id", s.adminUpdateProvider)
|
|
r.DELETE("/admin/providers/:id", s.adminDeleteProvider)
|
|
r.GET("/admin/providers/:id/models", s.adminListProviderModels)
|
|
r.POST("/admin/providers/:id/models", s.adminAddProviderModel)
|
|
r.DELETE("/admin/providers/:id/models/:modelId", s.adminDeleteProviderModel)
|
|
r.POST("/admin/providers/:id/models/sync", s.adminSyncProviderModels)
|
|
r.GET("/admin/generations", s.adminListGenerations)
|
|
r.GET("/admin/generations/:id", s.adminGetGeneration)
|
|
r.DELETE("/admin/generations/:id", s.adminDeleteGeneration)
|
|
r.GET("/admin/dashboard/stats", s.adminDashboardStats)
|
|
}
|
|
|
|
func (s *Server) adminListProviders(c *gin.Context) {
|
|
items, err := s.app.Store.ListProviders(c.Request.Context(), c.Query("service_type"), false)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
masked := make([]store.Provider, len(items))
|
|
for i, p := range items {
|
|
masked[i] = store.MaskProvider(p)
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"items": masked})
|
|
}
|
|
|
|
func (s *Server) adminCreateProvider(c *gin.Context) {
|
|
var in store.Provider
|
|
if err := c.ShouldBindJSON(&in); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := store.ValidateProvider(&in); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if in.ExtraJSON == "" {
|
|
in.ExtraJSON = "{}"
|
|
}
|
|
id, err := s.app.Store.CreateProvider(c.Request.Context(), &in)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
in.ID = id
|
|
_ = s.app.Store.InsertAuditLog(c.Request.Context(), "create", "provider", in.Name)
|
|
c.JSON(http.StatusOK, store.MaskProvider(in))
|
|
}
|
|
|
|
func (s *Server) adminGetProvider(c *gin.Context) {
|
|
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
p, err := s.app.Store.GetProvider(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, store.MaskProvider(*p))
|
|
}
|
|
|
|
func (s *Server) adminUpdateProvider(c *gin.Context) {
|
|
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
var in store.Provider
|
|
if err := c.ShouldBindJSON(&in); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
existing, err := s.app.Store.GetProvider(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
|
return
|
|
}
|
|
in.ID = id
|
|
if in.APIKey == "" || in.APIKey == "******" {
|
|
in.APIKey = existing.APIKey
|
|
}
|
|
if err := store.ValidateProvider(&in); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := s.app.Store.UpdateProvider(c.Request.Context(), &in); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
_ = s.app.Store.InsertAuditLog(c.Request.Context(), "update", "provider", in.Name)
|
|
c.JSON(http.StatusOK, store.MaskProvider(in))
|
|
}
|
|
|
|
func (s *Server) adminDeleteProvider(c *gin.Context) {
|
|
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err := s.app.Store.DeleteProvider(c.Request.Context(), id); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
_ = s.app.Store.InsertAuditLog(c.Request.Context(), "delete", "provider", strconv.FormatInt(id, 10))
|
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
|
}
|
|
|
|
func (s *Server) adminListProviderModels(c *gin.Context) {
|
|
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
breakdown, err := s.app.Models.Breakdown(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, breakdown)
|
|
}
|
|
|
|
func (s *Server) adminAddProviderModel(c *gin.Context) {
|
|
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
var in struct {
|
|
ModelID string `json:"model_id"`
|
|
Name string `json:"name"`
|
|
}
|
|
if err := c.ShouldBindJSON(&in); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if err := s.app.Models.AddManual(c.Request.Context(), id, in.ModelID, in.Name); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
breakdown, _ := s.app.Models.Breakdown(c.Request.Context(), id)
|
|
c.JSON(http.StatusOK, breakdown)
|
|
}
|
|
|
|
func (s *Server) adminDeleteProviderModel(c *gin.Context) {
|
|
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
modelID := c.Param("modelId")
|
|
if err := s.app.Models.RemoveManual(c.Request.Context(), id, modelID); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
breakdown, _ := s.app.Models.Breakdown(c.Request.Context(), id)
|
|
c.JSON(http.StatusOK, breakdown)
|
|
}
|
|
|
|
func (s *Server) adminSyncProviderModels(c *gin.Context) {
|
|
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
upstream, err := s.app.Models.RefreshUpstream(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
breakdown, _ := s.app.Models.Breakdown(c.Request.Context(), id)
|
|
c.JSON(http.StatusOK, gin.H{"upstream": upstream, "breakdown": breakdown})
|
|
}
|
|
|
|
func (s *Server) adminListGenerations(c *gin.Context) {
|
|
limit, offset := parseLimitOffset(c, 50, 500)
|
|
items, total, err := s.app.Store.ListAllGenerations(c.Request.Context(), c.Query("service_type"), limit, offset)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
|
|
}
|
|
|
|
func (s *Server) adminGetGeneration(c *gin.Context) {
|
|
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
item, err := s.app.Store.AdminGetGeneration(c.Request.Context(), id)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, item)
|
|
}
|
|
|
|
func (s *Server) adminDeleteGeneration(c *gin.Context) {
|
|
id, _ := strconv.ParseInt(c.Param("id"), 10, 64)
|
|
if err := s.app.Store.AdminDeleteGeneration(c.Request.Context(), id); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
_ = s.app.Store.InsertAuditLog(c.Request.Context(), "delete", "generation", strconv.FormatInt(id, 10))
|
|
c.JSON(http.StatusOK, gin.H{"ok": true})
|
|
}
|
|
|
|
func (s *Server) adminDashboardStats(c *gin.Context) {
|
|
stats, err := s.app.Store.DashboardStats(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, stats)
|
|
}
|