Skip to content

Commit 4a0d027

Browse files
committed
Enhance chatbot and search functionality
Added author and creation metadata comments to various files. Updated `Program.cs` to output and open generated files. Enhanced `ChatComponent.razor` with new UI elements, user input handling, and chat history clearing. Updated `SearchPlugin.cs` to use a specific index schema and adjusted search logic. Added configuration injection and initialization in `ChatComponent.razor`. Removed unused JavaScript functions and added new ones for UI interactions in `ChatComponent.razor`. Updated `TimeInformationPlugin.cs` to retrieve current UTC time. Added new configuration import in `Program.cs`. Updated solution settings to include specific words in the user dictionary.
1 parent ffc00e6 commit 4a0d027

File tree

7 files changed

+131
-54
lines changed

7 files changed

+131
-54
lines changed

src/Blazor.AI.SeedData/Program.cs

+38-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
using System.ClientModel;
1+
// ***********************************************************************
2+
// Author : glensouza
3+
// Created : 01-14-2025
4+
//
5+
// Last Modified By : glensouza
6+
// Last Modified On : 01-16-2025
7+
// ***********************************************************************
8+
// <summary>This Console App is to seed data on Azure AI Search.</summary>
9+
// ***********************************************************************
10+
11+
using System.ClientModel;
12+
using System.Diagnostics;
213
using System.Reflection;
314
using Azure;
415
using Azure.AI.OpenAI;
@@ -128,7 +139,6 @@
128139
new SystemChatMessage("You are an HR manager at Contoso Hotels, expert at employee handbook creation."),
129140
new UserChatMessage("Create a privacy policy, vacation policy, and describe a few job roles")
130141
);
131-
progress.Report("Seed data generation completed.");
132142

133143
// Collect the seed data from chat completion text
134144
string text = string.Empty;
@@ -139,6 +149,19 @@
139149
text += message.Text;
140150
}
141151

152+
// Output the employee handbook to text file, first checking if it already exists
153+
string filePath = "employee_handbook.txt";
154+
if (File.Exists(filePath))
155+
{
156+
File.Delete(filePath);
157+
}
158+
159+
File.WriteAllText(filePath, text);
160+
161+
// Open the text file for the user to review
162+
Process.Start(new ProcessStartInfo(filePath) { UseShellExecute = true });
163+
progress.Report("Seed data generation completed.");
164+
142165
// Chunk the text into smaller pieces
143166
progress.Report("Chunking text into smaller pieces...");
144167
const int maxChunkSize = 200;
@@ -189,6 +212,19 @@
189212
Console.WriteLine($"{message.Text}");
190213
text += message.Text;
191214
}
215+
216+
// Output the good questions to text file, first checking if it already exists
217+
filePath = "good_questions.txt";
218+
if (File.Exists(filePath))
219+
{
220+
File.Delete(filePath);
221+
}
222+
223+
File.WriteAllText(filePath, text);
224+
225+
// Open the text file for the user to review
226+
Process.Start(new ProcessStartInfo(filePath) { UseShellExecute = true });
227+
192228
progress.Report("Good questions generation completed.");
193229

194230
return;

src/Blazor.AI.SeedData/RAGSearchDocument.cs

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
using System.Text.Json.Serialization;
1+
// ***********************************************************************
2+
// Author : glensouza
3+
// Created : 01-14-2025
4+
//
5+
// Last Modified By : glensouza
6+
// Last Modified On : 01-16-2025
7+
// ***********************************************************************
8+
// <summary>This class is for Azure AI Search model.</summary>
9+
// ***********************************************************************
10+
11+
using System.Text.Json.Serialization;
212

313
namespace Blazor.AI.SeedData;
414

src/Blazor.AI.Web/Components/Components/ChatComponent.razor

+37-28
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
@rendermode InteractiveServer
1+
@*
2+
***********************************************************************
3+
Author : glensouza
4+
Created : 01-14-2025
5+
6+
Last Modified By : glensouza
7+
Last Modified On : 01-16-2025
8+
***********************************************************************
9+
<summary>Chatbot component to interact with Azure OpenAI</summary>
10+
***********************************************************************
11+
*@
12+
@rendermode InteractiveServer
213

314
@inject IJSRuntime JsRuntime
415
@using Azure
@@ -9,11 +20,14 @@
920
@using Microsoft.SemanticKernel.ChatCompletion;
1021
@using Microsoft.SemanticKernel.Connectors.OpenAI;
1122

