From 8c5731a3d0a3bd6e702b18aaf1b24eb17e00ac94 Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sun, 8 Sep 2024 00:35:20 +0530 Subject: [PATCH 01/16] OpenAI chat - initial draft --- .../BlazorBootstrap.Demo.RCL.csproj | 4 + .../OpenAIChat/OpenAIChatDocumentation.razor | 21 ++++ .../OpenAIChat_Demo_01_Examples.razor | 1 + .../Components/Pages/Index.razor | 14 +++ blazorbootstrap/BlazorBootstrap.csproj | 2 + .../Components/AI/Chat/AIJSInterop.cs | 45 +++++++ .../Components/AI/Chat/OpenAIChat.razor | 57 +++++++++ .../Components/AI/Chat/OpenAIChat.razor.cs | 89 +++++++++++++ blazorbootstrap/Config.cs | 21 ++-- blazorbootstrap/Models/AI/OpenAIOptions.cs | 8 ++ .../wwwroot/blazor.bootstrap.ai.js | 119 ++++++++++++++++++ 11 files changed, 372 insertions(+), 9 deletions(-) create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChatDocumentation.razor create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChat_Demo_01_Examples.razor create mode 100644 blazorbootstrap/Components/AI/Chat/AIJSInterop.cs create mode 100644 blazorbootstrap/Components/AI/Chat/OpenAIChat.razor create mode 100644 blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs create mode 100644 blazorbootstrap/Models/AI/OpenAIOptions.cs create mode 100644 blazorbootstrap/wwwroot/blazor.bootstrap.ai.js diff --git a/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj b/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj index 3db87cbfd..e519ac9b2 100644 --- a/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj +++ b/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj @@ -19,6 +19,10 @@ + + + + diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChatDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChatDocumentation.razor new file mode 100644 index 000000000..70e86d0f3 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChatDocumentation.razor @@ -0,0 +1,21 @@ +@page "/ai/open-ai-chat" + +@title + + + +

Blazor Open AI Chat

