@page "/Chat" @page "/" @using Common.Dtos.Conversation @using Common.Dtos.Group @using Common.Enums @using HushianWebApp.Components @using HushianWebApp.Service @using HushianWebApp.Services @using Microsoft.AspNetCore.SignalR.Client @inject ChatService chatService @inject GroupService groupService @inject UserService userService @inject IJSRuntime JS @inject ToastService toastService @inject ILocalStorageService localStorageService; @inject HttpClient _Http; @inject NavigationManager nav گفتمان
@if (ChatCurrent != null) { @ChatCurrent?.ID)

@SelectedChatUserName

@if (ChatCurrent.status == Common.Enums.ConversationStatus.InProgress) { } else if (ChatCurrent.status == Common.Enums.ConversationStatus.Finished && (CurrentUser.Role == "Company" || ChatCurrent.ExperID == CurrentUser.ExperID)) { }
} else if (isSelectedInbox1) {
نکته مهم

از انتخاب گفتگو مطمئن شوید، بعد از انتخاب شما مسئول بررسی می‌باشد

}
@if (ChatCurrent != null && ChatCurrent.Responses != null) {
@{ bool target = false; } @foreach (var msg in ChatCurrent?.Responses) { @if (!target && ((!msg.IsRead && msg.Type == Common.Enums.ConversationType.UE) || ChatCurrent.Responses.Last() == msg)) { target = true;
@if (!msg.IsRead && msg.Type == Common.Enums.ConversationType.UE) {
پیام جدید
}
}
@if (msg.FileContent != null && msg.FileContent.Length > 0 && !string.IsNullOrWhiteSpace(msg.FileType)) { @if (msg.FileType.StartsWith("image/")) { image } else if (msg.FileType.StartsWith("audio/")) {
} @if (!string.IsNullOrWhiteSpace(msg.text)) {
@msg.text
} } else { @msg.text }
@if (msg.Type != Common.Enums.ConversationType.UE) { if (msg.IsRead) { } else { } }
} @{ target = false; }
} else {

هوشیان

}
@if (ChatCurrent != null && ChatCurrent.status != Common.Enums.ConversationStatus.Finished && ChatCurrent.Responses != null) {
@if (SelectedImagePreview != null) {
preview
} @if (RecordedAudioBytes != null) {
@RecordedAudioDuration
} @if (IsRecording) {
در حال ضبط... @RecordingTime
}
}
@code { Common.Dtos.CurrentUserInfo CurrentUser { get; set; } List _Group = new List(); //------------------------------------- bool isSelectedInbox1 = false; List Inbox1Items { get; set; } = new(); bool isSelectedInbox2 = true; List Inbox2Items { get; set; } = new(); bool isSelectedInbox3 = false; List Inbox3Items { get; set; } = new(); ///////////// ChatItemDto? ChatCurrent { get; set; } = null; string MsgInput { get; set; } IBrowserFile? SelectedImageFile = null; byte[]? SelectedImageBytes = null; string? SelectedImagePreview = null; // Audio recording properties bool IsRecording = false; string RecordingTime = "00:00"; byte[]? RecordedAudioBytes = null; string? RecordedAudioUrl = null; string RecordedAudioDuration = "00:00"; private System.Threading.Timer? recordingTimer; private DateTime recordingStartTime; bool chatloading = false; string SelectedChatUserName = ""; private bool _shouldObserveVisibility = false; private ConfirmDialog dialog = default!; private Modal modal = default!; private HubConnection? hubConnection; } @functions { protected override async Task OnInitializedAsync() { CurrentUser = await userService.GetCurrentUserInfo(); _Group = await groupService.GetGroups(); Inbox1Items = await chatService.ChatAwaitingOurResponse(); Inbox2Items = await chatService.MyChatsIsInProgress(); Inbox3Items = await chatService.MyChatsIsFinished(); //-------------hub var token = await localStorageService.GetItem("C/key"); string AddressHub = _Http.BaseAddress.AbsoluteUri.Replace("api/", ""); hubConnection = new HubConnectionBuilder() .WithUrl($"{_Http.BaseAddress.AbsoluteUri.Replace("api/", "")}chatNotificationHub", options => { options.AccessTokenProvider = () => Task.FromResult(token); }) .WithAutomaticReconnect() .Build(); hubConnection.On("ReceiveNewChatItemFromUser", async (chatitem) => { if (ChatCurrent!=null && ChatCurrent.ID == chatitem.ChatItemID) { ChatCurrent.Responses.Add(chatitem); StateHasChanged(); await MarkAsRead(chatitem.ID); // Scroll to target if exists, otherwise scroll to bottom await JS.InvokeVoidAsync("scrollToTargetOrBottom"); } else if (Inbox2Items.Any(a => a.ID == chatitem.ChatItemID)) { Inbox2Items = await chatService.MyChatsIsInProgress(); StateHasChanged(); } }); hubConnection.On("CheckMarkAsRead", async (chatresponseid) => { if (ChatCurrent.Responses.Any(a => a.ID == chatresponseid && !a.IsRead && (a.Type == Common.Enums.ConversationType.EU || a.Type == Common.Enums.ConversationType.CU))) { ChatCurrent.Responses.First(a => a.ID == chatresponseid).IsRead = true; StateHasChanged(); } }); //NewChat hubConnection.On("NewChat", async (companyid) => { if (CurrentUser.CompanyID==companyid) { Inbox1Items = await chatService.ChatAwaitingOurResponse(); StateHasChanged(); } }); await hubConnection.StartAsync(); //---------end hub await base.OnInitializedAsync(); } protected override async Task OnAfterRenderAsync(bool firstRender) { if (_shouldObserveVisibility) { _shouldObserveVisibility = false; await JS.InvokeVoidAsync("observeVisibility", DotNetObjectReference.Create(this)); await JS.InvokeVoidAsync("scrollToTarget"); } } async Task OnclickInbox(int ID) { switch (ID) { case 1: isSelectedInbox1 = true; isSelectedInbox2 = false; isSelectedInbox3 = false; Inbox1Items = await chatService.ChatAwaitingOurResponse(); break; case 2: isSelectedInbox2 = true; isSelectedInbox1 = false; isSelectedInbox3 = false; Inbox2Items = await chatService.MyChatsIsInProgress(); break; case 3: isSelectedInbox3 = true; isSelectedInbox2 = false; isSelectedInbox1 = false; Inbox3Items = await chatService.MyChatsIsFinished(); break; } ChatCurrent = null; StateHasChanged(); } async Task OnClickSendMsg() { if ((!string.IsNullOrEmpty(MsgInput) || SelectedImageFile != null || RecordedAudioBytes != null) && ChatCurrent != null) { Common.Enums.ConversationType type = CurrentUser.Role == "Company" ? Common.Enums.ConversationType.CU : Common.Enums.ConversationType.EU; ChatItemResponseDto? model=null; if (SelectedImageFile != null) { var bytes = SelectedImageBytes ?? Array.Empty(); model = await chatService.ADDChatResponse( ChatCurrent.ID, MsgInput, type, SelectedImageFile.Name, SelectedImageFile.ContentType, bytes); } else if (RecordedAudioBytes != null) { // Send audio message var fileName = $"audio_{DateTimeOffset.Now.ToUnixTimeSeconds()}.wav"; model = await chatService.ADDChatResponse( ChatCurrent.ID, MsgInput, type, fileName, "audio/wav", RecordedAudioBytes); } else { model = await chatService.ADDChatResponse(ChatCurrent.ID, MsgInput, type); } if(model!=null) { ChatCurrent?.Responses.Add(model); ChatCurrent.LastText = MsgInput; await Task.Yield(); await JS.InvokeVoidAsync("scrollToBottom", "B1"); MsgInput = string.Empty; SelectedImageFile = null; SelectedImageBytes = null; SelectedImagePreview = null; // Clear recorded audio after sending RecordedAudioBytes = null; RecordedAudioUrl = null; RecordedAudioDuration = "00:00"; } } } async Task onClickSelectedChat(int InboxID, ChatItemDto chatItem) { chatloading = true; SelectedChatUserName = "در حال گفتگو با '" + chatItem.UserFullName + "'"; if (CurrentUser.Role == "Company") { var mo = chatItem.Responses.OrderBy(o => o.ID).Last(l => l.Type != ConversationType.UE); if (mo!=null && mo.Type == Common.Enums.ConversationType.EU) SelectedChatUserName = "گفتگوی '" + mo.ExperName + "' با '" + chatItem.UserFullName+"'"; } ChatCurrent = chatItem; _shouldObserveVisibility = true; // فعال کن تا در OnAfterRenderAsync صدا زده بشه StateHasChanged(); // مجبور کن Blazor رندر کنه chatloading = false; } [JSInvokable] public async Task MarkAsRead(int id) { var allowcjange = nav.Uri.Split('/'); if (allowcjange.Length == 4 && (allowcjange[3].ToLower() == "chat" || string.IsNullOrEmpty(allowcjange[3]) )) { var msg = ChatCurrent.Responses.FirstOrDefault(m => m.ID == id); if (msg != null && !msg.IsRead && msg.Type == Common.Enums.ConversationType.UE) { msg.IsRead = true; await chatService.MarkAsReadChatItemAsync(id); } // StateHasChanged(); await Task.CompletedTask; } } async Task onclickAttachedto() { Dictionary parameters = new Dictionary(); parameters.Add("chatID", ChatCurrent.ID); parameters.Add("OnMultipleOfThree", EventCallback.Factory.Create(this, CallBackAttachedto)); await modal.ShowAsync("پیوست کارشناس", parameters: parameters); } async Task CallBackAttachedto() { await modal.HideAsync(); toastService.Notify(new ToastMessage(ToastType.Success, "کارشناس جدید به این گفتگو پیوست")); } async Task OpenChat() { if (CurrentUser.Role == "Company" || CurrentUser.Role == "Exper" && ChatCurrent.ExperID == CurrentUser.ExperID) { if (ChatCurrent.status != Common.Enums.ConversationStatus.Finished) return; if (await chatService.OpenChat(ChatCurrent.ID)) { ChatCurrent.status = Common.Enums.ConversationStatus.InProgress; StateHasChanged(); } else toastService.Notify(new ToastMessage(ToastType.Danger, "تغییر وضعیت گفتگو موفق نبود")); } else toastService.Notify(new ToastMessage(ToastType.Danger, "دسترسی به این گفتگو ندارید")); } async Task CloseChat() { if (ChatCurrent.status == Common.Enums.ConversationStatus.Finished) return; var options = new ConfirmDialogOptions { YesButtonText = "بله", YesButtonColor = ButtonColor.Success, NoButtonText = "انصراف", NoButtonColor = ButtonColor.Danger }; var confirmation = await dialog.ShowAsync( title: "پایان دادن به گفتگو", message1: "اطمینان دارید ؟", confirmDialogOptions: options); if (confirmation) { if (await chatService.ChatIsFinish(ChatCurrent.ID)) { ChatCurrent.status = Common.Enums.ConversationStatus.Finished; StateHasChanged(); } } } private static string GetImageDataUrl(string? fileType, byte[]? content) => (string.IsNullOrWhiteSpace(fileType) || content == null || content.Length == 0) ? string.Empty : $"data:{fileType};base64,{Convert.ToBase64String(content)}"; private static string GetDownloadFileName(string? fileName, string? fileType) { if (!string.IsNullOrWhiteSpace(fileName)) return fileName; var ext = ""; if (!string.IsNullOrWhiteSpace(fileType) && fileType.StartsWith("image/")) { ext = "." + fileType.Split('/').Last(); } return $"image_{DateTimeOffset.Now.ToUnixTimeSeconds()}{ext}"; } private string GetAudioDataUrl(string? fileType, byte[]? content) => (string.IsNullOrWhiteSpace(fileType) || content == null || content.Length == 0) ? string.Empty : $"data:{fileType};base64,{Convert.ToBase64String(content)}"; // Audio recording methods private async Task ToggleAudioRecording() { if (IsRecording) { await StopAudioRecording(); } else { await StartAudioRecording(); } } private async Task StartAudioRecording() { try { var result = await JS.InvokeAsync("startAudioRecording"); if (result) { IsRecording = true; recordingStartTime = DateTime.Now; recordingTimer = new System.Threading.Timer(UpdateRecordingTime, null, 0, 1000); StateHasChanged(); } else { toastService.Notify(new ToastMessage(ToastType.Warning, "خطا در شروع ضبط صدا")); } } catch (Exception ex) { toastService.Notify(new ToastMessage(ToastType.Danger, $"خطا در ضبط صدا: {ex.Message}")); } } private async Task StopAudioRecording() { try { var audioData = await JS.InvokeAsync("stopAudioRecording"); if (!string.IsNullOrEmpty(audioData)) { // Convert base64 to bytes var base64Data = audioData.Split(',')[1]; RecordedAudioBytes = Convert.FromBase64String(base64Data); RecordedAudioUrl = audioData; RecordedAudioDuration = RecordingTime; IsRecording = false; recordingTimer?.Dispose(); recordingTimer = null; await ClearSelectedImage(); StateHasChanged(); } } catch (Exception ex) { toastService.Notify(new ToastMessage(ToastType.Danger, $"خطا در توقف ضبط صدا: {ex.Message}")); } finally { IsRecording = false; recordingTimer?.Dispose(); recordingTimer = null; StateHasChanged(); } } private void UpdateRecordingTime(object? state) { var elapsed = DateTime.Now - recordingStartTime; RecordingTime = $"{elapsed.Minutes:D2}:{elapsed.Seconds:D2}"; InvokeAsync(StateHasChanged); } private Task ClearRecordedAudio() { RecordedAudioBytes = null; RecordedAudioUrl = null; RecordedAudioDuration = "00:00"; return Task.CompletedTask; } private async Task OpenFileDialog() { await JS.InvokeVoidAsync("triggerClick", "chatImageInput"); } private async Task OnImageSelected(InputFileChangeEventArgs e) { var file = e.File; if (file is null) { SelectedImageFile = null; SelectedImageBytes = null; SelectedImagePreview = null; return; } SelectedImageFile = file; using var memoryStream = new MemoryStream(); await file.OpenReadStream().CopyToAsync(memoryStream); SelectedImageBytes = memoryStream.ToArray(); SelectedImagePreview = $"data:{file.ContentType};base64,{Convert.ToBase64String(SelectedImageBytes)}"; await ClearRecordedAudio(); } private Task ClearSelectedImage() { SelectedImageFile = null; SelectedImageBytes = null; SelectedImagePreview = null; return Task.CompletedTask; } }