mirror of
https://github.com/marcominerva/SqlDatabaseVectorSearch.git
synced 2026-08-04 09:48:57 +00:00
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:
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user