feat(chat): stream model thinking over SSE and show it in a collapsible block
This commit is contained in:
+34
-6
@@ -5,7 +5,8 @@
|
||||
> Anchors table are settled — do not re-litigate them in a phase.
|
||||
> **Revisions (2026-08-21, owner permission):** A7/A8/A9 revised (multi-format
|
||||
> ingestion, hybrid FTS+vector retrieval, re-tuned honesty gate); dark tech
|
||||
> theme (Phase 08); clickable document viewer (Phase 10). See roadmap §12.
|
||||
> theme (Phase 08); clickable document viewer (Phase 10); thinking display
|
||||
> (Phase 17, owner permission 2026-08-23). See roadmap §12.
|
||||
|
||||
---
|
||||
|
||||
@@ -134,14 +135,28 @@ All endpoints stateless (A10). Errors: standard JSON `{detail: str}`.
|
||||
|
||||
### SSE contract (`POST /api/chat`)
|
||||
```
|
||||
data: {"type":"thinking","text":"…"}\n\n
|
||||
data: {"type":"thinking","text":"…"}\n\n
|
||||
data: {"type":"delta","text":"Hey! "}\n\n
|
||||
data: {"type":"delta","text":"Good "}\n\n
|
||||
...
|
||||
data: {"type":"done","deflected":false,"sources":[{"source":"Homelab","path":"kubernetes.md","title":"Kubernetes Homelab Cluster"}],"suggestions":[]}\n\n
|
||||
```
|
||||
Client rules: render deltas as they arrive; on `done` append source chips /
|
||||
suggestion chips and clear the busy state; on HTTP/stream error show the
|
||||
error banner + retry (never a stuck button).
|
||||
Client rules: render deltas as they arrive; render `thinking` text in a
|
||||
collapsible block above the answer; auto-collapse on the first `delta`;
|
||||
tolerate interleaved `thinking` events (append — never reopen once the
|
||||
answer started); the `done` shape is unchanged (thinking never travels on
|
||||
`done`); on `done` append source chips / suggestion chips and clear the
|
||||
busy state; on HTTP/stream error show the error banner + retry (never a
|
||||
stuck button).
|
||||
|
||||
> **SSE revision (phase 17, owner permission 2026-08-23):** the contract
|
||||
> gains one event type — `{"type":"thinking","text":"…"}` — carrying the
|
||||
> model's reasoning ahead of the `delta` events (the `turbo` model emits
|
||||
> `delta.reasoning_content` chunks before the first content chunk, verified
|
||||
> live 2026-08-23; `BOR_STREAM_THINKING=0` suppresses the frames
|
||||
> server-side). `delta` and `done` shapes are unchanged — a recorded
|
||||
> extension of A15, not a silent deviation.
|
||||
|
||||
---
|
||||
|
||||
@@ -286,6 +301,7 @@ Rules:
|
||||
|-------|----|
|
||||
| **Idle** | Send button enabled, label "Send". |
|
||||
| **Thinking (pre-token)** | 3-dot typing bubble + button disabled with spinner, label "Thinking…". |
|
||||
| **Thinking (model reasoning)** | Collapsible `.thinking` block streams open (replaces the typing dots as the live indicator), auto-collapses on the first answer token, toggleable afterwards, persisted with the message (phase 14); 120s guard clears on the first `thinking` *or* `delta` event. |
|
||||
| **Streaming** | Deltas append live into the brain bubble; button stays busy. |
|
||||
| **Done (answer)** | Source chips under the bubble (mono, path-based); button re-enabled. |
|
||||
| **Done (deflected)** | Amber-bordered bubble + "Maybe try:" suggestion chips. |
|
||||
@@ -293,6 +309,9 @@ Rules:
|
||||
| **KB offline** | Amber banner at top of chat ("start Postgres…"); chat disabled with explanation. |
|
||||
| **Guard** | 120s client-side timeout → error state (a button can never sit "stuck" forever). |
|
||||
|
||||
> The **Thinking (model reasoning)** row is a phase-17 addition (owner
|
||||
> permission 2026-08-23) — see the §4 SSE revision.
|
||||
|
||||
### 7.5 Component inventory (ids used by tests)
|
||||
`#messages` (stream), `#empty-state`, `#suggestions`, `.suggestion-chip`,
|
||||
`#composer`, `#message-input`, `#send-btn` / `#send-label`, `#typing-indicator`,
|
||||
@@ -301,7 +320,9 @@ Rules:
|
||||
`#stat-last`, `#docs-table`, `#docs-tbody`, `#sources-empty`; viewer
|
||||
(Phase 10): `/document.html`, `#doc-title`, `#doc-meta`, `#doc-content`,
|
||||
`.doc-raw`, `.format-badge`, `#doc-not-found`, `.doc-link` (Sources table
|
||||
path links).
|
||||
path links); thinking (phase 17, owner permission 2026-08-23):
|
||||
`.thinking`, `.thinking-text` (collapsible thinking block; plain
|
||||
`<summary>`, no id).
|
||||
|
||||
---
|
||||
|
||||
@@ -322,7 +343,10 @@ path links).
|
||||
- **App logs:** single-line `timestamp LEVEL logger :: message` on stdout;
|
||||
uvicorn access logs on. INFO by default (`BOR_LOG_LEVEL`).
|
||||
- **Per-chat-turn log line (required):**
|
||||
`question=… embed_ms=… top_score=… fts_hits=… threshold=… deflected=… sources=… total_ms=…`
|
||||
`question=… embed_ms=… top_score=… fts_hits=… tuning=N threshold=… deflected=… sources=… thinking_chars=… total_ms=…`
|
||||
(`thinking_chars=` counts the turn's reasoning chars — phase 17, owner
|
||||
permission 2026-08-23 — and is counted even when `BOR_STREAM_THINKING=0`
|
||||
suppresses the frames.)
|
||||
- **Importer logs:** per-file `added|updated|unchanged|pruned` + summary
|
||||
(counts, embedding batches, total time).
|
||||
- **`query_log` table:** durable record of every question (score, deflection,
|
||||
@@ -385,6 +409,10 @@ ranks live hybrid results for a question (retrieval tuning).
|
||||
| 08 | `08_story_dark_tech_theme.md` | `dark-tech-theme.md` | `tests/e2e/test_dark_tech_theme.py` |
|
||||
| 09 | `09_story_retrieval_quality.md` | `retrieval-quality.md` | `tests/e2e/test_retrieval_quality.py` |
|
||||
| 10 | `10_story_document_viewer.md` | `document-viewer.md` | `tests/e2e/test_document_viewer.py` |
|
||||
| 17 | `17_thinking_display.md` | `thinking-display.md` | `tests/e2e/test_thinking_display.py` |
|
||||
|
||||
> Row 17 (thinking display) added 2026-08-23 with owner permission — the
|
||||
> A15 SSE extension recorded in §4.
|
||||
|
||||
Completion = unit+integration green, coverage >90%, story E2E green in
|
||||
isolation, UI verification passed, **one `--no-gpg-sign` commit**.
|
||||
|
||||
Reference in New Issue
Block a user