Skip to content

AI

AiMessageList

Bit.BlazorUI.AITranscript

BitAiMessageList is the scrolling transcript of a BitAiConversation: every message of it, laid out by role through BitAiChatMessage, with auto-scroll that follows new content only while the reader is at the newest message - scrolling up to re-read is never yanked away - and, for long threads, only the newest messages drawn past a threshold, the older ones a page at a time as the reader scrolls up to them. It is the transcript BitAiChat renders, public for a chat assembled by hand.

Notes

To use this component, you need to install the Bit.BlazorUI.AI(opens in a new tab) nuget package, as described in the Optional steps of the Getting started page.

The list renders a Conversation and follows it as messages are added and removed, from whatever thread the stream is on, re-rendering at most once per BitAiUiOptions.RenderInterval. A Session (see BitAiChat) is what enables the actions of a message - edit, regenerate, retry, approval. It is the scroll container itself, so give it a box with a height to take: it grows to fill a flex column. Whether it follows new content at all is the AutoScroll of the BitAiUiOptions in force (on by default), and how many messages it draws at once their VirtualizeThreshold.

The list is a polite live region (role="log", aria-relevant="additions"), named by its AriaLabel or the Transcript string ("Conversation"), so a new message is announced as it arrives without interrupting the one being read. Past BitAiUiOptions.VirtualizeThreshold (60) messages the list draws only that many of the newest - as a prerender does, rather than an empty box - and the older ones half that many at a time as the reader scrolls up to them, keeping what is being read in place as each page comes in above it. Nothing is estimated: every message drawn has its own height, so the scrollbar never jumps. See the performance scenario.

Usage

Every example is live. Open its code to see exactly what produced the component running underneath.

Basic

Give it a Conversation: user turns are bubbles at the end of the line, replies full-width prose with their actions. Without a session, a reply offers copy and feedback only, and a user message no action at all.

What is BitAiMessageList for?

It renders the transcript of a conversation: every message, laid out by role, in a column that scrolls.

Does it follow a streaming reply?

Only while you are at the bottom. Scroll up to re-read and it leaves you where you are.

Session

A Session drives the conversation: with one, a user message can be edited and the newest reply regenerated. Put a BitAiPromptInput of the same session under it and it is a chat assembled by hand - send something and the list follows the reply.

What is BitAiMessageList for?

It renders the transcript of a conversation: every message, laid out by role, in a column that scrolls.

Does it follow a streaming reply?

Only while you are at the bottom. Scroll up to re-read and it leaves you where you are.

Templates

HeaderTemplate and FooterTemplate scroll with the messages, above the first and below the last. EmptyTemplate is shown while there is no message at all - a BitAiEmptyState with starter prompts, here.

Today
Nothing here yet
Ask a question to get started.
Replies are scripted on this page.

Message template

MessageTemplate replaces how every message is drawn - a compact, log-like transcript here. It is keyed by the message, so removing one never recreates the ones after it, and it adds no element of its own.

You What is BitAiMessageList for?
Assistant It renders the transcript of a conversation: every message, laid out by role, in a column that scrolls.
You Can I draw the messages myself?
Assistant Yes: a MessageTemplate replaces how every message is drawn, like this compact log.

System messages

The system and developer messages - the instructions the model is given, not a turn of the conversation - are hidden by default. Turn HideSystemMessages off to show them as a centered note.

What is a transcript?

The written record of a conversation, in the order it was said.

Following the latest

The list follows a streaming reply only while the reader is at the bottom. Scroll up and it stops following: OnPinnedChange reports it (and IsPinned holds it), which is what shows the BitAiScrollToLatest button; ScrollToLatestAsync takes the reader back and follows again. Send something, then scroll up while it answers.

Following is the AutoScroll of the BitAiUiOptions in force, set here through a BitAiOptionsProvider. Turn it off and the list never follows on its own: a reply grows out of view while the reader stays where they are, and OnPinnedChange reports false as soon as the newest message leaves the view.

Question 1: what happens at scale here?

Answer 1. The cost is dominated by rendering, not by parsing. Coalescing updates removes most of it, and virtualization removes the rest.

Demo Large180 tokensInput: 120 Output: 60
Question 2: what happens at scale here?

Here is the shape of it:

csharp
var budget = TimeSpan.FromMilliseconds(18);
scheduler.Request();

The interval is the only dial that matters.