23+
@* when checkbox is checked, the chatbot appears, otherwise it clears chat history and hides *@
1224
<input type="checkbox" id="check" @onclick="this.ClearChat" />
1325
<label class="chat-btn" for="check">
1426
<i class="fa fa-commenting-o comment"></i>
1527
<i class="fa fa-close close"></i>
1628
</label>
29+
30+
@* the chatbot ui *@
1731
<div class="wrapper">
1832
<div class="container">
1933
<div class="d-flex justify-content-center">
@@ -22,10 +36,12 @@
2236
<p class="mb-0 fw-bold">Chat with Azure OpenAI</p>
2337
</div>
2438
<div class="card-body" id="messages-container" style="height: 500px; overflow-y: auto; background-color: #eee; border: 2px solid #0CCAF0">
39+
@* display each message in history *@
2540
@foreach (string chatMessage in this.messages)
2641
{
2742
@if (chatMessage.Contains("|AI|"))
2843
{
44+
@* AI message *@
2945
<div class="d-flex flex-row justify-content-start mb-4">
3046
<div class="avatarSticky">
3147
<img src="openai-chatgpt-logo-icon.webp" alt="avatar 1" style="width: 45px; height: 100%;">
@@ -37,6 +53,7 @@
3753
continue;
3854
}
3955

56+
@* User message *@
4057
<div class="d-flex flex-row justify-content-end mb-4">
4158
<div class="p-3 me-3 border" style="border-radius: 15px; background-color: #fbfbfb;">
4259
@((MarkupString)chatMessage)
@@ -47,6 +64,7 @@
4764
</div>
4865
}
4966

67+
@* display the streaming response *@
5068
@if (!string.IsNullOrEmpty(this.htmlStreamingResponse))
5169
{
5270
<div class="d-flex flex-row justify-content-start mb-4">
@@ -59,6 +77,8 @@
5977
</div>
6078
}
6179
</div>
80+
81+
@* the form for user to ask question *@
6282
<div class="card-footer bg-info text-white" style="border-bottom-left-radius: 15px; border-bottom-right-radius: 15px;">
6383
<div class="input-group">
6484
<input type="text" class="form-control" placeholder="Prompt..." @bind="this.message" id="Message" @onkeydown="this.EnterCheckMessage" disabled=@(!string.IsNullOrEmpty(this.htmlStreamingResponse)) />
@@ -74,20 +94,14 @@
7494
</div>
7595
</div>
7696

