# AiTokenUsageView
## Description
BitAiTokenUsageView is a compact readout of what a generation cost: its tokens, its money and its speed - the throughput in tokens per second and, on request, the time to the first token. Every figure is optional, because providers report different subsets of them and a row of blanks is worse than a short row: a figure the usage or the metadata does not carry is left out rather than shown empty. The breakdown of the tokens (input, output, cached and reasoning) is the native tooltip of the readout, and visually hidden text of it for the keyboard and screen readers. A message shows one under itself and a run view in its header when BitAiUiOptions.ShowUsage is on.
## Parameters
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Classes | `BitAiTokenUsageViewClassStyles?` | null | Custom CSS classes for different parts of the token usage view. |
| Metadata | `BitAiGenerationMetadata?` | null | The metadata of the generation, which supplies the speed (TokensPerSecond) and the latency (TimeToFirstToken). Without it the readout shows the tokens and the cost alone. |
| ShowLatency | `bool` | false | Shows the time to the first token, when the Metadata carries it. |
| ShowSpeed | `bool` | true | Shows the throughput in tokens per second, when the Metadata carries what it is computed from (the output tokens, the start and the end of the generation). |
| Styles | `BitAiTokenUsageViewClassStyles?` | null | Custom CSS styles for different parts of the token usage view. |
| Usage | `BitAiTokenUsage` | | The usage to show (required): its total of tokens (shortened, 1.6k), its cost in its currency, and - as the native tooltip - a line for each kind of token it reports. A figure it does not carry is left out rather than shown empty. |
| AriaLabel | `string?` | null | Gets or sets the accessible label for the component, used by assistive technologies. |
| Class | `string?` | null | Gets or sets the CSS class name(s) to apply to the rendered element. |
| Dir | `BitDir?` | null | Gets or sets the text directionality for the component's content. |
| Disabled | `bool` | false | Gets or sets a value indicating whether the component is disabled and cannot respond to user interaction. |
| ForceAnimation | `bool` | false | Gets or sets a value indicating whether the component's animations play at their full duration even when reduced motion is requested. |
| HtmlAttributes | `Dictionary<string, object>` | new Dictionary<string, object>() | Captures additional HTML attributes to be applied to the rendered element, in addition to the component's parameters. An attribute written on the component wins over an entry of the same name. |
| Id | `string?` | null | Gets or sets the unique identifier for the component's root element. |
| Style | `string?` | null | Gets or sets the CSS style string to apply to the rendered element. |
| TabIndex | `string?` | null | Gets or sets the tab order index for the component when navigating with the keyboard. |
| Visibility | `BitVisibility` | BitVisibility.Visible | Gets or sets the visibility state (visible, hidden, or collapsed) of the component. |
## Public Members
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| UniqueId | `Guid` | Guid.NewGuid() | Gets the readonly unique identifier for the component's root element, assigned when the component instance is constructed. |
| RootElement | `ElementReference` | | Gets the reference to the root HTML element associated with this component. |
## Enums
### BitAiFinishReason Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Unknown | 0 | The reason was not reported by the provider. |
| Stop | 1 | The model reached a natural end. |
| Length | 2 | The output hit the token limit. |
| ToolCalls | 3 | The model stopped to call one or more tools. |
| ContentFilter | 4 | The provider's safety system stopped the generation. |
| Cancelled | 5 | The caller cancelled the generation. |
| Error | 6 | The generation failed. |
### BitVisibility Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Visible | 0 | The content of the component is visible. |
| Hidden | 1 | The content of the component is hidden, but the space it takes on the page remains (visibility:hidden). |
| Collapsed | 2 | The component is hidden (display:none). |
### BitDir Enum
| Name | Value | Description |
| :--- | :--- | :---------- |
| Ltr | 0 | Ltr (left to right) is to be used for languages that are written from the left to the right (like English). |
| Rtl | 1 | Rtl (right to left) is to be used for languages that are written from the right to the left (like Arabic). |
| Auto | 2 | Auto lets the user agent decide. It uses a basic algorithm as it parses the characters inside the element until it finds a character with a strong directionality, then applies that directionality to the whole element. |
## Sub Classes
### BitAiTokenUsageViewClassStyles Properties
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| Root | `string?` | null | Custom CSS classes/styles for the root element of the BitAiTokenUsageView. |
| Tokens | `string?` | null | Custom CSS classes/styles for the token count of the BitAiTokenUsageView. |
| Cost | `string?` | null | Custom CSS classes/styles for the cost of the BitAiTokenUsageView. |
| Speed | `string?` | null | Custom CSS classes/styles for the throughput of the BitAiTokenUsageView. |
| Latency | `string?` | null | Custom CSS classes/styles for the time to the first token of the BitAiTokenUsageView. |
### BitAiTokenUsage Properties
The token accounting of one or more generations, a readonly record struct: new BitAiTokenUsage { InputTokens = ..., OutputTokens = ... }. Every field is optional, because providers report different subsets. Usages add up field by field (usage + other), which is how a message totals the requests of a tool loop and a run the requests made within it; a field missing on both sides stays missing, so a total never claims a zero the provider did not report.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| InputTokens | `long?` | null | The tokens consumed by the prompt. |
| OutputTokens | `long?` | null | The tokens produced by the model. |
| CachedInputTokens | `long?` | null | The input tokens served from a provider-side prompt cache. |
| CacheWriteTokens | `long?` | null | The input tokens written to a provider-side prompt cache. |
| ReasoningTokens | `long?` | null | The tokens spent on hidden reasoning, where the provider reports it separately. |
| Cost | `decimal?` | null | The cost in the currency named by Currency, when the host can compute it (BitAiModel.EstimateCost prices a usage with the rates of a model). |
| Currency | `string?` | null | The ISO 4217 code of the Cost. USD when a cost is present without one. |
| TotalTokens | `long?` | null | The input and the output tokens summed, when either is present. |
| IsEmpty | `bool` | true | Whether no field carries a value. |
| operator + | `BitAiTokenUsage` | | usage + other (or BitAiTokenUsage.Add(usage, other)): adds two usages field by field, keeping a field absent on both sides absent. |
| Merge | `BitAiTokenUsage` | | BitAiTokenUsage.Merge(existing, update): overlays a newer snapshot on an older one field by field - a field the update reports replaces the old value, one it omits keeps it. How a message accumulates usage from a provider that reports the input tokens first and the output tokens at the end. |
### BitAiGenerationMetadata Properties
Everything known about how a message was produced, separate from what it says (BitAiMessage.Metadata) - held apart from the message body so that the streaming updates never touch it. A transport reports it as BitAiMetadataEvent and BitAiUsageUpdatedEvent; the session adds the timings. A sealed record.
| Name | Type | Default Value | Description |
| :--- | :--- | :------------ | :---------- |
| ModelId | `string?` | null | The identifier of the model that produced the message. |
| ProviderId | `string?` | null | The identifier of the provider that served the request. |
| Usage | `BitAiTokenUsage` | | The token accounting of the generation. |
| FinishReason | `BitAiFinishReason` | BitAiFinishReason.Unknown | Why the generation stopped. |
| StartedAt | `DateTimeOffset?` | null | When the request was sent. |
| CompletedAt | `DateTimeOffset?` | null | When the last event arrived. |
| TimeToFirstToken | `TimeSpan?` | null | The time from the request to the first streamed token - the latency the readout shows with ShowLatency. |
| Duration | `TimeSpan?` | null | The total wall-clock duration of the generation, when both StartedAt and CompletedAt are known. |
| TokensPerSecond | `double?` | null | The output tokens per second over the streaming window (the duration less the time to the first token), or null when it cannot be computed from the reported values - the speed the readout shows. |
| Temperature | `double?` | null | The sampling temperature used, when reported. |
| ContextWindow | `long?` | null | The size of the context window of the model in tokens, which a BitAiContextMeter reads off a conversation. |
| Extra | `IReadOnlyDictionary<string, object?>?` | null | Provider-specific values that do not map onto the fields above. |
## CSS Variables
Public CSS custom properties read off the component's root. Set one on `:root` to restyle every instance, on any ancestor to restyle the ones inside it, or on the `Style` of one instance to restyle it and every instance nested inside it, which inherits the value too - a parameter changes one alone, as does the real CSS property on its `Style` / `Styles` when that property does not inherit (`opacity`); an inherited one (`color`, `font-family`) reaches the nested instances that do not set it themselves.
| Name | Default Value | Description |
| :--- | :------------ | :---------- |
| `--bit-AiTokenUsageView-color` | var(--bit-clr-fg-sec) | Text of the readout. |
| `--bit-AiTokenUsageView-font-size` | var(--bit-tpg-fs-xs) | Size of the readout. |
| `--bit-AiTokenUsageView-gap` | spacing(1.5) | Room between two figures. |
## Examples
\n**Basic**:
```razor
```
```csharp
private readonly BitAiTokenUsage basicUsage = new() { InputTokens = 1_240, OutputTokens = 386 };
```
\n**Cost**:
```razor
```
```csharp
private readonly BitAiTokenUsage centsUsage = new() { InputTokens = 1_240, OutputTokens = 386, Cost = 0.0095m, Currency = "USD" };
private readonly BitAiTokenUsage dollarsUsage = new() { InputTokens = 182_000, OutputTokens = 24_500, Cost = 0.91m };
private readonly BitAiTokenUsage eurosUsage = new() { InputTokens = 250_000, OutputTokens = 61_000, Cost = 1.25m, Currency = "EUR" };
private readonly BitAiTokenUsage yenUsage = new() { InputTokens = 1_250_000, OutputTokens = 92_000, Cost = 420m, Currency = "JPY" };
```
\n**Speed & latency**:
```razor
```
```csharp
// 386 output tokens streamed over 4.8 seconds, after a first token at 400ms.
private static readonly DateTimeOffset requestedAt = new(2026, 1, 1, 9, 0, 0, TimeSpan.Zero);
private readonly BitAiGenerationMetadata metadata = new()
{
ModelId = "demo-model-1",
Usage = new() { InputTokens = 1_240, OutputTokens = 386, Cost = 0.0095m, Currency = "USD" },
StartedAt = requestedAt,
TimeToFirstToken = TimeSpan.FromMilliseconds(400),
CompletedAt = requestedAt.AddSeconds(5.2),
FinishReason = BitAiFinishReason.Stop,
};
```
\n**Breakdown**:
```razor
```
```csharp
private readonly BitAiTokenUsage detailedUsage = new()
{
InputTokens = 12_400,
OutputTokens = 1_860,
CachedInputTokens = 9_800,
ReasoningTokens = 1_100,
Cost = 0.0312m,
};
```
\n**A live conversation**:
```razor
@implements IAsyncDisposable
Send a message
@if (meteredSession is not null)
{
@foreach (var message in meteredSession.Conversation.Messages.Where(m => m.Role.IsAssistant && m.Metadata is not null))
{
```
```csharp
private static readonly DateTimeOffset requestedAt = new(2026, 1, 1, 9, 0, 0, TimeSpan.Zero);
private readonly BitAiGenerationMetadata metadata = new()
{
ModelId = "demo-model-1",
Usage = new() { InputTokens = 1_240, OutputTokens = 386, Cost = 0.0095m, Currency = "USD" },
StartedAt = requestedAt,
TimeToFirstToken = TimeSpan.FromMilliseconds(400),
CompletedAt = requestedAt.AddSeconds(5.2),
FinishReason = BitAiFinishReason.Stop,
};
```
\n**Cascading parameters**:
```razor
```
```csharp
private readonly BitAiTokenUsageViewParams[] usageViewParams =
[
new() { ShowLatency = true, ShowSpeed = false, Styles = new() { Cost = "font-weight: bold" } }
];
private static readonly DateTimeOffset requestedAt = new(2026, 1, 1, 9, 0, 0, TimeSpan.Zero);
private readonly BitAiGenerationMetadata metadata = new()
{
ModelId = "demo-model-1",
Usage = new() { InputTokens = 1_240, OutputTokens = 386, Cost = 0.0095m, Currency = "USD" },
StartedAt = requestedAt,
TimeToFirstToken = TimeSpan.FromMilliseconds(400),
CompletedAt = requestedAt.AddSeconds(5.2),
FinishReason = BitAiFinishReason.Stop,
};
```
\n**Style & Class**:
```razor
```
```csharp
private static readonly DateTimeOffset requestedAt = new(2026, 1, 1, 9, 0, 0, TimeSpan.Zero);
private readonly BitAiGenerationMetadata metadata = new()
{
ModelId = "demo-model-1",
Usage = new() { InputTokens = 1_240, OutputTokens = 386, Cost = 0.0095m, Currency = "USD" },
StartedAt = requestedAt,
TimeToFirstToken = TimeSpan.FromMilliseconds(400),
CompletedAt = requestedAt.AddSeconds(5.2),
FinishReason = BitAiFinishReason.Stop,
};
```
\n**RTL**:
```razor
```
```csharp
private static readonly DateTimeOffset requestedAt = new(2026, 1, 1, 9, 0, 0, TimeSpan.Zero);
private readonly BitAiGenerationMetadata metadata = new()
{
ModelId = "demo-model-1",
Usage = new() { InputTokens = 1_240, OutputTokens = 386, Cost = 0.0095m, Currency = "USD" },
StartedAt = requestedAt,
TimeToFirstToken = TimeSpan.FromMilliseconds(400),
CompletedAt = requestedAt.AddSeconds(5.2),
FinishReason = BitAiFinishReason.Stop,
};
```
@message.Metadata!.ModelId
}
Total
}
```
```csharp
// AiDemoInteractiveTransports.Metered is a scripted transport of this site that reports usage, cost and context size,
// priced with the rates of the model of AiDemoCatalog:
// https://github.com/bitfoundation/bitplatform/blob/develop/src/BlazorUI/Demo/Client/Bit.BlazorUI.Demo.Client.Core/Services/AI/AiDemoInteractiveTransports.cs
private BitAiChatSession? meteredSession;
private bool isSending;
private async Task SendMetered()
{
isSending = true;
meteredSession ??= new BitAiChatSession(AiDemoInteractiveTransports.Metered(AiDemoCatalog.Models[0]));
await meteredSession.SubmitAsync("How much did that cost?");
isSending = false;
}
public async ValueTask DisposeAsync()
{
if (meteredSession is not null) await meteredSession.DisposeAsync();
}
```
\n**Disabled**:
```razor