This commit is contained in:
mmrbnjd
2025-08-21 16:40:31 +03:30
parent 69a75731ce
commit be9dc286e5
2 changed files with 183 additions and 63 deletions

View File

@@ -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>

View 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);
}
}