Show per-member token usage badges on panel bubbles
memberUsage was already populated for CLI members (from the result event) and Anthropic-API members (via the tool loop), but it was never written to the panel_member_done SSE event or the persisted content JSON, so the frontend had no token data to render. Add the token fields (input/output/cacheRead/cacheCreation) to both the SSE event and the persisted JSON, and render them via a shared buildPanelMemberMeta helper used by both the live stream and history, so the badge survives F5. OpenAI members (no per-turn tokens), error members, and pre-existing history without token fields fall through to no badge. The badge is display-only; aggregation into the worker/chain total via seedUsage is unchanged, so there is no double counting and no DB migration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
17ba4840dd
commit
8e5a961e94
4 files changed
+50
-2
No files matched your search
@@ -245,7 +245,15 @@ public partial class AnthropicService
|
|||||||
}
|
}
|
||||||
if (segments.Count > 0)
|
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
|
await _db.SaveMessage(new MessageRecord
|
||||||
{
|
{
|
||||||
ChainId = chainId, Iteration = layer * 100 + index + 1,
|
ChainId = chainId, Iteration = layer * 100 + index + 1,
|
||||||
@@ -255,7 +263,14 @@ public partial class AnthropicService
|
|||||||
}
|
}
|
||||||
|
|
||||||
await SseHelper.TryWriteSse(response, "panel_member_done",
|
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 };
|
return new PanelMemberResult { Index = index, Name = name, Model = member.ModelId, Text = text, Usage = memberUsage };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1237,6 +1237,17 @@
|
|||||||
border-color: var(--accent, #9b6dff);
|
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 */
|
/* MoA: Trenner zwischen Proposer-Runden */
|
||||||
.panel-layer-divider {
|
.panel-layer-divider {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -553,6 +553,8 @@ function _renderPanelMemberBubble(msg, archived) {
|
|||||||
empty.innerHTML = renderMarkdown('_(keine Antwort)_');
|
empty.innerHTML = renderMarkdown('_(keine Antwort)_');
|
||||||
bubble.appendChild(empty);
|
bubble.appendChild(empty);
|
||||||
}
|
}
|
||||||
|
var meta = buildPanelMemberMeta(data);
|
||||||
|
if (meta) bubble.appendChild(meta);
|
||||||
return bubble;
|
return bubble;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -324,6 +324,24 @@ function finishPanelMemberTool(data) {
|
|||||||
scrollDown();
|
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 '<span class="chain-meta-badge">' + esc(b) + '</span>';
|
||||||
|
}).join('');
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
function finalizePanelMember(data) {
|
function finalizePanelMember(data) {
|
||||||
var bubble = _panelGetBubble(data.index);
|
var bubble = _panelGetBubble(data.index);
|
||||||
if (!bubble) return;
|
if (!bubble) return;
|
||||||
@@ -348,6 +366,8 @@ function finalizePanelMember(data) {
|
|||||||
}
|
}
|
||||||
bubble._segText.innerHTML = renderMarkdown(text);
|
bubble._segText.innerHTML = renderMarkdown(text);
|
||||||
}
|
}
|
||||||
|
var meta = buildPanelMemberMeta(data);
|
||||||
|
if (meta) bubble.appendChild(meta);
|
||||||
delete _panelBubbles[_panelKey(data.index, _panelActiveLayer[data.index])];
|
delete _panelBubbles[_panelKey(data.index, _panelActiveLayer[data.index])];
|
||||||
scrollDown();
|
scrollDown();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user