fix(ui): show token stats and simplify context window calculation

- Track messageInfoVersion in cache signature to rebuild when tokens arrive via SSE
- Read tokens from step-finish part directly (embedded in SSE events)
- Simplify available tokens to show full context window when no explicit input limit
This commit is contained in:
Shantur Rathore
2026-04-08 22:19:10 +01:00
parent 0739ec857c
commit 0ef57df3bc
2 changed files with 19 additions and 18 deletions

View File

@@ -629,13 +629,12 @@ export default function MessageBlock(props: MessageBlockProps) {
const lastAssistantIdx = props.lastAssistantIndex() const lastAssistantIdx = props.lastAssistantIndex()
const isQueued = current.role === "user" && (lastAssistantIdx === -1 || index > lastAssistantIdx) const isQueued = current.role === "user" && (lastAssistantIdx === -1 || index > lastAssistantIdx)
// Intentionally untracked: messageInfoVersion updates should not trigger const messageInfoVersion = props.store().state.messageInfoVersion[current.id] ?? 0
// a full message block rebuild; record revision is the invalidation key.
const info = untrack(messageInfo)
const cacheSignature = [ const cacheSignature = [
current.id, current.id,
current.revision, current.revision,
messageInfoVersion,
isQueued ? 1 : 0, isQueued ? 1 : 0,
props.showThinking() ? 1 : 0, props.showThinking() ? 1 : 0,
props.thinkingDefaultExpanded() ? 1 : 0, props.thinkingDefaultExpanded() ? 1 : 0,
@@ -647,6 +646,9 @@ export default function MessageBlock(props: MessageBlockProps) {
return cachedBlock.block return cachedBlock.block
} }
// Only capture info after cache check fails - ensures fresh data on version bump
const info = untrack(messageInfo)
const { orderedParts } = buildRecordDisplayData(props.instanceId, current) const { orderedParts } = buildRecordDisplayData(props.instanceId, current)
const items: MessageBlockItem[] = [] const items: MessageBlockItem[] = []
const blockContentKeys: string[] = [] const blockContentKeys: string[] = []
@@ -1108,17 +1110,23 @@ function StepCard(props: StepCardProps) {
return null return null
} }
const info = props.messageInfo const info = props.messageInfo
if (!info || info.role !== "assistant" || !info.tokens) { const part = props.part as any
// step-finish parts have tokens embedded; also check messageInfo
const partTokens = part?.tokens
const infoTokens = info && info.role === "assistant" ? info.tokens : undefined
const tokens = partTokens ?? infoTokens
if (!tokens) {
return null return null
} }
const tokens = info.tokens
return { return {
input: tokens.input ?? 0, input: tokens.input ?? 0,
output: tokens.output ?? 0, output: tokens.output ?? 0,
reasoning: tokens.reasoning ?? 0, reasoning: tokens.reasoning ?? 0,
cacheRead: tokens.cache?.read ?? 0, cacheRead: tokens.cache?.read ?? 0,
cacheWrite: tokens.cache?.write ?? 0, cacheWrite: tokens.cache?.write ?? 0,
cost: info.cost ?? 0, cost: (part?.cost ?? (info && info.role === "assistant" ? info.cost : 0)) ?? 0,
} }
} }

View File

@@ -116,18 +116,11 @@ export function updateSessionInfo(instanceId: string, sessionId: string): void {
// Prefer explicit input limits when provided by the API. // Prefer explicit input limits when provided by the API.
// This is used by the UI "Avail" chip. // This is used by the UI "Avail" chip.
contextAvailableTokens = modelInputLimit contextAvailableTokens = modelInputLimit
} } else if (contextWindow > 0) {
// When no explicit input limit, show full context window capacity.
if (!contextAvailableFromPrevious && contextAvailableTokens === null) { contextAvailableTokens = contextWindow
if (contextWindow > 0) { } else {
if (latestHasContextUsage && actualUsageTokens > 0) { contextAvailableTokens = null
contextAvailableTokens = Math.max(contextWindow - (actualUsageTokens + outputBudget), 0)
} else {
contextAvailableTokens = contextWindow
}
} else {
contextAvailableTokens = null
}
} }
setSessionInfoByInstance((prev) => { setSessionInfoByInstance((prev) => {