In this blog post, we'll explore how to build a simple chatbot using Blazor WebAssembly and Google Gemini. We'll also use MudBlazor for UI. What you should end up with is something like this, where you can ask it all the important questions.
Live Demo 🌐
Click Here or copy and paste https://reggieray.github.io/chat-bot/
in your browser.
As a side note I think Google's Gemini (or what it will be called in the future) will always be at the forefront of gen ai or ai in general because of the people behind it. Google Deepmind has been described like the bell labs of AI, which is a world apart from how I would describe other AI's, which seem to be business focused.
Create the blazor project.
dotnet new blazorwasm -o Chat.Bot
I then moved this into a src
folder.
Add the following Nuget Packages:
Blazored.LocalStorage
- To store any data locally within the browser.Google_GenerativeAI
- A unofficial library to interface with Google gemini. There was no official SDK when this was created.MudBlazor.Markdown
- A component for displaying markdown. Useful for display gen ai content as they love to respond if markdown.The csproj
should look like:
<ItemGroup>
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageReference Include="Google_GenerativeAI" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.8" PrivateAssets="all" />
<PackageReference Include="MudBlazor.Markdown" Version="8.11.0" />
</ItemGroup>
We also need to register the necessary services in Program.cs
:
builder.Services.AddMudServices();
builder.Services.AddMudMarkdownServices();
builder.Services.AddBlazoredLocalStorage();
Also follow the instructions for setting up MudBlazor and MudBlazor.Markdown on the getting started documentation.
Update the home page with the following, this is to setup the Gemini key and make sure it connects successfully.
@page "/"
@using GenerativeAI
@using GenerativeAI.Exceptions
@inject Blazored.LocalStorage.ILocalStorageService localStorage
<PageTitle>Chat Bot</PageTitle>
<MudPaper Class="pa-6 mx-auto mt-6" Elevation="4" Rounded="true">
<MudMarkdown Value="@_welcomeContent" />
</MudPaper>
<MudPaper Class="pa-6 mx-auto mt-6" Elevation="4" Rounded="true">
<MudStack Spacing="3">
<MudText Typo="Typo.h5" GutterBottom="true">Gemini Settings</MudText>
<MudTextField @bind-Value="_inputText" Label="Api Key" Variant="Variant.Outlined" FullWidth="true" />
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SyncModels">
Sync Models
</MudButton>
@if (_options.Any())
{
<MudSelect T="string" @bind-Value="_selectedOption" Label="Gemini Model" Variant="Variant.Outlined"
FullWidth="true">
@foreach (var option in _options)
{
<MudSelectItem Value="@option">@option</MudSelectItem>
}
</MudSelect>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSettings">
Save
</MudButton>
}
@if (!string.IsNullOrEmpty(_errorMessage))
{
<MudAlert Severity="Severity.Error" ContentAlignment="HorizontalAlignment.End">@(_errorMessage)</MudAlert>
}
</MudStack>
</MudPaper>
@code {
private string _inputText { get; set; }
private string _selectedOption { get; set; }
private string _errorMessage { get; set; }
private string _welcomeContent =>
@"### Getting Started
1. Create a free [Genimi api key](https://aistudio.google.com/apikey)
1. Enter the api key below.
1. Click `SYNC MODELS`. A dropdown should appear with available models.
1. Select Model and click `SAVE`.
1. Navigate to `Chat` page from the side menu.
1. Chat away 🚀🚀🚀
> **_NOTE:_** Any data is stored on your local storage within the browser. This app doesn't log or send data
anywhere. See [source code](https://github.com/reggieray/chat-bot).
";
private List<string> _options = new();
protected override async Task OnInitializedAsync()
{
// Load saved settings from local storage
_inputText = await localStorage.GetItemAsync<string>("api_key") ?? string.Empty;
_selectedOption = await localStorage.GetItemAsync<string>("model") ?? string.Empty;
}
private async Task SaveSettings()
{
// Save to local storage
await localStorage.SetItemAsync("api_key", _inputText);
await localStorage.SetItemAsync("model", _selectedOption);
Console.WriteLine($"Saved -> Text: {_inputText}, Option: {_selectedOption}");
}
private async Task SyncModels()
{
try
{
var models = await new GoogleAi(_inputText).ListModelsAsync();
_options = models?.Models.Select(x => x.Name).ToList();
}
catch (ApiException ex)
{
_errorMessage = ex.ErrorMessage;
}
}
}
Set up the chat page with the following, this sets up the chat interface.
@page "/chat"
@using GenerativeAI
@inject Blazored.LocalStorage.ILocalStorageService localStorage
<MudContainer MaxWidth="MaxWidth.Medium" Class="pa-0">
<div class="d-flex flex-column" style="height: 90vh;">
<div id="chat-container" class="flex-grow-1 pa-4" style="overflow-y: auto;">
@foreach (var message in chatMessages)
{
<div class="@($"d-flex flex-row my-2 {(message.Author == "user" ? "flex-row-reverse" : "")}")">
<MudPaper Class="@($"pa-3 rounded-lg {(message.Author == "user" ? "mud-theme-primary" : "mud-theme-surface")}")" Elevation="2">
@if (message.Author == "user")
{
<MudText>@(message.Text)</MudText>
}
else
{
<MudMarkdown Value="@message.Text" />
}
</MudPaper>
</div>
}
@if (_isSending)
{
<div class="d-flex flex-row my-2">
<MudPaper Class="pa-3 rounded-lg mud-theme-surface" Elevation="2">
<MudProgressCircular Indeterminate="true" Size="Size.Small" />
</MudPaper>
</div>
}
</div>
<MudPaper Elevation="2" Class="pa-2 d-flex">
<MudTextField @bind-Value="newMessage"
Placeholder="Ask the AI anything..."
Variant="Variant.Outlined"
Adornment="Adornment.End"
AdornmentIcon="@Icons.Material.Filled.Send"
OnAdornmentClick="SendMessage"
OnKeyUp="HandleKeyUp"
Class="flex-grow-1"
Lines="1"
Disabled="_isSending" />
</MudPaper>
<MudText>@model</MudText>
</div>
</MudContainer>
@code {
private string apiKey = "";
private string model = "";
private string newMessage = "";
private List<ChatMessage> chatMessages = new List<ChatMessage>();
private bool _isSending = false;
private ChatSession? _geminiChatSession;
private async Task SendMessage()
{
_isSending = true;
if (!string.IsNullOrWhiteSpace(newMessage))
{
if (_geminiChatSession is null)
{
_geminiChatSession = GetGeminiClient().StartChat();
}
var userMessage = new ChatMessage { Text = newMessage, Author = "user" };
chatMessages.Add(userMessage);
var geminiResponse = await _geminiChatSession.GenerateContentAsync(newMessage);
chatMessages.Add(new ChatMessage { Text = geminiResponse.Text()!, Author = "ai" });
newMessage = "";
}
_isSending = false;
}
private async Task HandleKeyUp(KeyboardEventArgs e)
{
if (e.Key == "Enter")
{
await SendMessage();
}
}
private GenerativeModel GetGeminiClient()
{
return new GoogleAi(apiKey)
.CreateGenerativeModel(model);
}
protected override async Task OnInitializedAsync()
{
apiKey = await localStorage.GetItemAsync<string>("api_key") ?? string.Empty;
model = await localStorage.GetItemAsync<string>("model") ?? string.Empty;
}
public class ChatMessage
{
public string Text { get; set; }
public string Author { get; set; } // "user" or "ai"
}
}
ChatSession
, this keeps the context of the chat session.Don't forget to update the nav menu to link to each page.
<MudNavMenu>
<MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">Home</MudNavLink>
<MudNavLink Href="chat" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Chat">Chat</MudNavLink>
</MudNavMenu>
And that's it! We've built a simple but functional chatbot using Blazor and the Google's Gemini. You can extend this project by adding features like conversation history, user authentication, and more.
The source code can be found here or at this URL https://github.com/reggieray/chat-bot
.