MAUI Chat UI
Shiny.AiConversation.Maui ships AiChatView — a complete chat screen for your AI conversation.
It derives from the Shiny.Maui.Controls ChatView, so every style, template and
behavior property of the base control still applies, but the provider, session, history paging and
live AI events are already wired up.
There is no view model plumbing: the control resolves IAiConversationService from the app’s service
provider.
Installation
Section titled “Installation”dotnet add package Shiny.AiConversation.MauiThe package brings Shiny.Maui.Controls with it. Register the conversation service as usual in
MauiProgram.cs (see Getting Started) and make sure .UseShinyControls() is
called on the MauiAppBuilder.
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:ai="http://shiny.net/maui/aiconversation" x:Class="MyApp.ChatPage" Title="Chat">
<ai:AiChatView BotName="Aura" BotAvatar="bot.png" GreetingMessage="Hi! What can I help you with?" ShowMicrophoneAction="True" ShowTokenUsage="True"
MyBubbleColor="{StaticResource Primary}" MyTextColor="White" OtherBubbleColor="#F0EEFF" OtherTextColor="#1A1A2E" BubbleCornerRadius="16" PlaceholderText="Ask me something..." SendButtonText="Send" /></ContentPage>What it wires for you
Section titled “What it wires for you”- Sending — typed messages are forwarded to
IAiConversationService.TalkTo; replies arrive onAiRespondedand render as AI bubbles. - Voice — utterances heard by speech-to-text render as user bubbles via
SpeechOccurred. That covers wake-word conversations happening elsewhere in the app, so the transcript stays complete even when the user never types.ShowMicrophoneActionadds a push-to-talk action to the input bar that callsListenAndTalk. - History — the chat is backfilled from the registered
IMessageStoreand pages further back when the user scrolls to the top. With no message store registered, the chat simply starts empty and stays live-only — nothing throws. - Typing indicator — driven by
AiState(Thinking/Responding), with a heartbeat so a long turn keeps the bubble alive. - Errors — failures from
TalkToand anything raised onErrorOccurredrender as an AI bubble carryingIdentifier = "error", so aMessageTemplateSelectorcan style them. - Multiple choice — a turn that carries choices renders tappable buttons under the bubble; the tapped label is sent as the user’s answer.
Properties
Section titled “Properties”| Property | Default | Description |
|---|---|---|
AiService | resolved from DI | The IAiConversationService to drive. Leave unset to resolve it from the app’s service provider |
BotName | Assistant | Display name of the AI (also the chat session name) |
BotAvatar | null | ImageSource for the AI |
BotBubbleColor | null | Per-user bubble color for the AI; falls back to OtherBubbleColor |
UserName | Me | Display name of the device user |
UserAvatar | null | ImageSource for the device user |
UserBubbleColor | null | Per-user bubble color for the device user; falls back to MyBubbleColor |
LoadHistory | true | Backfill and page history from the message store |
GreetingMessage | null | AI message shown when there is no history to display |
ShowTokenUsage | false | Appends a token usage footer to AI messages when the provider reports usage |
ShowMicrophoneAction | false | Adds a push-to-talk action to the input bar; invoking it again cancels the listen |
MicrophoneActionText | 🎤 Voice Input | Label of that action |
ShowChoiceButtons | true | Renders a button per AiChoice under AI bubbles that ask a multiple-choice question |
ChoiceSendText | Send | Label of the commit button shown for questions that allow more than one choice |
Settings | — | The live AiChatSettings behind the properties above |
Refresh() | — | Method — tears down and reloads the conversation (use after ClearChatHistory) |
Styling
Section titled “Styling”Styling is entirely inherited from ChatView — MyBubbleColor, MyTextColor, OtherBubbleColor,
OtherTextColor, ChatBackgroundColor, BubbleFontSize, BubbleFontFamily, BubbleCornerRadius,
TimestampFontSize, PlaceholderText, SendButtonText, SendButtonBackgroundColor,
SendButtonTextColor, InputBarBackgroundColor, InputBarBorderColor, IsInputBarVisible,
ShowTypingIndicator, MessageTemplate, MessageTemplateSelector, InputActions,
CustomBubbleActions, PageSize, UseFeedback and AdjustForKeyboard all behave exactly as
documented for ChatView. Leave the colors unset to follow the active
Shiny theme.
Driving a plain ChatView
Section titled “Driving a plain ChatView”If you would rather lay out the chat yourself, register the bridge and bind Provider + SessionId
on a regular ChatView:
builder.Services.AddShinyAiConversation(opts =>{ opts.AddGithubCopilotChatClient(); opts.AddChatSessionProvider(cfg => { cfg.BotName = "Aura"; cfg.ShowTokenUsage = true; });});That registers an AiChatSessionProvider as IChatSessionProvider, configured by the same
AiChatSettings object the control uses.
Multiple choice
Section titled “Multiple choice”When the AI asks a question with a fixed set of answers, AiChatView renders each
AiChoice as a chip under the bubble. Tapping one sends its Label
back as the user’s answer — the model resolves it in context, so nothing is matched locally.
<ai:AiChatView ShowChoiceButtons="True" ChoiceSendText="Confirm" />Questions with AllowMultiple toggle their chips on and off and commit with a send chip, so the user
picks several before anything is sent. Once an answer goes out, that bubble’s chips are disabled.
Inside your own template
Section titled “Inside your own template”A custom MessageTemplate or MessageTemplateSelector replaces the whole bubble body, so the built-in
choice selector is only installed when you have set neither. To render choices in your own template,
read them off the message metadata:
public class MyTemplateSelector : DataTemplateSelector{ protected override DataTemplate? OnSelectTemplate(object item, BindableObject container) { if (item is ChatMessage message && AiChoiceTemplateSelector.ReadQuestions(message) is { Length: > 0 } questions) { // questions[i].Choices[j].Label - render however you like return this.MyChoiceTemplate; } return null; // null falls back to the control's default bubble }}The metadata key is AiChoiceTemplateSelector.QuestionsMetadataKey and the payload round-trips through
AiTurnSerializer.SerializeQuestions / DeserializeQuestions.
Set ShowChoiceButtons="False" to turn the feature off entirely.