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 mx-auto mt-2">
|
||||||
<div class="card-body">
|
<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)
|
@foreach (var message in messages)
|
||||||
{
|
{
|
||||||
if (message.Role == "user")
|
if (message.Role == "user")
|
||||||
@@ -98,18 +107,20 @@
|
|||||||
<div class="card-footer bg-white w-100 bottom-0 m-0 p-1">
|
<div class="card-footer bg-white w-100 bottom-0 m-0 p-1">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text bg-transparent border-0">
|
<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>
|
<Icon Class="d-flex" Color="IconColor.Success" Name="IconName.ShieldLockFill"></Icon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</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">
|
<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">
|
<Button Type="ButtonType.Submit" @ref="askButton" Color="ButtonColor.Primary" Disabled="@(isAsking || string.IsNullOrWhiteSpace(question))" @onclick="AskQuestion">
|
||||||
<Icon Name="IconName.Send" />
|
<Icon Name="IconName.Send" />
|
||||||
</Button>
|
</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">
|
<Button Type="ButtonType.Reset" @ref="resetButton" Class="ms-2" Color="ButtonColor.Secondary" Disabled="@isAsking" @onclick="Reset">
|
||||||
<Icon CustomIconName="bi bi-x-lg" />
|
<Icon CustomIconName="bi bi-x-lg" />
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,7 +140,7 @@
|
|||||||
private bool isAsking = false;
|
private bool isAsking = false;
|
||||||
|
|
||||||
private bool showCopyConfirmation = false;
|
private bool showCopyConfirmation = false;
|
||||||
private string toolTipText = "Copy to Clipboard";
|
private string toolTipText = "Copy to clipboard";
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
@@ -206,7 +217,7 @@
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
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;
|
assistantMessage.Status = MessageStatus.Completed;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -237,7 +248,7 @@
|
|||||||
|
|
||||||
await Task.Delay(3000); // Shows the checkmark for 3 seconds
|
await Task.Delay(3000); // Shows the checkmark for 3 seconds
|
||||||
|
|
||||||
toolTipText = "Copy to Clipboard";
|
toolTipText = "Copy to clipboard";
|
||||||
showCopyConfirmation = false;
|
showCopyConfirmation = false;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
|
|
||||||
<PageTitle>Documents</PageTitle>
|
<PageTitle>Documents</PageTitle>
|
||||||
|
|
||||||
<h4 class="mb-4">
|
<h4 class="mb-1">
|
||||||
<Icon Name="IconName.CloudArrowUpFill" Color="IconColor.Primary" class="me-2" />
|
<Icon Name="IconName.CloudArrowUpFill" Color="IconColor.Primary" class="me-2" />
|
||||||
Upload new document
|
Upload a new document
|
||||||
</h4>
|
</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">
|
<EditForm Model="Model" Enhance OnValidSubmit="UploadFile">
|
||||||
<DataAnnotationsValidator />
|
<DataAnnotationsValidator />
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
<div class="col-md-5 col-sm-4 col-5">
|
<div class="col-md-5 col-sm-4 col-5">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-text">
|
<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>
|
<Icon Class="d-flex" Color="IconColor.Info" Name="IconName.InfoCircle"></Icon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</span>
|
||||||
@@ -58,8 +59,18 @@ else
|
|||||||
<h4 class="mt-4 mb-4">
|
<h4 class="mt-4 mb-4">
|
||||||
<Icon Name="IconName.FileEarmarkTextFill" Color="IconColor.Info" class="me-2" />
|
<Icon Name="IconName.FileEarmarkTextFill" Color="IconColor.Info" class="me-2" />
|
||||||
Available documents
|
Available documents
|
||||||
|
<span class="badge bg-secondary-subtle text-secondary-emphasis ms-2 align-middle">@documents.Count</span>
|
||||||
</h4>
|
</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">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover align-middle mb-0 border rounded overflow-hidden">
|
<table class="table table-hover align-middle mb-0 border rounded overflow-hidden">
|
||||||
<thead class="table-light sticky-top">
|
<thead class="table-light sticky-top">
|
||||||
@@ -105,6 +116,7 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -179,7 +191,7 @@ else
|
|||||||
var documentId = string.IsNullOrWhiteSpace(Model.DocumentId) ? null : (Guid?)Guid.Parse(Model.DocumentId);
|
var documentId = string.IsNullOrWhiteSpace(Model.DocumentId) ? null : (Guid?)Guid.Parse(Model.DocumentId);
|
||||||
await vectorSearchService.ImportAsync(EmbeddingRequest.Create(stream, fileName, 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();
|
Model = new UploadDocument();
|
||||||
await JSRuntime.InvokeVoidAsync("resetFileInput", "fileInput");
|
await JSRuntime.InvokeVoidAsync("resetFileInput", "fileInput");
|
||||||
@@ -188,7 +200,7 @@ else
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
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
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,37 +3,112 @@
|
|||||||
|
|
||||||
<PageTitle>SQL Database Vector Search</PageTitle>
|
<PageTitle>SQL Database Vector Search</PageTitle>
|
||||||
|
|
||||||
<h1>SQL Database Vector Search</h1>
|
<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 class="mt-3 p-3 rounded bg-light text-dark shadow-sm">
|
<h2 class="h4 mb-3">
|
||||||
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>.
|
<Icon Name="IconName.Diagram3Fill" Color="IconColor.Secondary" Class="me-2" />
|
||||||
</p>
|
How it works
|
||||||
|
</h2>
|
||||||
|
|
||||||
<p>
|
<div class="row g-3 mb-4">
|
||||||
This application allows you to:
|
<div class="col-md-4">
|
||||||
<ul>
|
<div class="card h-100 border-0 shadow-sm">
|
||||||
<li>Load documents (PDF, DOCX, TXT, MD)</li>
|
<div class="card-body">
|
||||||
<li>Generate embeddings and save them as vectors in Azure SQL Database</li>
|
<Icon Name="IconName.FileEarmarkArrowUpFill" Color="IconColor.Primary" Size="IconSize.x3" />
|
||||||
<li>Perform semantic search and RAG using Azure OpenAI agents</li>
|
<h3 class="h6 mt-3 mb-2">1. Import</h3>
|
||||||
<li>Interact via a Blazor Web App or programmatically via Minimal API</li>
|
<p class="text-body-secondary mb-0 small">
|
||||||
</ul>
|
Upload a PDF, DOCX, TXT or MD file. Its content is split into chunks, and each chunk is turned into
|
||||||
Document import, question reformulation, and RAG are orchestrated with <a href="https://github.com/microsoft/agent-framework" target="_blank">Microsoft Agent Framework</a>.
|
an embedding by an Agent Framework workflow.
|
||||||
</p>
|
</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>
|
<h2 class="h4 mb-3">
|
||||||
<ul>
|
<Icon Name="IconName.ListStars" Color="IconColor.Warning" Class="me-2" />
|
||||||
<li><strong>Microsoft Agent Framework orchestration</strong>: Import documents through an embedding workflow and answer questions with dedicated reformulation and RAG agents.</li>
|
Supported features
|
||||||
<li><strong>Conversation history with question reformulation</strong>: Rewrite follow-up questions with the current conversation context before vector search.</li>
|
</h2>
|
||||||
<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>
|
<ul class="list-group list-group-flush mb-4 shadow-sm rounded overflow-hidden border">
|
||||||
<li><strong>Response streaming</strong>: Receive answer tokens in real time in the chat page and streaming API.</li>
|
<li class="list-group-item">
|
||||||
<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>
|
<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>
|
</ul>
|
||||||
|
|
||||||
<p class="mt-3 p-3 rounded bg-light text-dark shadow-sm">
|
<p class="text-body-secondary small">
|
||||||
Try <a href="/documents">uploading a document</a> or <a href="/ask">ask a question</a> to get started!
|
<Icon Name="IconName.InfoCircleFill" Color="IconColor.Info" Class="me-2" />
|
||||||
</p>
|
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 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>
|
</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