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) {