feat(chat): share a chat by link — anonymous read-only /shared/<token> page, share/unshare

This commit is contained in:
2026-08-30 01:34:44 -04:00
parent ece93a7c8f
commit 114b115034
28 changed files with 3442 additions and 54 deletions
+21 -7
View File
@@ -6,9 +6,10 @@ Two layers, one module:
append to their asset URLs (``?v=<token>``).
* **Response middleware** (``CachingMiddleware`` / ``configure_caching``)
— applies the caching behavior at the transport layer: the known
HTML pages (``HTML_PAGES``) are always revalidated (``no-cache``) and
their local asset references are rewritten to carry ``?v=<token>``;
``/assets/*`` is
HTML pages (``HTML_PAGES``) and the dynamic share page
``/shared/<token>`` (phase 51) are always revalidated (``no-cache``)
and their local asset references are rewritten to carry
``?v=<token>``; ``/assets/*`` is
served ``immutable`` for a year; everything else — all of ``/api/*``,
including the SSE chat stream — passes through byte-identical.
@@ -125,6 +126,11 @@ HTML_PAGES: tuple[str, ...] = (
"/tuning.html",
"/git-sources.html", # phase 35: the admin git sources page
"/history.html", # phase 50: the admin saved-chats page
# phase 51: the shared page's STATIC path (the static mount serves
# shared.html at /shared.html as well as the real route serves the
# dynamic /shared/<token> — both must carry the no-cache + ?v=
# contract, so the direct URL can never pin stale assets).
"/shared.html",
)
#: Prefix of the versioned static assets (header-only caching; the body is
@@ -194,9 +200,10 @@ class CachingMiddleware(BaseHTTPMiddleware):
* ``/assets/*`` — ``Cache-Control: public, max-age=31536000, immutable``
(header only — the body is never read).
* the known HTML pages (``HTML_PAGES``) — ``Cache-Control: no-cache``,
and (for ``text/html`` bodies) every local asset reference gains
``?v=<token>``.
* the known HTML pages (``HTML_PAGES``) plus the dynamic share page
``/shared/<token>`` (phase 51, by path prefix) —
``Cache-Control: no-cache``, and (for ``text/html`` bodies) every
local asset reference gains ``?v=<token>``.
Everything else — all of ``/api/*`` (including the SSE chat stream) —
passes through byte-identical: no header changes, the body stream is
@@ -213,7 +220,14 @@ class CachingMiddleware(BaseHTTPMiddleware):
response.headers["Cache-Control"] = ASSET_CACHE_CONTROL
return response
if path not in HTML_PAGES:
# Phase 51: the dynamic share page — ``/shared/<token>`` is a
# REAL route (not a static file) serving ``shared.html``, so it
# joins the known-page contract by path prefix: no-cache +
# ``?v=`` asset rewrite. (``/api/shared/<token>`` — the JSON
# read — starts with ``/api/`` and passes through below.)
is_known_page = path in HTML_PAGES or path.startswith("/shared/")
if not is_known_page:
# /api/* (incl. SSE), /favicon.ico, unknown paths: untouched.
return response