feat: add chat token usage indicator#9162
Conversation
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The
loadTokenProvidersresponse is force-cast toTokenProviderConfig[]viaas unknown as, which bypasses type safety; consider validating the shape or narrowing the type instead of double-casting. - There is overlapping logic for clamping/normalizing usage percentages between
formatUsagePercentinChat.vueandtokenUsagePercentinChatInput.vue; consolidating this into a single helper would reduce duplication and potential drift. - The
readTokenCounthelper currently ignores zero and negative values; if the API can report zero or negative token deltas, you may want to treat them explicitly rather than silently coercing to 0.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `loadTokenProviders` response is force-cast to `TokenProviderConfig[]` via `as unknown as`, which bypasses type safety; consider validating the shape or narrowing the type instead of double-casting.
- There is overlapping logic for clamping/normalizing usage percentages between `formatUsagePercent` in `Chat.vue` and `tokenUsagePercent` in `ChatInput.vue`; consolidating this into a single helper would reduce duplication and potential drift.
- The `readTokenCount` helper currently ignores zero and negative values; if the API can report zero or negative token deltas, you may want to treat them explicitly rather than silently coercing to 0.
## Individual Comments
### Comment 1
<location path="dashboard/src/components/chat/Chat.vue" line_range="858-862" />
<code_context>
selectedText: replyPreview(replyTarget.value.id, ""),
},
);
+const currentTokenProvider = computed(() => {
+ const selectedProvider = tokenProviderConfigs.value.find(
+ (provider) => provider.id === selectedTokenProviderId.value,
+ );
+ return selectedProvider || tokenProviderConfigs.value[0] || null;
+});
+const latestTokenUsageTotal = computed(() => {
</code_context>
<issue_to_address>
**suggestion:** Keep `selectedTokenProviderId` in sync when falling back to the first provider.
In `currentTokenProvider`, when `selectedTokenProviderId` doesn't match any loaded provider, you fall back to `tokenProviderConfigs[0]` but leave `selectedTokenProviderId` pointing to a non-existent provider. This desynchronizes the persisted selection (e.g. localStorage) from the actual provider used for `contextLimit`. Please either update `selectedTokenProviderId` to the fallback provider’s id when no match is found, or introduce a derived `effectiveSelectedProviderId` that always reflects the provider actually used.
Suggested implementation:
```
const tokenProviderConfigs = ref<TokenProviderConfig[]>([]);
const selectedTokenProviderId = ref("");
const effectiveSelectedTokenProviderId = computed(() => {
const hasSelectedProvider = tokenProviderConfigs.value.some(
(provider) => provider.id === selectedTokenProviderId.value,
);
if (hasSelectedProvider) {
return selectedTokenProviderId.value;
}
return tokenProviderConfigs.value[0]?.id ?? "";
});
const messagesContainer = ref<HTMLElement | null>(null);
```
```
const currentTokenProvider = computed(() => {
const selectedProvider = tokenProviderConfigs.value.find(
(provider) => provider.id === effectiveSelectedTokenProviderId.value,
);
return selectedProvider || tokenProviderConfigs.value[0] || null;
});
```
Any logic that depends on the “actually used” token provider (e.g. for `contextLimit`, pricing display, or usage calculation) should use `effectiveSelectedTokenProviderId` or `currentTokenProvider` rather than `selectedTokenProviderId`. Search for usages of `selectedTokenProviderId` elsewhere in `Chat.vue` (and related components) and update them where they should reflect the effective provider, not just the persisted/raw selection.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const currentTokenProvider = computed(() => { | ||
| const selectedProvider = tokenProviderConfigs.value.find( | ||
| (provider) => provider.id === selectedTokenProviderId.value, | ||
| ); | ||
| return selectedProvider || tokenProviderConfigs.value[0] || null; |
There was a problem hiding this comment.
suggestion: Keep selectedTokenProviderId in sync when falling back to the first provider.
In currentTokenProvider, when selectedTokenProviderId doesn't match any loaded provider, you fall back to tokenProviderConfigs[0] but leave selectedTokenProviderId pointing to a non-existent provider. This desynchronizes the persisted selection (e.g. localStorage) from the actual provider used for contextLimit. Please either update selectedTokenProviderId to the fallback provider’s id when no match is found, or introduce a derived effectiveSelectedProviderId that always reflects the provider actually used.
Suggested implementation:
const tokenProviderConfigs = ref<TokenProviderConfig[]>([]);
const selectedTokenProviderId = ref("");
const effectiveSelectedTokenProviderId = computed(() => {
const hasSelectedProvider = tokenProviderConfigs.value.some(
(provider) => provider.id === selectedTokenProviderId.value,
);
if (hasSelectedProvider) {
return selectedTokenProviderId.value;
}
return tokenProviderConfigs.value[0]?.id ?? "";
});
const messagesContainer = ref<HTMLElement | null>(null);
const currentTokenProvider = computed(() => {
const selectedProvider = tokenProviderConfigs.value.find(
(provider) => provider.id === effectiveSelectedTokenProviderId.value,
);
return selectedProvider || tokenProviderConfigs.value[0] || null;
});
Any logic that depends on the “actually used” token provider (e.g. for contextLimit, pricing display, or usage calculation) should use effectiveSelectedTokenProviderId or currentTokenProvider rather than selectedTokenProviderId. Search for usages of selectedTokenProviderId elsewhere in Chat.vue (and related components) and update them where they should reflect the effective provider, not just the persisted/raw selection.
There was a problem hiding this comment.
Code Review
This pull request introduces a token usage indicator to the chat interface, displaying a progress ring and tooltip in the chat input based on the current provider's context limit and the latest message's token usage. The feedback highlights a reactivity issue where the selected provider ID is not updated when changed in the dropdown menu until a message is sent, and suggests checking both enable and enabled properties when filtering active providers.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const currentTokenProvider = computed(() => { | ||
| const selectedProvider = tokenProviderConfigs.value.find( | ||
| (provider) => provider.id === selectedTokenProviderId.value, | ||
| ); | ||
| return selectedProvider || tokenProviderConfigs.value[0] || null; | ||
| }); |
There was a problem hiding this comment.
The selectedTokenProviderId is only updated when getSelectedProviderSelection() is called (which happens during message sending or editing) or on initial mount. If a user changes the active provider/model in the dropdown menu, selectedTokenProviderId will not update reactively. As a result, the tokenUsageIndicator (and the progress ring) will continue to use the context limit of the old provider until a message is sent.
To fix this, consider having ProviderModelMenu emit a change event when a new provider is selected, propagating it up through ChatInput to Chat.vue to update selectedTokenProviderId reactively.
| tokenProviderConfigs.value = ( | ||
| (response.data.data || []) as unknown as TokenProviderConfig[] | ||
| ).filter((provider) => provider.enable !== false); |
There was a problem hiding this comment.
Some providers might use the enabled property instead of enable to indicate their status (as seen in other parts of the API and configuration helpers). To ensure disabled providers are correctly filtered out, check both properties.
tokenProviderConfigs.value = (
(response.data.data || []) as unknown as TokenProviderConfig[]
).filter((provider) => provider.enable !== false && provider.enabled !== false);
7ef89cc to
8eec231
Compare
Summary
Tests
Summary by Sourcery
Add a chat token usage indicator derived from assistant token stats and provider context metadata, surfaced next to the Chat UI microphone action.
New Features:
Enhancements:
Chores: