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 else
{ {
<PageTitle>گفتگو با دستیار هوشمند @CompanyInfo.FullName</PageTitle> <PageTitle>گفتگو با دستیار هوشمند @CompanyInfo.FullName</PageTitle>
} }
<div class="container-fluid"> <div class="container-fluid">
<div class="row" style="height:85vh"> <div class="row" style="height:85vh">
@if (isReady) @if (isReady)
{ {
<div class="col-md-12 d-flex flex-column" style="margin-top:10px"> <div class="col-md-12 d-flex flex-column" style="margin-top:10px">
<div class="input-group"> <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> <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"> <Button Color="ButtonColor.Secondary" Size=ButtonSize.ExtraSmall Outline="true" @onclick="Logout" Class="logout-btn">
<Icon Name="IconName.BoxArrowRight" Class="me-1" /> خروج <Icon Name="IconName.BoxArrowRight" Class="me-1" /> خروج
</Button> </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>
</div> </div>
</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 else
{ {
@@ -98,29 +115,30 @@ else
</div> </div>
@code { @code {
public bool DelayShowResponse { get; set; } = false;
ReadANDUpdate_CompanyDto? CompanyInfo = new(); ReadANDUpdate_CompanyDto? CompanyInfo = new();
[Parameter] public int CompanyID { get; set; } [Parameter] public int CompanyID { get; set; }
List<aiResponseDto> messages = new(); List<aiResponseDto> messages = new();
string inputText = string.Empty; string inputText = string.Empty;
bool isReady = false; bool isReady = false;
bool isError= false; bool isError = false;
string msgError= ""; string msgError = "";
public string aikeyUser { get; set; } = ""; public string aikeyUser { get; set; } = "";
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await EnsureAuth(); await EnsureAuth();
CompanyInfo = await companyService.GetCompany(CompanyID); CompanyInfo = await companyService.GetCompany(CompanyID);
if (CompanyInfo != null && CompanyInfo.allowBot) if (CompanyInfo != null && CompanyInfo.allowBot)
{ {
await LoadMessages(); await LoadMessages();
isReady = true; isReady = true;
} }
else else
{ {
isError = true; isError = true;
msgError = "دستیار هوشمند یافت نشد"; msgError = "دستیار هوشمند یافت نشد";
} }
} }
private async Task EnsureAuth() private async Task EnsureAuth()
@@ -146,10 +164,23 @@ else
if (string.IsNullOrWhiteSpace(inputText)) return; if (string.IsNullOrWhiteSpace(inputText)) return;
disSend = true; disSend = true;
var dto = new aiNewResponseDto { companyId = CompanyID, aiKeyUser = aikeyUser, requestText = inputText }; 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); var okmodel = await conversationService.AiNewResponse(dto);
if (okmodel != null) if (okmodel != null)
{ {
DelayShowResponse = true;
messages.Add(okmodel); messages.Add(okmodel);
inputText = string.Empty; inputText = string.Empty;
StateHasChanged(); StateHasChanged();
await JS.InvokeVoidAsync("scrollToBottom", "ai-chat"); await JS.InvokeVoidAsync("scrollToBottom", "ai-chat");
@@ -158,6 +189,7 @@ else
{ {
toastService.Notify(new ToastMessage(ToastType.Danger, "ارسال ناموفق بود")); toastService.Notify(new ToastMessage(ToastType.Danger, "ارسال ناموفق بود"));
} }
messages.Remove(waiting);
disSend = false; disSend = false;
} }
@@ -287,7 +319,6 @@ else
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
background-clip: text; background-clip: text;
} }
</style> </style>
<script> <script>
@@ -325,5 +356,4 @@ else
color: #353; color: #353;
border-top-right-radius: 0; border-top-right-radius: 0;
} }
</style> </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);
}
}