From 0e71903cc1419d20fbd593fd7330defdc4628af2 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Fri, 12 Jun 2026 01:10:56 +0900 Subject: feat(chat): old-Dispatch composer layout — textarea + send + status bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restore the ergonomic composer from old Dispatch: an auto-resizing textarea (1→7 lines) with a fixed-width Send button beside it, and a status bar BELOW holding a status icon · context-window fill bar (escalating success/warning/ error color) · compact token count (current / limit · pct%). The bar reuses the latest turn's contextSize as current usage and HARDCODES a 1,000,000-token window limit as a placeholder (real per-model limit is the next backend ask). Absorbs the standalone ContextSizeBadge (removed). Pure helpers computeContextUsage + formatCompactTokens added to core/metrics (tested). 540 tests green. --- GLOSSARY.md | 2 +- backend-handoff.md | 11 +- src/app/App.svelte | 8 +- src/core/metrics/format.test.ts | 44 ++++++++ src/core/metrics/format.ts | 39 +++++++ src/core/metrics/index.ts | 3 + src/features/chat/index.ts | 1 - src/features/chat/ui/Composer.svelte | 152 ++++++++++++++++++++++++--- src/features/chat/ui/ContextSizeBadge.svelte | 20 ---- 9 files changed, 237 insertions(+), 43 deletions(-) delete mode 100644 src/features/chat/ui/ContextSizeBadge.svelte diff --git a/GLOSSARY.md b/GLOSSARY.md index d632c8d..2a6904a 100644 --- a/GLOSSARY.md +++ b/GLOSSARY.md @@ -20,7 +20,7 @@ | **TTFT** (time to first token) | Per-step latency: generation stream start → first content token (text or reasoning). One per step (each step re-prefills). On the wire as `step-complete.ttftMs` / `StepMetrics.ttftMs` (optional). | time-to-first-byte | | **decode time** | Per-step generation time after the first token (first token → stream end = `genTotalMs − ttftMs`). On the wire as `step-complete.decodeMs` / `StepMetrics.decodeMs` (optional). | — | | **context size** | The tokens a conversation currently occupies: the most recent turn's FINAL step `inputTokens + outputTokens` (NOT the aggregate per-turn `usage`, which sums per-step prompts and overcounts a multi-step turn). On the wire as `TurnDoneEvent.contextSize` (live `done`) + `TurnMetrics.contextSize` (persisted); the FE reads the LATEST turn's value as current usage, and treats `undefined` as "unknown" (renders a placeholder, never `0`). Mirrors the backend GLOSSARY. | context usage, context length, tokens used (and do NOT call it "context window" — that's the limit) | -| **context window** | The model's MAXIMUM token capacity (the limit a **context size** is measured against). A FUTURE backend field — not on the wire yet; the FE shows context size alone (no `size / limit` denominator) until it ships. | max context, token limit (distinct from **context size**, the current usage) | +| **context window** | The model's MAXIMUM token capacity (the limit a **context size** is measured against). A FUTURE backend field — not on the wire yet. **Placeholder:** the composer status bar currently HARDCODES a `1,000,000`-token window for the `size / limit · pct%` readout + fill bar; swap to the real per-model value when the backend ships it (see `backend-handoff.md` §3). | max context, token limit (distinct from **context size**, the current usage) | ## Frontend-specific | Term | Meaning | Aliases to avoid | diff --git a/backend-handoff.md b/backend-handoff.md index e9b128a..847282b 100644 --- a/backend-handoff.md +++ b/backend-handoff.md @@ -111,11 +111,12 @@ harden `/chat` to treat blank as "not provided" if we ever want it — not neede ## 3. Likely NEXT backend asks (heads-up, not yet requested) - **Model max context-window LIMIT** (the denominator for context size) — the context-size handoff - flagged this as the separate, later field. The FE now shows current size alone (e.g. "34,102 tokens - in context"); once a per-model/per-turn `contextWindow` (max token capacity) ships, the FE can render - `contextSize / limit` (e.g. "34,102 / 200,000") + a usage bar. GLOSSARY term reserved: "context window" - = the limit (distinct from "context size" = current usage). **Likely the next ask** — raise when the - backend can source the model's advertised window. + flagged this as the separate, later field. **The FE already renders `contextSize / limit · pct%` + a + fill bar in the composer status bar, but the limit is currently HARDCODED to `1,000,000` as a + placeholder** (`MAX_CONTEXT` in `features/chat/ui/Composer.svelte`; GLOSSARY "context window" notes it). + When a per-model/per-turn `contextWindow` (max token capacity) ships, wire the real value through (drop + the hardcode) so the bar/percent are accurate. **Likely the next ask** — raise when the backend can + source the model's advertised window. - `GET /conversations` — conversation list / sidebar (history explorer / switcher); could also expose a per-conversation "last model" so a reopened tab seeds its model from the server instead of localStorage. - `POST /conversations/:id/cancel` — "stop generating". diff --git a/src/app/App.svelte b/src/app/App.svelte index 32db54f..dbb346a 100644 --- a/src/app/App.svelte +++ b/src/app/App.svelte @@ -10,7 +10,6 @@ ChatView, Composer, manifest as chatManifest, - ContextSizeBadge, ModelSelector, } from "../features/chat"; import { manifest as conversationCacheManifest } from "../features/conversation-cache"; @@ -217,8 +216,11 @@ smartScroll.resume()} /> - - + +
+ + +
+ + +
+ + {#if status === "running"} + + {:else if status === "error"} + + + + + + {:else} + + + + {/if} + + + {#if usage.percent !== null} + + {:else} + + {/if} + + + {#if hasUsage} + {formatCompactTokens(usage.current)}{#if usage.max !== null} + / {formatCompactTokens(usage.max)}{/if} + {#if usage.percent !== null} + · {usage.percent.toFixed(1)}% + {/if} + {:else} + — tokens + {/if} + +
diff --git a/src/features/chat/ui/ContextSizeBadge.svelte b/src/features/chat/ui/ContextSizeBadge.svelte deleted file mode 100644 index 475d54f..0000000 --- a/src/features/chat/ui/ContextSizeBadge.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -
- - {label} - -
-- cgit v1.2.3