Background generation, unread replies, send/stop, PLAN.md
**Replies now run in the background.** Generation was driven by the SSE request, so navigating away or opening another chat cut the answer off mid-sentence. services/generation.py owns the work as its own task and the SSE endpoint merely follows it. Verified: attached briefly, closed the connection, went to another page -- the reply finished anyway, 832 characters, not marked stopped, auto-titled. Reattaching works because both `render` and `reasoning` frames now carry the whole block rather than a delta. A follower arriving late has no earlier fragments to append to, so deltas would leave it permanently missing the beginning. Verified: attached six seconds in and the first frame already contained 517 characters written while nobody watched. **Unread indicator.** A reply that lands with no follower attached marks its chat unread; the sidebar polls every 10s for out-of-band dot spans plus an HX-Trigger that raises a toast. Polled rather than pushed: a browser sitting on another chat has no connection to the one that finished, and an always-on channel per tab is a lot of machinery for a green dot. `unread_notified` stops the same arrival being announced every tick. Follower count is what decides "was anyone watching", so reading it as it arrives does not mark it unread -- verified both ways. **Stop is the send button.** While a reply is being written the send button becomes a red stop square, found via a MutationObserver on the thread since the composer and the streaming bubble are far apart in the document. The in-bubble Stop is gone. **Attachment border removed.** As asked -- an attachment is a picture, and the frame only ever drew at the wrong width. The anchor now shrink-wraps and the img's width/height attributes are overridden so a small image shows at its own size. Adds PLAN.md: what is built, what is not, known limits, and the decisions that look like oversights until you know the reason. 239 tests, ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -106,13 +106,10 @@
|
||||
rather than snapping into place at the end. #}
|
||||
<div class="msg__body msg__body--live" id="stream-{{ message.id }}"
|
||||
sse-swap="render" hx-swap="innerHTML"></div>
|
||||
{# No stop button here: the composer's send button becomes Stop while a
|
||||
reply is being written, which is where the hand already is. #}
|
||||
<div class="msg__waiting">
|
||||
<span class="dots"><i></i><i></i><i></i></span>
|
||||
<button class="btn btn--sm msg__stop" type="button"
|
||||
hx-post="/api/chats/{{ chat.id }}/messages/{{ message.id }}/stop"
|
||||
hx-swap="none">
|
||||
{{ icon("x", "icon--sm") }} Stop
|
||||
</button>
|
||||
</div>
|
||||
{% elif message.reasoning and not message.error %}
|
||||
{# Collapsed once finished: the answer is what the reader came for, and
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
<a class="nav-item__link" href="/chat/{{ chat_item.id }}">
|
||||
{{ icon("chat", "icon--sm") }}
|
||||
<span class="nav-item__label" id="chat-link-label-{{ chat_item.id }}">{{ chat_item.title }}</span>
|
||||
{# Toggled out of band by the unread poll; see /api/chats/unread. #}
|
||||
<span id="unread-{{ chat_item.id }}" class="unread-dot"
|
||||
{{ '' if chat_item.unread else 'hidden' }} title="New reply"></span>
|
||||
</a>
|
||||
<span class="nav-item__actions">
|
||||
<button class="btn btn--icon btn--sm" type="button"
|
||||
|
||||
@@ -27,6 +27,12 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# Every 10s, refresh the unread dots and announce anything that finished
|
||||
while this page was showing something else. Out-of-band spans only, so the
|
||||
folder tree keeps its open/closed state. #}
|
||||
<div hidden hx-get="/api/chats/unread" hx-trigger="every 10s"
|
||||
hx-swap="none"></div>
|
||||
|
||||
<nav class="sidebar__scroll" id="sidebar-tree" aria-label="Chats">
|
||||
{% if pinned_models and can.get("chat.create") %}
|
||||
{# Shortcuts to start a chat with a particular model. These link rather than
|
||||
|
||||
Reference in New Issue
Block a user