Demo Large184 tokensInput: 122 Output: 62
Question 3: what happens at scale here?
Searched the documentation
scale 4
  • Render modes 94%
  • Circuit lifetime 81%

Two passages matched, and they say the same thing.

Demo Large188 tokensInput: 124 Output: 64
Question 4: what happens at scale here?
MessagesRenderedNotes
60allbelow the threshold
500visible onlyvirtualized
Sources
1
Performance notes
Internal wiki
Demo Large192 tokensInput: 126 Output: 66
Question 5: what happens at scale here?

Answer 5. The cost is dominated by rendering, not by parsing. Coalescing updates removes most of it, and virtualization removes the rest.

Demo Large196 tokensInput: 128 Output: 68
Question 6: what happens at scale here?

Here is the shape of it:

csharp
var budget = TimeSpan.FromMilliseconds(26);
scheduler.Request();

The interval is the only dial that matters.

Demo Large200 tokensInput: 130 Output: 70

Following the newest message: True

Virtualization

Four hundred messages of prose, code, tables, tool calls and citations: past BitAiUiOptions.VirtualizeThreshold (60 by default) only the newest that many are drawn. Scroll up and the older ones come in thirty at a time above what you are reading, which stays where it is; scroll back down to the newest message and the list lets them go again.

Question 171: what happens at scale here?
Searched the documentation
scale 340
  • Render modes 94%
  • Circuit lifetime 81%

Two passages matched, and they say the same thing.

Demo Large590 tokensInput: 460 Output: 130
Question 172: what happens at scale here?
MessagesRenderedNotes
60allbelow the threshold
500visible onlyvirtualized
Sources
1
Performance notes
Internal wiki
Demo Large594 tokensInput: 462 Output: 132
Question 173: what happens at scale here?

Answer 173. The cost is dominated by rendering, not by parsing. Coalescing updates removes most of it, and virtualization removes the rest.

Demo Large598 tokensInput: 464 Output: 134
Question 174: what happens at scale here?

Here is the shape of it:

csharp
var budget = TimeSpan.FromMilliseconds(42);
scheduler.Request();

The interval is the only dial that matters.

Demo Large602 tokensInput: 466 Output: 136
Question 175: what happens at scale here?
Searched the documentation
scale 348
  • Render modes 94%
  • Circuit lifetime 81%

Two passages matched, and they say the same thing.

Demo Large606 tokensInput: 468 Output: 138
Question 176: what happens at scale here?
MessagesRenderedNotes
60allbelow the threshold
500visible onlyvirtualized
Sources
1
Performance notes
Internal wiki
Demo Large610 tokensInput: 470 Output: 140
Question 177: what happens at scale here?

Answer 177. The cost is dominated by rendering, not by parsing. Coalescing updates removes most of it, and virtualization removes the rest.

Demo Large614 tokensInput: 472 Output: 142
Question 178: what happens at scale here?

Here is the shape of it:

csharp
var budget = TimeSpan.FromMilliseconds(50);
scheduler.Request();

The interval is the only dial that matters.

Demo Large618 tokensInput: 474 Output: 144
Question 179: what happens at scale here?
Searched the documentation
scale 356
  • Render modes 94%
  • Circuit lifetime 81%

Two passages matched, and they say the same thing.

Demo Large622 tokensInput: 476 Output: 146
Question 180: what happens at scale here?
MessagesRenderedNotes
60allbelow the threshold
500visible onlyvirtualized
Sources
1
Performance notes
Internal wiki
Demo Large626 tokensInput: 478 Output: 148
Question 181: what happens at scale here?

Answer 181. The cost is dominated by rendering, not by parsing. Coalescing updates removes most of it, and virtualization removes the rest.

Demo Large540 tokensInput: 480 Output: 60
Question 182: what happens at scale here?

Here is the shape of it:

csharp
var budget = TimeSpan.FromMilliseconds(18);
scheduler.Request();

The interval is the only dial that matters.

Demo Large544 tokensInput: 482 Output: 62
Question 183: what happens at scale here?
Searched the documentation
scale 364
  • Render modes 94%
  • Circuit lifetime 81%

Two passages matched, and they say the same thing.

Demo Large548 tokensInput: 484 Output: 64
Question 184: what happens at scale here?
MessagesRenderedNotes
60allbelow the threshold
500visible onlyvirtualized
Sources
1
Performance notes
Internal wiki
Demo Large552 tokensInput: 486 Output: 66
Question 185: what happens at scale here?

