package handlers // summarize.go — Manual conversation summarization via HTTP. // // v0.29.0: Raw SQL replaced with ChannelStore methods. import ( "net/http" "github.com/gin-gonic/gin" "switchboard-core/compaction" "switchboard-core/roles" "switchboard-core/store" ) // SummarizeHandler handles manual conversation summarization via HTTP. type SummarizeHandler struct { stores store.Stores compaction *compaction.Service } // NewSummarizeHandler creates a new handler backed by the compaction service. func NewSummarizeHandler(stores store.Stores, svc *compaction.Service) *SummarizeHandler { return &SummarizeHandler{stores: stores, compaction: svc} } // ── Summarize & Continue ────────────────── // POST /channels/:id/summarize func (h *SummarizeHandler) Summarize(c *gin.Context) { channelID := c.Param("id") userID := getUserID(c) // ── Verify channel ownership ── ch, err := h.stores.Channels.GetByID(c.Request.Context(), channelID) if err != nil || ch == nil { c.JSON(http.StatusNotFound, gin.H{"error": "channel not found"}) return } if ch.UserID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "not your channel"}) return } // ── Check utility role is configured ── resolver := h.compaction.Resolver() if !resolver.IsConfigured(c.Request.Context(), roles.RoleUtility) { if !resolver.IsPersonalOverride(c.Request.Context(), userID, roles.RoleUtility) { c.JSON(http.StatusServiceUnavailable, gin.H{ "error": "Utility model role is not configured. Ask your admin to set one, or configure your own in Settings → Model Roles.", }) return } } // ── Rate limiting (org-funded calls only) ── isPersonal := resolver.IsPersonalOverride(c.Request.Context(), userID, roles.RoleUtility) if !isPersonal { if err := h.compaction.CheckRateLimit(c.Request.Context(), userID); err != nil { c.JSON(http.StatusTooManyRequests, gin.H{"error": err.Error()}) return } } // ── Compact ── teamID := h.compaction.GetUserTeamID(c.Request.Context(), userID) result, err := h.compaction.Compact(c.Request.Context(), compaction.CompactRequest{ ChannelID: channelID, UserID: userID, TeamID: teamID, Trigger: "manual", }) if err != nil { switch err.Error() { case "conversation too short to summarize": c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) case "not enough new messages since last summary": c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) default: c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{ "summary_id": result.SummaryID, "summarized_count": result.SummarizedCount, "model": result.Model, "used_fallback": result.UsedFallback, "content": result.Content, }) }