diff --git a/Services/AnthropicService.Panel.cs b/Services/AnthropicService.Panel.cs index 5155c68..9d5e8bf 100644 --- a/Services/AnthropicService.Panel.cs +++ b/Services/AnthropicService.Panel.cs @@ -245,7 +245,15 @@ public partial class AnthropicService } if (segments.Count > 0) { - var content = JsonSerializer.Serialize(new { name, model = member.ModelId, layer, totalLayers, segments }); + var content = JsonSerializer.Serialize(new + { + name, model = member.ModelId, layer, totalLayers, + inputTokens = memberUsage?.InputTokens ?? 0, + outputTokens = memberUsage?.OutputTokens ?? 0, + cacheReadTokens = memberUsage?.CacheReadTokens ?? 0, + cacheCreationTokens = memberUsage?.CacheCreationTokens ?? 0, + segments + }); await _db.SaveMessage(new MessageRecord { ChainId = chainId, Iteration = layer * 100 + index + 1, @@ -255,7 +263,14 @@ public partial class AnthropicService } await SseHelper.TryWriteSse(response, "panel_member_done", - new { index, name, model = member.ModelId, text }, sseCt, sseGate); + new + { + index, name, model = member.ModelId, text, + inputTokens = memberUsage?.InputTokens ?? 0, + outputTokens = memberUsage?.OutputTokens ?? 0, + cacheReadTokens = memberUsage?.CacheReadTokens ?? 0, + cacheCreationTokens = memberUsage?.CacheCreationTokens ?? 0 + }, sseCt, sseGate); return new PanelMemberResult { Index = index, Name = name, Model = member.ModelId, Text = text, Usage = memberUsage }; } diff --git a/wwwroot/css/chat.css b/wwwroot/css/chat.css index 834331c..4b44583 100644 --- a/wwwroot/css/chat.css +++ b/wwwroot/css/chat.css @@ -1237,6 +1237,17 @@ border-color: var(--accent, #9b6dff); } +/* Per-Member Token-Badge am unteren Rand der Panel-Bubble (live + History) */ +.panel-member-meta { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: var(--badge-spacing); + margin-top: var(--badge-spacing); + font-size: var(--font-size); + opacity: var(--semi-transparent); +} + /* MoA: Trenner zwischen Proposer-Runden */ .panel-layer-divider { display: flex; diff --git a/wwwroot/js/history.js b/wwwroot/js/history.js index 2a9ef23..f5a5dcf 100644 --- a/wwwroot/js/history.js +++ b/wwwroot/js/history.js @@ -553,6 +553,8 @@ function _renderPanelMemberBubble(msg, archived) { empty.innerHTML = renderMarkdown('_(keine Antwort)_'); bubble.appendChild(empty); } + var meta = buildPanelMemberMeta(data); + if (meta) bubble.appendChild(meta); return bubble; } diff --git a/wwwroot/js/stream-ui.js b/wwwroot/js/stream-ui.js index 03c4396..ae5f3d1 100644 --- a/wwwroot/js/stream-ui.js +++ b/wwwroot/js/stream-ui.js @@ -324,6 +324,24 @@ function finishPanelMemberTool(data) { scrollDown(); } +// Baut ein kompaktes Token-Badge für eine Panel-Member-Bubble (live + History, gleiche Quelle). +// Gibt ein DOM-Element zurück oder null, wenn keine Token-Daten vorliegen: OpenAI-Member +// (liefert keine per-turn Tokens), Fehler-Member oder Alt-History ohne Token-Felder → kein Badge. +function buildPanelMemberMeta(d) { + if (!d) return null; + var inT = d.inputTokens || 0, outT = d.outputTokens || 0; + var cacheR = d.cacheReadTokens || 0, cacheC = d.cacheCreationTokens || 0; + if (!inT && !outT && !cacheR && !cacheC) return null; + var badges = ['Tokens: ' + inT + ' / ' + outT]; + if (cacheR > 0 || cacheC > 0) badges.push('Cache: ' + cacheR + ' / ' + cacheC); + var meta = document.createElement('div'); + meta.className = 'panel-member-meta'; + meta.innerHTML = badges.map(function (b) { + return '' + esc(b) + ''; + }).join(''); + return meta; +} + function finalizePanelMember(data) { var bubble = _panelGetBubble(data.index); if (!bubble) return; @@ -348,6 +366,8 @@ function finalizePanelMember(data) { } bubble._segText.innerHTML = renderMarkdown(text); } + var meta = buildPanelMemberMeta(data); + if (meta) bubble.appendChild(meta); delete _panelBubbles[_panelKey(data.index, _panelActiveLayer[data.index])]; scrollDown(); }