Improve UI/UX for chat, documents, and home pages

- Chat page: Add empty state, clarify tooltips/placeholders, and enhance error/copy messages.
- Documents page: Add chunking/embedding info, clarify file support, show document count badge, and display empty state. Refactor table/button markup and improve upload toasts.
- Home page: Redesign with hero section, icons, workflow cards, and styled features list. Clarify README link.
- Add Home.razor.css for new visual styles.
This commit is contained in:
Marco Minerva
2026-07-28 14:57:57 +02:00
parent 9f0d4f6402
commit dc26a3d2e9
4 changed files with 195 additions and 83 deletions
@@ -9,6 +9,15 @@
<div class="card mx-auto mt-2">
<div class="card-body">
@if (messages.Count == 0)
{
<div class="h-100 d-flex flex-column justify-content-center align-items-center text-body-secondary">
<Icon Name="IconName.ChatSquareQuoteFill" Color="IconColor.Primary" Size="IconSize.x4" />
<p class="mt-3 mb-1 fw-semibold">Chat with your documents</p>
<p class="small mb-0">Ask a question about the documents you have uploaded. Press the up arrow key to recall your previous question.</p>
</div>
}
@foreach (var message in messages)
{
if (message.Role == "user")
@@ -98,18 +107,20 @@
<div class="card-footer bg-white w-100 bottom-0 m-0 p-1">
<div class="input-group">
<span class="input-group-text bg-transparent border-0">
<Tooltip Title="Messages aren't stored in any way on either the client or the server." Color="TooltipColor.Primary" Placement="TooltipPlacement.Bottom">
<Tooltip Title="Messages aren't stored in any way, either on the client or on the server." Color="TooltipColor.Primary" Placement="TooltipPlacement.Bottom">
<Icon Class="d-flex" Color="IconColor.Success" Name="IconName.ShieldLockFill"></Icon>
</Tooltip>
</span>
<input @ref="askInput" type="text" @bind="@question" @bind:event="oninput" placeholder="Ask me anything..." class="form-control border-0" maxlength="2000" @onkeydown="HandleKeyDown" />
<input @ref="askInput" type="text" @bind="@question" @bind:event="oninput" placeholder="Ask a question about your documents..." class="form-control border-0" maxlength="2000" @onkeydown="HandleKeyDown" />
<div class="input-group-text bg-transparent border-0">
<Button Type="ButtonType.Submit" @ref="askButton" Color="ButtonColor.Primary" Disabled="@(isAsking || string.IsNullOrWhiteSpace(question))" @onclick="AskQuestion">
<Icon Name="IconName.Send" />
</Button>
<Tooltip Title="Start a new conversation" Color="TooltipColor.Secondary" Placement="TooltipPlacement.Bottom">
<Button Type="ButtonType.Reset" @ref="resetButton" Class="ms-2" Color="ButtonColor.Secondary" Disabled="@isAsking" @onclick="Reset">
<Icon CustomIconName="bi bi-x-lg" />
</Button>
</Tooltip>
</div>
</div>
</div>
@@ -129,7 +140,7 @@
private bool isAsking = false;
private bool showCopyConfirmation = false;
private string toolTipText = "Copy to Clipboard";
private string toolTipText = "Copy to clipboard";
protected override async Task OnAfterRenderAsync(bool firstRender)
{
@@ -206,7 +217,7 @@
}
catch (Exception ex)
{
assistantMessage.Text = $"There was an error while processing the question: {ex.Message}";
assistantMessage.Text = $"There was an error while processing your question: {ex.Message}";
assistantMessage.Status = MessageStatus.Completed;
}
finally
@@ -237,7 +248,7 @@
await Task.Delay(3000); // Shows the checkmark for 3 seconds
toolTipText = "Copy to Clipboard";
toolTipText = "Copy to clipboard";
showCopyConfirmation = false;
await InvokeAsync(StateHasChanged);
}
@@ -8,10 +8,11 @@
<PageTitle>Documents</PageTitle>
<h4 class="mb-4">
<h4 class="mb-1">
<Icon Name="IconName.CloudArrowUpFill" Color="IconColor.Primary" class="me-2" />
Upload new document
Upload a new document
</h4>
<p class="text-body-secondary small mb-4">The document is split into chunks, and an embedding is generated and stored for each chunk.</p>
<EditForm Model="Model" Enhance OnValidSubmit="UploadFile">
<DataAnnotationsValidator />
@@ -20,7 +21,7 @@
<div class="col-md-5 col-sm-4 col-5">
<div class="input-group">
<span class="input-group-text">
<Tooltip Title="PDF, DOCX, TXT and MD files are supported" Color="TooltipColor.Primary" Placement="TooltipPlacement.Bottom">
<Tooltip Title="PDF, DOCX, TXT and MD files are supported, up to 20 MB" Color="TooltipColor.Primary" Placement="TooltipPlacement.Bottom">
<Icon Class="d-flex" Color="IconColor.Info" Name="IconName.InfoCircle"></Icon>
</Tooltip>
</span>
@@ -58,8 +59,18 @@ else
<h4 class="mt-4 mb-4">
<Icon Name="IconName.FileEarmarkTextFill" Color="IconColor.Info" class="me-2" />
Available documents
<span class="badge bg-secondary-subtle text-secondary-emphasis ms-2 align-middle">@documents.Count</span>
</h4>
@if (documents.Count == 0)
{
<div class="text-center border rounded py-5 text-body-secondary">
<Icon Name="IconName.Inbox" Color="IconColor.Secondary" Size="IconSize.x3" />
<p class="mt-3 mb-0">No documents have been indexed yet. Upload one to get started.</p>
</div>
}
else
{
<div class="table-responsive">
<table class="table table-hover align-middle mb-0 border rounded overflow-hidden">
<thead class="table-light sticky-top">
@@ -106,6 +117,7 @@ else
</div>
</div>
}
}
@code {
private ConfirmDialog dialog = default!;
@@ -179,7 +191,7 @@ else
var documentId = string.IsNullOrWhiteSpace(Model.DocumentId) ? null : (Guid?)Guid.Parse(Model.DocumentId);
await vectorSearchService.ImportAsync(EmbeddingRequest.Create(stream, fileName, documentId));
ToastService.Notify(await CreateToastMessageAsync(ToastType.Success, "Upload document", $"The document {fileName} has been successfully uploaded and indexed."));
ToastService.Notify(await CreateToastMessageAsync(ToastType.Success, "Upload document", $"The document '{fileName}' has been successfully uploaded and indexed."));
Model = new UploadDocument();
await JSRuntime.InvokeVoidAsync("resetFileInput", "fileInput");
@@ -188,7 +200,7 @@ else
}
catch (Exception ex)
{
ToastService.Notify(await CreateToastMessageAsync(ToastType.Danger, "Upload error", $"There was an error while uploading the document {fileName}: {ex.Message}"));
ToastService.Notify(await CreateToastMessageAsync(ToastType.Danger, "Upload error", $"There was an error while uploading the document '{fileName}': {ex.Message}"));
}
finally
{
@@ -3,37 +3,112 @@
<PageTitle>SQL Database Vector Search</PageTitle>
<h1>SQL Database Vector Search</h1>
<p class="mt-3 p-3 rounded bg-light text-dark shadow-sm">
A Blazor Web App and Minimal API for Retrieval Augmented Generation (RAG) and vector search using the native VECTOR type in <img src="/images/sqldatabase.svg" style="height:1.5em;vertical-align:middle;" /> Azure SQL Database with <img src="/images/openai.svg" style="height:1.5em;vertical-align:middle;" /> Azure OpenAI and <a href="https://github.com/microsoft/agent-framework" target="_blank">Microsoft Agent Framework</a>.
<div class="hero p-4 p-md-5 mb-4 rounded-3 shadow-sm">
<h1 class="display-6 fw-semibold mb-3">
<Icon Name="IconName.Search" Color="IconColor.Primary" Class="me-2" />
SQL Database Vector Search
</h1>
<p class="lead mb-4">
A Blazor Web App and Minimal API for Retrieval Augmented Generation (RAG) and vector search using the native
<code>VECTOR</code> type in
<img src="/images/sqldatabase.svg" class="inline-logo" alt="Azure SQL Database" /> Azure SQL Database, with
<img src="/images/openai.svg" class="inline-logo" alt="Azure OpenAI" /> Azure OpenAI and
<a href="https://github.com/microsoft/agent-framework" target="_blank" rel="noopener">Microsoft Agent Framework</a>.
</p>
<div class="d-flex flex-wrap gap-2">
<a href="/documents" class="btn btn-primary px-4 py-2 fw-semibold shadow-sm">
<Icon Name="IconName.CloudArrowUpFill" Class="me-2" />Upload a document
</a>
<a href="/ask" class="btn btn-outline-primary px-4 py-2 fw-semibold">
<Icon Name="IconName.ChatDotsFill" Class="me-2" />Ask a question
</a>
</div>
</div>
<p>
This application allows you to:
<ul>
<li>Load documents (PDF, DOCX, TXT, MD)</li>
<li>Generate embeddings and save them as vectors in Azure SQL Database</li>
<li>Perform semantic search and RAG using Azure OpenAI agents</li>
<li>Interact via a Blazor Web App or programmatically via Minimal API</li>
</ul>
Document import, question reformulation, and RAG are orchestrated with <a href="https://github.com/microsoft/agent-framework" target="_blank">Microsoft Agent Framework</a>.
<h2 class="h4 mb-3">
<Icon Name="IconName.Diagram3Fill" Color="IconColor.Secondary" Class="me-2" />
How it works
</h2>
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<Icon Name="IconName.FileEarmarkArrowUpFill" Color="IconColor.Primary" Size="IconSize.x3" />
<h3 class="h6 mt-3 mb-2">1. Import</h3>
<p class="text-body-secondary mb-0 small">
Upload a PDF, DOCX, TXT or MD file. Its content is split into chunks, and each chunk is turned into
an embedding by an Agent Framework workflow.
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<Icon Name="IconName.DatabaseFillCheck" Color="IconColor.Success" Size="IconSize.x3" />
<h3 class="h6 mt-3 mb-2">2. Store</h3>
<p class="text-body-secondary mb-0 small">
Embeddings are persisted in Azure SQL Database using the native <code>VECTOR</code> type, so no
external vector store is required.
</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body">
<Icon Name="IconName.ChatSquareQuoteFill" Color="IconColor.Info" Size="IconSize.x3" />
<h3 class="h6 mt-3 mb-2">3. Ask</h3>
<p class="text-body-secondary mb-0 small">
Your question is reformulated with the conversation context, matched against the stored vectors, and
answered with citations.
</p>
</div>
</div>
</div>
</div>
<h3>Supported Features</h3>
<ul>
<li><strong>Microsoft Agent Framework orchestration</strong>: Import documents through an embedding workflow and answer questions with dedicated reformulation and RAG agents.</li>
<li><strong>Conversation history with question reformulation</strong>: Rewrite follow-up questions with the current conversation context before vector search.</li>
<li><strong>SQL vector-search context</strong>: Retrieve relevant chunks from Azure SQL Database through native VECTOR search.</li>
<li><strong>Information about token usage</strong>: Access token usage for reformulation and final answer generation.</li>
<li><strong>Response streaming</strong>: Receive answer tokens in real time in the chat page and streaming API.</li>
<li><strong>Markdown source citations</strong>: Get citations directly in the answer text with source name, page number when available, and a short supporting excerpt.</li>
<h2 class="h4 mb-3">
<Icon Name="IconName.ListStars" Color="IconColor.Warning" Class="me-2" />
Supported features
</h2>
<ul class="list-group list-group-flush mb-4 shadow-sm rounded overflow-hidden border">
<li class="list-group-item">
<Icon Name="IconName.Diagram3Fill" Color="IconColor.Primary" Class="me-2" />
<strong>Microsoft Agent Framework orchestration</strong>: documents are imported through an embedding workflow,
and questions are answered by dedicated reformulation and RAG agents.
</li>
<li class="list-group-item">
<Icon Name="IconName.ArrowRepeat" Color="IconColor.Secondary" Class="me-2" />
<strong>Conversation history with question reformulation</strong>: follow-up questions are rewritten with the
current conversation context before the vector search is performed.
</li>
<li class="list-group-item">
<Icon Name="IconName.DatabaseFillCheck" Color="IconColor.Success" Class="me-2" />
<strong>SQL vector search</strong>: the most relevant chunks are retrieved from Azure SQL Database through
native <code>VECTOR</code> cosine-distance search.
</li>
<li class="list-group-item">
<Icon Name="IconName.CashCoin" Color="IconColor.Warning" Class="me-2" />
<strong>Token usage details</strong>: input, output and total tokens are reported for both question
reformulation and answer generation.
</li>
<li class="list-group-item">
<Icon Name="IconName.LightningChargeFill" Color="IconColor.Danger" Class="me-2" />
<strong>Response streaming</strong>: answers are streamed token by token in the chat page and through the
Server-Sent Events API.
</li>
<li class="list-group-item">
<Icon Name="IconName.ChatQuoteFill" Color="IconColor.Info" Class="me-2" />
<strong>Markdown source citations</strong>: citations are embedded in the answer with the source name, the page
number when available, and a short supporting excerpt.
</li>
</ul>
<p class="mt-3 p-3 rounded bg-light text-dark shadow-sm">
Try <a href="/documents">uploading a document</a> or <a href="/ask">ask a question</a> to get started!
</p>
<p class="mt-4">
<em>For API usage and more details, see the <a href="https://github.com/marcominerva/SqlDatabaseVectorSearch#how-to-use" target="_blank">README</a>.</em>
<p class="text-body-secondary small">
<Icon Name="IconName.InfoCircleFill" Color="IconColor.Info" Class="me-2" />
For API usage and more details, see the
<a href="https://github.com/marcominerva/SqlDatabaseVectorSearch#how-to-use" target="_blank" rel="noopener">README</a>.
</p>
@@ -0,0 +1,14 @@
.hero {
background: linear-gradient(135deg, #eef4ff 0%, #f8f9fa 100%);
border: 1px solid #e3e8f0;
}
.inline-logo {
height: 1.5em;
vertical-align: middle;
}
.card:hover {
transform: translateY(-2px);
transition: transform 0.15s ease-in-out;
}