diff --git a/CHANGELOG.md b/CHANGELOG.md
index b565670..e25dea1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,9 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
---
## [Unreleased]
+### Changed
+- Conversation Playground: the metrics (Token Usage and STT / LLM first token / TTS first audio TTFB charts) now sit in the right-hand Latency column below Turn history instead of a second "Metrics" tab; the Conversation | Metrics tablist is removed. Charts gain axis titles ("Time (ms)", "Time"), a legend and three y-ticks.
+
### Added
- Conversation Playground: Metrics view (Conversation | Metrics tabs) with Pipecat-style TTFB line charts for STT, LLM first token and TTS, plus session Token Usage (prompt/completion/total, "n/a" when the backend reports none). The stream now requests `stream_options.include_usage` (dropped and retried on 400/422) and forwards `usage` in the final stats event; the TTS value is now the time to the first audio chunk instead of including the remaining LLM generation time.
- Conversation Playground: a Thinking on/off switch next to the Language Model (default off, setting `conversation_thinking`). Off sends `chat_template_kwargs.enable_thinking=false` (plus `think:false` for Ollama) with each reply request and never touches the system prompt; the empty-reply error now says to turn Thinking off.
diff --git a/Manual.md b/Manual.md
index a72d673..1df28f3 100644
--- a/Manual.md
+++ b/Manual.md
@@ -878,19 +878,19 @@ offline cache yet.
**Privacy:** the setting is stored in the local app settings only; nothing extra is sent anywhere.
-## Conversation Playground: Metrics view
+## Conversation Playground: Metrics (Latency column)
**Purpose:** see how fast each stage responds over a session, like the Pipecat Playground "Metrics" tab: Token Usage (prompt / completion / total) and a TTFB line chart each for STT, LLM (first token) and TTS (time to the first audio chunk, not the end of playback).
**Prerequisites:** at least one completed turn in Conversation. Token counts need a backend that reports `usage` in streaming replies (Ollama, llama.cpp, vLLM and llama-swap do).
-**Usage:** switch the Conversation | Metrics tabs at the top (click, tap, or Left/Right/Home/End arrows). Each completed turn adds one point (X = time of day HH:MM:SS, Y = ms). Use "Show as table" under a chart for the numbers. The Clear button resets charts and token totals.
+**Usage:** scroll the right-hand Latency column below "Turn history" to Token Usage and the three chart boxes (STT, LLM first token, TTS first audio). Each completed turn adds one point (X = time of day HH:MM:SS, Y = "Time (ms)", legend "TTFB (STT|LLM|TTS)"). Use "Show as table" under a chart for the numbers. The Clear button resets charts and token totals.
-**Expected result:** three charts with one point per turn (last 50 kept) and summed token counts. Voice turns include STT; typed turns have no STT point. The Latency panel in the Conversation tab is unchanged.
+**Expected result:** three charts with one point per turn (last 50 kept) and summed token counts. Voice turns include STT; typed turns have no STT point. The Latency panel and Turn history above are unchanged.
**Errors:** a backend that sends no usage shows "n/a" (never 0). Failed turns add no point. With no turns yet, "No turns yet." is shown.
-**Mobile / accessibility:** charts stack on phones; 44 px tabs (tablist, aria-selected); every chart has a text summary (e.g. "LLM first token, last 142 ms, 5 points") and a table alternative; values are drawn as line plus markers, not colour only; no animation.
+**Mobile / accessibility:** on phones the column stacks below the chat and the chart boxes stay full width; every chart has a text summary (e.g. "LLM first token, last 142 ms, 5 points") and a table alternative; values are drawn as line plus markers with a distinct colour per chart, never colour only; no animation.
**Offline:** works with a local LLM; no network beyond your configured endpoint.
diff --git a/PROJECT_MAP.md b/PROJECT_MAP.md
index 089f74f..777c819 100644
--- a/PROJECT_MAP.md
+++ b/PROJECT_MAP.md
@@ -58,7 +58,7 @@ Two self-hosted Docker instances, both port 7890:
| Voice library / clone / design | `routes/library.py`, `core/voice.py` | `library.js` | mature |
| Voice archetype gallery (40 own German-first design voices, cached previews) | `core/voice_archetypes.py`, `routes/archetypes.py` | `voice-archetypes.js`, `s-design.html` | **new** — see [architecture/voice-archetypes.md](architecture/voice-archetypes.md) |
| Read Aloud (books, PDFs) | `routes/reader.py` | `reader.js` | mature |
-| Conversation Metrics view (TTFB charts STT/LLM/TTS, token usage) | `routes/conversation.py` (`_usage_from_chunk`, `_post_llm_stream`) | `conversation.js`, `s-conversation.html` | **new 2026-10-06** — in-memory, last 50 turns |
+| Conversation metrics in the Latency column (TTFB charts STT/LLM/TTS, token usage) | `routes/conversation.py` (`_usage_from_chunk`, `_post_llm_stream`) | `conversation.js`, `s-conversation.html` | **new 2026-10-06** — in-memory, last 50 turns |
| Audiobook casting (span mode: deterministic quote spans, LLM names speakers only; verify/recast) | `routes/conversation.py`, `core/attribution_spans.py` | `audiobook*.js`, `audiobook-spans.js`, `audiobook-cast-spans.js` | **new 2026-09-19** — see [architecture/audiobook-attribution.md](architecture/audiobook-attribution.md) |
| Characters (avatar/concept identity, appearance forms) | `routes/characters.py`, `routes/conversation.py` | `library-characters.js`, `character-sheets.js` | **reworked 2026-09-19** — see [architecture/character-visual-identity.md](architecture/character-visual-identity.md) |
| Rehearser | `routes/rehearsals_db.py` | `rehearser-*.js` | mature |
diff --git a/static/js/conversation.js b/static/js/conversation.js
index 471021c..5de0483 100644
--- a/static/js/conversation.js
+++ b/static/js/conversation.js
@@ -573,7 +573,7 @@ $('s-import-voices-file')?.addEventListener('change', async function () {
}
- // ── Metrics view (TTFB charts + token usage; in-memory, last 50 turns) ────
+ // ── Metrics (latency column; TTFB charts + token usage; in-memory, last 50 turns) ────
const METRIC_MAX = 50;
let metricTurns = []; // {ts, stt, llm, tts} (ms, null when not measured)
let metricUsage = null; // {p, c, t} summed over turns whose server reported usage
@@ -595,22 +595,25 @@ $('s-import-voices-file')?.addEventListener('change', async function () {
renderMetrics();
}
- function chartSvg(pts, label) {
- const W = 300, H = 140, L = 40, R = 10, T = 8, B = 22;
+ function chartSvg(pts, label, k) {
+ const W = 300, H = 160, L = 46, R = 10, T = 8, B = 36;
const max = Math.max(...pts.map(p => p.v), 1);
const t0 = pts[0].ts, span = pts[pts.length - 1].ts - t0;
const x = ts => L + (span ? (ts - t0) / span : 0.5) * (W - L - R);
const y = v => T + (1 - v / max) * (H - T - B);
const line = pts.map(p => `${x(p.ts).toFixed(1)},${y(p.v).toFixed(1)}`).join(' ');
const dots = pts.map(p => ``).join('');
+ const yt = [0, max / 2, max].map(v => `${Math.round(v)}`).join('');
return ``;
+ ${yt}
+ ${hms(t0)}
+ ${span ? `${hms(pts[pts.length - 1].ts)}` : ''}
+ ${tr('Time')}
+ ${tr('Time (ms)')}
+ ● TTFB (${k.toUpperCase()})
+ ${dots}`;
}
function renderMetrics() {
@@ -624,29 +627,12 @@ $('s-import-voices-file')?.addEventListener('change', async function () {
const summary = pts.length ? `${tr(what)}, ${tr('last')} ${pts[pts.length - 1].v} ms, ${pts.length} ${tr('points')}` : `${tr(what)}: ${tr('no data')}`;
const rows = pts.map(p => `