!explain via reaction: thread-reply with a 3-bullet TL;DR

When a user reacts  on one of Pete's posts, fetch the article body,
ask Ollama for a 3-bullet summary, and post it as a threaded reply
rooted at the original story event. Per-process cooldown of 5min per
story keeps repeated reactions from re-summarizing.

- ingestion.FetchArticleBody: visible <p> text capped at 8000 chars
- classifier.OllamaClient.GenerateText: non-JSON variant
- storage.GetStoryByGUID: full row lookup
- matrix.PostThreadedReply: m.thread + m.in_reply_to fallback
- poster.SetReactionCallback: optional hook fired after recording
- New package: internal/explainer
This commit is contained in:
prosolis
2026-05-22 18:27:11 -07:00
parent e26b69e43f
commit 3537e073e9
7 changed files with 320 additions and 10 deletions

View File

@@ -2,6 +2,7 @@ package poster
import (
"log/slog"
"sync"
"time"
"pete/internal/storage"
@@ -9,6 +10,24 @@ import (
"maunium.net/go/mautrix/id"
)
// ReactionCallback is an optional hook fired after a reaction is recorded.
// Used to wire reaction-driven features (e.g. ❓ → summary) without coupling
// poster to those packages.
type ReactionCallback func(roomID id.RoomID, targetEventID id.EventID, guid, channel, emoji string)
var (
reactionCallbackMu sync.RWMutex
reactionCallback ReactionCallback
)
// SetReactionCallback installs a hook called after every recorded reaction.
// Pass nil to clear. Safe to call before or after the matrix client starts.
func SetReactionCallback(fn ReactionCallback) {
reactionCallbackMu.Lock()
reactionCallback = fn
reactionCallbackMu.Unlock()
}
// HandleReaction processes a reaction event by mapping it back to the story GUID.
func HandleReaction(roomID id.RoomID, eventID id.EventID, targetEventID id.EventID, emoji string, userID id.UserID) {
guid, channel, found := storage.LookupPostGUID(string(targetEventID))
@@ -24,4 +43,11 @@ func HandleReaction(roomID id.RoomID, eventID id.EventID, targetEventID id.Event
"emoji", emoji,
"user", userID,
)
reactionCallbackMu.RLock()
cb := reactionCallback
reactionCallbackMu.RUnlock()
if cb != nil {
go cb(roomID, targetEventID, guid, channel, emoji)
}
}