Files
2026-10-31 16:56:08 +00:00

68 lines
1.6 KiB
Go

package conversation
import (
"context"
"database/sql"
"errors"
"fmt"
"strings"
"unicode/utf8"
"code.chimeric.al/chimerical/odidere/internal/db"
"code.chimeric.al/chimerical/odidere/internal/llm"
)
// MaxTitleLength is the maximum length of a conversation title in
// Unicode code points.
const MaxTitleLength = 256
// deriveTitle returns a title from the first line of text that
// contains non-whitespace characters. It replaces runs of
// whitespace on that line with single spaces and truncates to
// MaxTitleLength runes. It returns "" if text has no
// non-whitespace characters.
func deriveTitle(text string) string {
for line := range strings.SplitSeq(text, "\n") {
title := strings.Join(strings.Fields(line), " ")
if title == "" {
continue
}
if utf8.RuneCountInString(title) <= MaxTitleLength {
return title
}
runes := []rune(title)
return string(runes[:MaxTitleLength])
}
return ""
}
// setTitle derives a title from msg and sets the conversation
// title if the conversation has no title yet. It does nothing if
// msg has no non-whitespace text or if the conversation already
// has a title.
func (c *Conversation) setTitle(
ctx context.Context,
msg llm.Message,
) error {
title := deriveTitle(msg.Text())
if title == "" {
return nil
}
_, err := c.dbw.UpdateConversationTitleIfUnset(
ctx,
db.UpdateConversationTitleIfUnsetParams{
Id: c.id,
Title: title,
},
)
// sql.ErrNoRows means the conversation already has a title (or
// does not exist).
if errors.Is(err, sql.ErrNoRows) {
return nil
}
if err != nil {
return fmt.Errorf("update conversation title: %w", err)
}
return nil
}