!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

@@ -294,6 +294,35 @@ func (c *Client) Stop() {
}
}
// PostThreadedReply sends a plain/HTML message into the thread rooted at
// rootEventID. Resolves channel name → room ID like PostStory. The is_falling_back
// flag plus m.in_reply_to gives non-thread-aware clients a sensible quoted reply.
func (c *Client) PostThreadedReply(channel string, rootEventID id.EventID, plain, htmlBody string) error {
roomID, ok := c.channels[channel]
if !ok {
return fmt.Errorf("unknown channel: %s", channel)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
content := &event.MessageEventContent{
MsgType: event.MsgText,
Body: plain,
Format: event.FormatHTML,
FormattedBody: htmlBody,
RelatesTo: &event.RelatesTo{
Type: event.RelThread,
EventID: rootEventID,
InReplyTo: &event.InReplyTo{
EventID: rootEventID,
},
IsFallingBack: true,
},
}
_, err := c.mx.SendMessageEvent(ctx, roomID, event.EventMessage, content)
return err
}
// PostStory sends a story to a channel. Returns the text event ID for reaction tracking.
// If the story has a validated image, it's uploaded and sent as a separate m.image event first.
func (c *Client) PostStory(channel string, story *PostableStory) (id.EventID, error) {