Answer 185. The cost is dominated by rendering, not by parsing. Coalescing updates removes most of it, and virtualization removes the rest.

Demo Large556 tokensInput: 488 Output: 68
Question 186: what happens at scale here?

Here is the shape of it:

csharp
var budget = TimeSpan.FromMilliseconds(26);
scheduler.Request();

The interval is the only dial that matters.

Demo Large560 tokensInput: 490 Output: 70
Question 187: what happens at scale here?
Searched the documentation
scale 372
  • Render modes 94%
  • Circuit lifetime 81%

Two passages matched, and they say the same thing.

Demo Large564 tokensInput: 492 Output: 72
Question 188: what happens at scale here?
MessagesRenderedNotes
60allbelow the threshold
500visible onlyvirtualized
Sources
1
Performance notes
Internal wiki
Demo Large568 tokensInput: 494 Output: 74
Question 189: what happens at scale here?

Answer 189. The cost is dominated by rendering, not by parsing. Coalescing updates removes most of it, and virtualization removes the rest.

Demo Large572 tokensInput: 496 Output: 76
Question 190: what happens at scale here?

Here is the shape of it:

csharp
var budget = TimeSpan.FromMilliseconds(34);
scheduler.Request();

The interval is the only dial that matters.

Demo Large576 tokensInput: 498 Output: 78
Question 191: what happens at scale here?
Searched the documentation
scale 380
  • Render modes 94%
  • Circuit lifetime 81%

Two passages matched, and they say the same thing.

Demo Large580 tokensInput: 500 Output: 80
Question 192: what happens at scale here?
MessagesRenderedNotes
60allbelow the threshold
500visible onlyvirtualized
Sources
1
Performance notes
Internal wiki
Demo Large584 tokensInput: 502 Output: 82
Question 193: what happens at scale here?

Answer 193. The cost is dominated by rendering, not by parsing. Coalescing updates removes most of it, and virtualization removes the rest.

Demo Large588 tokensInput: 504 Output: 84
Question 194: what happens at scale here?

Here is the shape of it:

csharp
var budget = TimeSpan.FromMilliseconds(42);
scheduler.Request();

The interval is the only dial that matters.

Demo Large592 tokensInput: 506 Output: 86
Question 195: what happens at scale here?
Searched the documentation
scale 388
  • Render modes 94%
  • Circuit lifetime 81%

Two passages matched, and they say the same thing.

Demo Large596 tokensInput: 508 Output: 88
Question 196: what happens at scale here?
MessagesRenderedNotes
60allbelow the threshold
500visible onlyvirtualized
Sources
1
Performance notes
Internal wiki
Demo Large600 tokensInput: 510 Output: 90
Question 197: what happens at scale here?

Answer 197. The cost is dominated by rendering, not by parsing. Coalescing updates removes most of it, and virtualization removes the rest.

Demo Large604 tokensInput: 512 Output: 92
Question 198: what happens at scale here?

Here is the shape of it:

csharp
var budget = TimeSpan.FromMilliseconds(50);
scheduler.Request();

The interval is the only dial that matters.

Demo Large608 tokensInput: 514 Output: 94
Question 199: what happens at scale here?
Searched the documentation
scale 396
  • Render modes 94%
  • Circuit lifetime 81%

Two passages matched, and they say the same thing.

Demo Large612 tokensInput: 516 Output: 96
Question 200: what happens at scale here?
MessagesRenderedNotes
60allbelow the threshold
500visible onlyvirtualized
Sources
1
Performance notes
Internal wiki
Demo Large616 tokensInput: 518 Output: 98

Citations & feedback

OnCitationClick reports a numbered citation chip, OnSourceClick a source card under the reply and OnFeedback a thumbs up or down, with its message - the message's Feedback is already updated, so a host only has to persist it.

Where does an Interactive Server component keep its state?

On the server, for as long as the circuit lives. When the circuit drops, the state goes with it.

Sources
1
Blazor render modes
Microsoft Learn
2
SignalR circuit lifetime
Microsoft Learn

Last event: -

Disabled

A disabled list stays readable and scrollable; the actions of its messages are disabled with it.

What is BitAiMessageList for?

It renders the transcript of a conversation: every message, laid out by role, in a column that scrolls.

Does it follow a streaming reply?

Only while you are at the bottom. Scroll up to re-read and it leaves you where you are.

Cascading parameters

