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 }