mirror of
https://github.com/marcominerva/SqlDatabaseVectorSearch.git
synced 2026-06-20 12:23:10 +00:00
Enhance chat interface and update styles
Significantly updated `Ask.razor` to improve the chat interface with a new layout for user and assistant messages, added input area for questions, and buttons for submission and reset. Removed the previous count display and introduced asynchronous message handling and a new `Message` class. Minor change in `Documents.razor` by removing a 2000 ms delay before loading documents. Updated `Ask.razor.css` with new styles for tooltips, avatars, input fields, card bodies, and progress indicators. Added or updated `assistant.png` and `user.png` for new avatar images in the chat interface.
This commit is contained in:
@@ -1,16 +1,151 @@
|
||||
@page "/ask"
|
||||
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IJSRuntime JSRuntime
|
||||
|
||||
<PageTitle>Chat with your data</PageTitle>
|
||||
|
||||
<p role="status">Current count: @currentCount</p>
|
||||
<div class="card mx-auto">
|
||||
<div class="card-body">
|
||||
|
||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||
@foreach (var message in messages)
|
||||
{
|
||||
if (message.Role == "user")
|
||||
{
|
||||
<div class="d-flex align-items-baseline text-end justify-content-end">
|
||||
<div class="pe-2">
|
||||
<div>
|
||||
<div class="card card-text d-inline-block p-2 px-3 m-1">
|
||||
<Markdown style="overflow-y:auto;">@message.Text</Markdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="position-relative avatar">
|
||||
<Image src="/images/user.png" class="img-fluid rounded-circle" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else if (message.Role == "assistant")
|
||||
{
|
||||
<div class="d-flex align-items-baseline">
|
||||
<div class="position-relative avatar">
|
||||
<Image src="/images/assistant.png" class="img-fluid rounded-circle" alt="" />
|
||||
</div>
|
||||
<div class="pe-2">
|
||||
<div>
|
||||
@if (message.Text is null)
|
||||
{
|
||||
<div class="card card-text d-inline-block p-3 px-3 m-1">
|
||||
<div class="progress-chat" role="progressbar" aria-label="I'm thinking" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
||||
<div class="progress-bar-chat">
|
||||
<div class="progress-bar-indeterminate"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card card-text d-inline-block p-2 px-3 m-1">
|
||||
<div>
|
||||
<Markdown style="overflow-y:auto;">@message.Text</Markdown>
|
||||
</div>
|
||||
@if (message.IsCompleted)
|
||||
{
|
||||
<div class="text-end bg-transparent border-0">
|
||||
<Tooltip Title="Copy to Clipboard" Color="TooltipColor.Dark" Placement="TooltipPlacement.Bottom">
|
||||
<button class="btn" @onclick="@(async () => await CopyToClipboard(message.Text))">
|
||||
<Icon Name="IconName.Clipboard" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
<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">
|
||||
<Icon Class="d-flex text-body-secondary" Name="IconName.InfoCircle"></Icon>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<input @bind="@question" @bind:event="oninput" placeholder="Ask me anything..." class="form-control border-0" maxlength="2000" autofocus />
|
||||
<div class="input-group-text bg-transparent border-0">
|
||||
<Button @ref="askButton" Color="ButtonColor.Primary" Disabled="@(isAsking || string.IsNullOrWhiteSpace(question))" @onclick="AskQuestion">
|
||||
<Icon Name="IconName.Send" />
|
||||
</Button>
|
||||
<Button @ref="resetButton" Class="ms-2" Color="ButtonColor.Secondary" Disabled="@isAsking" @onclick="Reset">
|
||||
<Icon CustomIconName="bi bi-x-lg" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
private void IncrementCount()
|
||||
@code
|
||||
{
|
||||
private Button askButton = default!;
|
||||
private Button resetButton = default!;
|
||||
|
||||
private IList<Message> messages = [];
|
||||
private string? question;
|
||||
|
||||
private Guid conversationId = Guid.NewGuid();
|
||||
private bool isAsking = false;
|
||||
|
||||
|
||||
private async Task AskQuestion()
|
||||
{
|
||||
currentCount++;
|
||||
isAsking = true;
|
||||
|
||||
try
|
||||
{
|
||||
var userMessage = new Message { Text = question, Role = "user" };
|
||||
messages.Add(userMessage);
|
||||
|
||||
var assistantMessage = new Message { Role = "assistant" };
|
||||
messages.Add(assistantMessage);
|
||||
|
||||
await using var scope = ServiceProvider.CreateAsyncScope();
|
||||
var vectorSearchService = scope.ServiceProvider.GetRequiredService<VectorSearchService>();
|
||||
|
||||
var response = await vectorSearchService.AskQuestionAsync(new Question(conversationId, question!));
|
||||
|
||||
assistantMessage.Text = response.Answer;
|
||||
assistantMessage.IsCompleted = true;
|
||||
|
||||
question = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
isAsking = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
question = null;
|
||||
conversationId = Guid.NewGuid();
|
||||
messages.Clear();
|
||||
}
|
||||
|
||||
private ValueTask CopyToClipboard(string text)
|
||||
{
|
||||
return JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", text);
|
||||
}
|
||||
|
||||
public class Message
|
||||
{
|
||||
public string? Text { get; set; }
|
||||
|
||||
public required string Role { get; set; }
|
||||
|
||||
public bool IsCompleted { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
.tooltip-inner {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #ddd;
|
||||
padding: 2px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: 0px !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="checkbox"] + label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
overflow: auto;
|
||||
height: 490px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.card-body {
|
||||
height: 595px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 2560px) {
|
||||
.card-body {
|
||||
height: 950px;
|
||||
}
|
||||
}
|
||||
|
||||
.card-text {
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.progress-chat {
|
||||
width: 200px;
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.progress-bar-chat {
|
||||
height: 4px;
|
||||
background-color: rgba(5, 114, 206, 0.2);
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar-indeterminate {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgb(5, 114, 206);
|
||||
animation: indeterminate-animation 1s infinite linear;
|
||||
transform-origin: 0% 50%;
|
||||
}
|
||||
|
||||
@keyframes indeterminate-animation {
|
||||
0% {
|
||||
transform: translateX(0) scaleX(0);
|
||||
}
|
||||
|
||||
40% {
|
||||
transform: translateX(0) scaleX(0.4);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(100%) scaleX(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-clipboard {
|
||||
line-height: 1;
|
||||
color: var(--bs-body-color);
|
||||
background-color: var(--bd-pre-bg);
|
||||
border: 0;
|
||||
border-radius: .25rem;
|
||||
margin-right: -.4em
|
||||
}
|
||||
|
||||
.btn-clipboard:hover {
|
||||
color: var(--bs-link-hover-color)
|
||||
}
|
||||
|
||||
.btn-clipboard:focus {
|
||||
z-index: 3
|
||||
}
|
||||
|
||||
.btn-clipboard {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
@@ -124,7 +124,6 @@ else
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Delay(2000);
|
||||
await using var scope = ServiceProvider.CreateAsyncScope();
|
||||
await LoadDocumentsAsync(scope.ServiceProvider);
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1010 B |
Reference in New Issue
Block a user