BitParams with a BitAiMessageListParams sets whether the lists inside it show the system messages, and their classes and styles; a list's own value wins (the second keeps hiding them). A BitAiChat sets these on its transcript itself - from its own HideSystemMessages and Styles / Classes - so a chat takes them from a BitAiChatParams instead.

You are a concise assistant. Answer in one sentence.
What is a transcript?

The written record of a conversation, in the order it was said.


What is a transcript?

The written record of a conversation, in the order it was said.

Style & Class

Style / Class reach the root, which is the scroll container, and Styles / Classes its Content column. The public --bit-AiMessageList-* variables (see the CSS variables table) re-skin it from :root, an ancestor or Style.

What is BitAiMessageList for?

It renders the transcript of a conversation: every message, laid out by role, in a column that scrolls.

Does it follow a streaming reply?

Only while you are at the bottom. Scroll up to re-read and it leaves you where you are.


What is BitAiMessageList for?

It renders the transcript of a conversation: every message, laid out by role, in a column that scrolls.

Does it follow a streaming reply?

Only while you are at the bottom. Scroll up to re-read and it leaves you where you are.


What is BitAiMessageList for?

It renders the transcript of a conversation: every message, laid out by role, in a column that scrolls.

Does it follow a streaming reply?

Only while you are at the bottom. Scroll up to re-read and it leaves you where you are.

RTL

Use BitAiMessageList in right-to-left (RTL).

این فهرست پیام‌ها برای چیست؟

متن کامل یک گفتگو را نشان می‌دهد: هر پیام به ترتیب، با چیدمان نقش خودش.

CSS variables

The public custom properties this component reads off its root, for what no parameter covers.

Every variable is read with a fallback and never declared by the component, so it inherits like any other custom property: set one on :root (or in a [bit-theme] block) to restyle every instance, on any ancestor to restyle the ones inside it, or on the Style of one instance to restyle it - and any instance nested inside it, which inherits the value too; a parameter changes one alone, as does the real CSS property on the instance's Style / Styles when that property does not inherit (opacity: 1 keeps one disabled instance undimmed without undimming a disabled one inside it), while an inherited one such as color or font-family also reaches the nested instances that do not set it themselves. Leave a variable unset and the component falls back to the theme token beside it.

BitAiMessageList CSS variables

Name Default value Description
--bit-AiMessageList-max-width spacing(92) Width the column of messages stops growing at, centered in the list. A BitAiChat lines its composer up with it through --bit-AiChat-max-width: set both to the same value.
--bit-AiMessageList-gap spacing(4), spacing(3) on a phone Room between two messages.
--bit-AiMessageList-padding spacing(3) spacing(2) spacing(2), spacing(2) spacing(1.5) spacing(1.5) on a phone Inset of the column of messages.
--bit-AiMessageList-scrollbar-color var(--bit-clr-brd-sec) Thumb of the scrollbar of the list.

API

Every parameter, public member, sub-class and enum this component exposes.

BitAiMessageList parameters

Name Type Default value Description
Classes BitAiMessageListClassStyles? null Custom CSS classes for different parts of the message list.
Conversation BitAiConversation The conversation to render (required). The list subscribes to it for the messages added to and removed from it; passing another one starts that one at its newest message, without animating.
EmptyTemplate RenderFragment? null The content rendered instead of an empty list, while the conversation has no message to show - the system messages the list hides do not count.
FooterTemplate RenderFragment? null The content rendered below the last message, inside the scroll area.
HeaderTemplate RenderFragment? null The content rendered above the first message, inside the scroll area. Past BitAiUiOptions.VirtualizeThreshold messages it is drawn once the reader has scrolled up to the first message, along with it.
HideSystemMessages bool true Hides the messages the user did not write and the model did not produce (the system and developer ones). Turned off, they are shown as a centered note.
MessageTemplate RenderFragment<BitAiMessage>? null The template that replaces the built-in rendering of a message (a BitAiChatMessage). It is keyed by the message, so removing one message never recreates the ones after it, and it adds no element of its own around what it renders. The list draws it again as the newest message changes, so a template showing the text of a reply follows it as it streams.
OnCitationClick EventCallback<BitAiCitation> The callback for when the user activates a numbered citation in a reply.
OnFeedback EventCallback<BitAiMessageFeedback> The callback for when the user records feedback on a reply, so a host can persist it. The message's Feedback is already updated when it is raised.
OnPinnedChange EventCallback<bool> The callback for when the view stops or starts following the newest message: false when the user scrolls away from it (or, with BitAiUiOptions.AutoScroll off, when the newest message grows out of view), true when they scroll back to it or ScrollToLatestAsync brings them there. It is reported once per change, not on every scroll.
OnSourceClick EventCallback<BitAiCitationSource> The callback for when the user activates a source card in the list of sources under a reply.
Session BitAiChatSession? null The session driving the conversation, which enables the message-level actions: editing a user message, regenerating the newest reply, retrying a failed one, deciding a tool approval.
Styles BitAiMessageListClassStyles? null Custom CSS styles for different parts of the message list.

