diff --git a/wwwroot/css/chat.css b/wwwroot/css/chat.css
index 646e4da..fed5ae2 100644
--- a/wwwroot/css/chat.css
+++ b/wwwroot/css/chat.css
@@ -213,6 +213,27 @@
margin-left: auto;
}
+/* Chain Meta Badges */
+
+.chain-meta {
+ align-self: center;
+ display: flex;
+ flex-wrap: wrap;
+ gap: var(--badge-spacing);
+ justify-content: center;
+ font-size: .8em;
+ filter: sepia(1) saturate(1) hue-rotate(-45deg);
+ padding: 0 0 var(--badge-spacing);
+}
+
+.chain-meta-badge {
+ padding: var(--badge-padding);
+ background: var(--f3-bg);
+ border: var(--border-width) solid var(--f3-border);
+ border-radius: var(--border-radius);
+ white-space: nowrap;
+}
+
.input-area {
display: flex;
flex-shrink: 0;
diff --git a/wwwroot/js/chat.js b/wwwroot/js/chat.js
index b0f0486..485f418 100644
--- a/wwwroot/js/chat.js
+++ b/wwwroot/js/chat.js
@@ -1,13 +1,27 @@
// ── Helpers ──
-function buildMetaString(d) {
- let meta = `Model: ${d.model || '?'} | In: ${d.inputTokens || 0} | Out: ${d.outputTokens || 0}`;
- if (d.cacheCreationTokens > 0) meta += ` | Cache-Write: ${d.cacheCreationTokens}`;
- if (d.cacheReadTokens > 0) meta += ` | Cache-Read: ${d.cacheReadTokens}`;
- if ((d.iterations || 0) > 1) meta += ` | Iterations: ${d.iterations}`;
- if (d.durationMs) meta += ` | ${(d.durationMs / 1000).toFixed(1)}s`;
- if (d.stopReason && d.stopReason !== 'end_turn' && d.stopReason !== 'tool_use') meta += ` | Stop: ${d.stopReason}`;
- return meta;
+function buildMetaBadges(d) {
+ const badges = [];
+ badges.push(d.model || '?');
+ badges.push('In: ' + (d.inputTokens || 0));
+ badges.push('Out: ' + (d.outputTokens || 0));
+ if (d.cacheCreationTokens > 0) badges.push('Cache-Write: ' + d.cacheCreationTokens);
+ if (d.cacheReadTokens > 0) badges.push('Cache-Read: ' + d.cacheReadTokens);
+ if ((d.iterations || 0) > 1) badges.push('Iterations: ' + d.iterations);
+ if (d.durationMs) badges.push((d.durationMs / 1000).toFixed(1) + 's');
+ if (d.stopReason && d.stopReason !== 'end_turn' && d.stopReason !== 'tool_use') badges.push('Stop: ' + d.stopReason);
+ return badges;
+}
+
+function addChainMetaBubble(d, streamed) {
+ if (!d || !d.model) return;
+ const badges = buildMetaBadges(d);
+ const div = document.createElement('div');
+ div.className = 'chain-meta';
+ if (streamed) div.classList.add('streamed');
+ div.innerHTML = badges.map(b => '' + esc(b) + '').join('');
+ chatEl().appendChild(div);
+ scrollDown();
}
// ── Chat State ──
@@ -148,13 +162,13 @@ async function sendMessage() {
addWarningBubble('⚠️ Antwort wurde abgeschnitten (max_tokens erreicht). Bitte frage nach dem Rest.', true);
}
if (data.model) {
- setMeta(buildMetaString(data));
+ addChainMetaBubble(data, false);
} else if (data.chainId) {
- // Leeres done (z.B. nach error/max_iterations) – Chain-Daten nachladen
apiGetChain(data.chainId).then(chain => {
- if (chain) setMeta(buildMetaString(chain));
+ if (chain) addChainMetaBubble(chain, false);
}).catch(() => {});
}
+ setMeta('Bereit');
break;
}
eventType = null;
diff --git a/wwwroot/js/history.js b/wwwroot/js/history.js
index 1fe3d08..d8f7c3e 100644
--- a/wwwroot/js/history.js
+++ b/wwwroot/js/history.js
@@ -17,6 +17,7 @@ async function loadHistory() {
for (const entry of data) {
renderMessages(entry.messages, true);
+ if (entry.chain) addChainMetaBubble(entry.chain, false);
}
scrollDown();
@@ -156,6 +157,14 @@ async function loadLatestArchive() {
}
fragment.appendChild(div);
}
+ // Chain-Meta-Badges nach jeder Chain einfügen
+ if (entry.chain) {
+ const badges = buildMetaBadges(entry.chain);
+ const metaDiv = document.createElement('div');
+ metaDiv.className = 'chain-meta archived';
+ metaDiv.innerHTML = badges.map(b => '' + esc(b) + '').join('');
+ fragment.appendChild(metaDiv);
+ }
}
// Älteres Archiv vor dem bisherigen Inhalt einfügen
diff --git a/wwwroot/js/pages/chat-page.js b/wwwroot/js/pages/chat-page.js
index b10f92d..88ad015 100644
--- a/wwwroot/js/pages/chat-page.js
+++ b/wwwroot/js/pages/chat-page.js
@@ -21,15 +21,13 @@ function startStreamingPoll(sessionId, sendBtn, chainId) {
// Finale Nachrichten nochmal laden
await pollHistory(true);
- // Chain-Daten laden für Meta-Info
+ // Chain-Daten laden für Meta-Info im Chat
const cid = _recoveryChainId || status.chainId;
if (cid) {
const chain = await apiGetChain(cid);
- if (chain) setMeta(buildMetaString(chain));
- else setMeta('Bereit');
- } else {
- setMeta('Bereit');
+ if (chain) addChainMetaBubble(chain, false);
}
+ setMeta('Bereit');
_recoveryChainId = null;
}
} catch (e) {