Changeset 0.17.0 (#75)
This commit is contained in:
110
server/handlers/title.go
Normal file
110
server/handlers/title.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/providers"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/roles"
|
||||
"git.gobha.me/xcaliber/chat-switchboard/store"
|
||||
)
|
||||
|
||||
// TitleHandler generates chat titles using the utility role.
|
||||
type TitleHandler struct {
|
||||
stores store.Stores
|
||||
resolver *roles.Resolver
|
||||
}
|
||||
|
||||
// NewTitleHandler creates a title generation handler.
|
||||
func NewTitleHandler(s store.Stores, r *roles.Resolver) *TitleHandler {
|
||||
return &TitleHandler{stores: s, resolver: r}
|
||||
}
|
||||
|
||||
// GenerateTitle generates a title for a channel using the utility role.
|
||||
// POST /channels/:id/generate-title
|
||||
func (h *TitleHandler) GenerateTitle(c *gin.Context) {
|
||||
channelID := c.Param("id")
|
||||
userID := c.GetString("user_id")
|
||||
teamID := c.GetString("team_id")
|
||||
|
||||
// Verify channel access
|
||||
owns, err := h.stores.Channels.UserOwns(c.Request.Context(), channelID, userID)
|
||||
if err != nil || !owns {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "channel not found"})
|
||||
return
|
||||
}
|
||||
|
||||
ch, err := h.stores.Channels.GetByID(c.Request.Context(), channelID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "channel not found"})
|
||||
return
|
||||
}
|
||||
|
||||
// Load first few messages
|
||||
msgs, err := h.stores.Messages.ListForChannel(c.Request.Context(), channelID, store.ListOptions{
|
||||
Limit: 4, Order: "asc",
|
||||
})
|
||||
if err != nil || len(msgs) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{"title": ch.Title}) // keep existing
|
||||
return
|
||||
}
|
||||
|
||||
// Build a snippet from the first messages
|
||||
var snippet strings.Builder
|
||||
for _, m := range msgs {
|
||||
content := m.Content
|
||||
if len(content) > 200 {
|
||||
content = content[:200] + "…"
|
||||
}
|
||||
fmt.Fprintf(&snippet, "%s: %s\n", m.Role, content)
|
||||
}
|
||||
|
||||
// Call utility role
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var tID *string
|
||||
if teamID != "" {
|
||||
tID = &teamID
|
||||
}
|
||||
|
||||
result, err := h.resolver.Complete(ctx, roles.RoleUtility, userID, tID, []providers.Message{
|
||||
{
|
||||
Role: "system",
|
||||
Content: "You are a title generator. Given a conversation snippet, produce a concise title of 6 words or fewer. Respond with ONLY the title, no quotes, no punctuation at the end, no explanation.",
|
||||
},
|
||||
{
|
||||
Role: "user",
|
||||
Content: "Title this conversation:\n\n" + snippet.String(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
// Fallback: truncate first user message
|
||||
c.JSON(http.StatusOK, gin.H{"title": ch.Title})
|
||||
return
|
||||
}
|
||||
|
||||
title := strings.TrimSpace(result.Content)
|
||||
// Strip surrounding quotes if the model added them
|
||||
title = strings.Trim(title, "\"'`\u201c\u201d\u2018\u2019")
|
||||
if title == "" {
|
||||
c.JSON(http.StatusOK, gin.H{"title": ch.Title})
|
||||
return
|
||||
}
|
||||
// Cap at 100 chars
|
||||
if len(title) > 100 {
|
||||
title = title[:100]
|
||||
}
|
||||
|
||||
// Update channel
|
||||
_ = h.stores.Channels.Update(c.Request.Context(), channelID, map[string]interface{}{
|
||||
"title": title,
|
||||
})
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"title": title})
|
||||
}
|
||||
Reference in New Issue
Block a user