From 0856c8ae59fde08797d4204a1c7eb8d3cd9f59c7 Mon Sep 17 00:00:00 2001 From: mmrbnjd Date: Sun, 10 Aug 2025 20:52:45 +0330 Subject: [PATCH] ... --- .../Pages/FromUserSide/UserCP.razor | 459 +++++++++++++++++- 1 file changed, 454 insertions(+), 5 deletions(-) diff --git a/Presentation/HushianWebApp/Pages/FromUserSide/UserCP.razor b/Presentation/HushianWebApp/Pages/FromUserSide/UserCP.razor index 1ac1449..65bc7bd 100644 --- a/Presentation/HushianWebApp/Pages/FromUserSide/UserCP.razor +++ b/Presentation/HushianWebApp/Pages/FromUserSide/UserCP.razor @@ -6,6 +6,7 @@ @using HushianWebApp.Service @using HushianWebApp.Services @using Microsoft.AspNetCore.SignalR.Client; +@using System.Threading; @inject NavigationManager NavigationManager @inject ChatService ChatService @@ -77,11 +78,26 @@
- @if (msg.FileContent != null && msg.FileContent.Length > 0 && !string.IsNullOrWhiteSpace(msg.FileType) && msg.FileType.StartsWith("image/")) + @if (msg.FileContent != null && msg.FileContent.Length > 0 && !string.IsNullOrWhiteSpace(msg.FileType)) { - - image - + @if (msg.FileType.StartsWith("image/")) + { + + image + + } + else if (msg.FileType.StartsWith("audio/")) + { +
+ +
+ @GetAudioDuration(msg.FileContent) +
+
+ } @if (!string.IsNullOrWhiteSpace(msg.text)) {
@msg.text
@@ -159,10 +175,31 @@ + + + +
+ + @if (SelectedImagePreview != null) {
@@ -170,6 +207,33 @@
} + + + @if (RecordedAudioBytes != null) + { +
+
+ +
+ @RecordedAudioDuration +
+
+ +
+ } + + + @if (IsRecording) + { +
+
+ + در حال ضبط... @RecordingTime +
+
+ }
} @@ -214,6 +278,16 @@ 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; public bool IsLogin { get; set; } = false; public bool IsEndFirstProcess { get; set; } = false; @@ -243,16 +317,118 @@ return value; } } + + // 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; + 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 string GetAudioDataUrl(string? fileType, byte[]? content) + => (string.IsNullOrWhiteSpace(fileType) || content == null || content.Length == 0) + ? string.Empty + : $"data:{fileType};base64,{Convert.ToBase64String(content)}"; + + private string GetAudioDuration(byte[]? content) + { + // Simple duration calculation based on file size (approximate) + if (content == null || content.Length == 0) return "00:00"; + + // Assuming 16-bit PCM at 44.1kHz, mono + var bytesPerSecond = 44100 * 2; // 44.1kHz * 2 bytes per sample + var durationSeconds = content.Length / bytesPerSecond; + var minutes = durationSeconds / 60; + var seconds = durationSeconds % 60; + return $"{minutes:D2}:{seconds:D2}"; + } } @functions { async Task OnClickSendMsg() { - if (!string.IsNullOrEmpty(MsgInput) || SelectedImageFile != null) + if (!string.IsNullOrEmpty(MsgInput) || SelectedImageFile != null || RecordedAudioBytes != null) { if (LastOpenChat != null) { Common.Enums.ConversationType type = Common.Enums.ConversationType.UE; ChatItemResponseDto? model; + if (SelectedImageFile != null) { var bytes = SelectedImageBytes ?? Array.Empty(); @@ -264,10 +440,23 @@ SelectedImageFile.ContentType, bytes); } + else if (RecordedAudioBytes != null) + { + // Send audio message + var fileName = $"audio_{DateTimeOffset.Now.ToUnixTimeSeconds()}.wav"; + model = await chatService.ADDChatResponse( + LastOpenChat.ID, + MsgInput, + type, + fileName, + "audio/wav", + RecordedAudioBytes); + } else { model = await chatService.ADDChatResponse(LastOpenChat.ID, MsgInput, type); } + LastOpenChat?.Responses.Add(model); LastOpenChat.LastText = MsgInput; @@ -297,6 +486,11 @@ SelectedImageFile = null; SelectedImageBytes = null; SelectedImagePreview = null; + + // Clear recorded audio after sending + RecordedAudioBytes = null; + RecordedAudioUrl = null; + RecordedAudioDuration = "00:00"; } } @@ -923,6 +1117,192 @@ box-shadow: 0 2px 8px rgba(108, 117, 125, 0.2); } + /* Audio recording button styling */ + .audio-btn { + border-radius: 50%; + width: 38px; + height: 38px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); + border: 1px solid #e9ecef; + box-shadow: 0 4px 12px rgba(108, 117, 125, 0.2); + transition: all 0.3s ease; + color: #495057; + position: relative; + overflow: hidden; + } + + .audio-btn:hover { + transform: translateY(-2px) scale(1.05); + box-shadow: 0 6px 16px rgba(108, 117, 125, 0.3); + background: linear-gradient(135deg, #e9ecef 0%, #dee2e6 100%); + } + + .audio-btn:active { + transform: translateY(0) scale(0.95); + box-shadow: 0 2px 8px rgba(108, 117, 125, 0.2); + } + + .audio-btn.recording { + background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); + border-color: #dc3545; + color: white; + animation: recordingPulse 1.5s ease-in-out infinite; + } + + .audio-btn.recording:hover { + background: linear-gradient(135deg, #c82333 0%, #bd2130 100%); + transform: translateY(-2px) scale(1.05); + box-shadow: 0 6px 16px rgba(220, 53, 69, 0.4); + } + + .recording-pulse { + animation: iconPulse 1s ease-in-out infinite; + } + + @@keyframes recordingPulse { + 0%, 100% { + box-shadow: 0 4px 12px rgba(220, 53, 69, 0.3); + } + 50% { + box-shadow: 0 4px 20px rgba(220, 53, 69, 0.6), 0 0 30px rgba(220, 53, 69, 0.3); + } + } + + @@keyframes iconPulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.1); + } + } + + /* Audio preview styling */ + .audio-preview { + background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + border: 1px solid #e9ecef; + border-radius: 12px; + padding: 0.75rem; + margin-top: 0.5rem; + } + + .audio-preview-controls { + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + .audio-preview-controls audio { + border-radius: 8px; + background: #f8f9fa; + } + + .audio-preview-info { + text-align: center; + color: #6c757d; + font-size: 0.875rem; + } + + /* Recording status styling */ + .recording-status { + background: linear-gradient(135deg, #fff3cd 0%, #ffeaa7 100%); + border: 1px solid #ffc107; + border-radius: 12px; + padding: 0.75rem; + margin-top: 0.5rem; + animation: recordingStatusPulse 2s ease-in-out infinite; + } + + @@keyframes recordingStatusPulse { + 0%, 100% { + box-shadow: 0 2px 8px rgba(255, 193, 7, 0.2); + } + 50% { + box-shadow: 0 4px 16px rgba(255, 193, 7, 0.4); + } + } + + .recording-indicator { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + } + + .recording-dot { + width: 12px; + height: 12px; + background: #dc3545; + border-radius: 50%; + animation: recordingDotPulse 1s ease-in-out infinite; + } + + @@keyframes recordingDotPulse { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.7; + transform: scale(1.2); + } + } + + .recording-text { + color: #856404; + font-weight: 600; + font-size: 0.875rem; + } + + /* Audio message styling in chat */ + .audio-message { + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: center; + } + + .audio-message audio { + border-radius: 8px; + background: #f8f9fa; + border: 1px solid #e9ecef; + } + + .audio-info { + text-align: center; + color: #6c757d; + font-size: 0.75rem; + } + + /* Responsive design for audio elements */ + @@media (max-width: 768px) { + .audio-preview-controls audio { + max-width: 200px; + } + + .audio-message audio { + max-width: 200px; + } + } + + @@media (max-width: 480px) { + .audio-preview-controls audio { + max-width: 180px; + } + + .audio-message audio { + max-width: 180px; + } + + .recording-text { + font-size: 0.8rem; + } + } + /* Beautiful chat separator styling */ .chat-separator { text-align: center; @@ -1209,6 +1589,75 @@