!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:
@@ -101,6 +101,69 @@ func FetchOGImage(articleURL string) string {
|
||||
return FetchArticleMeta(articleURL).ImageURL
|
||||
}
|
||||
|
||||
// MaxBodyChars is the cap on body text returned by FetchArticleBody. Keeps
|
||||
// LLM prompts bounded; most news articles fit well under this.
|
||||
const MaxBodyChars = 8000
|
||||
|
||||
// FetchArticleBody fetches the article and returns the concatenated visible
|
||||
// body text (<article>/<main> <p> tags, falling back to all <p>), trimmed
|
||||
// and capped at MaxBodyChars. Returns "" on any fetch failure.
|
||||
func FetchArticleBody(articleURL string) string {
|
||||
if articleURL == "" {
|
||||
return ""
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", articleURL, nil)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
req.Header.Set("Accept", "text/html,application/xhtml+xml")
|
||||
|
||||
resp, err := articleClient.Do(req)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return ""
|
||||
}
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return extractBodyText(doc)
|
||||
}
|
||||
|
||||
func extractBodyText(doc *goquery.Document) string {
|
||||
sel := doc.Find("article p, main p")
|
||||
if sel.Length() == 0 {
|
||||
sel = doc.Find("p")
|
||||
}
|
||||
var b strings.Builder
|
||||
sel.Each(func(_ int, s *goquery.Selection) {
|
||||
t := strings.TrimSpace(s.Text())
|
||||
if t == "" {
|
||||
return
|
||||
}
|
||||
if b.Len() > 0 {
|
||||
b.WriteString("\n\n")
|
||||
}
|
||||
b.WriteString(t)
|
||||
if b.Len() >= MaxBodyChars {
|
||||
return
|
||||
}
|
||||
})
|
||||
out := strings.TrimSpace(b.String())
|
||||
if len(out) > MaxBodyChars {
|
||||
out = out[:MaxBodyChars]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func extractOGImage(doc *goquery.Document, base string) string {
|
||||
selectors := []string{
|
||||
`meta[property="og:image:secure_url"]`,
|
||||
|
||||
Reference in New Issue
Block a user