From 1d715759822ffad2d3715e57b766bbdf6680f3df Mon Sep 17 00:00:00 2001 From: jay Date: Wed, 3 Jun 2026 18:08:40 +0000 Subject: [PATCH] Share pages: lazy, cached, our-own-words article summaries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /a/ page now carries an original short summary so it stands on its own, without republishing the publisher's article: - summarize.py: transient SSRF-guarded fetch of the article text → local LLM writes a 2-4 sentence ORIGINAL summary (our words). Cached in article_summaries forever; we store only our summary, never the body. Generated lazily (only for shared/viewed articles), de-duped so concurrent hits don't double-generate. - /a serves cached-or-pending; when pending it shows a calm "summary on its way, read at {source}" note and self-polls /api/summary/, swapping the summary in the moment it's ready (never blocks the page on the batch-tier LLM). - Share menu warms generation on open so recipients usually get the rich version. - Container reaches the arbiter at arbiter:8080 over caddy_web (LLM env added to the API container). 124 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/lib/components/ArticleCard.svelte | 7 +- goodnews/api.py | 46 +++++++- goodnews/db.py | 9 ++ goodnews/share.py | 43 ++++++- goodnews/summarize.py | 108 ++++++++++++++++++ tests/test_summary.py | 56 +++++++++ 6 files changed, 256 insertions(+), 13 deletions(-) create mode 100644 goodnews/summarize.py create mode 100644 tests/test_summary.py diff --git a/frontend/src/lib/components/ArticleCard.svelte b/frontend/src/lib/components/ArticleCard.svelte index ead2410..34f87bf 100644 --- a/frontend/src/lib/components/ArticleCard.svelte +++ b/frontend/src/lib/components/ArticleCard.svelte @@ -57,6 +57,11 @@ } shareOpen = false; } + // Kick off summary generation when the user signals intent to share, so it's + // cached by the time a recipient opens the link. + function warmSummary() { + fetch(`/api/summary/${article.id}`).catch(() => {}); + } async function copy(text, label) { try { await navigator.clipboard.writeText(text); @@ -127,7 +132,7 @@ {/if} - + {#if shareOpen}