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"
|
@page "/ask"
|
||||||
|
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
|
@inject IJSRuntime JSRuntime
|
||||||
|
|
||||||
<PageTitle>Chat with your data</PageTitle>
|
<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 {
|
<div class="card-footer bg-white w-100 bottom-0 m-0 p-1">
|
||||||
private int currentCount = 0;
|
<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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(2000);
|
|
||||||
await using var scope = ServiceProvider.CreateAsyncScope();
|
await using var scope = ServiceProvider.CreateAsyncScope();
|
||||||
await LoadDocumentsAsync(scope.ServiceProvider);
|
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