BitAiMessageList public members

Name Type Default value Description
IsPinned bool true Whether the reader is at the newest message, where the view follows new content (unless BitAiUiOptions.AutoScroll is off).
ScrollToLatestAsync Task Scrolls to the newest message and resumes following new content (raising OnPinnedChange with true, when the view was not following it). Its smooth argument (true by default) animates the scroll, unless the reader asked for reduced motion.

BitComponentBase parameters

Name Type Default value Description
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.

BitComponentBase 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.

BitAiMessageListClassStyles properties

Name Type Default value Description
Root string? null Custom CSS classes/styles for the root element (the scroll container) of the BitAiMessageList.
Content string? null Custom CSS classes/styles for the content column of the BitAiMessageList, which holds the messages.

BitAiConversation properties

An ordered set of messages plus the artifacts and agent runs that belong to the whole thread - the unit of state a chat UI binds to. Create one with new BitAiConversation(conversationId?) (an id is generated when none is given), fill it from history with AddMessage, and hand it to a session. It is observable at three grains: Changed for thread-level edits, MessageAdded / MessageRemoved for the list, and the events of each message for its content. Safe for one writer and concurrent readers.

Name Type Default value Description
ConversationId string The identifier of the conversation, given to its constructor or generated.
Title string? null The display title, set with SetTitle - or by a session with AutoTitle from the first user message.
Messages IReadOnlyList<BitAiMessage> [] The messages, oldest first.
LastMessage BitAiMessage? null The last message, or null when the conversation is empty.
IsGenerating bool false True while the last message is pending or streaming.
CreatedAt / UpdatedAt DateTimeOffset When the conversation was created, and when it last changed.
TotalUsage BitAiTokenUsage The token usage summed across every assistant message.
Artifacts / Runs IReadOnlyDictionary<string, BitAiArtifact> / IReadOnlyDictionary<string, BitAiAgentRun> The artifacts produced and the agent runs made in this conversation, keyed by their identifiers. They belong to the thread rather than to a message, so a revision in a later turn updates the one artifact.
Extensions Dictionary<string, object?> [] Application-defined values attached to the conversation.
Version int 0 A number that goes up on every change of the conversation, for a cheap check of whether anything changed since it was last read.
AddMessage(message) / AddMessage(role, text) BitAiMessage Appends a message - one built by hand, or one built from a role and its text - and returns it.
RemoveMessage(messageId) bool Removes a message; true when one was removed.
FindMessage(messageId) BitAiMessage? Finds a message by its identifier.
TruncateAfter(messageId, inclusive?) IReadOnlyList<BitAiMessage> Removes every message after the given one (and that one too when inclusive is true) and returns them, newest first - how retry and edit rewind a thread.
Clear() void Removes every message, with the artifacts and runs.
SetTitle(title) void Sets the title of the conversation.
GetOrAddArtifact(artifactId, kind, title) / FindArtifact(artifactId) BitAiArtifact / BitAiArtifact? Finds an artifact of the conversation, creating it with that kind and title when it does not exist yet - or only finds it, null when there is none.
GetOrAddRun(runId) / FindRun(runId) BitAiAgentRun / BitAiAgentRun? Finds an agent run of the conversation, creating it when it does not exist yet - or only finds it, null when there is none.
ToSummary() BitAiConversationSummary Projects the conversation into the shape a BitAiConversationList renders.
Changed event Action<BitAiConversation>? Raised after a thread-level change, such as the title or an artifact.
MessageAdded / MessageRemoved event Action<BitAiConversation, BitAiMessage>? Raised after a message is appended or inserted, and after one is removed - from whatever thread the stream is on.
MessageUpdated event Action<BitAiConversation, BitAiMessage>? Raised when any message reports a change, structural or in one of its parts: convenient for a coarse observer such as an auto-save, while a component subscribes to the message itself.

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.

Feedback

Found a mistake, a gap, or something that could be clearer? Every page and every component is one click from its source.