Initial commit: LlamaLink Go rewrite
Complete rewrite from Python/FastAPI to Go/Gin: - Go backend: auth (API keys + bcrypt), llama.cpp subprocess manager, hot-swap multi-model, rate limiting, quota system, webhooks - Vue 3 SPA admin panel (src/) with Tailwind CSS - Deployment: Docker multi-stage, docker-compose, nginx, systemd - GORM/SQLite models: ApiKey, Model, UsageLog, Quota, Webhook - REST API: /api/v1/admin/* (keys, models, chat, usage, health) - Embedded frontend via go:embed (build output at web/dist/) Removed legacy Python artifacts (app/, tests/, pyproject.toml, etc.)
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/llamalink/llamalink/internal/db"
|
||||
"github.com/llamalink/llamalink/internal/llama"
|
||||
)
|
||||
|
||||
type ModelsHandler struct {
|
||||
manager *llama.Manager
|
||||
}
|
||||
|
||||
func NewModelsHandler(manager *llama.Manager) *ModelsHandler {
|
||||
return &ModelsHandler{manager: manager}
|
||||
}
|
||||
|
||||
type CreateModelRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
ModelPath string `json:"model_path" binding:"required"`
|
||||
Alias string `json:"alias" binding:"required"`
|
||||
CtxSize int `json:"ctx_size"`
|
||||
NGPULayers int `json:"n_gpu_layers"`
|
||||
ExtraArgs map[string]interface{} `json:"extra_args"`
|
||||
IsDefault bool `json:"is_default"`
|
||||
}
|
||||
|
||||
func (h *ModelsHandler) ListModels(c *gin.Context) {
|
||||
var models []db.Model
|
||||
db := h.manager.GetDB()
|
||||
if err := db.Where("is_enabled = ?", true).Order("name").Find(&models).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
status := h.manager.GetStatus()
|
||||
currentModel := status.CurrentModel
|
||||
|
||||
result := make([]gin.H, len(models))
|
||||
for i, m := range models {
|
||||
result[i] = gin.H{
|
||||
"id": m.ID,
|
||||
"name": m.Name,
|
||||
"model_path": m.ModelPath,
|
||||
"alias": m.Alias,
|
||||
"ctx_size": m.CtxSize,
|
||||
"n_gpu_layers": m.NGPULayers,
|
||||
"is_default": m.IsDefault,
|
||||
"is_active": m.Name == currentModel && status.Status == llama.StatusReady,
|
||||
"loaded_at": m.LoadedAt,
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": result})
|
||||
}
|
||||
|
||||
func (h *ModelsHandler) GetActiveModel(c *gin.Context) {
|
||||
status := h.manager.GetStatus()
|
||||
|
||||
if status.CurrentModel == "" {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": gin.H{
|
||||
"status": status.Status,
|
||||
"current_model": nil,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"data": gin.H{
|
||||
"status": status.Status,
|
||||
"current_model": status.CurrentModel,
|
||||
"loaded_at": status.LoadedAt,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ModelsHandler) CreateModel(c *gin.Context) {
|
||||
var req CreateModelRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if req.CtxSize == 0 {
|
||||
req.CtxSize = 8192
|
||||
}
|
||||
if req.NGPULayers == 0 {
|
||||
req.NGPULayers = -1
|
||||
}
|
||||
|
||||
extraArgsJSON, _ := json.Marshal(req.ExtraArgs)
|
||||
|
||||
model := &db.Model{
|
||||
ID: uuid.New(),
|
||||
Name: req.Name,
|
||||
ModelPath: req.ModelPath,
|
||||
Alias: req.Alias,
|
||||
CtxSize: req.CtxSize,
|
||||
NGPULayers: req.NGPULayers,
|
||||
ExtraArgs: db.StringArray{string(extraArgsJSON)},
|
||||
IsDefault: req.IsDefault,
|
||||
IsEnabled: true,
|
||||
IsActive: false,
|
||||
}
|
||||
|
||||
db := h.manager.GetDB()
|
||||
if err := db.Create(model).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"id": model.ID,
|
||||
"name": model.Name,
|
||||
"model_path": model.ModelPath,
|
||||
"alias": model.Alias,
|
||||
"ctx_size": model.CtxSize,
|
||||
"is_default": model.IsDefault,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ModelsHandler) LoadModel(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
|
||||
if err := h.manager.LoadModel(name); err != nil {
|
||||
if err == llama.ErrModelNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "model not found"})
|
||||
return
|
||||
}
|
||||
if err == llama.ErrSwapInProgress {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusAccepted, gin.H{
|
||||
"status": "loading",
|
||||
"model": name,
|
||||
"message": "Model loading initiated",
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user