...
This commit is contained in:
@@ -19,63 +19,80 @@
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
<PageTitle>گفتگو با دستیار هوشمند @CompanyInfo.FullName</PageTitle>
|
||||
}
|
||||
<div class="container-fluid">
|
||||
<div class="row" style="height:85vh">
|
||||
@if (isReady)
|
||||
{
|
||||
<div class="col-md-12 d-flex flex-column" style="margin-top:10px">
|
||||
<div class="input-group">
|
||||
<p type="text" class="form-control fw-bold text-primary" style="border:none;align-self: center;" aria-describedby="basic-addon1">دستیار هوشمند @CompanyInfo.FullName</p>
|
||||
<div class="col-md-12 d-flex flex-column" style="margin-top:10px">
|
||||
<div class="input-group">
|
||||
<p type="text" class="form-control fw-bold text-primary" style="border:none;align-self: center;" aria-describedby="basic-addon1">دستیار هوشمند @CompanyInfo.FullName</p>
|
||||
|
||||
<div class="d-flex gap-2 ms-auto">
|
||||
<div class="d-flex gap-2 ms-auto">
|
||||
|
||||
<Button Color="ButtonColor.Secondary" Size=ButtonSize.ExtraSmall Outline="true" @onclick="Logout" Class="logout-btn">
|
||||
<Icon Name="IconName.BoxArrowRight" Class="me-1" /> خروج
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-fill chat-area-container" id="ai-chat">
|
||||
@if (messages is not null)
|
||||
{
|
||||
<div class="chat-container p-3">
|
||||
@foreach (var msg in messages)
|
||||
{
|
||||
<div class="d-flex mb-2 justify-content-start">
|
||||
<div class="chat-bubble chat-other">
|
||||
@msg.requestText
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex mb-2 justify-content-end">
|
||||
<div class="chat-bubble chat-mine">
|
||||
@msg.responseText
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="message-input-container mt-2">
|
||||
<div class="input-wrapper">
|
||||
<input type="text" @bind-value="inputText" disabled="@disSend" class="message-input" placeholder="متن خود را بنویسید..." @onkeydown="HandleKeyDown" />
|
||||
<Button Color="ButtonColor.Primary" Size=ButtonSize.Small @onclick="Send" class="send-btn" title="ارسال">
|
||||
@if (!disSend)
|
||||
{
|
||||
<Icon Name="IconName.Send" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<Spinner Type="SpinnerType.Grow" Color="SpinnerColor.Primary" />
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
<Button Color="ButtonColor.Secondary" Size=ButtonSize.ExtraSmall Outline="true" @onclick="Logout" Class="logout-btn">
|
||||
<Icon Name="IconName.BoxArrowRight" Class="me-1" /> خروج
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill chat-area-container" id="ai-chat">
|
||||
@if (messages is not null)
|
||||
{
|
||||
<div class="chat-container p-3">
|
||||
@foreach (var msg in messages)
|
||||
{
|
||||
<div class="d-flex mb-2 justify-content-start">
|
||||
<div class="chat-bubble chat-other">
|
||||
@msg.requestText
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex mb-2 justify-content-end">
|
||||
|
||||
@if (msg.responseText=="null" && msg.dateTime==new DateTime(1400,01,01))
|
||||
{
|
||||
<Spinner Type="SpinnerType.Dots" Class="me-3" Color="SpinnerColor.Info" />
|
||||
}
|
||||
else{
|
||||
<div class="chat-bubble chat-mine">
|
||||
@if (DelayShowResponse && messages.Last()==msg)
|
||||
{
|
||||
<TypewriterComponent Text="@msg.responseText" />
|
||||
}
|
||||
else
|
||||
{
|
||||
@msg.responseText
|
||||
}
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="message-input-container mt-2">
|
||||
<div class="input-wrapper">
|
||||
<input type="text" @bind-value="inputText" disabled="@disSend" class="message-input" placeholder="متن خود را بنویسید..." @onkeydown="HandleKeyDown" />
|
||||
<Button Color="ButtonColor.Primary" Size=ButtonSize.Small @onclick="Send" class="send-btn" title="ارسال">
|
||||
@if (!disSend)
|
||||
{
|
||||
<Icon Name="IconName.Send" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<Spinner Type="SpinnerType.Grow" Color="SpinnerColor.Primary" />
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -98,29 +115,30 @@ else
|
||||
</div>
|
||||
|
||||
@code {
|
||||
public bool DelayShowResponse { get; set; } = false;
|
||||
ReadANDUpdate_CompanyDto? CompanyInfo = new();
|
||||
[Parameter] public int CompanyID { get; set; }
|
||||
List<aiResponseDto> messages = new();
|
||||
string inputText = string.Empty;
|
||||
bool isReady = false;
|
||||
bool isError= false;
|
||||
string msgError= "";
|
||||
bool isError = false;
|
||||
string msgError = "";
|
||||
public string aikeyUser { get; set; } = "";
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await EnsureAuth();
|
||||
|
||||
CompanyInfo = await companyService.GetCompany(CompanyID);
|
||||
if (CompanyInfo != null && CompanyInfo.allowBot)
|
||||
{
|
||||
await LoadMessages();
|
||||
isReady = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isError = true;
|
||||
msgError = "دستیار هوشمند یافت نشد";
|
||||
}
|
||||
|
||||
CompanyInfo = await companyService.GetCompany(CompanyID);
|
||||
if (CompanyInfo != null && CompanyInfo.allowBot)
|
||||
{
|
||||
await LoadMessages();
|
||||
isReady = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isError = true;
|
||||
msgError = "دستیار هوشمند یافت نشد";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task EnsureAuth()
|
||||
@@ -146,10 +164,23 @@ else
|
||||
if (string.IsNullOrWhiteSpace(inputText)) return;
|
||||
disSend = true;
|
||||
var dto = new aiNewResponseDto { companyId = CompanyID, aiKeyUser = aikeyUser, requestText = inputText };
|
||||
|
||||
|
||||
|
||||
var waiting = new aiResponseDto()
|
||||
{
|
||||
requestText = inputText,
|
||||
responseText = "null",
|
||||
dateTime = new(1400, 01, 01)
|
||||
};
|
||||
messages.Add(waiting);
|
||||
await JS.InvokeVoidAsync("scrollToBottom", "ai-chat");
|
||||
var okmodel = await conversationService.AiNewResponse(dto);
|
||||
if (okmodel != null)
|
||||
{
|
||||
DelayShowResponse = true;
|
||||
messages.Add(okmodel);
|
||||
|
||||
inputText = string.Empty;
|
||||
StateHasChanged();
|
||||
await JS.InvokeVoidAsync("scrollToBottom", "ai-chat");
|
||||
@@ -158,6 +189,7 @@ else
|
||||
{
|
||||
toastService.Notify(new ToastMessage(ToastType.Danger, "ارسال ناموفق بود"));
|
||||
}
|
||||
messages.Remove(waiting);
|
||||
disSend = false;
|
||||
}
|
||||
|
||||
@@ -287,7 +319,6 @@ else
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -325,5 +356,4 @@ else
|
||||
color: #353;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
</style>
|
90
Presentation/HushianWebApp/Pages/TypewriterComponent.razor
Normal file
90
Presentation/HushianWebApp/Pages/TypewriterComponent.razor
Normal file
@@ -0,0 +1,90 @@
|
||||
|
||||
@* File: Typewriter.razor *@
|
||||
@inherits ComponentBase
|
||||
|
||||
@* <span>@_display</span> *@
|
||||
@_display
|
||||
|
||||
|
||||
@code {
|
||||
[Parameter] public string Text { get; set; }
|
||||
|
||||
public int WordDelay { get; set; } = 100;
|
||||
public bool StartOnRender { get; set; } = true;
|
||||
// [Parameter] public EventCallback OnFinished { get; set; }
|
||||
|
||||
private string _display = string.Empty;
|
||||
private string[] _words = Array.Empty<string>();
|
||||
private int _index = 0;
|
||||
private CancellationTokenSource? _cts;
|
||||
private bool _isRunning = false;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
// Prepare tokenized words once per parameter set
|
||||
_words = string.IsNullOrWhiteSpace(Text)
|
||||
? Array.Empty<string>()
|
||||
: System.Text.RegularExpressions.Regex.Split(Text.Trim(), "\\s+");
|
||||
|
||||
if (StartOnRender && !_isRunning && _index == 0)
|
||||
{
|
||||
await StartAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start typing from the current index.
|
||||
/// </summary>
|
||||
public async Task StartAsync()
|
||||
{
|
||||
if (_isRunning) return;
|
||||
_cts?.Cancel();
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
_isRunning = true;
|
||||
|
||||
try
|
||||
{
|
||||
while (_index < _words.Length && !token.IsCancellationRequested)
|
||||
{
|
||||
if (_index > 0)
|
||||
{
|
||||
_display += " ";
|
||||
}
|
||||
_display += _words[_index++];
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await Task.Delay(Math.Max(0, WordDelay), token);
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException) { }
|
||||
finally
|
||||
{
|
||||
_isRunning = false;
|
||||
// if (_index >= _words.Length && OnFinished.HasDelegate)
|
||||
// {
|
||||
// await OnFinished.InvokeAsync();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pause typing (can be resumed with StartAsync()).
|
||||
/// </summary>
|
||||
public void Pause()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_isRunning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop and reset the text to the beginning.
|
||||
/// </summary>
|
||||
public async Task ResetAsync()
|
||||
{
|
||||
Pause();
|
||||
_display = string.Empty;
|
||||
_index = 0;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user