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>
160 lines
3.9 KiB
Go
160 lines
3.9 KiB
Go
package replicate
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/rose_cat707/luminary/internal/model"
|
|
"github.com/rose_cat707/luminary/internal/prediction"
|
|
)
|
|
|
|
type Handler struct {
|
|
svc *prediction.Service
|
|
baseURL func() string
|
|
}
|
|
|
|
func New(svc *prediction.Service, baseURL func() string) *Handler {
|
|
return &Handler{svc: svc, baseURL: baseURL}
|
|
}
|
|
|
|
func (h *Handler) Register(r *gin.RouterGroup) {
|
|
r.POST("/predictions", h.Create)
|
|
r.GET("/predictions", h.List)
|
|
r.GET("/predictions/:id", h.Get)
|
|
r.POST("/predictions/:id/cancel", h.Cancel)
|
|
r.GET("/predictions/:id/stream", h.Stream)
|
|
}
|
|
|
|
func (h *Handler) ingress(c *gin.Context) (*model.IngressKey, bool) {
|
|
v, ok := c.Get("ingress_key")
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
k, ok := v.(*model.IngressKey)
|
|
return k, ok
|
|
}
|
|
|
|
func (h *Handler) Create(c *gin.Context) {
|
|
ingress, ok := h.ingress(c)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"detail": "unauthorized"})
|
|
return
|
|
}
|
|
var req prediction.CreateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"detail": "invalid request body"})
|
|
return
|
|
}
|
|
wait := strings.Contains(c.GetHeader("Prefer"), "wait")
|
|
p, err := h.svc.Create(c.Request.Context(), ingress, req, c.ClientIP(), wait)
|
|
if err != nil {
|
|
code := http.StatusBadRequest
|
|
if strings.Contains(err.Error(), "not found") {
|
|
code = http.StatusNotFound
|
|
}
|
|
c.JSON(code, gin.H{"detail": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, h.svc.APIView(p, h.baseURL()))
|
|
}
|
|
|
|
func (h *Handler) Get(c *gin.Context) {
|
|
ingress, ok := h.ingress(c)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"detail": "unauthorized"})
|
|
return
|
|
}
|
|
p, err := h.svc.Get(c.Request.Context(), c.Param("id"), ingress)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, h.svc.APIView(p, h.baseURL()))
|
|
}
|
|
|
|
func (h *Handler) List(c *gin.Context) {
|
|
ingress, ok := h.ingress(c)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"detail": "unauthorized"})
|
|
return
|
|
}
|
|
items, err := h.svc.List(c.Request.Context(), ingress, c.Query("cursor"), 100)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
|
return
|
|
}
|
|
results := make([]map[string]any, 0, len(items))
|
|
for _, p := range items {
|
|
results = append(results, h.svc.APIView(p, h.baseURL()))
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"results": results})
|
|
}
|
|
|
|
func (h *Handler) Cancel(c *gin.Context) {
|
|
ingress, ok := h.ingress(c)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"detail": "unauthorized"})
|
|
return
|
|
}
|
|
p, err := h.svc.Cancel(c.Request.Context(), c.Param("id"), ingress)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, h.svc.APIView(p, h.baseURL()))
|
|
}
|
|
|
|
func (h *Handler) Stream(c *gin.Context) {
|
|
ingress, ok := h.ingress(c)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"detail": "unauthorized"})
|
|
return
|
|
}
|
|
id := c.Param("id")
|
|
if _, err := h.svc.Get(c.Request.Context(), id, ingress); err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.Header("Content-Type", "text/event-stream")
|
|
c.Header("Cache-Control", "no-cache")
|
|
c.Header("Connection", "keep-alive")
|
|
c.Status(http.StatusOK)
|
|
|
|
flusher, ok := c.Writer.(http.Flusher)
|
|
if !ok {
|
|
return
|
|
}
|
|
w := bufio.NewWriter(c.Writer)
|
|
ch := h.svc.Hub().Subscribe(id)
|
|
defer h.svc.Hub().Unsubscribe(id, ch)
|
|
|
|
for {
|
|
select {
|
|
case <-c.Request.Context().Done():
|
|
return
|
|
case ev, open := <-ch:
|
|
if !open {
|
|
return
|
|
}
|
|
payload, _ := json.Marshal(map[string]any{
|
|
"id": id, "status": ev.Status, "logs": ev.Logs,
|
|
"metrics": ev.Metrics, "output": ev.Output, "error": ev.Error,
|
|
})
|
|
_, _ = fmt.Fprintf(w, "data: %s\n\n", payload)
|
|
w.Flush()
|
|
flusher.Flush()
|
|
if ev.Done {
|
|
_, _ = fmt.Fprintf(w, "event: done\ndata: {}\n\n")
|
|
w.Flush()
|
|
flusher.Flush()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|