Changeset 0.10.0 (#56)
This commit is contained in:
243
server/handlers/roles.go
Normal file
243
server/handlers/roles.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/providers"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/roles"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
)
|
||||
|
||||
// RolesHandler manages model role configuration.
|
||||
type RolesHandler struct {
|
||||
stores store.Stores
|
||||
resolver *roles.Resolver
|
||||
}
|
||||
|
||||
// NewRolesHandler creates a roles handler.
|
||||
func NewRolesHandler(s store.Stores, resolver *roles.Resolver) *RolesHandler {
|
||||
return &RolesHandler{stores: s, resolver: resolver}
|
||||
}
|
||||
|
||||
// ── List All Role Configs ──────────────────
|
||||
// GET /admin/roles
|
||||
|
||||
func (h *RolesHandler) ListRoles(c *gin.Context) {
|
||||
allRoles, err := h.stores.GlobalConfig.Get(c.Request.Context(), "model_roles")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load roles"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, allRoles)
|
||||
}
|
||||
|
||||
// ── Get Single Role Config ─────────────────
|
||||
// GET /admin/roles/:role
|
||||
|
||||
func (h *RolesHandler) GetRole(c *gin.Context) {
|
||||
role := c.Param("role")
|
||||
if !roles.IsValidRole(role) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "unknown role: " + role})
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := h.resolver.GetConfig(c.Request.Context(), role, nil)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, cfg)
|
||||
}
|
||||
|
||||
// ── Update Role Config ─────────────────────
|
||||
// PUT /admin/roles/:role
|
||||
|
||||
func (h *RolesHandler) UpdateRole(c *gin.Context) {
|
||||
role := c.Param("role")
|
||||
if !roles.IsValidRole(role) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "unknown role: " + role})
|
||||
return
|
||||
}
|
||||
|
||||
var req roles.RoleConfig
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Load current global model_roles
|
||||
allRoles, err := h.stores.GlobalConfig.Get(c.Request.Context(), "model_roles")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load roles"})
|
||||
return
|
||||
}
|
||||
if allRoles == nil {
|
||||
allRoles = models.JSONMap{}
|
||||
}
|
||||
|
||||
// Update the specific role
|
||||
allRoles[role] = req
|
||||
|
||||
// Persist
|
||||
userID := getUserID(c)
|
||||
if err := h.stores.GlobalConfig.Set(c.Request.Context(), "model_roles", allRoles, userID); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save role"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, req)
|
||||
}
|
||||
|
||||
// ── Test Role ──────────────────────────────
|
||||
// POST /admin/roles/:role/test
|
||||
|
||||
func (h *RolesHandler) TestRole(c *gin.Context) {
|
||||
role := c.Param("role")
|
||||
if !roles.IsValidRole(role) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "unknown role: " + role})
|
||||
return
|
||||
}
|
||||
|
||||
if role == roles.RoleEmbedding {
|
||||
// Test embedding
|
||||
result, err := h.resolver.Embed(c.Request.Context(), role, nil, []string{"test embedding"})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "ok",
|
||||
"model": result.Model,
|
||||
"provider": result.ProviderID,
|
||||
"dimensions": len(result.Embeddings[0]),
|
||||
"used_fallback": result.UsedFallback,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Test completion with a minimal prompt
|
||||
result, err := h.resolver.Complete(c.Request.Context(), role, nil, []providers.Message{
|
||||
{Role: "user", Content: "Say 'ok' and nothing else."},
|
||||
})
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"status": "ok",
|
||||
"model": result.Model,
|
||||
"provider": result.ProviderID,
|
||||
"content": result.Content,
|
||||
"input_tokens": result.InputTokens,
|
||||
"output_tokens": result.OutputTokens,
|
||||
"used_fallback": result.UsedFallback,
|
||||
})
|
||||
}
|
||||
|
||||
// ── Team Role Overrides ────────────────────
|
||||
|
||||
// ListTeamRoles returns role overrides for a specific team.
|
||||
// GET /teams/:teamId/roles
|
||||
func (h *RolesHandler) ListTeamRoles(c *gin.Context) {
|
||||
teamID := c.Param("teamId")
|
||||
|
||||
team, err := h.stores.Teams.GetByID(c.Request.Context(), teamID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "team not found"})
|
||||
return
|
||||
}
|
||||
|
||||
roleOverrides := make(map[string]interface{})
|
||||
if team.Settings != nil {
|
||||
if raw, ok := team.Settings["model_roles"]; ok {
|
||||
if m, ok := raw.(map[string]interface{}); ok {
|
||||
roleOverrides = m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, roleOverrides)
|
||||
}
|
||||
|
||||
// UpdateTeamRole sets a team role override.
|
||||
// PUT /teams/:teamId/roles/:role
|
||||
func (h *RolesHandler) UpdateTeamRole(c *gin.Context) {
|
||||
teamID := c.Param("teamId")
|
||||
role := c.Param("role")
|
||||
if !roles.IsValidRole(role) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "unknown role: " + role})
|
||||
return
|
||||
}
|
||||
|
||||
var req roles.RoleConfig
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
team, err := h.stores.Teams.GetByID(c.Request.Context(), teamID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "team not found"})
|
||||
return
|
||||
}
|
||||
|
||||
settings := team.Settings
|
||||
if settings == nil {
|
||||
settings = models.JSONMap{}
|
||||
}
|
||||
|
||||
roleOverrides, _ := settings["model_roles"].(map[string]interface{})
|
||||
if roleOverrides == nil {
|
||||
roleOverrides = make(map[string]interface{})
|
||||
}
|
||||
roleOverrides[role] = req
|
||||
settings["model_roles"] = roleOverrides
|
||||
|
||||
if err := h.stores.Teams.Update(c.Request.Context(), teamID, map[string]interface{}{
|
||||
"settings": settings,
|
||||
}); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save team role"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, req)
|
||||
}
|
||||
|
||||
// DeleteTeamRole removes a team role override (falls back to global).
|
||||
// DELETE /teams/:teamId/roles/:role
|
||||
func (h *RolesHandler) DeleteTeamRole(c *gin.Context) {
|
||||
teamID := c.Param("teamId")
|
||||
role := c.Param("role")
|
||||
|
||||
team, err := h.stores.Teams.GetByID(c.Request.Context(), teamID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "team not found"})
|
||||
return
|
||||
}
|
||||
|
||||
settings := team.Settings
|
||||
if settings == nil {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "no override to remove"})
|
||||
return
|
||||
}
|
||||
|
||||
roleOverrides, _ := settings["model_roles"].(map[string]interface{})
|
||||
if roleOverrides != nil {
|
||||
delete(roleOverrides, role)
|
||||
settings["model_roles"] = roleOverrides
|
||||
}
|
||||
|
||||
if err := h.stores.Teams.Update(c.Request.Context(), teamID, map[string]interface{}{
|
||||
"settings": settings,
|
||||
}); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to remove team role"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "override removed"})
|
||||
}
|
||||
Reference in New Issue
Block a user