Skip to content
# AiChat **Also known as:** Chatbot, Assistant, Copilot ## Description BitAiChat is the whole chat surface in one component: the streaming transcript, the composer with attachments, slash commands and mentions, the empty state with its starter prompts, and the scroll-to-latest button, wired to a BitAiChatSession. Give it a session, or let it create one from the transport registered with AddBitBlazorUIAIServices - a working AI chat in one line of markup. Every part it composes is a public component of its own, so anything it does can also be assembled by hand. ## Parameters | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | AllowAttachments | `bool` | true | Offers attachments in the composer: files picked with its button, pasted or dropped onto it, up to MaxAttachmentSize each. | | AllowDictation | `bool` | false | Offers dictation in the composer: a microphone button that transcribes speech into the text, shown only where the browser can recognize speech. | | Classes | `BitAiChatClassStyles?` | null | Custom CSS classes for different parts of the chat. | | Commands | `IReadOnlyList<BitAiSlashCommand>?` | null | The commands offered when the user types a forward slash in the composer. A command either inserts its Template into the text or runs its Handler. | | ComposerHintTemplate | `RenderFragment?` | null | A hint rendered under the composer, such as the keys that send. | | ComposerPrefixTemplate | `RenderFragment?` | null | The content placed before the text of the composer, such as a model picker. | | ComposerSuffixTemplate | `RenderFragment?` | null | The content placed beside the send button of the composer. | | ComposerTemplate | `RenderFragment?` | null | The content that replaces the whole composer. Whatever it holds submits through the session (ActiveSession) itself. | | Conversation | `BitAiConversation?` | null | The conversation to show, such as one restored from history. Used only when no Session is supplied - a supplied session owns its own conversation. Passing another one switches the chat to it: the session the chat created for the old one is replaced. | | EmptyBody | `string?` | null | The supporting line of the empty state. Defaults to the EmptyBody of the string table ("Ask a question to get started."). | | EmptyTemplate | `RenderFragment?` | null | The content that replaces the empty state (and with it EmptyTitle, EmptyBody and Suggestions) while the conversation has no message to show - the system messages it hides do not count. | | EmptyTitle | `string?` | null | The heading of the empty state. Defaults to the EmptyTitle of the string table ("How can I help?"). | | FooterTemplate | `RenderFragment?` | null | The content below the composer, outside the scroll area, such as a disclaimer. | | HeaderTemplate | `RenderFragment?` | null | The content above the transcript, outside the scroll area, such as a title bar. | | HideSystemMessages | `bool` | true | Hides the system (and developer) messages from the transcript. Turned off, they are shown as a centered note. | | ListFooterTemplate | `RenderFragment?` | null | The content at the bottom of the scroll area, below the last message. | | ListHeaderTemplate | `RenderFragment?` | null | The content at the top of the scroll area, above the first message. Past BitAiUiOptions.VirtualizeThreshold messages it is drawn once the reader has scrolled up to the first message, along with it. | | MaxAttachmentSize | `long` | 10485760 | The largest file the composer will stage, in bytes (10 MB by default). A larger one is turned away, the user is told, and OnAttachmentRejected is raised. | | Mentions | `IReadOnlyList<BitAiMentionItem>?` | null | The items offered when the user types an at sign in the composer: files, people or data sources the application resolves by their Id later. A chosen one is inserted as @Label. | | MessageTemplate | `RenderFragment<BitAiMessage>?` | null | The template that replaces how a message is rendered. It is keyed by the message and adds no element of its own; render a BitAiChatMessage inside it to keep the built-in streaming, parts and actions. | | OnAttachmentRejected | `EventCallback<BitAiAttachmentRejection>` | | The callback for each file the composer turns away (too large, of a type it does not take, one too many). The user is told either way; this is the same information for the application. | | 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 (thumbs up or down, or withdrawn), so a host can persist it. The message's Feedback is already updated when it is raised. | | OnSourceClick | `EventCallback<BitAiCitationSource>` | | The callback for when the user activates a source card in the list of sources under a reply. | | OnSubmit | `EventCallback<BitAiPromptSubmission>` | | The callback for when the user submits (the composer or a suggestion). Handle it to submit yourself - after uploading the attachments, or adding context to the text - through the session; leave it unset and the chat submits to the session. | | Placeholder | `string?` | null | The placeholder of the composer, which is also its accessible name. Defaults to the PromptPlaceholder of the string table ("Send a message..."). | | Session | `BitAiChatSession?` | null | The session to drive. When unset, the chat creates one - through the registered BitAiChatSessionFactory, or over the registered IBitAiTransport - and disposes it with itself; a supplied session belongs to the page, which disposes it. | | SessionOptions | `BitAiChatSessionOptions?` | null | The settings of a session this component creates. Ignored when a Session is supplied, which carries its own. Left null, the defaults configured with AddBitBlazorUIAIServices (ai.ConfigureSessions) apply. Options that name no Strings take the ones of the UI options in force, and SmoothStreaming off in those options drops their Pacing. | | Styles | `BitAiChatClassStyles?` | null | Custom CSS styles for different parts of the chat. | | Suggestions | `IReadOnlyList<string>?` | null | The example prompts offered in the empty state. A tap submits one as it is. | | UiOptions | `BitAiUiOptions?` | null | The presentation settings of everything inside this chat (the string table, the render interval, what a message shows), in place of the ones a BitAiOptionsProvider cascades or AddBitBlazorUIAIServices registers - a whole record, not merged with them. A session the chat creates follows them too: its errors are worded in their Strings, and with SmoothStreaming off its text is not paced. | | 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 | | :--- | :--- | :------------ | :---------- | | ActiveSession | `BitAiChatSession` | | The session in use, whether supplied through Session or created by the chat. Reading it before the first render creates the chat's own session. | | ActiveConversation | `BitAiConversation` | | The conversation in use, the one of ActiveSession. | | FocusComposerAsync | `ValueTask` | | Puts the caret in the composer. Does nothing when a ComposerTemplate replaces it. | | 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 ### BitAiChatClassStyles Properties | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Root | `string?` | null | Custom CSS classes/styles for the root element of the BitAiChat. | | MessageList | `BitAiMessageListClassStyles?` | null | Custom CSS classes/styles for the BitAiMessageList (the transcript) of the BitAiChat. | | EmptyState | `BitAiEmptyStateClassStyles?` | null | Custom CSS classes/styles for the BitAiEmptyState of the BitAiChat. | | Footer | `string?` | null | Custom CSS classes/styles for the footer of the BitAiChat, which holds the composer. | | FooterColumn | `string?` | null | Custom CSS classes/styles for the column inside the footer of the BitAiChat, which lines the composer up with the messages above it. | | ScrollToLatest | `BitAiScrollToLatestClassStyles?` | null | Custom CSS classes/styles for the BitAiScrollToLatest of the BitAiChat. | ### BitAiChatSession Properties Drives one conversation: submission, streaming, tool execution, approval, cancellation, retry and regeneration. Create one with the injected BitAiChatSessionFactory (Create(conversation, options)), which brings the registered transport, tools and approval policy, or with new BitAiChatSession(transport, conversation, options, ...). Submitting while a turn is in flight is a no-op. Dispose it (DisposeAsync) when the page goes: that cancels the turn in flight and detaches every handler. | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | Conversation | `BitAiConversation` | | The conversation this session drives, given to its constructor or created with it. | | Options | `BitAiChatSessionOptions` | new() | The settings in force, given to its constructor. It is settable - assign a changed copy (session.Options with { ... }) - and each turn reads the ones in force as it starts. | | Processor | `BitAiConversationStreamProcessor` | | The reducer that applies the events of the transport to the conversation: it turns them into parts, tool calls, artifacts and agent steps. Its EventApplied event is raised for every event once it is applied, which suits logging and tracing. | | Strings | `BitAiStrings` | BitAiStrings.Default | The text of the errors the session writes itself: the Strings of its Options, else BitAiStrings.Default. | | IsGenerating | `bool` | false | True while a turn is in flight. | | LastError | `BitAiError?` | null | The failure of the last turn, cleared when a new turn starts. | | SubmitAsync(text, attachments?, cancellationToken?) | `Task<BitAiMessage?>` | | Appends a user message (the text may be empty when attachments carry the request) and generates a reply. Returns the assistant message, or null when a turn was already running. | | RegenerateAsync(messageId?, cancellationToken?) | `Task<BitAiMessage?>` | | Regenerates an assistant message - the most recent one when no id is given - discarding it and everything after it. Returns the new assistant message, or null when there was no assistant message to regenerate or a turn was already running. | | RetryAsync(cancellationToken?) | `Task<BitAiMessage?>` | | Retries the last turn after a failure: the failed assistant message is removed and the user message before it is answered again. Returns null when the conversation is empty or a turn was already running. | | EditAsync(messageId, text, cancellationToken?) | `Task<BitAiMessage?>` | | Replaces the text of a user message and regenerates everything after it. Returns null when no user message of that id was found or a turn was already running. | | Stop() | `void` | | Stops the current turn. The partial answer is kept and marked cancelled. Safe to call from any thread, and a no-op with no turn in flight. | | Approve(toolCallId, decision) | `bool` | | Records a human decision (a BitAiApprovalDecision) on a tool call that is waiting for one. Returns false, changing nothing, when no call of that id is waiting - it has run, been decided or been cancelled, or is not known. | | RespondToAgentAsync(runId, answer, cancellationToken?) | `Task<BitAiMessage?>` | | Answers a question an agent run is blocked on (appended as a user message), then continues the turn. Returns null when that run was not waiting for input or a turn was already running. | | StateChanged | `event Action?` | | Raised whenever anything the UI cares about changes. Coarse by design and raised from whatever thread the stream is on: marshal it with InvokeAsync, and coalesce the renders it asks for. | | TurnCompleted | `event Action<BitAiMessage>?` | | Raised when a turn ends, successfully or not, with its assistant message. | | ApprovalRequested | `event Action<BitAiToolCallPart>?` | | Raised when a tool call needs a human decision. | | DisposeAsync() | `ValueTask` | | Ends the session: cancels the turn in flight and any decision it waits for, and detaches every handler, so no event is raised after it. A session the page created is the page's to dispose; one a BitAiChat created, the chat disposes. | ### BitAiChatSessionOptions Properties The settings of a BitAiChatSession, an immutable record. Every one has a defensible default: tools execute automatically, destructive ones ask first, and a runaway tool loop is bounded. AddBitBlazorUIAIServices sets the defaults of the sessions its factory creates (ai.ConfigureSessions). | Name | Type | Default Value | Description | | :--- | :--- | :------------ | :---------- | | AutoTitle | `bool` | true | Derives the title of the conversation from the first user message when it has none. | | CatchTransportExceptions | `bool` | true | Whether a failed turn only leaves a retryable error on the assistant message, rather than throwing. Either way the message ends failed with the error on it; turned off, the exception also reaches the caller of the turn (SubmitAsync and the rest). | | ExecuteTools | `bool` | true | Executes the tool calls of the model in the session. Turn it off when the transport runs the tools on the server and only reports them as events. | | MaxToolRoundtrips | `int` | 10 | How many times the session feeds tool results back and asks the model again in one turn - the bound that keeps a model calling tools in a loop from spending without end. | | Pacing | `BitAiTextPacingOptions?` | BitAiTextPacingOptions.Default | Evens out bursty text output so it renders at a steady rate. Null renders the deltas exactly as the transport produces them; contradicting settings throw when the options are built. | | RequestOptions | `BitAiRequestOptions?` | null | The generation settings (the model, the temperature, the token limit, ...) passed to the transport on every request. | | Strings | `BitAiStrings?` | null | The text of the errors the session writes itself: a stopped or timed-out turn, a declined tool call, the tool loop limit. Null uses the strings of the UI options in force where the session is created by the factory or a BitAiChat, else BitAiStrings.Default. | | Timeout | `TimeSpan?` | null | How long to wait for the whole turn, tool round trips included. Null sets no limit beyond what the transport enforces. | | ToolResultHoldTime | `TimeSpan` | 10 seconds | How long a tool result the transport reports ahead of the call it answers is held for that call to start. A call that starts in time is given the result and is not run again; when none has, the result is dropped, leaving an error part (code orphaned_tool_result, carrying a BitAiOrphanedToolResultException) on the message, and the session logs it. Zero drops such a result at once; a negative value throws when the options are built. | ## 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-AiChat-background` | var(--bit-clr-bg-pri) | Background of the chat, which the footer fades the last lines of the transcript into. | | `--bit-AiChat-max-width` | spacing(92) | Width the composer stops growing at. Set --bit-AiMessageList-max-width to the same value to keep it lined up with the column of messages. | | `--bit-AiChat-footer-padding` | 0 spacing(2) spacing(2), 0 spacing(1.5) spacing(1.5) on a phone | Inset of the footer around the composer. | ## Examples \n**Basic**: ```razor
``` \n**Session**: ```razor @implements IAsyncDisposable Ask from code Focus the composer
``` ```csharp private BitAiChat? sessionChat; // Any IBitAiTransport: this one streams a scripted Markdown answer. Inject BitAiChatSessionFactory and call // Create() instead for a session over the registered transport, tools and approval policy. private readonly BitAiChatSession markdownSession = new(AiDemoTransports.Markdown()); private async Task AskFromCode() { await markdownSession.SubmitAsync("Where does Blazor run my code?"); } private async Task FocusComposer() { if (sessionChat is not null) await sessionChat.FocusComposerAsync(); } // A session you create is yours to dispose: it cancels the turn in flight and detaches every handler. public ValueTask DisposeAsync() => markdownSession.DisposeAsync(); ``` \n**Conversation**: ```razor
``` ```csharp private bool hideSystemMessages = true; private readonly BitAiConversation history = CreateHistory(); private readonly BitAiChatSessionOptions historyOptions = new() { AutoTitle = false, Timeout = TimeSpan.FromSeconds(30) }; private static BitAiConversation CreateHistory() { var conversation = new BitAiConversation(); conversation.AddMessage(BitAiRole.System, "You are the assistant of the bit BlazorUI docs."); conversation.AddMessage(BitAiRole.User, "Which package are the AI components in?"); conversation.AddMessage(BitAiRole.Assistant, "In **Bit.BlazorUI.AI**, which builds on the core Bit.BlazorUI package."); return conversation; } ``` \n**Empty state**: ```razor
``` ```csharp private readonly string[] suggestions = [ "What can these components do?", "Show me streaming", "Explain render modes", ]; ``` \n**Templates**: ```razor
Support assistant New chat
What can we help you with? How do refunds work?
Chats with the assistant are not saved.
Replies are scripted on this page.
The assistant can make mistakes. Check important information.
``` ```csharp private BitAiChat? templatesChat; private void NewTemplatesChat() { if (templatesChat is null) return; templatesChat.ActiveSession.Stop(); templatesChat.ActiveConversation.Clear(); } private async Task AskAboutRefunds() { if (templatesChat is null) return; await templatesChat.ActiveSession.SubmitAsync("How do refunds work?"); } ``` \n**Composer**: ```razor
Picked: @modelId. Enter sends, Shift+Enter starts a new line.
``` ```csharp private BitAiChat? composerChat; // The models come from the catalog registered with ai.AddModels(...). A session of your own takes the picked one // through its options: new BitAiChatSessionOptions { RequestOptions = new() { ModelId = modelId } }. private string? modelId = "demo-model-1"; private void ClearComposerChat() { if (composerChat is null) return; composerChat.ActiveSession.Stop(); composerChat.ActiveConversation.Clear(); } ``` \n**Custom composer**: ```razor @implements IAsyncDisposable
@foreach (var reply in guidedReplies) { @reply }
``` ```csharp private readonly BitAiChatSession guidedSession = new(AiDemoTransports.Echo()); private readonly string[] guidedReplies = ["Track my order", "Change my address", "Talk to a person"]; public ValueTask DisposeAsync() => guidedSession.DisposeAsync(); ``` \n**Attachments & commands**: ```razor @implements IAsyncDisposable
Last file turned away: @(rejectedFile ?? "-")
``` ```csharp private bool allowAttachments = true; private bool allowDictation; private bool offerCommands = true; private string? rejectedFile; // A transport that names the slash command and the @-mentions it was sent. private readonly BitAiChatSession commandsSession = new(AiDemoInteractiveTransports.Commands()); private IReadOnlyList commands = []; private readonly string[] commandSuggestions = ["Compare @roadmap.md with @pricing.xlsx", "Ask @priya about the launch date"]; private readonly IReadOnlyList mentions = [ new("file-roadmap", "roadmap.md", "Product roadmap, updated Monday", BitIconName.Page), new("file-pricing", "pricing.xlsx", "Pricing model, three tiers", BitIconName.Page), new("user-priya", "priya", "Priya Raman, product lead", BitIconName.Contact), new("source-analytics", "analytics", "Usage analytics, last 30 days", BitIconName.Database), ]; protected override void OnInitialized() { commands = [ // A command with a Template inserts it into the composer... new("summarize", "Summarize the conversation so far", BitIconName.BulletedList) { Template = "Summarize the conversation so far in three bullet points.", }, // ...and one with a Handler runs it instead. new("clear", "Start over with an empty conversation", BitIconName.Delete) { Handler = () => { commandsSession.Stop(); commandsSession.Conversation.Clear(); return Task.CompletedTask; }, }, ]; } private void HandleAttachmentRejected(BitAiAttachmentRejection rejection) { rejectedFile = $"{rejection.FileName} ({rejection.Reason})"; } public ValueTask DisposeAsync() => commandsSession.DisposeAsync(); ``` \n**Submitting yourself**: ```razor @implements IAsyncDisposable
Last submission: @(lastSubmission ?? "-")
``` ```csharp private string? lastSubmission; private readonly BitAiChatSession submitSession = new(AiDemoTransports.Echo()); private async Task HandleSubmit(BitAiPromptSubmission submission) { lastSubmission = $"\"{submission.Text}\" with {submission.Attachments.Count} attachment(s)"; // Upload the attachments to your own storage here, or add what the model should know - this adds the page. await submitSession.SubmitAsync($"[asked on the AiChat page] {submission.Text}", submission.Attachments); } public ValueTask DisposeAsync() => submitSession.DisposeAsync(); ``` \n**Message template**: ```razor @implements IAsyncDisposable
``` ```csharp private readonly BitAiChatSession templateSession = new(AiDemoTransports.Echo()); private readonly string[] suggestions = [ "What can these components do?", "Show me streaming", "Explain render modes", ]; public ValueTask DisposeAsync() => templateSession.DisposeAsync(); ``` \n**Citations & feedback**: ```razor @implements IAsyncDisposable
Last event: @(lastEvent ?? "-")
``` ```csharp private string? lastEvent; // A transport whose answer cites two sources, anchored to the sentences they support. private readonly BitAiChatSession citedSession = new(AiDemoContentTransports.Cited()); private readonly string[] citedSuggestions = ["What happens to my state when the circuit drops?"]; 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}"; } public ValueTask DisposeAsync() => citedSession.DisposeAsync(); ``` \n**Options**: ```razor @implements IAsyncDisposable
``` ```csharp // A transport that streams its reasoning before the answer. private readonly BitAiChatSession reasoningSession = new(AiDemoTransports.Reasoning()); private readonly BitAiUiOptions uiOptions = new() { CollapseReasoning = false, ShowTimestamps = true, ShowUsage = true }; private readonly string[] reasoningSuggestions = ["How long does a round trip to the model take?"]; public ValueTask DisposeAsync() => reasoningSession.DisposeAsync(); ``` \n**Disabled**: ```razor
``` ```csharp private bool isDisabled = true; private readonly BitAiConversation disabledHistory = CreateHistory(); private static BitAiConversation CreateHistory() { var conversation = new BitAiConversation(); conversation.AddMessage(BitAiRole.System, "You are the assistant of the bit BlazorUI docs."); conversation.AddMessage(BitAiRole.User, "Which package are the AI components in?"); conversation.AddMessage(BitAiRole.Assistant, "In **Bit.BlazorUI.AI**, which builds on the core Bit.BlazorUI package."); return conversation; } ``` \n**Cascading parameters**: ```razor
``` ```csharp private readonly BitAiChatParams[] chatParams = [ new() { EmptyTitle = "Ask the docs", EmptyBody = "Both chats take this heading and line from the BitParams around them.", Placeholder = "Ask about a component...", AllowAttachments = false, } ]; ``` \n**Style & Class**: ```razor
``` \n**RTL**: ```razor
``` ```csharp private readonly string[] rtlSuggestions = ["این کامپوننت‌ها چه کارهایی می‌کنند؟", "استریم را نشانم بده"]; ```