+
Provide contextual feedback messages for typical user actions with a handful of available and flexible alert messages.
+ + + + +
Alerts are available for any length of text, as well as an optional close button. For proper styling, use one of the eight colors.
+ + +@code{ + private string pageUrl = "/ai/open-ai-chat"; + private string title = "Blazor Open AI Chat Component"; + private string description = "Provide contextual feedback messages for typical user actions with the handful of available and flexible Blazor Bootstrap alert messages."; // TODO: update + private string imageUrl = "https://i.imgur.com/FGgEMp6.jpg"; // TODO: update +} \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChat_Demo_01_Examples.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChat_Demo_01_Examples.razor new file mode 100644 index 000000000..c5c4c26df --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChat_Demo_01_Examples.razor @@ -0,0 +1 @@ + diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor index a874822ef..fbff4e690 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor @@ -233,6 +233,20 @@ +
+
+

AI Components

+
+ + +
+

Form Components

diff --git a/blazorbootstrap/BlazorBootstrap.csproj b/blazorbootstrap/BlazorBootstrap.csproj index 34c3dbc0f..7d0120794 100644 --- a/blazorbootstrap/BlazorBootstrap.csproj +++ b/blazorbootstrap/BlazorBootstrap.csproj @@ -46,10 +46,12 @@ + + \ No newline at end of file diff --git a/blazorbootstrap/Components/AI/Chat/AIJSInterop.cs b/blazorbootstrap/Components/AI/Chat/AIJSInterop.cs new file mode 100644 index 000000000..0041ed2b7 --- /dev/null +++ b/blazorbootstrap/Components/AI/Chat/AIJSInterop.cs @@ -0,0 +1,45 @@ +namespace BlazorBootstrap; +internal class AIJSInterop : IAsyncDisposable +{ + + #region Fields and Constants + + private readonly Lazy> moduleTask; + + #endregion + + #region Constructors + + public AIJSInterop(IJSRuntime jsRuntime) + { + moduleTask = new Lazy>(() + => jsRuntime.InvokeAsync("import", "./_content/Blazor.Bootstrap/blazor.bootstrap.ai.js").AsTask()); + } + + #endregion + + #region Methods + + public async ValueTask DisposeAsync() + { + if (moduleTask.IsValueCreated) + { + var module = await moduleTask.Value; + await module.DisposeAsync(); + } + } + + public async Task CreateChatCompletionsAsync(string key, object message, object objRef) + { + var module = await moduleTask.Value; + await module.InvokeVoidAsync("createChatCompletions", key, message, objRef); + } + + public async Task CreateChatCompletions2Async(string key, object message, object objRef) + { + var module = await moduleTask.Value; + await module.InvokeVoidAsync("createChatCompletions2", key, message, objRef); + } + + #endregion +} diff --git a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor new file mode 100644 index 000000000..2e02f88bd --- /dev/null +++ b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor @@ -0,0 +1,57 @@ +@namespace BlazorBootstrap +@inherits BlazorBootstrapComponentBase + +
+
+ @foreach (var message in conversationHistory) + { +
+
+ +
+
+ @if (message.Role == "user") + { + @message.Content + } + else + { + @((MarkupString)message.Content) + } +
+
+ } + @if (!string.IsNullOrWhiteSpace(currentCompletion)) + { +
+
+ +
+ @((MarkupString)currentCompletion!) +
+
+ } +
+ +
+ @if (isSendingMessage) + { +
+
+ Loading... +
+
+ } +
+ + +
+
+
\ No newline at end of file diff --git a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs new file mode 100644 index 000000000..283af91b0 --- /dev/null +++ b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs @@ -0,0 +1,89 @@ +namespace BlazorBootstrap; + +public partial class OpenAIChat : BlazorBootstrapComponentBase +{ + #region Fields and Constants + + private string key = ""; + private string model = "gpt-4"; + private string userQuestion = string.Empty; + private readonly List conversationHistory = new(); + private bool isSendingMessage; + private string? currentCompletion; + private DotNetObjectReference? objRef; + + #endregion + + #region Methods + + protected override async Task OnInitializedAsync() + { + objRef ??= DotNetObjectReference.Create(this); + await base.OnInitializedAsync(); + } + + private async Task OnKeyPress(KeyboardEventArgs e) + { + if (e.Key is not "Enter") return; + await SendMessageAsync(); + } + + private async Task SendMessageAsync() + { + if (string.IsNullOrWhiteSpace(userQuestion)) return; + + var message = new Message("user", userQuestion); + conversationHistory.Add(message); + + StateHasChanged(); + + await CreateCompletionAsync(message); + ClearInput(); + + StateHasChanged(); + } + + private void ClearInput() => userQuestion = string.Empty; + + private void ClearConversation() + { + ClearInput(); + conversationHistory.Clear(); + } + + private async Task CreateCompletionAsync(Message message) + { + isSendingMessage = true; + //await AIJSInterop.CreateChatCompletionsAsync(key, message, objRef!); // OpenAI + await AIJSInterop.CreateChatCompletions2Async("", message, objRef!); // Azure OpenAI + //await OpenAIChatJsInterop.CreateChatCompletionsApiAsync(key, message, objRef!); // API + } + + [JSInvokable] + public void ChartCompletetionsStreamJs(string content, bool done) + { + if (isSendingMessage) + isSendingMessage = false; + + if (done) + { + conversationHistory.Add(new Message("system", currentCompletion!)); + currentCompletion = ""; + StateHasChanged(); + return; + } + + currentCompletion += content; + StateHasChanged(); + } + + #endregion + + #region Properties, Indexers + + [Inject] private AIJSInterop AIJSInterop { get; set; } = default!; + + #endregion +} + +public record Message(string Role, string Content); \ No newline at end of file diff --git a/blazorbootstrap/Config.cs b/blazorbootstrap/Config.cs index 19fb67f36..3c629a901 100644 --- a/blazorbootstrap/Config.cs +++ b/blazorbootstrap/Config.cs @@ -1,4 +1,5 @@ using BlazorBootstrap; +using Microsoft.Extensions.Configuration; namespace Microsoft.Extensions.DependencyInjection; @@ -9,19 +10,21 @@ public static class Config /// /// Adds a bootstrap providers and component mappings. /// - /// + /// /// IServiceCollection - public static IServiceCollection AddBlazorBootstrap(this IServiceCollection serviceCollection) + public static IServiceCollection AddBlazorBootstrap(this IServiceCollection services, IConfiguration configuration = null!) { - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped(); + services.AddScoped(); + services.AddScoped(); - return serviceCollection; + services.AddScoped(); + + return services; } #endregion diff --git a/blazorbootstrap/Models/AI/OpenAIOptions.cs b/blazorbootstrap/Models/AI/OpenAIOptions.cs new file mode 100644 index 000000000..de8d72cae --- /dev/null +++ b/blazorbootstrap/Models/AI/OpenAIOptions.cs @@ -0,0 +1,8 @@ +namespace BlazorBootstrap; + +public class OpenAIOptions +{ + public string? ApiKey { get; set; } + public string? ApiUrl { get; set; } + public string? GtpModel { get; set; } +} diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js b/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js new file mode 100644 index 000000000..b97fb86a9 --- /dev/null +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js @@ -0,0 +1,119 @@ +export async function createChatCompletions(key, message, dotNetHelper) { + const API_KEY = key; + const API_URL = 'https://api.openai.com/v1/chat/completions'; + + try { + // Fetch the response from the OpenAI API with the signal from AbortController + const response = await fetch(API_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${API_KEY}`, + }, + body: JSON.stringify({ + model: "gpt-3.5-turbo", //"gpt-4", + messages: [message], + max_tokens: 200, + stream: true, // For streaming responses + }) + }); + + // Read the response as a stream of data + const reader = response.body.getReader(); + const decoder = new TextDecoder("utf-8"); + let i = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + + // Massage and parse the chunk of data + const chunk = decoder.decode(value); + const lines = chunk.split("\n"); + + for (const payload of lines) { + + if (payload.includes('[DONE]')) { + dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', '', true); + return; + } + + if (payload.startsWith("data:")) { + const data = JSON.parse(payload.replace("data:", "")); + const content = data.choices[0].delta.content; + if (content) { + dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', content, false); + } + } + } + } + } catch (error) { + // Handle fetch request errors + console.log(error); + } finally { + // TODO: cleanup + } +} + +export async function createChatCompletions2(key, message, dotNetHelper) { + const API_KEY = key; + const API_URL = 'https://vk-aoai-test.openai.azure.com/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-02-15-preview'; + + try { + // Fetch the response from the OpenAI API with the signal from AbortController + const response = await fetch(API_URL, { + method: "POST", + headers: { + "Content-Type": "application/json", + "api-key": `${API_KEY}`, + }, + body: JSON.stringify({ + messages: [{ + "role": "user", + "content": [{ "type": "text", "text": "What is Blazor?" }] + }], + "temperature": 0.7, + "top_p":0.95, + max_tokens: 200, + stream: true, // For streaming responses + }) + }); + + // Read the response as a stream of data + const reader = response.body.getReader(); + const decoder = new TextDecoder("utf-8"); + let i = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + + // Massage and parse the chunk of data + const chunk = decoder.decode(value); + const lines = chunk.split("\n"); + + for (const payload of lines) { + + if (payload.includes('[DONE]')) { + dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', '', true); + return; + } + + if (payload.startsWith("data:")) { + const data = JSON.parse(payload.replace("data:", "")); + const content = data.choices[0].delta.content; + if (content) { + dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', content, false); + } + } + } + } + } catch (error) { + // Handle fetch request errors + console.log(error); + } finally { + // TODO: cleanup + } +} \ No newline at end of file From f00ca0f10024c13b816432ffe02f65b3911fc532 Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sun, 8 Sep 2024 12:41:58 +0530 Subject: [PATCH 02/16] OpenAI chat component updates --- .../Components/AI/Chat/OpenAIChat.razor.cs | 6 ++--- .../wwwroot/blazor.bootstrap.ai.js | 24 +++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs index 283af91b0..8d4988c3b 100644 --- a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs +++ b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs @@ -60,7 +60,7 @@ private async Task CreateCompletionAsync(Message message) } [JSInvokable] - public void ChartCompletetionsStreamJs(string content, bool done) + public async Task ChartCompletetionsStreamJs(string content, bool done) { if (isSendingMessage) isSendingMessage = false; @@ -69,12 +69,12 @@ public void ChartCompletetionsStreamJs(string content, bool done) { conversationHistory.Add(new Message("system", currentCompletion!)); currentCompletion = ""; - StateHasChanged(); + await InvokeAsync(StateHasChanged); return; } currentCompletion += content; - StateHasChanged(); + await InvokeAsync(StateHasChanged); } #endregion diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js b/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js index b97fb86a9..51c227492 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js @@ -59,6 +59,9 @@ export async function createChatCompletions(key, message, dotNetHelper) { export async function createChatCompletions2(key, message, dotNetHelper) { const API_KEY = key; const API_URL = 'https://vk-aoai-test.openai.azure.com/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-02-15-preview'; + let messages = []; + let notificationTriggered = false; + let stopSetInterval = false; try { // Fetch the response from the OpenAI API with the signal from AbortController @@ -97,19 +100,36 @@ export async function createChatCompletions2(key, message, dotNetHelper) { for (const payload of lines) { if (payload.includes('[DONE]')) { + stopSetInterval = true; dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', '', true); return; } if (payload.startsWith("data:")) { const data = JSON.parse(payload.replace("data:", "")); - const content = data.choices[0].delta.content; + const content = data.choices[0]?.delta?.content; if (content) { - dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', content, false); + messages.push(content); + if (!notificationTriggered) { + notificationTriggered = true; + triggerNotify(); + } } } } } + + function triggerNotify() { + setInterval(() => { + if (stopSetInterval) { + stopSetInterval = false; + clearInterval(); + } + const content = messages.shift(); + dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', content, false); + }, 100); + } + } catch (error) { // Handle fetch request errors console.log(error); From cca9d085aa70337805ba136e7a09f9d55f77301f Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sun, 8 Sep 2024 15:00:43 +0530 Subject: [PATCH 03/16] OpenAI chat - draft --- .../Components/AI/Chat/OpenAIChat.razor | 25 ++++++++++--------- .../Components/AI/Chat/OpenAIChat.razor.cs | 24 +++++++----------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor index 2e02f88bd..af19ed5db 100644 --- a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor +++ b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor @@ -1,8 +1,8 @@ @namespace BlazorBootstrap @inherits BlazorBootstrapComponentBase -
-
+
+
@foreach (var message in conversationHistory) {
@@ -35,8 +35,8 @@ }
-
- @if (isSendingMessage) +
+ @if (isRequestInProgress) {
@@ -44,14 +44,15 @@
} -
- - +
+ + Submit
\ No newline at end of file diff --git a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs index 8d4988c3b..a570009d8 100644 --- a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs +++ b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs @@ -6,9 +6,9 @@ public partial class OpenAIChat : BlazorBootstrapComponentBase private string key = ""; private string model = "gpt-4"; - private string userQuestion = string.Empty; + private string userPrompt = string.Empty; private readonly List conversationHistory = new(); - private bool isSendingMessage; + private bool isRequestInProgress; private string? currentCompletion; private DotNetObjectReference? objRef; @@ -22,17 +22,11 @@ protected override async Task OnInitializedAsync() await base.OnInitializedAsync(); } - private async Task OnKeyPress(KeyboardEventArgs e) + private async Task SendPromptAsync() { - if (e.Key is not "Enter") return; - await SendMessageAsync(); - } - - private async Task SendMessageAsync() - { - if (string.IsNullOrWhiteSpace(userQuestion)) return; + if (string.IsNullOrWhiteSpace(userPrompt)) return; - var message = new Message("user", userQuestion); + var message = new Message("user", userPrompt); conversationHistory.Add(message); StateHasChanged(); @@ -43,7 +37,7 @@ private async Task SendMessageAsync() StateHasChanged(); } - private void ClearInput() => userQuestion = string.Empty; + private void ClearInput() => userPrompt = string.Empty; private void ClearConversation() { @@ -53,7 +47,7 @@ private void ClearConversation() private async Task CreateCompletionAsync(Message message) { - isSendingMessage = true; + isRequestInProgress = true; //await AIJSInterop.CreateChatCompletionsAsync(key, message, objRef!); // OpenAI await AIJSInterop.CreateChatCompletions2Async("", message, objRef!); // Azure OpenAI //await OpenAIChatJsInterop.CreateChatCompletionsApiAsync(key, message, objRef!); // API @@ -62,8 +56,8 @@ private async Task CreateCompletionAsync(Message message) [JSInvokable] public async Task ChartCompletetionsStreamJs(string content, bool done) { - if (isSendingMessage) - isSendingMessage = false; + if (isRequestInProgress) + isRequestInProgress = false; if (done) { From dce2102831895764bbac8b7ae4139113217403b0 Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Wed, 11 Sep 2024 22:10:58 +0530 Subject: [PATCH 04/16] Azure OpenAI chat --- blazorbootstrap/wwwroot/blazor.bootstrap.ai.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js b/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js index 51c227492..c7dd1401c 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js @@ -61,7 +61,7 @@ export async function createChatCompletions2(key, message, dotNetHelper) { const API_URL = 'https://vk-aoai-test.openai.azure.com/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-02-15-preview'; let messages = []; let notificationTriggered = false; - let stopSetInterval = false; + let streamComplete = false; try { // Fetch the response from the OpenAI API with the signal from AbortController @@ -100,8 +100,7 @@ export async function createChatCompletions2(key, message, dotNetHelper) { for (const payload of lines) { if (payload.includes('[DONE]')) { - stopSetInterval = true; - dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', '', true); + streamComplete = true; return; } @@ -120,13 +119,14 @@ export async function createChatCompletions2(key, message, dotNetHelper) { } function triggerNotify() { - setInterval(() => { - if (stopSetInterval) { - stopSetInterval = false; - clearInterval(); - } + let handler = setInterval(() => { const content = messages.shift(); dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', content, false); + + if (streamComplete && messages.length === 0) { + clearInterval(handler); + dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', '', true); + } }, 100); } From 1206e2cd8bcfcb34da9d386f134f4c79fe76fd5d Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Thu, 12 Sep 2024 00:00:50 +0530 Subject: [PATCH 05/16] OpenAI chat component updates --- .../Components/AI/Chat/AIJSInterop.cs | 8 +- .../Components/AI/Chat/OpenAIChat.razor.cs | 93 +++++++++++++++---- .../wwwroot/blazor.bootstrap.ai.js | 43 ++++----- 3 files changed, 96 insertions(+), 48 deletions(-) diff --git a/blazorbootstrap/Components/AI/Chat/AIJSInterop.cs b/blazorbootstrap/Components/AI/Chat/AIJSInterop.cs index 0041ed2b7..6ce0566e7 100644 --- a/blazorbootstrap/Components/AI/Chat/AIJSInterop.cs +++ b/blazorbootstrap/Components/AI/Chat/AIJSInterop.cs @@ -29,16 +29,16 @@ public async ValueTask DisposeAsync() } } - public async Task CreateChatCompletionsAsync(string key, object message, object objRef) + public async Task CreateChatCompletionsAsync(string key, object messages, object objRef) { var module = await moduleTask.Value; - await module.InvokeVoidAsync("createChatCompletions", key, message, objRef); + await module.InvokeVoidAsync("createChatCompletions", key, messages, objRef); } - public async Task CreateChatCompletions2Async(string key, object message, object objRef) + public async Task CreateChatCompletions2Async(string url, string key, object payload, object objRef) { var module = await moduleTask.Value; - await module.InvokeVoidAsync("createChatCompletions2", key, message, objRef); + await module.InvokeVoidAsync("createChatCompletions2", url, key, payload, objRef); } #endregion diff --git a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs index a570009d8..e066b0af0 100644 --- a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs +++ b/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs @@ -24,38 +24,43 @@ protected override async Task OnInitializedAsync() private async Task SendPromptAsync() { - if (string.IsNullOrWhiteSpace(userPrompt)) return; + if (string.IsNullOrWhiteSpace(userPrompt)) + return; var message = new Message("user", userPrompt); conversationHistory.Add(message); - StateHasChanged(); - - await CreateCompletionAsync(message); - ClearInput(); - - StateHasChanged(); + await CreateCompletionAsync(new List { message }); } private void ClearInput() => userPrompt = string.Empty; - private void ClearConversation() - { - ClearInput(); - conversationHistory.Clear(); - } - - private async Task CreateCompletionAsync(Message message) + private async Task CreateCompletionAsync(List messages) { isRequestInProgress = true; - //await AIJSInterop.CreateChatCompletionsAsync(key, message, objRef!); // OpenAI - await AIJSInterop.CreateChatCompletions2Async("", message, objRef!); // Azure OpenAI - //await OpenAIChatJsInterop.CreateChatCompletionsApiAsync(key, message, objRef!); // API + //await AIJSInterop.CreateChatCompletionsAsync(key, messages, objRef!); // OpenAI + + var payload = new OpenAIChatPayload { + Messages = messages, + MaximumTokens = 200, + Temperature = 0.7, + TopP = 0.95, + }; + + await AIJSInterop.CreateChatCompletions2Async( + url: "", + key: "", + payload: payload, + objRef: objRef!); // Azure OpenAI + + //await OpenAIChatJsInterop.CreateChatCompletionsApiAsync(key, messages, objRef!); // API } [JSInvokable] public async Task ChartCompletetionsStreamJs(string content, bool done) { + ClearInput(); + if (isRequestInProgress) isRequestInProgress = false; @@ -80,4 +85,56 @@ public async Task ChartCompletetionsStreamJs(string content, bool done) #endregion } -public record Message(string Role, string Content); \ No newline at end of file +public record Message(string Role, string Content); + +public record class OpenAIChatPayload +{ + [JsonPropertyName("messages")] + public List? Messages { get; set; } + + /// + /// Controls randomness: Lowering results in less random completions. + /// As the temperature approaches zero, the model will become deterministic and repetitive. + /// Minimum 1 and the maximum is 2. + /// + /// Default value is 1. + [JsonPropertyName("temperature")] + public double Temperature { get; set; } = 1; + + /// + /// The maximum number of tokens to generate shared between the prompt and completion. + /// The exact limit varies by model. (One token is roughly 4 characters for standard English text) + /// Minimum 1 and the maximum tokens is 4096. + /// + /// Default value is 2048. This value is limited by gpt-3.5-turbo. + [JsonPropertyName("max_tokens")] + public long MaximumTokens { get; set; } = 2048; + + /// + /// Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered. + /// + /// Default value is 1. + [JsonPropertyName("top_p")] + public double TopP { get; set; } = 1; + + /// + /// How much to penalize new tokens based on their existing frequency in the text so far. + /// Decreases the model's likelihood to repeat the same line verbatim. + /// Minimum 1 and the maximum is 2. + /// + /// Default value is 0. + [JsonPropertyName("frequency_penalty")] + public double FrequencyPenalty { get; set; } = 0; + + /// + /// How much to penalize new tokens based on whether they appear in the text so far. + /// Increases the model's likelihood to talk about new topics. + /// Minimum 1 and the maximum is 2. + /// + /// Default value is 0. + [JsonPropertyName("presence_penalty")] + public float PresencePenalty { get; set; } = 0; + + [JsonPropertyName("stream")] + public bool Stream { get; } = true; +} diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js b/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js index c7dd1401c..17b554e06 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js @@ -1,4 +1,4 @@ -export async function createChatCompletions(key, message, dotNetHelper) { +export async function createChatCompletions(key, messages, dotNetHelper) { const API_KEY = key; const API_URL = 'https://api.openai.com/v1/chat/completions'; @@ -12,7 +12,7 @@ export async function createChatCompletions(key, message, dotNetHelper) { }, body: JSON.stringify({ model: "gpt-3.5-turbo", //"gpt-4", - messages: [message], + messages: messages, max_tokens: 200, stream: true, // For streaming responses }) @@ -56,31 +56,21 @@ export async function createChatCompletions(key, message, dotNetHelper) { } } -export async function createChatCompletions2(key, message, dotNetHelper) { - const API_KEY = key; - const API_URL = 'https://vk-aoai-test.openai.azure.com/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-02-15-preview'; - let messages = []; +export async function createChatCompletions2(url, key, payload, dotNetHelper) { + const API_URL = ''; + let contentArray = []; let notificationTriggered = false; let streamComplete = false; try { // Fetch the response from the OpenAI API with the signal from AbortController - const response = await fetch(API_URL, { + const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", - "api-key": `${API_KEY}`, + "api-key": `${key}`, }, - body: JSON.stringify({ - messages: [{ - "role": "user", - "content": [{ "type": "text", "text": "What is Blazor?" }] - }], - "temperature": 0.7, - "top_p":0.95, - max_tokens: 200, - stream: true, // For streaming responses - }) + body: JSON.stringify(payload) }); // Read the response as a stream of data @@ -97,18 +87,18 @@ export async function createChatCompletions2(key, message, dotNetHelper) { const chunk = decoder.decode(value); const lines = chunk.split("\n"); - for (const payload of lines) { + for (const line of lines) { - if (payload.includes('[DONE]')) { + if (line.includes('[DONE]')) { streamComplete = true; return; } - if (payload.startsWith("data:")) { - const data = JSON.parse(payload.replace("data:", "")); + if (line.startsWith("data:")) { + const data = JSON.parse(line.replace("data:", "")); const content = data.choices[0]?.delta?.content; if (content) { - messages.push(content); + contentArray.push(content); if (!notificationTriggered) { notificationTriggered = true; triggerNotify(); @@ -120,10 +110,11 @@ export async function createChatCompletions2(key, message, dotNetHelper) { function triggerNotify() { let handler = setInterval(() => { - const content = messages.shift(); - dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', content, false); + const content = contentArray.shift(); + if (content && content.length > 0) + dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', content, false); - if (streamComplete && messages.length === 0) { + if (streamComplete && contentArray.length === 0) { clearInterval(handler); dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', '', true); } From af5654e846c14bad2a548c213e27917ad03245ca Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Thu, 12 Sep 2024 22:01:46 +0530 Subject: [PATCH 06/16] Chat component updates. --- .../BlazorBootstrap.Demo.RCL.csproj | 4 - .../AIChatDocumentation.razor} | 2 +- .../AI/AIChat/AIChat_Demo_01_Examples.razor | 1 + .../OpenAIChat_Demo_01_Examples.razor | 1 - BlazorBootstrap.Demo.Server/appsettings.json | 6 ++ .../Chat/{OpenAIChat.razor => AIChat.razor} | 0 .../{OpenAIChat.razor.cs => AIChat.razor.cs} | 83 +++++++++++++++---- .../Components/AI/Chat/AIChatInterop.cs.cs | 12 +++ .../Components/AI/Chat/AIJSInterop.cs | 45 ---------- .../Core/BlazorBootstrapComponentBase.cs | 6 +- blazorbootstrap/Config.cs | 2 - .../wwwroot/blazor.bootstrap.ai.js | 73 ---------------- blazorbootstrap/wwwroot/blazor.bootstrap.js | 80 ++++++++++++++++++ 13 files changed, 172 insertions(+), 143 deletions(-) rename BlazorBootstrap.Demo.RCL/Components/Pages/AI/{OpenAIChat/OpenAIChatDocumentation.razor => AIChat/AIChatDocumentation.razor} (93%) create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/AI/AIChat/AIChat_Demo_01_Examples.razor delete mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChat_Demo_01_Examples.razor rename blazorbootstrap/Components/AI/Chat/{OpenAIChat.razor => AIChat.razor} (100%) rename blazorbootstrap/Components/AI/Chat/{OpenAIChat.razor.cs => AIChat.razor.cs} (55%) create mode 100644 blazorbootstrap/Components/AI/Chat/AIChatInterop.cs.cs delete mode 100644 blazorbootstrap/Components/AI/Chat/AIJSInterop.cs diff --git a/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj b/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj index e519ac9b2..3db87cbfd 100644 --- a/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj +++ b/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj @@ -19,10 +19,6 @@ - - - - diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChatDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/AI/AIChat/AIChatDocumentation.razor similarity index 93% rename from BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChatDocumentation.razor rename to BlazorBootstrap.Demo.RCL/Components/Pages/AI/AIChat/AIChatDocumentation.razor index 70e86d0f3..559e797cd 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChatDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/AI/AIChat/AIChatDocumentation.razor @@ -11,7 +11,7 @@
Alerts are available for any length of text, as well as an optional close button. For proper styling, use one of the eight colors.
- + @code{ private string pageUrl = "/ai/open-ai-chat"; diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/AI/AIChat/AIChat_Demo_01_Examples.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/AI/AIChat/AIChat_Demo_01_Examples.razor new file mode 100644 index 000000000..eeafc3f3b --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/AI/AIChat/AIChat_Demo_01_Examples.razor @@ -0,0 +1 @@ + diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChat_Demo_01_Examples.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChat_Demo_01_Examples.razor deleted file mode 100644 index c5c4c26df..000000000 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/AI/OpenAIChat/OpenAIChat_Demo_01_Examples.razor +++ /dev/null @@ -1 +0,0 @@ - diff --git a/BlazorBootstrap.Demo.Server/appsettings.json b/BlazorBootstrap.Demo.Server/appsettings.json index 0fc7e90f8..03290f577 100644 --- a/BlazorBootstrap.Demo.Server/appsettings.json +++ b/BlazorBootstrap.Demo.Server/appsettings.json @@ -23,5 +23,11 @@ }, "GoogleMap": { "ApiKey": "AIzaSyDc110Rvu20IMJhlZcWTOPoLbVQdnjLyXs" + }, + "AzureOpenAI": { + "Endpoint": "", + "DeploymentName": "", + "ApiKey": "", + "ApiVersion": "" } } diff --git a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor b/blazorbootstrap/Components/AI/Chat/AIChat.razor similarity index 100% rename from blazorbootstrap/Components/AI/Chat/OpenAIChat.razor rename to blazorbootstrap/Components/AI/Chat/AIChat.razor diff --git a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs b/blazorbootstrap/Components/AI/Chat/AIChat.razor.cs similarity index 55% rename from blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs rename to blazorbootstrap/Components/AI/Chat/AIChat.razor.cs index e066b0af0..b27593943 100644 --- a/blazorbootstrap/Components/AI/Chat/OpenAIChat.razor.cs +++ b/blazorbootstrap/Components/AI/Chat/AIChat.razor.cs @@ -1,16 +1,19 @@ namespace BlazorBootstrap; -public partial class OpenAIChat : BlazorBootstrapComponentBase +public partial class AIChat : BlazorBootstrapComponentBase { #region Fields and Constants - private string key = ""; - private string model = "gpt-4"; + private string? endpoint; + private string? deploymentName; + private string? apiKey; + private string? apiVersion; + private string userPrompt = string.Empty; private readonly List conversationHistory = new(); private bool isRequestInProgress; private string? currentCompletion; - private DotNetObjectReference? objRef; + private DotNetObjectReference? objRef; #endregion @@ -18,6 +21,26 @@ public partial class OpenAIChat : BlazorBootstrapComponentBase protected override async Task OnInitializedAsync() { + var configurationSection = Configuration.GetSection("AzureOpenAI"); + if(Configuration is null) + throw new ArgumentException($"`AzureOpenAI` section was not found in the application configuration."); + + endpoint = configurationSection["Endpoint"]; + if (endpoint is null) + throw new ArgumentException($"`Endpoint` key/value was not found in the 'AzureOpenAI' section of the application configuration."); + + deploymentName = configurationSection["DeploymentName"]; + if (deploymentName is null) + throw new ArgumentException($"`DeploymentName` key/value was not found in the 'AzureOpenAI' section of the application configuration."); + + apiKey = configurationSection["ApiKey"]; + if (apiKey is null) + throw new ArgumentException($"`ApiKey` key/value was not found in the 'AzureOpenAI' section of the application configuration."); + + apiVersion = configurationSection["ApiVersion"]; + if (apiVersion is null) + throw new ArgumentException($"`ApiVersion` key/value was not found in the 'AzureOpenAI' section of the application configuration."); + objRef ??= DotNetObjectReference.Create(this); await base.OnInitializedAsync(); } @@ -38,22 +61,27 @@ private async Task SendPromptAsync() private async Task CreateCompletionAsync(List messages) { isRequestInProgress = true; - //await AIJSInterop.CreateChatCompletionsAsync(key, messages, objRef!); // OpenAI var payload = new OpenAIChatPayload { Messages = messages, - MaximumTokens = 200, - Temperature = 0.7, - TopP = 0.95, + MaximumTokens = MaximumTokens, + Temperature = Temperature, + TopP = TopP }; - await AIJSInterop.CreateChatCompletions2Async( - url: "", - key: "", - payload: payload, - objRef: objRef!); // Azure OpenAI - - //await OpenAIChatJsInterop.CreateChatCompletionsApiAsync(key, messages, objRef!); // API + try + { + await JSRuntime.InvokeVoidAsync( + AIChatInterop.AzureOpenAIChatCompletions, + $"{endpoint}openai/deployments/{deploymentName}/chat/completions?api-version={apiVersion}", + apiKey, + payload, + objRef!); + } + catch + { + isRequestInProgress = false; + } } [JSInvokable] @@ -80,7 +108,30 @@ public async Task ChartCompletetionsStreamJs(string content, bool done) #region Properties, Indexers - [Inject] private AIJSInterop AIJSInterop { get; set; } = default!; + /// + /// The maximum number of tokens to generate shared between the prompt and completion. + /// The exact limit varies by model. (One token is roughly 4 characters for standard English text) + /// Minimum 1 and the maximum tokens is 4096. + /// + /// Default value is 2048. This value is limited by gpt-3.5-turbo. + [Parameter] + public long MaximumTokens { get; set; } = 2048; + + /// + /// Controls randomness: Lowering results in less random completions. + /// As the temperature approaches zero, the model will become deterministic and repetitive. + /// Minimum 1 and the maximum is 2. + /// + /// Default value is 1. + [Parameter] + public double Temperature { get; set; } = 1; + + /// + /// Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered. + /// + /// Default value is 1. + [Parameter] + public double TopP { get; set; } = 1; #endregion } diff --git a/blazorbootstrap/Components/AI/Chat/AIChatInterop.cs.cs b/blazorbootstrap/Components/AI/Chat/AIChatInterop.cs.cs new file mode 100644 index 000000000..8dc241100 --- /dev/null +++ b/blazorbootstrap/Components/AI/Chat/AIChatInterop.cs.cs @@ -0,0 +1,12 @@ +namespace BlazorBootstrap; + +public class AIChatInterop +{ + #region Fields and Constants + + private const string Prefix = "window.blazorBootstrap.ai."; + + public const string AzureOpenAIChatCompletions = Prefix + "azureOpenAI.chat.completions"; + + #endregion +} diff --git a/blazorbootstrap/Components/AI/Chat/AIJSInterop.cs b/blazorbootstrap/Components/AI/Chat/AIJSInterop.cs deleted file mode 100644 index 6ce0566e7..000000000 --- a/blazorbootstrap/Components/AI/Chat/AIJSInterop.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace BlazorBootstrap; -internal class AIJSInterop : IAsyncDisposable -{ - - #region Fields and Constants - - private readonly Lazy> moduleTask; - - #endregion - - #region Constructors - - public AIJSInterop(IJSRuntime jsRuntime) - { - moduleTask = new Lazy>(() - => jsRuntime.InvokeAsync("import", "./_content/Blazor.Bootstrap/blazor.bootstrap.ai.js").AsTask()); - } - - #endregion - - #region Methods - - public async ValueTask DisposeAsync() - { - if (moduleTask.IsValueCreated) - { - var module = await moduleTask.Value; - await module.DisposeAsync(); - } - } - - public async Task CreateChatCompletionsAsync(string key, object messages, object objRef) - { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("createChatCompletions", key, messages, objRef); - } - - public async Task CreateChatCompletions2Async(string url, string key, object payload, object objRef) - { - var module = await moduleTask.Value; - await module.InvokeVoidAsync("createChatCompletions2", url, key, payload, objRef); - } - - #endregion -} diff --git a/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs b/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs index e5386389f..a8bfe8dfa 100644 --- a/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs +++ b/blazorbootstrap/Components/Core/BlazorBootstrapComponentBase.cs @@ -1,4 +1,6 @@ -namespace BlazorBootstrap; +using Microsoft.Extensions.Configuration; + +namespace BlazorBootstrap; public abstract class BlazorBootstrapComponentBase : ComponentBase, IDisposable, IAsyncDisposable { @@ -141,6 +143,8 @@ protected virtual ValueTask DisposeAsyncCore(bool disposing) protected virtual string? ClassNames => Class; + [Inject] protected IConfiguration Configuration { get; set; } = default!; + public ElementReference Element { get; set; } [Parameter] public string? Id { get; set; } diff --git a/blazorbootstrap/Config.cs b/blazorbootstrap/Config.cs index 3c629a901..fe8146e37 100644 --- a/blazorbootstrap/Config.cs +++ b/blazorbootstrap/Config.cs @@ -22,8 +22,6 @@ public static IServiceCollection AddBlazorBootstrap(this IServiceCollection serv services.AddScoped(); services.AddScoped(); - services.AddScoped(); - return services; } diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js b/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js index 17b554e06..f9241c524 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.ai.js @@ -55,76 +55,3 @@ export async function createChatCompletions(key, messages, dotNetHelper) { // TODO: cleanup } } - -export async function createChatCompletions2(url, key, payload, dotNetHelper) { - const API_URL = ''; - let contentArray = []; - let notificationTriggered = false; - let streamComplete = false; - - try { - // Fetch the response from the OpenAI API with the signal from AbortController - const response = await fetch(url, { - method: "POST", - headers: { - "Content-Type": "application/json", - "api-key": `${key}`, - }, - body: JSON.stringify(payload) - }); - - // Read the response as a stream of data - const reader = response.body.getReader(); - const decoder = new TextDecoder("utf-8"); - let i = 0; - while (true) { - const { done, value } = await reader.read(); - if (done) { - break; - } - - // Massage and parse the chunk of data - const chunk = decoder.decode(value); - const lines = chunk.split("\n"); - - for (const line of lines) { - - if (line.includes('[DONE]')) { - streamComplete = true; - return; - } - - if (line.startsWith("data:")) { - const data = JSON.parse(line.replace("data:", "")); - const content = data.choices[0]?.delta?.content; - if (content) { - contentArray.push(content); - if (!notificationTriggered) { - notificationTriggered = true; - triggerNotify(); - } - } - } - } - } - - function triggerNotify() { - let handler = setInterval(() => { - const content = contentArray.shift(); - if (content && content.length > 0) - dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', content, false); - - if (streamComplete && contentArray.length === 0) { - clearInterval(handler); - dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', '', true); - } - }, 100); - } - - } catch (error) { - // Handle fetch request errors - console.log(error); - } finally { - // TODO: cleanup - } -} \ No newline at end of file diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index 1d45c7a38..8cd4d669a 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -1992,3 +1992,83 @@ window.blazorChart.scatter = { } } } + +if (!window.blazorBootstrap.ai) { + window.blazorBootstrap.ai = {}; +} + +window.blazorBootstrap.ai = { + azureOpenAI: { + chat: { + completions: async (url, key, payload, dotNetHelper) => { + let contentArray = []; + let notificationTriggered = false; + let streamComplete = false; + + try { + const response = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + "api-key": `${key}`, + }, + body: JSON.stringify(payload) + }); + + // Read the response as a stream of data + const reader = response.body.getReader(); + const decoder = new TextDecoder("utf-8"); + let i = 0; + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + + // Message and parse the chunk of data + const chunk = decoder.decode(value); + const lines = chunk.split("\n"); + + for (const line of lines) { + + if (line.includes('[DONE]')) { + streamComplete = true; + return; + } + + if (line.startsWith("data:")) { + const data = JSON.parse(line.replace("data:", "")); + const content = data.choices[0]?.delta?.content; + if (content) { + contentArray.push(content); + if (!notificationTriggered) { + notificationTriggered = true; + triggerNotify(); + } + } + } + } + } + + function triggerNotify() { + let handler = setInterval(() => { + const content = contentArray.shift(); + if (content && content.length > 0) + dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', content, false); + + if (streamComplete && contentArray.length === 0) { + clearInterval(handler); + dotNetHelper.invokeMethodAsync('ChartCompletetionsStreamJs', '', true); + } + }, 100); + } + + } catch (error) { + console.log(error); + } finally { + // TODO: cleanup + } + } + } + } +} From 8601131d5e2954d4c6a5932a4ad73968d42c4f04 Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Thu, 12 Sep 2024 22:19:35 +0530 Subject: [PATCH 07/16] Chat component - code cleanup --- .../Components/AI/Chat/AIChat.razor.cs | 138 ++++++------------ .../Models/AI/OpenAIChatMessage.cs | 3 + .../Models/AI/OpenAIChatPayload.cs | 56 +++++++ blazorbootstrap/Models/AI/OpenAIOptions.cs | 8 - 4 files changed, 104 insertions(+), 101 deletions(-) create mode 100644 blazorbootstrap/Models/AI/OpenAIChatMessage.cs create mode 100644 blazorbootstrap/Models/AI/OpenAIChatPayload.cs delete mode 100644 blazorbootstrap/Models/AI/OpenAIOptions.cs diff --git a/blazorbootstrap/Components/AI/Chat/AIChat.razor.cs b/blazorbootstrap/Components/AI/Chat/AIChat.razor.cs index b27593943..ef9cca972 100644 --- a/blazorbootstrap/Components/AI/Chat/AIChat.razor.cs +++ b/blazorbootstrap/Components/AI/Chat/AIChat.razor.cs @@ -4,16 +4,15 @@ public partial class AIChat : BlazorBootstrapComponentBase { #region Fields and Constants - private string? endpoint; - private string? deploymentName; + private readonly List conversationHistory = new(); private string? apiKey; private string? apiVersion; - - private string userPrompt = string.Empty; - private readonly List conversationHistory = new(); - private bool isRequestInProgress; private string? currentCompletion; + private string? deploymentName; + private string? endpoint; + private bool isRequestInProgress; private DotNetObjectReference? objRef; + private string userPrompt = string.Empty; #endregion @@ -22,52 +21,63 @@ public partial class AIChat : BlazorBootstrapComponentBase protected override async Task OnInitializedAsync() { var configurationSection = Configuration.GetSection("AzureOpenAI"); - if(Configuration is null) - throw new ArgumentException($"`AzureOpenAI` section was not found in the application configuration."); + + if (Configuration is null) + throw new ArgumentException("`AzureOpenAI` section was not found in the application configuration."); endpoint = configurationSection["Endpoint"]; + if (endpoint is null) - throw new ArgumentException($"`Endpoint` key/value was not found in the 'AzureOpenAI' section of the application configuration."); + throw new ArgumentException("`Endpoint` key/value was not found in the 'AzureOpenAI' section of the application configuration."); deploymentName = configurationSection["DeploymentName"]; + if (deploymentName is null) - throw new ArgumentException($"`DeploymentName` key/value was not found in the 'AzureOpenAI' section of the application configuration."); + throw new ArgumentException("`DeploymentName` key/value was not found in the 'AzureOpenAI' section of the application configuration."); apiKey = configurationSection["ApiKey"]; + if (apiKey is null) - throw new ArgumentException($"`ApiKey` key/value was not found in the 'AzureOpenAI' section of the application configuration."); + throw new ArgumentException("`ApiKey` key/value was not found in the 'AzureOpenAI' section of the application configuration."); apiVersion = configurationSection["ApiVersion"]; + if (apiVersion is null) - throw new ArgumentException($"`ApiVersion` key/value was not found in the 'AzureOpenAI' section of the application configuration."); + throw new ArgumentException("`ApiVersion` key/value was not found in the 'AzureOpenAI' section of the application configuration."); objRef ??= DotNetObjectReference.Create(this); + await base.OnInitializedAsync(); } - private async Task SendPromptAsync() + [JSInvokable] + public async Task ChartCompletetionsStreamJs(string content, bool done) { - if (string.IsNullOrWhiteSpace(userPrompt)) - return; + ClearInput(); - var message = new Message("user", userPrompt); - conversationHistory.Add(message); + if (isRequestInProgress) + isRequestInProgress = false; + + if (done) + { + conversationHistory.Add(new OpenAIChatMessage("system", currentCompletion!)); + currentCompletion = ""; + await InvokeAsync(StateHasChanged); + + return; + } - await CreateCompletionAsync(new List { message }); + currentCompletion += content; + await InvokeAsync(StateHasChanged); } private void ClearInput() => userPrompt = string.Empty; - private async Task CreateCompletionAsync(List messages) + private async Task CreateCompletionAsync(List messages) { isRequestInProgress = true; - var payload = new OpenAIChatPayload { - Messages = messages, - MaximumTokens = MaximumTokens, - Temperature = Temperature, - TopP = TopP - }; + var payload = new OpenAIChatPayload { Messages = messages, MaximumTokens = MaximumTokens, Temperature = Temperature, TopP = TopP }; try { @@ -76,7 +86,8 @@ await JSRuntime.InvokeVoidAsync( $"{endpoint}openai/deployments/{deploymentName}/chat/completions?api-version={apiVersion}", apiKey, payload, - objRef!); + objRef! + ); } catch { @@ -84,24 +95,15 @@ await JSRuntime.InvokeVoidAsync( } } - [JSInvokable] - public async Task ChartCompletetionsStreamJs(string content, bool done) + private async Task SendPromptAsync() { - ClearInput(); - - if (isRequestInProgress) - isRequestInProgress = false; - - if (done) - { - conversationHistory.Add(new Message("system", currentCompletion!)); - currentCompletion = ""; - await InvokeAsync(StateHasChanged); + if (string.IsNullOrWhiteSpace(userPrompt)) return; - } - currentCompletion += content; - await InvokeAsync(StateHasChanged); + var message = new OpenAIChatMessage("user", userPrompt); + conversationHistory.Add(message); + + await CreateCompletionAsync(new List { message }); } #endregion @@ -109,7 +111,7 @@ public async Task ChartCompletetionsStreamJs(string content, bool done) #region Properties, Indexers /// - /// The maximum number of tokens to generate shared between the prompt and completion. + /// The maximum number of tokens to generate shared between the prompt and completion. /// The exact limit varies by model. (One token is roughly 4 characters for standard English text) /// Minimum 1 and the maximum tokens is 4096. /// @@ -118,7 +120,7 @@ public async Task ChartCompletetionsStreamJs(string content, bool done) public long MaximumTokens { get; set; } = 2048; /// - /// Controls randomness: Lowering results in less random completions. + /// Controls randomness: Lowering results in less random completions. /// As the temperature approaches zero, the model will become deterministic and repetitive. /// Minimum 1 and the maximum is 2. /// @@ -136,56 +138,6 @@ public async Task ChartCompletetionsStreamJs(string content, bool done) #endregion } -public record Message(string Role, string Content); -public record class OpenAIChatPayload -{ - [JsonPropertyName("messages")] - public List? Messages { get; set; } - /// - /// Controls randomness: Lowering results in less random completions. - /// As the temperature approaches zero, the model will become deterministic and repetitive. - /// Minimum 1 and the maximum is 2. - /// - /// Default value is 1. - [JsonPropertyName("temperature")] - public double Temperature { get; set; } = 1; - /// - /// The maximum number of tokens to generate shared between the prompt and completion. - /// The exact limit varies by model. (One token is roughly 4 characters for standard English text) - /// Minimum 1 and the maximum tokens is 4096. - /// - /// Default value is 2048. This value is limited by gpt-3.5-turbo. - [JsonPropertyName("max_tokens")] - public long MaximumTokens { get; set; } = 2048; - - /// - /// Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered. - /// - /// Default value is 1. - [JsonPropertyName("top_p")] - public double TopP { get; set; } = 1; - - /// - /// How much to penalize new tokens based on their existing frequency in the text so far. - /// Decreases the model's likelihood to repeat the same line verbatim. - /// Minimum 1 and the maximum is 2. - /// - /// Default value is 0. - [JsonPropertyName("frequency_penalty")] - public double FrequencyPenalty { get; set; } = 0; - - /// - /// How much to penalize new tokens based on whether they appear in the text so far. - /// Increases the model's likelihood to talk about new topics. - /// Minimum 1 and the maximum is 2. - /// - /// Default value is 0. - [JsonPropertyName("presence_penalty")] - public float PresencePenalty { get; set; } = 0; - - [JsonPropertyName("stream")] - public bool Stream { get; } = true; -} diff --git a/blazorbootstrap/Models/AI/OpenAIChatMessage.cs b/blazorbootstrap/Models/AI/OpenAIChatMessage.cs new file mode 100644 index 000000000..4a3950e63 --- /dev/null +++ b/blazorbootstrap/Models/AI/OpenAIChatMessage.cs @@ -0,0 +1,3 @@ +namespace BlazorBootstrap; + +public record OpenAIChatMessage(string Role, string Content); diff --git a/blazorbootstrap/Models/AI/OpenAIChatPayload.cs b/blazorbootstrap/Models/AI/OpenAIChatPayload.cs new file mode 100644 index 000000000..92f69887d --- /dev/null +++ b/blazorbootstrap/Models/AI/OpenAIChatPayload.cs @@ -0,0 +1,56 @@ +namespace BlazorBootstrap; + +public record OpenAIChatPayload +{ + #region Properties, Indexers + + /// + /// How much to penalize new tokens based on their existing frequency in the text so far. + /// Decreases the model's likelihood to repeat the same line verbatim. + /// Minimum 1 and the maximum is 2. + /// + /// Default value is 0. + [JsonPropertyName("frequency_penalty")] + public double FrequencyPenalty { get; set; } = 0; + + /// + /// The maximum number of tokens to generate shared between the prompt and completion. + /// The exact limit varies by model. (One token is roughly 4 characters for standard English text) + /// Minimum 1 and the maximum tokens is 4096. + /// + /// Default value is 2048. This value is limited by gpt-3.5-turbo. + [JsonPropertyName("max_tokens")] + public long MaximumTokens { get; set; } = 2048; + + [JsonPropertyName("messages")] + public List? Messages { get; set; } + + /// + /// How much to penalize new tokens based on whether they appear in the text so far. + /// Increases the model's likelihood to talk about new topics. + /// Minimum 1 and the maximum is 2. + /// + /// Default value is 0. + [JsonPropertyName("presence_penalty")] + public float PresencePenalty { get; set; } = 0; + + [JsonPropertyName("stream")] public bool Stream { get; } = true; + + /// + /// Controls randomness: Lowering results in less random completions. + /// As the temperature approaches zero, the model will become deterministic and repetitive. + /// Minimum 1 and the maximum is 2. + /// + /// Default value is 1. + [JsonPropertyName("temperature")] + public double Temperature { get; set; } = 1; + + /// + /// Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered. + /// + /// Default value is 1. + [JsonPropertyName("top_p")] + public double TopP { get; set; } = 1; + + #endregion +} diff --git a/blazorbootstrap/Models/AI/OpenAIOptions.cs b/blazorbootstrap/Models/AI/OpenAIOptions.cs deleted file mode 100644 index de8d72cae..000000000 --- a/blazorbootstrap/Models/AI/OpenAIOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BlazorBootstrap; - -public class OpenAIOptions -{ - public string? ApiKey { get; set; } - public string? ApiUrl { get; set; } - public string? GtpModel { get; set; } -} From 752010d332b1ff5215c2d655ec411df5e909ac04 Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sat, 14 Sep 2024 08:01:45 +0530 Subject: [PATCH 08/16] Markdown component setup inprogress --- .../BlazorBootstrap.Demo.RCL.csproj | 4 ++++ .../Components/Layout/MainLayout.razor.cs | 1 + .../Components/Pages/Index.razor | 5 +++++ .../Markdown/MarkdownDocumentation.razor | 21 +++++++++++++++++++ .../Markdown/Markdown_Demo_01_Examples.razor | 8 +++++++ .../Components/Markdown/Markdown.razor | 6 ++++++ .../Components/Markdown/Markdown.razor.cs | 17 +++++++++++++++ 7 files changed, 62 insertions(+) create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor create mode 100644 blazorbootstrap/Components/Markdown/Markdown.razor create mode 100644 blazorbootstrap/Components/Markdown/Markdown.razor.cs diff --git a/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj b/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj index 3db87cbfd..84bfe77db 100644 --- a/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj +++ b/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj @@ -19,6 +19,10 @@
+ + + + diff --git a/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs b/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs index 97b8ebb37..d4797337d 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs +++ b/BlazorBootstrap.Demo.RCL/Components/Layout/MainLayout.razor.cs @@ -62,6 +62,7 @@ internal override IEnumerable GetNavItems() #endregion Grid + new (){ Id = "514", Text = "Markdown", Href = "/markdown", IconName = IconName.MarkdownFill, ParentId = "5" }, new (){ Id = "514", Text = "Modals", Href = "/modals", IconName = IconName.WindowStack, ParentId = "5" }, new (){ Id = "515", Text = "Offcanvas", Href = "/offcanvas", IconName = IconName.LayoutSidebarReverse, ParentId = "5" }, new (){ Id = "516", Text = "Pagination", Href = "/pagination", IconName = IconName.ThreeDots, ParentId = "5" }, diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor index fbff4e690..e63d9fc44 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Index.razor @@ -130,6 +130,11 @@

Images New

+

Modals

diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor new file mode 100644 index 000000000..c21a0e8ee --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor @@ -0,0 +1,21 @@ +@page "/markdown" + +@title + + + +

Blazor Markdown

+
Provide contextual feedback messages for typical user actions with a handful of available and flexible alert messages.
+ + + + +
Alerts are available for any length of text, as well as an optional close button. For proper styling, use one of the eight colors.
+ + +@code{ + private string pageUrl = "/markdown"; + private string title = "Blazor Markdown Component"; + private string description = "Provide contextual feedback messages for typical user actions with the handful of available and flexible Blazor Bootstrap alert messages."; + private string imageUrl = "https://i.imgur.com/FGgEMp6.jpg"; // TODO: update +} \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor new file mode 100644 index 000000000..9cba09abf --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor @@ -0,0 +1,8 @@ + + # Heading level 1 + ## Heading level 2 + ### Heading level 3 + #### Heading level 4 + ##### Heading level 5 + ###### Heading level 6 + \ No newline at end of file diff --git a/blazorbootstrap/Components/Markdown/Markdown.razor b/blazorbootstrap/Components/Markdown/Markdown.razor new file mode 100644 index 000000000..a4b5e65dc --- /dev/null +++ b/blazorbootstrap/Components/Markdown/Markdown.razor @@ -0,0 +1,6 @@ +@namespace BlazorBootstrap +@inherits BlazorBootstrapComponentBase + + \ No newline at end of file diff --git a/blazorbootstrap/Components/Markdown/Markdown.razor.cs b/blazorbootstrap/Components/Markdown/Markdown.razor.cs new file mode 100644 index 000000000..ef9a81af1 --- /dev/null +++ b/blazorbootstrap/Components/Markdown/Markdown.razor.cs @@ -0,0 +1,17 @@ +namespace BlazorBootstrap; + +public partial class Markdown : BlazorBootstrapComponentBase +{ + #region Properties, Indexers + + /// + /// Gets or sets the content to be rendered within the component. + /// + /// + /// Default value is null. + /// + [Parameter] + public RenderFragment? ChildContent { get; set; } + + #endregion +} From f1ac7b8d356760fc114e2b2a95f98579770c6dd2 Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sat, 28 Sep 2024 10:12:25 +0530 Subject: [PATCH 09/16] Markdown updates --- .../Grid_Demo_14_A_DetailView.razor | 101 +++++++++++------- ...id_Demo_14_B_DetailView_Dynamic_Data.razor | 66 ++++++++++++ .../Grid_DetailView_Documentation.razor | 4 + .../Markdown/Markdown_Demo_01_Examples.razor | 1 + .../Components/Markdown/Markdown.razor | 2 +- .../Components/Markdown/Markdown.razor.cs | 59 +++++++++- 6 files changed, 190 insertions(+), 43 deletions(-) create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_Demo_14_B_DetailView_Dynamic_Data.razor diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_Demo_14_A_DetailView.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_Demo_14_A_DetailView.razor index 38c47764c..a17d058ce 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_Demo_14_A_DetailView.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_Demo_14_A_DetailView.razor @@ -1,66 +1,85 @@ - - + @context.Id - + @context.Name - - @context.Designation - - - @context.DOJ - - + @context.IsActive - -
-
Id
-
@context.Id
-
-
-
Name
-
@context.Name
-
-
-
Designation
-
@context.Designation
-
-
-
DOJ
-
@context.DOJ
-
-
-
IsActive
-
@context.IsActive
-
+ + + + + + + @emp1.Id + + + @emp1.Description + + + @emp1.Unit + + + @emp1.Quantity + + + + +
@code { - private List employees = new List { - new Employee1 { Id = 107, Name = "Alice", Designation = "AI Engineer", DOJ = new DateOnly(1998, 11, 17), IsActive = true }, - new Employee1 { Id = 103, Name = "Bob", Designation = "Senior DevOps Engineer", DOJ = new DateOnly(1985, 1, 5), IsActive = true }, - new Employee1 { Id = 106, Name = "John", Designation = "Data Engineer", DOJ = new DateOnly(1995, 4, 17), IsActive = true }, - new Employee1 { Id = 104, Name = "Pop", Designation = "Associate Architect", DOJ = new DateOnly(1985, 6, 8), IsActive = false }, - new Employee1 { Id = 105, Name = "Ronald", Designation = "Senior Data Engineer", DOJ = new DateOnly(1991, 8, 23), IsActive = true } + private List products = new List { + new Product { Id = 1, Name = "Product 1", IsActive = true }, + new Product { Id = 2, Name = "Product 2", IsActive = true }, + new Product { Id = 3, Name = "Product 3", IsActive = true }, + new Product { Id = 4, Name = "Product 4", IsActive = true }, + new Product { Id = 5, Name = "Product 5", IsActive = true } }; - public record class Employee1 + private List ingredients = new List { + new Ingredient { Id = 105, ProductId = 1, Description = "Ingredient 1", Unit = "UNIT1", Quantity = 350 }, + new Ingredient { Id = 106, ProductId = 1, Description = "Ingredient 2", Unit = "UNIT1", Quantity = 600 }, + new Ingredient { Id = 107, ProductId = 1, Description = "Ingredient 3", Unit = "UNIT2", Quantity = 13 }, + new Ingredient { Id = 108, ProductId = 1, Description = "Ingredient 4", Unit = "UNIT3", Quantity = 25 }, + new Ingredient { Id = 109, ProductId = 2, Description = "Ingredient 5", Unit = "UNIT1", Quantity = 750 }, + new Ingredient { Id = 110, ProductId = 2, Description = "Ingredient 3", Unit = "UNIT2", Quantity = 13 }, + new Ingredient { Id = 111, ProductId = 1, Description = "Ingredient 4", Unit = "UNIT3", Quantity = 25 }, + new Ingredient { Id = 112, ProductId = 2, Description = "Ingredient 5", Unit = "UNIT1", Quantity = 750 }, + new Ingredient { Id = 113, ProductId = 4, Description = "Ingredient 3", Unit = "UNIT2", Quantity = 13 }, + new Ingredient { Id = 114, ProductId = 5, Description = "Ingredient 4", Unit = "UNIT3", Quantity = 25 }, + new Ingredient { Id = 115, ProductId = 2, Description = "Ingredient 5", Unit = "UNIT1", Quantity = 750 }, + }; + + private IEnumerable GetIngredients(int productId) => ingredients.Where(i => i.ProductId == productId); + + public record class Product { public int Id { get; set; } public string? Name { get; set; } - public string? Designation { get; set; } - public DateOnly DOJ { get; set; } public bool IsActive { get; set; } } + + public record class Ingredient + { + public int Id { get; set; } + public int ProductId { get; set; } + public string? Description { get; set; } + public string? Unit { get; set; } + public int Quantity { get; set; } + } } diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_Demo_14_B_DetailView_Dynamic_Data.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_Demo_14_B_DetailView_Dynamic_Data.razor new file mode 100644 index 000000000..38c47764c --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_Demo_14_B_DetailView_Dynamic_Data.razor @@ -0,0 +1,66 @@ + + + + + @context.Id + + + @context.Name + + + @context.Designation + + + @context.DOJ + + + @context.IsActive + + + + +
+
Id
+
@context.Id
+
+
+
Name
+
@context.Name
+
+
+
Designation
+
@context.Designation
+
+
+
DOJ
+
@context.DOJ
+
+
+
IsActive
+
@context.IsActive
+
+
+ +
+ +@code { + private List employees = new List { + new Employee1 { Id = 107, Name = "Alice", Designation = "AI Engineer", DOJ = new DateOnly(1998, 11, 17), IsActive = true }, + new Employee1 { Id = 103, Name = "Bob", Designation = "Senior DevOps Engineer", DOJ = new DateOnly(1985, 1, 5), IsActive = true }, + new Employee1 { Id = 106, Name = "John", Designation = "Data Engineer", DOJ = new DateOnly(1995, 4, 17), IsActive = true }, + new Employee1 { Id = 104, Name = "Pop", Designation = "Associate Architect", DOJ = new DateOnly(1985, 6, 8), IsActive = false }, + new Employee1 { Id = 105, Name = "Ronald", Designation = "Senior Data Engineer", DOJ = new DateOnly(1991, 8, 23), IsActive = true } + }; + + public record class Employee1 + { + public int Id { get; set; } + public string? Name { get; set; } + public string? Designation { get; set; } + public DateOnly DOJ { get; set; } + public bool IsActive { get; set; } + } +} diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_DetailView_Documentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_DetailView_Documentation.razor index 5e33560fc..beae70930 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_DetailView_Documentation.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Grid/14-detail-view/Grid_DetailView_Documentation.razor @@ -13,6 +13,10 @@
To enable detail view, set the AllowDetailView parameter to true. In the following example, existing <GridColumn> tags are nested under <GridColumns> tag to distinguish them from <GridDetailView>.
+ +
+ + @code { private const string pageUrl = "/grid/detail-view"; private const string title = "Blazor Grid Component - Detail View"; diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor index 9cba09abf..d48958545 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor @@ -1,4 +1,5 @@  + Test # Heading level 1 ## Heading level 2 ### Heading level 3 diff --git a/blazorbootstrap/Components/Markdown/Markdown.razor b/blazorbootstrap/Components/Markdown/Markdown.razor index a4b5e65dc..0a5a86345 100644 --- a/blazorbootstrap/Components/Markdown/Markdown.razor +++ b/blazorbootstrap/Components/Markdown/Markdown.razor @@ -2,5 +2,5 @@ @inherits BlazorBootstrapComponentBase \ No newline at end of file diff --git a/blazorbootstrap/Components/Markdown/Markdown.razor.cs b/blazorbootstrap/Components/Markdown/Markdown.razor.cs index ef9a81af1..0ef2bfd1d 100644 --- a/blazorbootstrap/Components/Markdown/Markdown.razor.cs +++ b/blazorbootstrap/Components/Markdown/Markdown.razor.cs @@ -1,7 +1,13 @@ -namespace BlazorBootstrap; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.RenderTree; +using System.Text.RegularExpressions; + +namespace BlazorBootstrap; public partial class Markdown : BlazorBootstrapComponentBase { + private string? html; + #region Properties, Indexers /// @@ -14,4 +20,55 @@ public partial class Markdown : BlazorBootstrapComponentBase public RenderFragment? ChildContent { get; set; } #endregion + + protected override void OnInitialized() + { + if (ChildContent is not null) + { + var builder = new RenderTreeBuilder(); + ChildContent.Invoke(builder); + var frames = builder.GetFrames().Array; + var lines = GetLines(frames); + var patterns = GetRules(); + foreach (var pattern in patterns) + { + for (var i = 0; i < lines.Count; i++) + lines[i] = Regex.Replace(lines[i], pattern.Rule, pattern.Template); + } + + html = string.Join("\n", lines); + } + + base.OnInitialized(); + } + List GetLines(RenderTreeFrame[] frames) + { + var inputs = new List(); + foreach (var frame in frames) + { + if (frame.MarkupContent is not null) + { + var lines = frame.MarkupContent.Split("\r\n"); + foreach (var line in lines) + inputs.Add(line.Trim()); + } + } + + return inputs; + } + + private List GetRules() + { + return new List{ + // header rules + new(@"^#{6}\s?([^\n]+)", "
$1
"), + new(@"^#{5}\s?([^\n]+)", "
$1
"), + new(@"^#{4}\s?([^\n]+)", "

$1

"), + new(@"^#{3}\s?([^\n]+)", "

$1

"), + new(@"^#{2}\s?([^\n]+)", "

$1

"), + new(@"^#{1}\s?([^\n]+)", "

$1

"), + }; + } } + +public record MarkdownPattern(string Rule, string Template); \ No newline at end of file From 871a3977b21cbdb156ceb9f5060c7e710fed1fef Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sat, 28 Sep 2024 11:55:05 +0530 Subject: [PATCH 10/16] Markdown updates --- .../Markdown/MarkdownDocumentation.razor | 26 ++++++++++++++++++- .../Markdown/Markdown_Demo_01_Examples.razor | 6 +++++ .../Markdown/Markdown_Demo_02_Headers.razor | 7 +++++ ...n_Demo_03_Paragraphs_and_Line_Breaks.razor | 4 +++ .../Markdown_Demo_04_Blockquotes.razor | 6 +++++ .../Markdown_Demo_05_Horizontal_Rules.razor | 6 +++++ ..._Emphasis_bold_italics_strikethrough.razor | 5 ++++ .../Markdown_Demo_07_Code_Highlighting.razor | 5 ++++ .../Components/Markdown/Markdown.razor.cs | 23 +++++++++++++++- 9 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_02_Headers.razor create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_03_Paragraphs_and_Line_Breaks.razor create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_04_Blockquotes.razor create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_05_Horizontal_Rules.razor create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_06_Emphasis_bold_italics_strikethrough.razor create mode 100644 BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_07_Code_Highlighting.razor diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor index c21a0e8ee..7fd85488b 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor @@ -10,9 +10,33 @@ -
Alerts are available for any length of text, as well as an optional close button. For proper styling, use one of the eight colors.
+
+ +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + @code{ private string pageUrl = "/markdown"; private string title = "Blazor Markdown Component"; diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor index d48958545..1a0d708d7 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_01_Examples.razor @@ -6,4 +6,10 @@ #### Heading level 4 ##### Heading level 5 ###### Heading level 6 + + + __bold text__ + **bold text** + _italic text_ + *italic text* \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_02_Headers.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_02_Headers.razor new file mode 100644 index 000000000..9792ee11e --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_02_Headers.razor @@ -0,0 +1,7 @@ + + # This is a H1 header + ## This is a H2 header + ### This is a H3 header + #### This is a H4 header + ##### This is a H5 header + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_03_Paragraphs_and_Line_Breaks.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_03_Paragraphs_and_Line_Breaks.razor new file mode 100644 index 000000000..17fd6f8ce --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_03_Paragraphs_and_Line_Breaks.razor @@ -0,0 +1,4 @@ + + Add lines between your text with the **Enter** key. + Your text gets better spaced and makes it easier to read. + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_04_Blockquotes.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_04_Blockquotes.razor new file mode 100644 index 000000000..8e065dd41 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_04_Blockquotes.razor @@ -0,0 +1,6 @@ + + > Single line quote + >> Nested quote + >> multiple line + >> quote + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_05_Horizontal_Rules.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_05_Horizontal_Rules.razor new file mode 100644 index 000000000..e7d9c79e5 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_05_Horizontal_Rules.razor @@ -0,0 +1,6 @@ + + above + + ---- + below + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_06_Emphasis_bold_italics_strikethrough.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_06_Emphasis_bold_italics_strikethrough.razor new file mode 100644 index 000000000..9ab8f7c56 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_06_Emphasis_bold_italics_strikethrough.razor @@ -0,0 +1,5 @@ + + Use _emphasis_ in comments to express **strong** opinions and point out ~~corrections~~ + **_Bold, italicized text_** + **~~Bold, strike-through text~~** + \ No newline at end of file diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_07_Code_Highlighting.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_07_Code_Highlighting.razor new file mode 100644 index 000000000..bd191e4c3 --- /dev/null +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_07_Code_Highlighting.razor @@ -0,0 +1,5 @@ + + ``` + sudo npm install vsoagent-installer -g + ``` + \ No newline at end of file diff --git a/blazorbootstrap/Components/Markdown/Markdown.razor.cs b/blazorbootstrap/Components/Markdown/Markdown.razor.cs index 0ef2bfd1d..95c91ae88 100644 --- a/blazorbootstrap/Components/Markdown/Markdown.razor.cs +++ b/blazorbootstrap/Components/Markdown/Markdown.razor.cs @@ -60,13 +60,34 @@ List GetLines(RenderTreeFrame[] frames) private List GetRules() { return new List{ - // header rules + // Headers new(@"^#{6}\s?([^\n]+)", "
$1
"), new(@"^#{5}\s?([^\n]+)", "
$1
"), new(@"^#{4}\s?([^\n]+)", "

$1

"), new(@"^#{3}\s?([^\n]+)", "

$1

"), new(@"^#{2}\s?([^\n]+)", "

$1

"), new(@"^#{1}\s?([^\n]+)", "

$1

"), + + // Paragragh + new(@"\n\n", "

$1

"), + + // Blockquotes + new(@"^> (.*)$", "
$1
"), + + // Horizontal rules + new(@"^\-{3,}$", "
"), + + // Emphasis (bold, italics, strikethrough) + new(@"\*\*(.*?)\*\*", "$1"), + new(@"__(.*?)__", "$1"), + new(@"\*(.*?)\*", "$1"), + new(@"_(.*?)_", "$1"), + new(@"~~(.*?)~~", "$1"), + + // Code highlighting + new(@"\`(\w+)\n(.*?)\n```", "
$2
"), + + // Tables }; } } From 30223407b9726461e8ad980914a258bbffd6eac5 Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sat, 28 Sep 2024 19:59:50 +0530 Subject: [PATCH 11/16] Markdown - Code highlighting changes --- .../Components/Pages/Markdown/MarkdownDocumentation.razor | 4 ++-- .../Markdown/Markdown_Demo_07_Code_Highlighting.razor | 8 +++++++- blazorbootstrap/Components/Markdown/Markdown.razor.cs | 3 ++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor index 7fd85488b..659c2acd1 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor @@ -9,7 +9,7 @@ - +@*
@@ -31,7 +31,7 @@
- + *@
diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_07_Code_Highlighting.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_07_Code_Highlighting.razor index bd191e4c3..137c1352d 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_07_Code_Highlighting.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_07_Code_Highlighting.razor @@ -1,5 +1,11 @@  + ```js + const count = records.length; ``` - sudo npm install vsoagent-installer -g + + + + ```csharp + Console.WriteLine("Hello, World!"); ``` \ No newline at end of file diff --git a/blazorbootstrap/Components/Markdown/Markdown.razor.cs b/blazorbootstrap/Components/Markdown/Markdown.razor.cs index 95c91ae88..c57fc71c2 100644 --- a/blazorbootstrap/Components/Markdown/Markdown.razor.cs +++ b/blazorbootstrap/Components/Markdown/Markdown.razor.cs @@ -85,7 +85,8 @@ private List GetRules() new(@"~~(.*?)~~", "$1"), // Code highlighting - new(@"\`(\w+)\n(.*?)\n```", "
$2
"), + new(@"\```(\w+)", "
"),
+            new(@"```", "
"), // Tables }; From e7602ae18956d4a1dce65cd2eabedd2ae08cb7f1 Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sun, 29 Sep 2024 16:10:19 +0530 Subject: [PATCH 12/16] Markdown - Paragraphs and line breaks - updates --- .../Markdown/MarkdownDocumentation.razor | 4 +- ...n_Demo_03_Paragraphs_and_Line_Breaks.razor | 6 +- .../Components/Markdown/Markdown.razor.cs | 89 +++++++++++++------ blazorbootstrap/Models/MarkdownPattern.cs | 3 + 4 files changed, 73 insertions(+), 29 deletions(-) create mode 100644 blazorbootstrap/Models/MarkdownPattern.cs diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor index 659c2acd1..0318c8faa 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor @@ -11,7 +11,7 @@ @*
- + *@
@@ -31,7 +31,7 @@
- *@ +
diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_03_Paragraphs_and_Line_Breaks.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_03_Paragraphs_and_Line_Breaks.razor index 17fd6f8ce..24b026e26 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_03_Paragraphs_and_Line_Breaks.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/Markdown_Demo_03_Paragraphs_and_Line_Breaks.razor @@ -1,4 +1,8 @@  - Add lines between your text with the **Enter** key. + Add lines between your text with the **Enter** key. + Your text gets better spaced and makes it easier to read. + + + Add lines between your text with the **Enter** key. Your text gets better spaced and makes it easier to read. \ No newline at end of file diff --git a/blazorbootstrap/Components/Markdown/Markdown.razor.cs b/blazorbootstrap/Components/Markdown/Markdown.razor.cs index c57fc71c2..815c29c3d 100644 --- a/blazorbootstrap/Components/Markdown/Markdown.razor.cs +++ b/blazorbootstrap/Components/Markdown/Markdown.razor.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Components.Rendering; -using Microsoft.AspNetCore.Components.RenderTree; using System.Text.RegularExpressions; namespace BlazorBootstrap; @@ -23,34 +22,66 @@ public partial class Markdown : BlazorBootstrapComponentBase protected override void OnInitialized() { - if (ChildContent is not null) + var lines = GetLines(); + if (lines.Any()) { - var builder = new RenderTreeBuilder(); - ChildContent.Invoke(builder); - var frames = builder.GetFrames().Array; - var lines = GetLines(frames); - var patterns = GetRules(); - foreach (var pattern in patterns) - { - for (var i = 0; i < lines.Count; i++) - lines[i] = Regex.Replace(lines[i], pattern.Rule, pattern.Template); - } + // remove start and end blank lines + if (lines[0] == "") + lines.RemoveAt(0); - html = string.Join("\n", lines); + if (lines[lines.Count - 1] == "") + lines.RemoveAt(lines.Count - 1); } + var markup = ApplyRules(lines); + html = ApplyFullMarkupRules(markup); base.OnInitialized(); } - List GetLines(RenderTreeFrame[] frames) + + private string ApplyRules(List lines) + { + var patterns = GetRules(); + foreach (var pattern in patterns) + { + for (var i = 0; i < lines.Count; i++) + lines[i] = Regex.Replace(lines[i], pattern.Rule, pattern.Template); + } + + return string.Join("\n", lines); + } + + private string ApplyFullMarkupRules(string markup) + { + var outputMarkup = markup; + + var patterns = GetFullMarkupRules(); + + foreach (var pattern in patterns) + { + markup = Regex.Replace(markup, pattern.Rule, pattern.Template); + } + + return markup; + } + + List GetLines() { var inputs = new List(); - foreach (var frame in frames) + + if (ChildContent is not null) { - if (frame.MarkupContent is not null) + var builder = new RenderTreeBuilder(); + ChildContent.Invoke(builder); + + var frames = builder.GetFrames().Array; + foreach (var frame in frames) { - var lines = frame.MarkupContent.Split("\r\n"); - foreach (var line in lines) - inputs.Add(line.Trim()); + if (frame.MarkupContent is not null) + { + var lines = frame.MarkupContent.Split("\r\n"); + foreach (var line in lines) + inputs.Add(line.Trim()); + } } } @@ -59,7 +90,8 @@ List GetLines(RenderTreeFrame[] frames) private List GetRules() { - return new List{ + return new List + { // Headers new(@"^#{6}\s?([^\n]+)", "
$1
"), new(@"^#{5}\s?([^\n]+)", "
$1
"), @@ -68,14 +100,11 @@ private List GetRules() new(@"^#{2}\s?([^\n]+)", "

$1

"), new(@"^#{1}\s?([^\n]+)", "

$1

"), - // Paragragh - new(@"\n\n", "

$1

"), - // Blockquotes new(@"^> (.*)$", "
$1
"), // Horizontal rules - new(@"^\-{3,}$", "
"), + new(@"^\-{3,}$", "
"), // Emphasis (bold, italics, strikethrough) new(@"\*\*(.*?)\*\*", "$1"), @@ -91,6 +120,14 @@ private List GetRules() // Tables }; } -} -public record MarkdownPattern(string Rule, string Template); \ No newline at end of file + private List GetFullMarkupRules() + { + return new List + { + // Paragraphs and line breaks + new(@"(?"), + new(@"([^\n\n]+\n?)", "

$1

"), + }; + } +} diff --git a/blazorbootstrap/Models/MarkdownPattern.cs b/blazorbootstrap/Models/MarkdownPattern.cs new file mode 100644 index 000000000..c5095e5b7 --- /dev/null +++ b/blazorbootstrap/Models/MarkdownPattern.cs @@ -0,0 +1,3 @@ +namespace BlazorBootstrap; + +public record MarkdownPattern(string Rule, string Template); From 025872b3e470d28e6f95cc074cb4c2ac8497c05a Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sun, 29 Sep 2024 21:21:35 +0530 Subject: [PATCH 13/16] Markdown updates --- .../Pages/Markdown/MarkdownDocumentation.razor | 4 ++-- blazorbootstrap/Components/Markdown/Markdown.razor.cs | 10 ++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor index 0318c8faa..ca711ea73 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/Markdown/MarkdownDocumentation.razor @@ -5,7 +5,7 @@

Blazor Markdown

-
Provide contextual feedback messages for typical user actions with a handful of available and flexible alert messages.
+
Use Blazor Bootstrap Markdown component to add formatting, tables, images, and more to your project pages.
@@ -40,6 +40,6 @@ @code{ private string pageUrl = "/markdown"; private string title = "Blazor Markdown Component"; - private string description = "Provide contextual feedback messages for typical user actions with the handful of available and flexible Blazor Bootstrap alert messages."; + private string description = "Use Blazor Bootstrap Markdown component to add formatting, tables, images, and more to your project pages."; private string imageUrl = "https://i.imgur.com/FGgEMp6.jpg"; // TODO: update } \ No newline at end of file diff --git a/blazorbootstrap/Components/Markdown/Markdown.razor.cs b/blazorbootstrap/Components/Markdown/Markdown.razor.cs index 815c29c3d..ccf2ca898 100644 --- a/blazorbootstrap/Components/Markdown/Markdown.razor.cs +++ b/blazorbootstrap/Components/Markdown/Markdown.razor.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.Components.Rendering; using System.Text.RegularExpressions; namespace BlazorBootstrap; @@ -52,14 +53,10 @@ private string ApplyRules(List lines) private string ApplyFullMarkupRules(string markup) { - var outputMarkup = markup; - var patterns = GetFullMarkupRules(); foreach (var pattern in patterns) - { markup = Regex.Replace(markup, pattern.Rule, pattern.Template); - } return markup; } @@ -101,7 +98,8 @@ private List GetRules() new(@"^#{1}\s?([^\n]+)", "

$1

"), // Blockquotes - new(@"^> (.*)$", "
$1
"), + new(@"^>{1}\s(.*)$", "
$1
"), + //new(@"^(>>)+ (.*)$", "

$2

"), // Horizontal rules new(@"^\-{3,}$", "
"), From b22d74094fe53c4c45f1f4349df53ecb1f539202 Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sun, 29 Sep 2024 21:28:47 +0530 Subject: [PATCH 14/16] Markdown updates --- .../Components/Markdown/Markdown.razor.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/blazorbootstrap/Components/Markdown/Markdown.razor.cs b/blazorbootstrap/Components/Markdown/Markdown.razor.cs index ccf2ca898..86508bedd 100644 --- a/blazorbootstrap/Components/Markdown/Markdown.razor.cs +++ b/blazorbootstrap/Components/Markdown/Markdown.razor.cs @@ -1,5 +1,4 @@ -using Microsoft.AspNetCore.Components.Forms; -using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Rendering; using System.Text.RegularExpressions; namespace BlazorBootstrap; @@ -116,6 +115,27 @@ private List GetRules() new(@"```", ""), // Tables + + // Lists + // Ordered or numbered lists + + // Bulleted lists + + // Nested lists + + // Links + + // Anchor links + + // Images + + // Checklist or task list + + // Emoji + + // Mathematical notation and characters + + // Mermaid diagrams }; } From 76e70b2bdda440d62b4c5ca8ee0b44f9fd311697 Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sun, 29 Sep 2024 23:05:44 +0530 Subject: [PATCH 15/16] AIChat and Markdown updates --- .../AI/AIChat/AIChat_Demo_01_Examples.razor | 5 ++++- blazorbootstrap/Components/AI/Chat/AIChat.razor | 15 ++++++++++----- .../Components/AI/Chat/AIChat.razor.cs | 14 +++++++++++++- .../Components/Core/BlazorBootstrapInterop.cs | 14 ++++++++++++++ .../Components/Markdown/Markdown.razor.cs | 16 +++++++++++++++- blazorbootstrap/Constants/BootstrapClass.cs | 2 ++ blazorbootstrap/wwwroot/blazor.bootstrap.js | 10 ++++++++++ 7 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 blazorbootstrap/Components/Core/BlazorBootstrapInterop.cs diff --git a/BlazorBootstrap.Demo.RCL/Components/Pages/AI/AIChat/AIChat_Demo_01_Examples.razor b/BlazorBootstrap.Demo.RCL/Components/Pages/AI/AIChat/AIChat_Demo_01_Examples.razor index eeafc3f3b..a9cdd295b 100644 --- a/BlazorBootstrap.Demo.RCL/Components/Pages/AI/AIChat/AIChat_Demo_01_Examples.razor +++ b/BlazorBootstrap.Demo.RCL/Components/Pages/AI/AIChat/AIChat_Demo_01_Examples.razor @@ -1 +1,4 @@ - + diff --git a/blazorbootstrap/Components/AI/Chat/AIChat.razor b/blazorbootstrap/Components/AI/Chat/AIChat.razor index af19ed5db..254561235 100644 --- a/blazorbootstrap/Components/AI/Chat/AIChat.razor +++ b/blazorbootstrap/Components/AI/Chat/AIChat.razor @@ -1,8 +1,8 @@ @namespace BlazorBootstrap @inherits BlazorBootstrapComponentBase -
-
+ } diff --git a/blazorbootstrap/Components/AI/Chat/AIChat.razor.cs b/blazorbootstrap/Components/AI/Chat/AIChat.razor.cs index ef9cca972..ab33601c9 100644 --- a/blazorbootstrap/Components/AI/Chat/AIChat.razor.cs +++ b/blazorbootstrap/Components/AI/Chat/AIChat.razor.cs @@ -63,12 +63,13 @@ public async Task ChartCompletetionsStreamJs(string content, bool done) conversationHistory.Add(new OpenAIChatMessage("system", currentCompletion!)); currentCompletion = ""; await InvokeAsync(StateHasChanged); - + await JSRuntime.InvokeVoidAsync(BlazorBootstrapInterop.ScrollToElementBottom, Id); return; } currentCompletion += content; await InvokeAsync(StateHasChanged); + await JSRuntime.InvokeVoidAsync(BlazorBootstrapInterop.ScrollToElementBottom, Id); } private void ClearInput() => userPrompt = string.Empty; @@ -110,6 +111,17 @@ private async Task SendPromptAsync() #region Properties, Indexers + protected override string? ClassNames => + BuildClassNames(Class, + (BootstrapClass.Container, true)); + + protected override string? StyleNames => + BuildStyleNames(Style, + //("min-height:200px", true), + //("max-height:400px", true), + ("overflow-x:hidden", true), + ("overflow-y:auto", true)); + /// /// The maximum number of tokens to generate shared between the prompt and completion. /// The exact limit varies by model. (One token is roughly 4 characters for standard English text) diff --git a/blazorbootstrap/Components/Core/BlazorBootstrapInterop.cs b/blazorbootstrap/Components/Core/BlazorBootstrapInterop.cs new file mode 100644 index 000000000..a3d057186 --- /dev/null +++ b/blazorbootstrap/Components/Core/BlazorBootstrapInterop.cs @@ -0,0 +1,14 @@ +namespace BlazorBootstrap; + +public class BlazorBootstrapInterop +{ + #region Fields and Constants + + private const string Prefix = "window.blazorBootstrap."; + + public const string ScrollToElementBottom = Prefix + "scrollToElementBottom"; + + public const string ScrollToElementTop = Prefix + "scrollToElementTop"; + + #endregion +} diff --git a/blazorbootstrap/Components/Markdown/Markdown.razor.cs b/blazorbootstrap/Components/Markdown/Markdown.razor.cs index 86508bedd..fe95e23e7 100644 --- a/blazorbootstrap/Components/Markdown/Markdown.razor.cs +++ b/blazorbootstrap/Components/Markdown/Markdown.razor.cs @@ -21,6 +21,21 @@ public partial class Markdown : BlazorBootstrapComponentBase #endregion protected override void OnInitialized() + { + ParseMarkdown(); + + base.OnInitialized(); + } + + protected override void OnParametersSet() + { + if (IsRenderComplete) + ParseMarkdown(); + + base.OnParametersSet(); + } + + private void ParseMarkdown() { var lines = GetLines(); if (lines.Any()) @@ -35,7 +50,6 @@ protected override void OnInitialized() var markup = ApplyRules(lines); html = ApplyFullMarkupRules(markup); - base.OnInitialized(); } private string ApplyRules(List lines) diff --git a/blazorbootstrap/Constants/BootstrapClass.cs b/blazorbootstrap/Constants/BootstrapClass.cs index 03fc8791d..73919b3ad 100644 --- a/blazorbootstrap/Constants/BootstrapClass.cs +++ b/blazorbootstrap/Constants/BootstrapClass.cs @@ -65,6 +65,8 @@ public static class BootstrapClass public const string ConfirmationModal = "modal-confirmation"; + public const string Container = "container"; + public const string Disabled = "disabled"; public const string DisplayNone = "d-none"; public const string DisplayBlock = "d-block"; diff --git a/blazorbootstrap/wwwroot/blazor.bootstrap.js b/blazorbootstrap/wwwroot/blazor.bootstrap.js index 8cd4d669a..055742f0d 100644 --- a/blazorbootstrap/wwwroot/blazor.bootstrap.js +++ b/blazorbootstrap/wwwroot/blazor.bootstrap.js @@ -933,6 +933,16 @@ window.blazorBootstrap = { return false; }, + scrollToElementBottom: (elementId) => { + let el = document.getElementById(elementId); + if (el) + el.scrollTop = el.scrollHeight; + }, + scrollToElementTop: (elementId) => { + let el = document.getElementById(elementId); + if (el) + el.scrollTop = 0; + } } window.blazorChart = { From 0f2141d91cc015ecd26ce58660831b56b0f9547c Mon Sep 17 00:00:00 2001 From: Vikram Reddy Date: Sun, 29 Sep 2024 23:06:59 +0530 Subject: [PATCH 16/16] Removed unnecessary .csproj entires --- BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj b/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj index 84bfe77db..3db87cbfd 100644 --- a/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj +++ b/BlazorBootstrap.Demo.RCL/BlazorBootstrap.Demo.RCL.csproj @@ -19,10 +19,6 @@ - - - -