Add /tldr: a private overview of what you missed in a channel

/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]>
This commit is contained in:
FernandoJVideiraandClaude Opus 5.5 committed 2026-10-20 18:46:18 +01:00
1 parent b1ea5f17e3
commit 7ae1fa5747
21 files changed
+2423 -2

No files matched your search

+27
View File
@@ -3,6 +3,7 @@ package config
import (
"fmt"
"os"
"strconv"
"github.com/joho/godotenv"
)
@@ -15,6 +16,9 @@ type Config struct {
LavalinkHost string
LavalinkPass string
TwitchClientID string
OllamaHost string
TLDRModel string
TLDRThreads int
}
func LoadConfig() (*Config, error) {
@@ -46,6 +50,26 @@ func LoadConfig() (*Config, error) {
return nil, fmt.Errorf("TWITCH_CLIENT_ID is not set")
}
ollamaHost := os.Getenv("OLLAMA_HOST")
if ollamaHost == "" {
ollamaHost = "http://ollama:11434"
}
tldrModel := os.Getenv("TLDR_MODEL")
if tldrModel == "" {
tldrModel = "qwen3:4b-instruct-2507-q4_K_M"
}
// CPU threads the summarizer may use. Keep it well under the machine's
// core count so music playback never has to compete with inference.
tldrThreads := 6
if v := os.Getenv("TLDR_THREADS"); v != "" {
n, err := strconv.Atoi(v)
if err != nil || n < 1 {
return nil, fmt.Errorf("TLDR_THREADS must be a positive integer, got %q", v)
}
tldrThreads = n
}
return &Config{
BotToken: token,
AppID: appID,
@@ -54,5 +78,8 @@ func LoadConfig() (*Config, error) {
LavalinkHost: lavalinkHost,
LavalinkPass: lavalinkPass,
TwitchClientID: twitchClientID,
OllamaHost: ollamaHost,
TLDRModel: tldrModel,
TLDRThreads: tldrThreads,
}, nil
}