This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/handlers/roles.go
2026-03-12 10:22:08 +00:00

244 lines
6.6 KiB
Go

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, gin.H{"data": 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"})
}