From eba0d4c272167dd924d36f192bf36b90b43a7b82 Mon Sep 17 00:00:00 2001 From: Marco Minerva Date: Thu, 20 Feb 2025 11:56:48 +0100 Subject: [PATCH] Add footer to layout and enhance document handling - Updated `MainLayout.razor` to include a footer displaying the framework description, styled with new CSS. - Modified `MainLayout.razor.css` to add styles for the footer. - Enhanced `Ask.razor` with a new `ToastService` for user notifications and improved message handling. - Updated `Documents.razor` to restrict file uploads to specific formats and improved error handling with notifications for uploads and deletions. --- .../Components/Layout/MainLayout.razor | 10 ++- .../Components/Layout/MainLayout.razor.css | 17 ++++ .../Components/Pages/Ask.razor | 83 +++++++++++++++---- .../Components/Pages/Documents.razor | 14 +++- 4 files changed, 104 insertions(+), 20 deletions(-) diff --git a/SqlDatabaseVectorSearch/Components/Layout/MainLayout.razor b/SqlDatabaseVectorSearch/Components/Layout/MainLayout.razor index c6700e3..6b1f8e4 100644 --- a/SqlDatabaseVectorSearch/Components/Layout/MainLayout.razor +++ b/SqlDatabaseVectorSearch/Components/Layout/MainLayout.razor @@ -1,4 +1,6 @@ -@inherits LayoutComponentBase +@using System.Runtime.InteropServices + +@inherits LayoutComponentBase @@ -21,6 +23,12 @@ @Body + + + + @code { diff --git a/SqlDatabaseVectorSearch/Components/Layout/MainLayout.razor.css b/SqlDatabaseVectorSearch/Components/Layout/MainLayout.razor.css index a3ae64a..fd8d52c 100644 --- a/SqlDatabaseVectorSearch/Components/Layout/MainLayout.razor.css +++ b/SqlDatabaseVectorSearch/Components/Layout/MainLayout.razor.css @@ -1,3 +1,20 @@ +.footer-content { + display: flex; + justify-content: space-between; + align-items: center; + background-color: #f8f9fa; + border-top: 1px solid #dee2e6; +} + + .footer-content span { + font-size: 0.9rem; + color: #6c757d; + } + + .footer-content .ms-auto { + margin-left: auto; + } + #blazor-error-ui { color-scheme: light only; background: lightyellow; diff --git a/SqlDatabaseVectorSearch/Components/Pages/Ask.razor b/SqlDatabaseVectorSearch/Components/Pages/Ask.razor index 06239eb..f9d4691 100644 --- a/SqlDatabaseVectorSearch/Components/Pages/Ask.razor +++ b/SqlDatabaseVectorSearch/Components/Pages/Ask.razor @@ -52,15 +52,17 @@ @if (message.IsCompleted) {
- @*
- +
+ -
*@ +
- + + +
} @@ -105,6 +107,9 @@ private Guid conversationId = Guid.NewGuid(); private bool isAsking = false; + [Inject] + protected ToastService ToastService { get; set; } = default!; + private async Task HandleKeyDown(KeyboardEventArgs e) { if (e.Key == "Enter" && !isAsking && !string.IsNullOrWhiteSpace(question)) @@ -117,24 +122,28 @@ { isAsking = true; + var userQuestion = new Question(conversationId, question!); + var userMessage = new Message { Text = userQuestion.Text, Role = "user" }; + messages.Add(userMessage); + + var assistantMessage = new Message { Role = "assistant" }; + messages.Add(assistantMessage); + + question = null; + await Task.Yield(); + try { - var userQuestion = new Question(conversationId, question!); - var userMessage = new Message { Text = userQuestion.Text, Role = "user" }; - messages.Add(userMessage); - - var assistantMessage = new Message { Role = "assistant" }; - messages.Add(assistantMessage); - - question = null; - await Task.Yield(); - await using var scope = ServiceProvider.CreateAsyncScope(); var vectorSearchService = scope.ServiceProvider.GetRequiredService(); var response = vectorSearchService.AskStreamingAsync(userQuestion); await foreach (var delta in response) { + if (delta.StreamState == StreamState.Start) + { + assistantMessage.TokenUsage = FormatTokenUsage(delta.TokenUsage); + } if (delta.StreamState == StreamState.Append) { assistantMessage.Text += delta.Answer; @@ -142,12 +151,18 @@ else if (delta.StreamState == StreamState.End) { assistantMessage.IsCompleted = true; + assistantMessage.TokenUsage += FormatTokenUsage(delta.TokenUsage); } await Task.Yield(); StateHasChanged(); } } + catch (Exception ex) + { + assistantMessage.Text = $"There was an error while processing the question: {ex.Message}"; + assistantMessage.IsCompleted = true; + } finally { isAsking = false; @@ -166,6 +181,40 @@ await JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", text); } + private string FormatTokenUsage(TokenUsageResponse? tokenUsageResponse) + { + if (tokenUsageResponse is null) + { + return string.Empty; + } + + var reformulation = tokenUsageResponse.Reformulation is not null + ? $"

Reformulation:
{FormatTokenUsageDetails(tokenUsageResponse.Reformulation)}

" + : string.Empty; + + var embeddingTokenCount = tokenUsageResponse.EmbeddingTokenCount.HasValue + ? $"

Embedding Token Count: {tokenUsageResponse.EmbeddingTokenCount}

" + : string.Empty; + + var question = tokenUsageResponse.Question is not null + ? $"

Question:
{FormatTokenUsageDetails(tokenUsageResponse.Question)}

" + : string.Empty; + + return $"{reformulation}{embeddingTokenCount}{question}"; + } + + private string FormatTokenUsageDetails(TokenUsage? tokenUsage) + { + if (tokenUsage is null) + { + return string.Empty; + } + + return $"Input Token Count: {tokenUsage.InputTokenCount}
" + + $"Output Token Count: {tokenUsage.OutputTokenCount}
" + + $"Total Token Count: {tokenUsage.TotalTokenCount}"; + } + public class Message { public string? Text { get; set; } @@ -173,5 +222,7 @@ public required string Role { get; set; } public bool IsCompleted { get; set; } + + public string? TokenUsage { get; set; } } } \ No newline at end of file diff --git a/SqlDatabaseVectorSearch/Components/Pages/Documents.razor b/SqlDatabaseVectorSearch/Components/Pages/Documents.razor index 695ee40..3722858 100644 --- a/SqlDatabaseVectorSearch/Components/Pages/Documents.razor +++ b/SqlDatabaseVectorSearch/Components/Pages/Documents.razor @@ -24,7 +24,7 @@ - +
@@ -105,7 +105,6 @@ else private Button deleteButton = default!; private IList documents = []; - private List messages = []; private UploadDocumentRequest uploadDocumentRequest = new(); private EditContext? editContext; @@ -161,9 +160,10 @@ else uploadButton.ShowLoading(); + var fileName = uploadDocumentRequest.File.Name; + try { - var fileName = uploadDocumentRequest.File.Name; await using var inputStream = uploadDocumentRequest.File.OpenReadStream(20 * 1024 * 1024); // 20 MB await using var stream = await inputStream.GetMemoryStreamAsync(); @@ -177,6 +177,10 @@ else await LoadDocumentsAsync(scope.ServiceProvider); } + catch (Exception ex) + { + ToastService.Notify(await CreateToastMessageAsync(ToastType.Danger, "Upload error", $"There was an error while uploading the document {fileName}: {ex.Message}")); + } finally { uploadButton.HideLoading(); @@ -218,6 +222,10 @@ else await LoadDocumentsAsync(scope.ServiceProvider); ToastService.Notify(await CreateToastMessageAsync(ToastType.Info, "Delete documents", "The selected documents have been successfully deleted.")); } + catch (Exception ex) + { + ToastService.Notify(await CreateToastMessageAsync(ToastType.Danger, "Delete error", $"There was an error while deleting the documents: {ex.Message}")); + } finally { deleteButton.HideLoading();