Skip to content
# AiMessageList **Also known as:** Transcript ## Description 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. ## 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. | | 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 | | :--- | :--- | :------------ | :---------- | | 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. | | 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 ### 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 ### 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. | ## 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-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. | ## Examples \n**Basic**: ```razor
``` ```csharp private readonly BitAiConversation conversation = CreateConversation(); private static BitAiConversation CreateConversation() { var conversation = new BitAiConversation(); conversation.AddMessage(BitAiRole.User, "What is BitAiMessageList for?"); conversation.AddMessage(BitAiRole.Assistant, "It renders the **transcript** of a conversation: every message, laid out by role, in a column that scrolls."); conversation.AddMessage(BitAiRole.User, "Does it follow a streaming reply?"); conversation.AddMessage(BitAiRole.Assistant, "Only while you are at the bottom. Scroll up to re-read and it leaves you where you are."); return conversation; } ``` \n**Session**: ```razor @implements IAsyncDisposable
``` ```csharp // A transport that echoes the prompt, over a conversation that already has some history. private readonly BitAiChatSession session = new(AiDemoTransports.Echo(), CreateConversation()); private static BitAiConversation CreateConversation() { var conversation = new BitAiConversation(); conversation.AddMessage(BitAiRole.User, "What is BitAiMessageList for?"); conversation.AddMessage(BitAiRole.Assistant, "It renders the **transcript** of a conversation: every message, laid out by role, in a column that scrolls."); conversation.AddMessage(BitAiRole.User, "Does it follow a streaming reply?"); conversation.AddMessage(BitAiRole.Assistant, "Only while you are at the bottom. Scroll up to re-read and it leaves you where you are."); return conversation; } public ValueTask DisposeAsync() => session.DisposeAsync(); ``` \n**Templates**: ```razor @implements IAsyncDisposable
Today
Replies are scripted on this page.
``` ```csharp private readonly BitAiChatSession templatesSession = new(AiDemoTransports.Echo()); private readonly string[] suggestions = ["What can these components do?", "Show me streaming"]; private async Task AskTemplatesSession(string suggestion) { await templatesSession.SubmitAsync(suggestion); } public ValueTask DisposeAsync() => templatesSession.DisposeAsync(); ``` \n**Message template**: ```razor
@(message.Role.IsUser ? "You" : "Assistant") @message.GetText()
``` ```csharp // The template prints the text of a message as it is, so its conversation is written without Markdown. private readonly BitAiConversation templateConversation = CreateTemplateConversation(); private static BitAiConversation CreateTemplateConversation() { var conversation = new BitAiConversation(); conversation.AddMessage(BitAiRole.User, "What is BitAiMessageList for?"); conversation.AddMessage(BitAiRole.Assistant, "It renders the transcript of a conversation: every message, laid out by role, in a column that scrolls."); conversation.AddMessage(BitAiRole.User, "Can I draw the messages myself?"); conversation.AddMessage(BitAiRole.Assistant, "Yes: a MessageTemplate replaces how every message is drawn, like this compact log."); return conversation; } ``` \n**System messages**: ```razor
``` ```csharp private bool hideSystemMessages = true; private readonly BitAiConversation systemConversation = CreateSystemConversation(); private static BitAiConversation CreateSystemConversation() { var conversation = new BitAiConversation(); conversation.AddMessage(BitAiRole.System, "You are a concise assistant. Answer in one sentence."); conversation.AddMessage(BitAiRole.User, "What is a transcript?"); conversation.AddMessage(BitAiRole.Assistant, "The written record of a conversation, in the order it was said."); return conversation; } ``` \n**Following the latest**: ```razor @implements IAsyncDisposable
Following the newest message: @isPinned
``` ```csharp private BitAiMessageList? followList; private bool isPinned = true; // Null hands the registered options down unchanged, which follow new content (AutoScroll is on by default). private bool autoScroll = true; private readonly BitAiUiOptions manualScrollOptions = new() { AutoScroll = false }; // A transport that streams a long Markdown answer, over a conversation that already has some history. private readonly BitAiChatSession followSession = new(AiDemoTransports.Markdown(), AiDemoConversations.Long(12)); private void HandlePinnedChange(bool pinned) { isPinned = pinned; } private async Task ScrollToLatest() { if (followList is not null) await followList.ScrollToLatestAsync(); } public ValueTask DisposeAsync() => followSession.DisposeAsync(); ``` \n**Virtualization**: ```razor
``` ```csharp // Four hundred messages of prose, code, tables, tool calls and citations. private readonly BitAiConversation longConversation = AiDemoConversations.Long(400); ``` \n**Citations & feedback**: ```razor
Last event: @(lastEvent ?? "-")
``` ```csharp private string? lastEvent; private readonly BitAiConversation citedConversation = CreateCitedConversation(); private static BitAiConversation CreateCitedConversation() { var conversation = new BitAiConversation(); conversation.AddMessage(BitAiRole.User, "Where does an Interactive Server component keep its state?"); const string first = "On the server, for as long as the circuit lives."; const string second = " When the circuit drops, the state goes with it."; var reply = new BitAiMessage(BitAiRole.Assistant); reply.AddPart(new BitAiTextPart(first + second)); // Each citation is anchored to the span of the text it supports: its numbered chip goes at the end of it. reply.AddCitation(new BitAiCitation("c1", new BitAiCitationSource("s1", "Blazor render modes") { Kind = BitAiSourceKind.Web, SiteName = "Microsoft Learn", Url = "https://learn.microsoft.com/aspnet/core/blazor/components/render-modes", }) { StartIndex = 0, EndIndex = first.Length, }); reply.AddCitation(new BitAiCitation("c2", new BitAiCitationSource("s2", "SignalR circuit lifetime") { Kind = BitAiSourceKind.Document, SiteName = "Microsoft Learn", Url = "https://learn.microsoft.com/aspnet/core/blazor/fundamentals/signalr", }) { StartIndex = first.Length, EndIndex = first.Length + second.Length, }); conversation.AddMessage(reply); return conversation; } private void HandleCitationClick(BitAiCitation citation) { lastEvent = $"Citation [{citation.Number}] of \"{citation.Source.Title}\""; } private void HandleSourceClick(BitAiCitationSource source) { lastEvent = $"Source \"{source.Title}\" ({source.Url})"; } private void HandleFeedback(BitAiMessageFeedback feedback) { lastEvent = $"Feedback {feedback.Feedback} on message {feedback.Message.MessageId}"; } ``` \n**Disabled**: ```razor @implements IAsyncDisposable
``` ```csharp private bool isDisabled = true; private readonly BitAiChatSession disabledSession = new(AiDemoTransports.Echo(), CreateConversation()); private static BitAiConversation CreateConversation() { var conversation = new BitAiConversation(); conversation.AddMessage(BitAiRole.User, "What is BitAiMessageList for?"); conversation.AddMessage(BitAiRole.Assistant, "It renders the **transcript** of a conversation: every message, laid out by role, in a column that scrolls."); conversation.AddMessage(BitAiRole.User, "Does it follow a streaming reply?"); conversation.AddMessage(BitAiRole.Assistant, "Only while you are at the bottom. Scroll up to re-read and it leaves you where you are."); return conversation; } public ValueTask DisposeAsync() => disabledSession.DisposeAsync(); ``` \n**Cascading parameters**: ```razor
``` ```csharp private readonly BitAiMessageListParams[] listParams = [ new() { HideSystemMessages = false, Styles = new() { Root = "border:1px dashed var(--bit-clr-brd-pri);border-radius:0.5rem" }, } ]; private readonly BitAiConversation cascadeConversation = CreateSystemConversation(); private readonly BitAiConversation cascadeOwnConversation = CreateSystemConversation(); private static BitAiConversation CreateSystemConversation() { var conversation = new BitAiConversation(); conversation.AddMessage(BitAiRole.System, "You are a concise assistant. Answer in one sentence."); conversation.AddMessage(BitAiRole.User, "What is a transcript?"); conversation.AddMessage(BitAiRole.Assistant, "The written record of a conversation, in the order it was said."); return conversation; } ``` \n**Style & Class**: ```razor
``` ```csharp private readonly BitAiConversation styleConversation = CreateConversation(); private readonly BitAiConversation classConversation = CreateConversation(); private readonly BitAiConversation variablesConversation = CreateConversation(); private static BitAiConversation CreateConversation() { var conversation = new BitAiConversation(); conversation.AddMessage(BitAiRole.User, "What is BitAiMessageList for?"); conversation.AddMessage(BitAiRole.Assistant, "It renders the **transcript** of a conversation: every message, laid out by role, in a column that scrolls."); conversation.AddMessage(BitAiRole.User, "Does it follow a streaming reply?"); conversation.AddMessage(BitAiRole.Assistant, "Only while you are at the bottom. Scroll up to re-read and it leaves you where you are."); return conversation; } ``` \n**RTL**: ```razor
``` ```csharp private readonly BitAiConversation rtlConversation = CreateRtlConversation(); private static BitAiConversation CreateRtlConversation() { var conversation = new BitAiConversation(); conversation.AddMessage(BitAiRole.User, "این فهرست پیام‌ها برای چیست؟"); conversation.AddMessage(BitAiRole.Assistant, "متن کامل یک گفتگو را نشان می‌دهد: هر پیام به ترتیب، با چیدمان نقش خودش."); return conversation; } ```