- go.mod module name
- All 714 import references across 289 Go files
- VERSION: 0.1.0
- CI DB names: switchboard_core_{ci,dev,test}
- Docker image: gobha/switchboard-core
- Test fixtures: JWT issuer, repo names
- .env.example, docker-compose container name
- Compiles clean (go build exit 0)
94 lines
2.8 KiB
Go
94 lines
2.8 KiB
Go
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,
|
|
})
|
|
}
|