/tldr [since] summarizes the messages in the channel it is run in that the caller has not been summarized yet, using a small local model on Ollama, and replies ephemerally. The answer streams into the reply as it is written. - Per-user, per-channel cursor (tldr_cursors) so each run picks up where the last left off; `since:` (e.g. 6h, 2d, max 7d) peeks without moving it. First use covers the last 24h, and a stale cursor never reaches back more than 7 days. - Only summarizes channels both the caller and the bot can read. - The transcript drops bots, system messages, reactions and filler, turns links, GIFs, images and files into short tags ([link: site], [image], [file: name], preview titles), marks replies, and writes a date marker where the chat paused for 3h or more so the model can keep topics apart. - The prompt asks for one bullet per conversation, oldest first, with a worked example; it was tuned against a synthetic chat and a real export with qwen3:4b-instruct-2507 (the default model). - One model call at a time across the bot, one /tldr per user. - docker-compose: Ollama service (CPU only, 8g memory cap: 6g killed the runner mid-request), model pull on startup. Needs the tldr_cursors migration (velox-dashboard-api 000016). Co-Authored-By: Claude Opus 5.5 <[email protected]>
47 lines
1.1 KiB
Go
47 lines
1.1 KiB
Go
package tldr
|
|
|
|
import (
|
|
"sort"
|
|
|
|
"github.com/bwmarrin/discordgo"
|
|
)
|
|
|
|
type MessageFetcher interface {
|
|
ChannelMessages(channelID string, limit int, beforeID, afterID, aroundID string, options ...discordgo.RequestOption) ([]*discordgo.Message, error)
|
|
}
|
|
|
|
// FetchAfter pages forward from afterID and returns up to limit messages,
|
|
// oldest first. Discord returns the messages immediately following the ID,
|
|
// so paging with the newest ID of each batch walks toward the present.
|
|
func FetchAfter(f MessageFetcher, channelID, afterID string, limit int) ([]*discordgo.Message, error) {
|
|
var all []*discordgo.Message
|
|
after := afterID
|
|
|
|
for len(all) < limit {
|
|
want := min(100, limit-len(all))
|
|
batch, err := f.ChannelMessages(channelID, want, "", after, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(batch) == 0 {
|
|
break
|
|
}
|
|
|
|
newest := batch[0].ID
|
|
for _, m := range batch {
|
|
if CompareIDs(m.ID, newest) > 0 {
|
|
newest = m.ID
|
|
}
|
|
}
|
|
all = append(all, batch...)
|
|
after = newest
|
|
|
|
if len(batch) < want {
|
|
break
|
|
}
|
|
}
|
|
|
|
sort.Slice(all, func(i, j int) bool { return CompareIDs(all[i].ID, all[j].ID) < 0 })
|
|
return all, nil
|
|
}
|