97+
@* Include this javascript on the head section of html to be used by the chatbot *@
7798
<HeadContent>
7899
<script>
79100
function scrollToBottom() {
80101
var objDiv = document.getElementById("messages-container");
81102
objDiv.scrollTop = objDiv.scrollHeight;
82103
}
83104
84-
function focusOnWhatAmI() {
85-
var what = document.getElementById("WhatAmI");
86-
if (what != null) {
87-
setTimeout(() => what.focus(), 100);
88-
}
89-
}
90-
91105
function focusOnMessage() {
92106
var message = document.getElementById("Message");
93107
if (message != null) {
@@ -97,6 +111,7 @@
97111
</script>
98112
</HeadContent>
99113

114+
@* ReSharper disable once CSharpWarnings::CS8618 *@
100115
@code {
101116
#pragma warning disable SKEXP0010 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
102117
@@ -118,8 +133,11 @@
118133
private string htmlStreamingResponse = string.Empty;
119134
private readonly List<string> messages = [];
120135

136+
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
121137
protected override async Task OnInitializedAsync()
138+
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
122139
{
140+
// Get Azure OpenAI settings
123141
string aoaiKey = this.Configuration["AzureOpenAI:Key"] ?? throw new Exception("AzureOpenAI:Key needs to be set"); string aoaiEndpoint = this.Configuration["AzureOpenAI:Endpoint"] ?? throw new Exception("AzureOpenAI:Endpoint needs to be set");
124142
string aoaiChatDeploymentName = this.Configuration["AzureOpenAI:ChatDeploymentName"] ?? throw new Exception("AzureOpenAI:ChatDeploymentName needs to be set");
125143
string aoaiEmbeddingDeploymentName = this.Configuration["AzureOpenAI:EmbeddingDeploymentName"] ?? throw new Exception("AzureOpenAI:EmbeddingDeploymentName needs to be set");
@@ -144,34 +162,23 @@
144162
// Finalize Kernel Builder
145163
this.kernel = kernelBuilder.Build();
146164

147-
// Add Search Plugin
148-
this.kernel.Plugins.AddFromType<SearchPlugin>("SearchPlugin", this.kernel.Services);
149-
150-
// Add Time Information Plugin
151-
this.kernel.Plugins.AddFromType<TimeInformationPlugin>();
165+
// Add Plugins
166+
this.kernel.Plugins.AddFromType<SearchPlugin>(nameof(SearchPlugin), this.kernel.Services);
167+
this.kernel.Plugins.AddFromType<TimeInformationPlugin>(nameof(TimeInformationPlugin), this.kernel.Services);
152168

153169
// Chat Completion Service
154170
this.chatCompletionService = this.kernel.Services.GetRequiredService<IChatCompletionService>();
155171

156172
// Create OpenAIPromptExecutionSettings
157173
this.openAIPromptExecutionSettings = new OpenAIPromptExecutionSettings
158-
{
159-
ChatSystemPrompt = "You are an HR virtual assistant at Contoso Hotels, expert at answering employee questions related to privacy policy, vacation policy, and describe a few job roles. Ask followup questions if something is unclear or more data is needed to complete a task.",
160-
Temperature = 0.9, // Set the temperature to 0.9
161-
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() // Auto invoke kernel functions
162-
};
163-
}
164-
165-
protected override async Task OnAfterRenderAsync(bool firstRender)
166-
{
167-
if (firstRender)
168174
{
169-
this.StateHasChanged();
170-
}
171-
172-
await base.OnAfterRenderAsync(firstRender);
175+
ChatSystemPrompt = "You are an HR virtual assistant at Contoso Hotels, expert at answering employee questions related to privacy policy, vacation policy, and describe a few job roles. Ask followup questions if something is unclear or more data is needed to complete a task. You can also answer generic questions about the world as it is a global company.",
176+
Temperature = 0.9, // Set the temperature to 0.9
177+
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() // Auto invoke kernel functions
178+
};
173179
}
174180

181+
// Check if the user pressed Enter key
175182
private async Task EnterCheckMessage(KeyboardEventArgs e)
176183
{
177184
if (e.Key == "Enter")
@@ -180,6 +187,7 @@
180187
}
181188
}
182189

190+
// Send chat message to the AI
183191
private async Task SendChat()
184192
{
185193
if (string.IsNullOrEmpty(this.message))
@@ -211,11 +219,12 @@
211219
await this.JsRuntime.InvokeVoidAsync("focusOnMessage");
212220
}
213221

222+
// Clear the chat
214223
private async Task ClearChat()
215224
{
216225
this.messages.Clear();
217226
this.chatHistory.Clear();
218-
this.chatHistory.AddSystemMessage("You are an HR virtual assistant at Contoso Hotels, expert at answering employee questions related to privacy policy, vacation policy, and describe a few job roles. Ask followup questions if something is unclear or more data is needed to complete a task.");
227+
this.chatHistory.AddSystemMessage("You are an HR virtual assistant at Contoso Hotels, expert at answering employee questions related to privacy policy, vacation policy, and describe a few job roles. Ask followup questions if something is unclear or more data is needed to complete a task. You can also answer generic questions about the world as it is a global company.");
219228
this.htmlStreamingResponse = string.Empty;
220229
this.message = string.Empty;
221230
this.messages.Add("|AI|Welcome to the AI chatbot! I am an AI chatbot trained by OpenAI.");
+27-22
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1-
using Azure.Search.Documents;
1+
// ***********************************************************************
2+
// Author : glensouza
3+
// Created : 01-14-2025
4+
//
5+
// Last Modified By : glensouza
6+
// Last Modified On : 01-16-2025
7+
// ***********************************************************************
8+
// <summary>This plugin is used to search documents for the employer Contoso</summary>
9+
// ***********************************************************************
10+
11+
using System.ComponentModel;
12+
using System.Text.Json.Serialization;
213
using Azure;
14+
using Azure.Search.Documents;
315
using Azure.Search.Documents.Indexes;
416
using Azure.Search.Documents.Models;
517
using Microsoft.SemanticKernel;
618
using Microsoft.SemanticKernel.Embeddings;
7-
using System.ComponentModel;
8-
using System.Text.Json.Serialization;
919

1020
namespace Blazor.AI.Web.Plugins;
1121

1222
#pragma warning disable SKEXP0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
1323

24+
// ReSharper disable once ClassNeverInstantiated.Global
1425
public class SearchPlugin(ITextEmbeddingGenerationService textEmbeddingGenerationService, SearchIndexClient indexClient)
1526
{
1627
[KernelFunction("contoso_search")]
@@ -20,40 +31,34 @@ public async Task<string> SearchAsync([Description("The users optimized semantic
2031
// Convert string query to vector
2132
ReadOnlyMemory<float> embedding = await textEmbeddingGenerationService.GenerateEmbeddingAsync(query);
2233

23-
// Set the index to use in AI Search
34+
// Set the index to use in AI Search, the hard coded "demo" index was established in the SeedData project
2435
SearchClient searchClient = indexClient.GetSearchClient("demo");
2536

2637
// Configure request parameters
2738
VectorizedQuery vectorQuery = new(embedding);
28-
vectorQuery.Fields.Add("contentVector"); // name of the vector field from index schema
29-
30-
SearchOptions searchOptions = new() { VectorSearch = new() { Queries = { vectorQuery } } };
31-
32-
//var response = await searchClient.SearchAsync<SearchDocument>(searchOptions);
39+
vectorQuery.Fields.Add("contentVector"); // name of the vector field from Azure AI Search schema established in the SeedData project
3340

3441
// Perform search request
35-
Response<SearchResults<IndexSchema>> response = await searchClient.SearchAsync<IndexSchema>(searchOptions);
42+
SearchOptions searchOptions = new() { VectorSearch = new VectorSearchOptions { Queries = { vectorQuery } } };
43+
Response<SearchResults<RAGSearchDocument>> response = await searchClient.SearchAsync<RAGSearchDocument>(searchOptions);
3644

37-
//// Collect search results
38-
await foreach (SearchResult<IndexSchema> result in response.Value.GetResultsAsync())
45+
// Collect search results
46+
await foreach (SearchResult<RAGSearchDocument> result in response.Value.GetResultsAsync())
3947
{
4048
return result.Document.Content; // Return text from first result
4149
}
4250

4351
return string.Empty;
4452
}
4553

46-
//This schema comes from the index schema in Azure AI Search
47-
private sealed class IndexSchema
54+
//This schema comes from the Azure AI Search model established in the SeedData project
55+
// ReSharper disable once ClassNeverInstantiated.Local
56+
private sealed class RAGSearchDocument
4857
{
49-
[JsonPropertyName("content")]
50-
public string Content { get; set; }
51-
5258
[JsonPropertyName("title")]
53-
public string Title { get; set; }
59+
public string Title { get; init; } = string.Empty;
5460

55-
[JsonPropertyName("url")]
56-
public string Url { get; set; }
61+
[JsonPropertyName("content")]
62+
public string Content { get; init; } = string.Empty;
5763
}
58-
59-
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1-
using Microsoft.SemanticKernel;
1+
// ***********************************************************************
2+
// Author : glensouza
3+
// Created : 01-14-2025
4+
//
5+
// Last Modified By : glensouza
6+
// Last Modified On : 01-16-2025
7+
// ***********************************************************************
8+
// <summary>This plugin provides a function to retrieve the current time in UTC.</summary>
9+
// ***********************************************************************
10+
211
using System.ComponentModel;
12+
using Microsoft.SemanticKernel;
313

414
namespace Blazor.AI.Web.Plugins;
515

16+
// ReSharper disable once ClassNeverInstantiated.Global
617
public class TimeInformationPlugin
718
{
819
[KernelFunction]
920
[Description("Retrieves the current time in UTC.")]
21+
// ReSharper disable once UnusedMember.Global
1022
public string GetCurrentUtcTime() => DateTime.UtcNow.ToString("R");
1123
}

src/Blazor.AI.Web/Program.cs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Blazor.AI.Web.Components;
2+
using Microsoft.Extensions.Configuration;
23

34
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
45

src/Blazor.AI.sln.DotSettings

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Contoso/@EntryIndexedValue">True</s:Boolean>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Hnsw/@EntryIndexedValue">True</s:Boolean>
4+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Vectorizer/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

0 commit comments

Comments
 (0)