package handlers import ( "net/http" "github.com/gin-gonic/gin" "chat-switchboard/models" "chat-switchboard/providers" "chat-switchboard/roles" "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"}) }