/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]>
86 lines
1.9 KiB
Go
86 lines
1.9 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
|
|
"github.com/joho/godotenv"
|
|
)
|
|
|
|
type Config struct {
|
|
BotToken string
|
|
AppID string
|
|
GuildID string
|
|
DBHost string
|
|
LavalinkHost string
|
|
LavalinkPass string
|
|
TwitchClientID string
|
|
OllamaHost string
|
|
TLDRModel string
|
|
TLDRThreads int
|
|
}
|
|
|
|
func LoadConfig() (*Config, error) {
|
|
// Load .env if present (e.g. local dev). Ignore if missing (e.g. Docker injects env vars).
|
|
_ = godotenv.Load()
|
|
|
|
token := os.Getenv("BOT_TOKEN")
|
|
if token == "" {
|
|
return nil, fmt.Errorf("BOT_TOKEN is not set")
|
|
}
|
|
|
|
appID := os.Getenv("APP_ID")
|
|
if appID == "" {
|
|
return nil, fmt.Errorf("APP_ID is not set")
|
|
}
|
|
|
|
guildID := os.Getenv("GUILD_ID")
|
|
|
|
dbHost := os.Getenv("DB_HOST")
|
|
if dbHost == "" {
|
|
return nil, fmt.Errorf("DB_HOST is not set")
|
|
}
|
|
|
|
lavalinkHost := os.Getenv("LAVALINK_HOST")
|
|
lavalinkPass := os.Getenv("LAVALINK_PASSWORD")
|
|
|
|
twitchClientID := os.Getenv("TWITCH_CLIENT_ID")
|
|
if twitchClientID == "" {
|
|
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,
|
|
GuildID: guildID,
|
|
DBHost: dbHost,
|
|
LavalinkHost: lavalinkHost,
|
|
LavalinkPass: lavalinkPass,
|
|
TwitchClientID: twitchClientID,
|
|
OllamaHost: ollamaHost,
|
|
TLDRModel: tldrModel,
|
|
TLDRThreads: tldrThreads,
|
|
}, nil
|
|
}
|