| @@ -187,7 +187,7 @@ | |||
| protected override async Task OnInitializedAsync() | |||
| { | |||
| var id = AuthService.ID; | |||
| await LoadChatConversations(); | |||
| @@ -288,8 +288,7 @@ | |||
| private void OnSelectConversation(string conversationId, int typeLLM) | |||
| { | |||
| CurrentConversationId = conversationId; | |||
| Navigation.NavigateTo($"chatroom_nav/{typeLLM}?conversationId={conversationId}"); | |||
| Navigation.NavigateTo($"chatroom_base/{typeLLM}/{conversationId}", true); | |||
| } | |||
| private void NavigateTo(string route) | |||
| @@ -1,98 +1,167 @@ | |||
| @page "/chatroom_base/{typellm:int}" | |||
| @page "/chatroom_base/{typellm:int}/{conversationid}" | |||
| <div class="chatroom-container"> | |||
| <!-- Zone de messages --> | |||
| <div class="chatroom-messages"> | |||
| @if (SelectedConversation == null || !Messages.Any()) | |||
| { | |||
| <div class="chatroom-empty"> | |||
| <div class="empty-icon">💬</div> | |||
| <h3>Commencez une conversation</h3> | |||
| <p>Posez une question ou envoyez un message pour démarrer</p> | |||
| </div> | |||
| } | |||
| else | |||
| { | |||
| @foreach (var msg in Messages) | |||
| { | |||
| <div class="message-row @(msg.IsUser ? "user" : "assistant")"> | |||
| <div class="avatar @(msg.IsUser ? "avatar-user" : "avatar-assistant")"> | |||
| @if (msg.IsUser) | |||
| { | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0" /> | |||
| <path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1" /> | |||
| </svg> | |||
| } | |||
| else | |||
| { | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.6 26.6 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.93.93 0 0 1-.765.935c-.845.147-2.34.346-4.235.346s-3.39-.2-4.235-.346A.93.93 0 0 1 3 9.219zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a25 25 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25 25 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135" /> | |||
| <path d="M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2zM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5" /> | |||
| </svg> | |||
| } | |||
| </div> | |||
| <!-- Liste des conversations --> | |||
| <div class="conversation-list"> | |||
| <button class="btn btn-primary" @onclick="CreateConversation"> | |||
| Nouvelle conversation | |||
| </button> | |||
| <div class="message-content-wrapper"> | |||
| <div class="bubble @(msg.IsUser ? "bubble-user" : "bubble-assistant")"> | |||
| @msg.Text | |||
| </div> | |||
| <ul> | |||
| @foreach (var conv in Conversations) | |||
| @if (!msg.IsUser) | |||
| { | |||
| <div class="message-actions"> | |||
| <button class="btn-message-action" @onclick="() => CopyMessage(msg.Text)" title="Copier"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1z" /> | |||
| <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0z" /> | |||
| </svg> | |||
| </button> | |||
| </div> | |||
| } | |||
| </div> | |||
| </div> | |||
| } | |||
| @if (isTyping) | |||
| { | |||
| <li @onclick="() => SelectConversation(conv.Id)" | |||
| class="@(conv.Id == SelectedConversationId ? "selected" : "")"> | |||
| @conv.Title | |||
| </li> | |||
| <div class="message-row assistant"> | |||
| <div class="avatar avatar-assistant"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.6 26.6 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.93.93 0 0 1-.765.935c-.845.147-2.34.346-4.235.346s-3.39-.2-4.235-.346A.93.93 0 0 1 3 9.219zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a25 25 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25 25 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135" /> | |||
| <path d="M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2zM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5" /> | |||
| </svg> | |||
| </div> | |||
| <div class="message-content-wrapper"> | |||
| <div class="bubble bubble-assistant"> | |||
| <div class="typing-indicator"> | |||
| <span></span> | |||
| <span></span> | |||
| <span></span> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| } | |||
| </ul> | |||
| } | |||
| </div> | |||
| <!-- Zone principale --> | |||
| <div class="chat-area"> | |||
| @if (SelectedConversation != null) | |||
| <!-- Zone de saisie moderne --> | |||
| <div class="chat-input-container"> | |||
| <!-- Fichiers attachés --> | |||
| @if (UploadedDocuments.Any()) | |||
| { | |||
| <h3>@SelectedConversation.Title</h3> | |||
| <div class="messages"> | |||
| @foreach (var msg in Messages) | |||
| <div class="attached-files"> | |||
| @foreach (var doc in UploadedDocuments) | |||
| { | |||
| <div class="message-row @(msg.IsUser ? "user" : "assistant")"> | |||
| <div class="avatar @(msg.IsUser ? "avatar-user" : "avatar-assistant")"> | |||
| @(msg.IsUser ? "👤" : "🤖") | |||
| <div class="file-item"> | |||
| <div class="file-preview"> | |||
| <div class="file-icon"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5z" /> | |||
| </svg> | |||
| </div> | |||
| </div> | |||
| <div class="bubble @(msg.IsUser ? "bubble-user" : "bubble-assistant")"> | |||
| @msg.Text | |||
| <div class="file-info"> | |||
| <span class="file-name">@doc.FileName</span> | |||
| <span class="file-size">@FormatFileSize(doc.Bytes.Length)</span> | |||
| </div> | |||
| <button class="remove-file-button" @onclick="() => RemoveDocument(doc)" aria-label="Supprimer le fichier"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z" /> | |||
| </svg> | |||
| </button> | |||
| </div> | |||
| } | |||
| </div> | |||
| } | |||
| <div class="chat-input"> | |||
| <textarea @bind="CurrentInput"></textarea> | |||
| <div class="options"> | |||
| <select @bind="SelectedMode" disabled> | |||
| <option value="llm">LLM</option> | |||
| <option value="rag">RAG</option> | |||
| <option value="code">Code</option> | |||
| <option value="image">Image</option> | |||
| </select> | |||
| <!-- Input wrapper --> | |||
| <div class="input-wrapper @(UploadedDocuments.Any() ? "has-files" : "")"> | |||
| <textarea @bind="CurrentInput" | |||
| @bind:event="oninput" | |||
| @onkeydown="HandleKeyPress" | |||
| placeholder="Nouvelle conversation..." | |||
| rows="1"></textarea> | |||
| <select @bind="SelectedModel" hidden> | |||
| @foreach (var model in OllamaModels) | |||
| { | |||
| <option value="@model">@model</option> | |||
| } | |||
| </select> | |||
| <div class="actions-row"> | |||
| <!-- Bouton attachement --> | |||
| <label class="attach-button" for="fileUpload"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5a.5.5 0 0 1 1 0v7a.5.5 0 0 0 1 0V3a1.5 1.5 0 1 0-3 0v9a2.5 2.5 0 0 0 5 0V5a.5.5 0 0 1 1 0v7a3.5 3.5 0 1 1-7 0z" /> | |||
| </svg> | |||
| <span>Importer un fichier</span> | |||
| <InputFile id="fileUpload" OnChange="UploadDocuments" multiple style="display: none;" /> | |||
| </label> | |||
| <label for="SelectedDomain" class="col-form-label" hidden="@hiddenDomain">Domaine RAG :</label> | |||
| <select @bind="SelectedDomain" hidden="@hiddenDomain"> | |||
| <option value="">(Aucun domaine)</option> | |||
| @foreach (var d in RagDomains) | |||
| { | |||
| <option value="@d">@d</option> | |||
| } | |||
| </select> | |||
| <label for="IsWithHistorique" class="col-form-label">Inclure l'historique ? :</label> | |||
| <input id="IsWithHistorique" @bind="IsWithHistorique" type="checkbox" class="form-check-input" /> | |||
| </div> | |||
| <div class="document-upload"> | |||
| <InputFile OnChange="UploadDocuments" multiple /> | |||
| @if (UploadedDocuments.Any()) | |||
| <div class="right-actions"> | |||
| <!-- Sélecteur de domaine RAG --> | |||
| @if (!hiddenDomain && RagDomains.Any()) | |||
| { | |||
| <div class="doc-list"> | |||
| @foreach (var doc in UploadedDocuments) | |||
| <select class="domain-select" @bind="SelectedDomain"> | |||
| @foreach (var domain in RagDomains) | |||
| { | |||
| <div class="doc-item"> | |||
| <span>@doc.FileName (@(doc.Bytes.Length / 1024) Ko)</span> | |||
| <button class="btn btn-sm btn-danger" @onclick="() => RemoveDocument(doc)">X</button> | |||
| </div> | |||
| <option value="@domain">@domain</option> | |||
| } | |||
| </div> | |||
| </select> | |||
| } | |||
| <!-- Bouton historique --> | |||
| <button class="history-button @(IsWithHistorique ? "active" : "desactive")" @onclick="ToggleHistory" title="@(IsWithHistorique ? "Historique activé" : "Historique désactivé")"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M8.515 1.019A7 7 0 0 0 8 1V0a8 8 0 0 1 .589.022zm2.004.45a7 7 0 0 0-.985-.299l.219-.976q.576.129 1.126.342zm1.37.71a7 7 0 0 0-.439-.27l.493-.87a8 8 0 0 1 .979.654l-.615.789a7 7 0 0 0-.418-.302zm1.834 1.79a7 7 0 0 0-.653-.796l.724-.69q.406.429.747.91zm.744 1.352a7 7 0 0 0-.214-.468l.893-.45a8 8 0 0 1 .45 1.088l-.95.313a7 7 0 0 0-.179-.483m.53 2.507a7 7 0 0 0-.1-1.025l.985-.17q.1.58.116 1.17zm-.131 1.538q.05-.254.081-.51l.993.123a8 8 0 0 1-.23 1.155l-.964-.267q.069-.247.12-.501m-.952 2.379q.276-.436.486-.908l.914.405q-.24.54-.555 1.038zm-.964 1.205q.183-.183.35-.378l.758.653a8 8 0 0 1-.401.432z" /> | |||
| <path d="M8 1a7 7 0 1 0 4.95 11.95l.707.707A8.001 8.001 0 1 1 8 0z" /> | |||
| <path d="M7.5 3a.5.5 0 0 1 .5.5v5.21l3.248 1.856a.5.5 0 0 1-.496.868l-3.5-2A.5.5 0 0 1 7 9V3.5a.5.5 0 0 1 .5-.5" /> | |||
| </svg> | |||
| </button> | |||
| <button class="voice-button" @onclick="RecordVoice" aria-label="Message vocal"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path fill-rule="evenodd" d="M8.5 2a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-1 0v-11a.5.5 0 0 1 .5-.5m-2 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m4 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m-6 1.5A.5.5 0 0 1 5 6v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m8 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m-10 1A.5.5 0 0 1 3 7v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5m12 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5" /> | |||
| </svg> | |||
| </button> | |||
| <!-- Bouton envoyer --> | |||
| <button class="send-button" | |||
| @onclick="SendMessage" | |||
| disabled="@(string.IsNullOrWhiteSpace(CurrentInput) && !UploadedDocuments.Any())" | |||
| aria-label="Envoyer le message"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path fill-rule="evenodd" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5" /> | |||
| </svg> | |||
| </button> | |||
| </div> | |||
| <button class="btn btn-primary" @onclick="SendMessage">Envoyer</button> | |||
| </div> | |||
| } | |||
| else | |||
| { | |||
| <p>Sélectionne ou crée une conversation.</p> | |||
| } | |||
| </div> | |||
| </div> | |||
| </div> | |||
| @@ -2,6 +2,8 @@ | |||
| using Microsoft.AspNetCore.Components; | |||
| using Microsoft.AspNetCore.Components.Authorization; | |||
| using Microsoft.AspNetCore.Components.Forms; | |||
| using Microsoft.AspNetCore.Components.Web; // ← AJOUT pour KeyboardEventArgs | |||
| using Microsoft.JSInterop; | |||
| using ReAct_PME.Domain; | |||
| using ReAct_PME.WebUI.ServicesUI; | |||
| using System.Net.Http.Json; | |||
| @@ -10,9 +12,11 @@ using System.Text.Json; | |||
| namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| { | |||
| public partial class ChatRoom_base: ComponentBase | |||
| public partial class ChatRoom_base : ComponentBase | |||
| { | |||
| [Parameter] public int TypeLLM { get; set; } | |||
| [Parameter] public string? ConversationId { get; set; } | |||
| [Inject] public HttpClient Http { get; set; } = default!; | |||
| [Inject] private ReAct_PME.WebUI.ServicesUI.AuthService AuthService { get; set; } = default!; | |||
| @@ -20,6 +24,8 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| [Inject] private AuthenticationStateProvider AuthenticationStateProvider { get; set; } = default!; | |||
| [Inject] private NavigationManager Navigation { get; set; } = default!; | |||
| [Inject] private IToastService ToastService { get; set; } = default!; | |||
| [Inject] private IJSRuntime JSRuntime { get; set; } = default!; | |||
| public class UploadedDoc | |||
| { | |||
| @@ -27,12 +33,14 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| public byte[] Bytes { get; set; } = Array.Empty<byte>(); | |||
| } | |||
| public List<UploadedDoc> UploadedDocuments { get; set; } = new(); | |||
| // État de l'UI | |||
| public List<ConversationDto> Conversations { get; set; } = new(); | |||
| public ConversationDto? SelectedConversation { get; set; } | |||
| public string SelectedConversationId { get; set; } = ""; | |||
| public List<MessageDto> Messages { get; set; } = new(); | |||
| private bool IsWithHistorique { get; set; } | |||
| private bool isTyping { get; set; } = false; | |||
| public string CurrentInput { get; set; } = ""; | |||
| @@ -47,7 +55,7 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| protected override async Task OnInitializedAsync() | |||
| { | |||
| #region Vérification si utilisateur est connecté et s''il a les droits | |||
| #region Vérification si utilisateur est connecté et s'il a les droits | |||
| var tok = await PagesInitializer.VerifConnexionAndRules(AuthenticationStateProvider, Navigation, Http, ToastService, "/api/ChatRoom/rag/listeDomaines"); | |||
| if (tok == null || tok.Length == 0) | |||
| @@ -65,9 +73,9 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| if (TypeLLM == 1) | |||
| { | |||
| SelectedMode = "llm"; | |||
| IsWithHistorique= true; | |||
| IsWithHistorique = true; | |||
| } | |||
| else if(TypeLLM == 2) | |||
| else if (TypeLLM == 2) | |||
| { | |||
| SelectedMode = "rag"; | |||
| IsWithHistorique = false; | |||
| @@ -85,13 +93,23 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| SelectedMode = "image"; | |||
| IsWithHistorique = false; | |||
| } | |||
| else | |||
| else | |||
| { | |||
| SelectedMode = "llm"; | |||
| IsWithHistorique = true; | |||
| } | |||
| var uri = new Uri(Navigation.Uri); | |||
| if (!string.IsNullOrEmpty(ConversationId)) | |||
| { | |||
| SelectConversation(ConversationId); | |||
| } | |||
| else | |||
| { | |||
| Console.WriteLine("No conversationId in URL"); | |||
| // IsNewConversation reste false par défaut | |||
| } | |||
| } | |||
| private async Task UploadDocuments(InputFileChangeEventArgs e) | |||
| @@ -111,7 +129,7 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| StateHasChanged(); | |||
| } | |||
| private void RemoveDocument(UploadedDoc doc) | |||
| { | |||
| UploadedDocuments.Remove(doc); | |||
| @@ -161,9 +179,9 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| { | |||
| SelectedConversationId = id; | |||
| SelectedConversation = Conversations.Where(c => c.Id == id).FirstOrDefault(); | |||
| SelectedConversation = Conversations.Where(c => c.Id == id).FirstOrDefault(); | |||
| Messages = SelectedConversation?.Messages.OrderBy(m=>m.CreatedAt).ToList() ?? new(); | |||
| Messages = SelectedConversation?.Messages.OrderBy(m => m.CreatedAt).ToList() ?? new(); | |||
| StateHasChanged(); | |||
| } | |||
| @@ -175,6 +193,9 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| if (string.IsNullOrWhiteSpace(CurrentInput) || SelectedConversation == null) | |||
| return; | |||
| isTyping = true; | |||
| StateHasChanged(); | |||
| // Ajoute ton message dans l'UI immédiatement | |||
| var userMsg = new MessageDto | |||
| { | |||
| @@ -199,22 +220,12 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| }; | |||
| CurrentInput = ""; | |||
| /* | |||
| var response = await Http.PostAsJsonAsync("/api/ChatRoom/sendMessage", payload); | |||
| if (response.IsSuccessStatusCode) | |||
| { | |||
| var assistantMsg = await response.Content.ReadFromJsonAsync<MessageDto>(); | |||
| if (assistantMsg != null) | |||
| { | |||
| Messages.Add(assistantMsg); | |||
| } | |||
| } | |||
| */ | |||
| var assistantMsg = await ApiService.EnvoiRequete<MessageDto>("/api/ChatRoom/sendMessage", payload); | |||
| isTyping = false; | |||
| if (assistantMsg != null) | |||
| { | |||
| Messages.Add(assistantMsg); | |||
| @@ -228,16 +239,66 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| } | |||
| } | |||
| UploadedDocuments.Clear(); | |||
| StateHasChanged(); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| catch (Exception ex) | |||
| { | |||
| Console.WriteLine(ex.Message); | |||
| isTyping = false; | |||
| StateHasChanged(); | |||
| } | |||
| } | |||
| private async Task HandleKeyPress(KeyboardEventArgs e) | |||
| { | |||
| if (e.Key == "Enter" && !e.ShiftKey) | |||
| { | |||
| await SendMessage(); | |||
| } | |||
| } | |||
| private void GoBack() | |||
| { | |||
| Navigation.NavigateTo("/"); | |||
| } | |||
| private void ToggleHistory() | |||
| { | |||
| IsWithHistorique = !IsWithHistorique; | |||
| ToastService.ShowInfo(IsWithHistorique ? "Historique activé" : "Historique désactivé"); | |||
| } | |||
| private async Task CopyMessage(string text) | |||
| { | |||
| try | |||
| { | |||
| await JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", text); | |||
| ToastService.ShowSuccess("Message copié !"); | |||
| } | |||
| catch (Exception) | |||
| { | |||
| // Fallback si clipboard API n'est pas disponible | |||
| ToastService.ShowWarning("Impossible de copier le message"); | |||
| } | |||
| } | |||
| private void RecordVoice() | |||
| { | |||
| ToastService.ShowInfo("Fonctionnalité d'enregistrement vocal en cours de développement"); | |||
| } | |||
| private string FormatFileSize(long bytes) | |||
| { | |||
| string[] sizes = { "B", "KB", "MB", "GB" }; | |||
| double len = bytes; | |||
| int order = 0; | |||
| while (len >= 1024 && order < sizes.Length - 1) | |||
| { | |||
| order++; | |||
| len = len / 1024; | |||
| } | |||
| return $"{len:0.##} {sizes[order]}"; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,105 +1,36 @@ | |||
| /* ======================================== | |||
| CHATROOM - AI DASHBOARD | |||
| ======================================== */ | |||
| | |||
| .chatroom-container { | |||
| display: flex; | |||
| flex-direction: column; | |||
| height: 100vh; | |||
| background: var(--ai-bg, #eaf8ff); | |||
| } | |||
| /* ======================================== | |||
| HEADER | |||
| ======================================== */ | |||
| .chatroom-header { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| align-items: center; | |||
| padding: 1rem 2rem; | |||
| background: white; | |||
| border-bottom: 1px solid var(--ai-border, #e0e0e0); | |||
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); | |||
| } | |||
| .chatroom-header-left { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 1rem; | |||
| } | |||
| .chatroom-title { | |||
| font-size: 1.5rem; | |||
| font-weight: 600; | |||
| color: var(--ai-text-dark, #243b5d); | |||
| margin: 0; | |||
| } | |||
| .chatroom-header-right { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 1rem; | |||
| height: 100%; | |||
| background: var(--bg-light); | |||
| } | |||
| .domain-selector { | |||
| .chatroom-messages { | |||
| flex: 1; | |||
| overflow-y: auto; | |||
| padding: 2rem; | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 0.5rem; | |||
| flex-direction: column; | |||
| gap: 1.5rem; | |||
| } | |||
| .domain-selector label { | |||
| font-size: 0.9rem; | |||
| color: var(--ai-text-light, #6c757d); | |||
| margin: 0; | |||
| .chatroom-messages::-webkit-scrollbar { | |||
| width: 8px; | |||
| } | |||
| .domain-selector select { | |||
| padding: 0.5rem 2rem 0.5rem 0.75rem; | |||
| border: 1px solid var(--ai-border, #e0e0e0); | |||
| border-radius: 8px; | |||
| font-size: 0.9rem; | |||
| background: white; | |||
| cursor: pointer; | |||
| min-width: 150px; | |||
| .chatroom-messages::-webkit-scrollbar-track { | |||
| background: transparent; | |||
| } | |||
| .btn-icon { | |||
| width: 40px; | |||
| height: 40px; | |||
| border-radius: 50%; | |||
| border: none; | |||
| background: transparent; | |||
| color: var(--ai-text-dark, #243b5d); | |||
| font-size: 1.1rem; | |||
| cursor: pointer; | |||
| transition: all 0.2s ease; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| .btn-icon:hover { | |||
| background: var(--ai-bg, #eaf8ff); | |||
| .chatroom-messages::-webkit-scrollbar-thumb { | |||
| background: var(--secondary); | |||
| border-radius: 4px; | |||
| } | |||
| .btn-back:hover { | |||
| background: var(--ai-primary, #243b5d); | |||
| color: white; | |||
| } | |||
| /* ======================================== | |||
| MESSAGES AREA | |||
| ======================================== */ | |||
| .chatroom-messages { | |||
| flex: 1; | |||
| overflow-y: auto; | |||
| padding: 2rem; | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 1.5rem; | |||
| } | |||
| .chatroom-messages::-webkit-scrollbar-thumb:hover { | |||
| background: var(--primary); | |||
| } | |||
| .chatroom-empty { | |||
| display: flex; | |||
| @@ -107,20 +38,20 @@ | |||
| align-items: center; | |||
| justify-content: center; | |||
| height: 100%; | |||
| color: var(--ai-text-light, #6c757d); | |||
| color: var(--text-light); | |||
| text-align: center; | |||
| } | |||
| .empty-icon { | |||
| font-size: 4rem; | |||
| color: var(--ai-secondary, #2984a1); | |||
| color: var(--secondary); | |||
| margin-bottom: 1rem; | |||
| opacity: 0.5; | |||
| } | |||
| .chatroom-empty h3 { | |||
| font-size: 1.5rem; | |||
| color: var(--ai-text-dark, #243b5d); | |||
| color: var(--text-dark); | |||
| margin-bottom: 0.5rem; | |||
| } | |||
| @@ -159,7 +90,6 @@ | |||
| justify-content: flex-start; | |||
| } | |||
| /* Avatars */ | |||
| .avatar { | |||
| width: 40px; | |||
| height: 40px; | |||
| @@ -167,21 +97,19 @@ | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| font-size: 1.2rem; | |||
| flex-shrink: 0; | |||
| } | |||
| .avatar-user { | |||
| background: linear-gradient(135deg, var(--ai-secondary, #2984a1) 0%, var(--ai-primary, #243b5d) 100%); | |||
| background: linear-gradient(135deg, var(--secondary) 0%, var(--primary) 100%); | |||
| color: white; | |||
| } | |||
| .avatar-assistant { | |||
| background: linear-gradient(135deg, var(--ai-accent, #f63e63) 0%, #e02851 100%); | |||
| background: linear-gradient(135deg, var(--accent) 0%, #e02851 100%); | |||
| color: white; | |||
| } | |||
| /* Message content */ | |||
| .message-content-wrapper { | |||
| display: flex; | |||
| flex-direction: column; | |||
| @@ -207,19 +135,18 @@ | |||
| } | |||
| .bubble-user { | |||
| background: linear-gradient(135deg, var(--ai-secondary, #2984a1) 0%, var(--ai-primary, #243b5d) 100%); | |||
| background: linear-gradient(135deg, var(--secondary) 0%, var(--primary) 100%); | |||
| color: white; | |||
| border-bottom-right-radius: 4px; | |||
| } | |||
| .bubble-assistant { | |||
| background: white; | |||
| color: var(--ai-text-dark, #243b5d); | |||
| color: var(--text-dark); | |||
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); | |||
| border-bottom-left-radius: 4px; | |||
| } | |||
| /* Message actions */ | |||
| .message-actions { | |||
| display: flex; | |||
| gap: 0.5rem; | |||
| @@ -231,21 +158,19 @@ | |||
| border-radius: 6px; | |||
| border: none; | |||
| background: transparent; | |||
| color: var(--ai-text-light, #6c757d); | |||
| color: var(--text-light); | |||
| cursor: pointer; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| font-size: 0.9rem; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .btn-message-action:hover { | |||
| background: var(--ai-bg, #eaf8ff); | |||
| color: var(--ai-primary, #243b5d); | |||
| background: var(--bg-light); | |||
| color: var(--primary); | |||
| } | |||
| /* Typing indicator */ | |||
| .typing-indicator { | |||
| display: flex; | |||
| gap: 0.4rem; | |||
| @@ -256,7 +181,7 @@ | |||
| width: 8px; | |||
| height: 8px; | |||
| border-radius: 50%; | |||
| background-color: var(--ai-secondary, #2984a1); | |||
| background-color: var(--secondary); | |||
| animation: typing 1.4s infinite; | |||
| } | |||
| @@ -280,235 +205,355 @@ | |||
| } | |||
| } | |||
| /* ======================================== | |||
| UPLOADED DOCUMENTS PREVIEW | |||
| ======================================== */ | |||
| .uploaded-docs-preview { | |||
| .chat-input-container { | |||
| width: 100%; | |||
| padding: 16px 24px; | |||
| background: transparent; | |||
| display: flex; | |||
| gap: 0.75rem; | |||
| padding: 0.75rem 2rem; | |||
| background: white; | |||
| border-top: 1px solid var(--ai-border, #e0e0e0); | |||
| overflow-x: auto; | |||
| flex-direction: column; | |||
| justify-content: center; | |||
| position: relative; | |||
| } | |||
| .doc-preview-item { | |||
| /* Fichiers attachés */ | |||
| .attached-files { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 8px; | |||
| padding: 12px 16px; | |||
| max-width: 1000px; | |||
| margin: 0 auto; | |||
| width: 100%; | |||
| } | |||
| .file-item { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 0.5rem; | |||
| padding: 0.5rem 0.75rem; | |||
| background: var(--ai-bg, #eaf8ff); | |||
| gap: 8px; | |||
| padding: 8px 12px; | |||
| background: #ffffff; | |||
| border: 1px solid var(--border); | |||
| border-radius: 8px; | |||
| font-size: 0.9rem; | |||
| white-space: nowrap; | |||
| max-width: 32%; | |||
| transition: all 0.2s ease; | |||
| animation: fileSlideIn 0.3s ease; | |||
| } | |||
| .doc-preview-item i { | |||
| color: var(--ai-secondary, #2984a1); | |||
| font-size: 1rem; | |||
| .file-item:hover { | |||
| border-color: #d1d5db; | |||
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); | |||
| } | |||
| .btn-remove-doc { | |||
| width: 20px; | |||
| height: 20px; | |||
| border-radius: 50%; | |||
| border: none; | |||
| background: rgba(0, 0, 0, 0.1); | |||
| color: var(--ai-text-dark, #243b5d); | |||
| cursor: pointer; | |||
| .file-preview { | |||
| flex-shrink: 0; | |||
| width: 40px; | |||
| height: 40px; | |||
| border-radius: 6px; | |||
| overflow: hidden; | |||
| background: #ffffff; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| font-size: 0.9rem; | |||
| border: 1px solid var(--border); | |||
| } | |||
| .file-icon { | |||
| font-size: 20px; | |||
| color: var(--secondary); | |||
| } | |||
| .file-info { | |||
| flex: 1; | |||
| min-width: 0; | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 2px; | |||
| } | |||
| .file-name { | |||
| font-size: 13px; | |||
| font-weight: 500; | |||
| color: #374151; | |||
| white-space: nowrap; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| } | |||
| .file-size { | |||
| font-size: 11px; | |||
| color: #9ca3af; | |||
| } | |||
| .remove-file-button { | |||
| flex-shrink: 0; | |||
| width: 24px; | |||
| height: 24px; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| background: transparent; | |||
| border: none; | |||
| border-radius: 4px; | |||
| color: #9ca3af; | |||
| cursor: pointer; | |||
| transition: all 0.2s ease; | |||
| padding: 0; | |||
| } | |||
| .btn-remove-doc:hover { | |||
| background: var(--ai-accent, #f63e63); | |||
| color: white; | |||
| .remove-file-button:hover { | |||
| background: #fee2e2; | |||
| color: #ef4444; | |||
| } | |||
| /* ======================================== | |||
| INPUT AREA | |||
| ======================================== */ | |||
| @keyframes fileSlideIn { | |||
| from { | |||
| opacity: 0; | |||
| transform: translateY(-10px); | |||
| } | |||
| .chatroom-input-container { | |||
| padding: 1.5rem 2rem; | |||
| background: white; | |||
| border-top: 1px solid var(--ai-border, #e0e0e0); | |||
| to { | |||
| opacity: 1; | |||
| transform: translateY(0); | |||
| } | |||
| } | |||
| .chatroom-input-wrapper { | |||
| max-width: 1200px; | |||
| /* Input wrapper */ | |||
| .input-wrapper { | |||
| width: 100%; | |||
| max-width: 1000px; | |||
| margin: 0 auto; | |||
| background: var(--ai-bg, #eaf8ff); | |||
| background: #ffffff; | |||
| border: 1px solid var(--border); | |||
| border-radius: 16px; | |||
| padding: 1rem 1.25rem; | |||
| padding: 16px 20px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 12px; | |||
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); | |||
| transition: all 0.2s ease; | |||
| } | |||
| .chatroom-input { | |||
| width: 100%; | |||
| border: none; | |||
| outline: none; | |||
| font-size: 1rem; | |||
| padding: 0.5rem 0; | |||
| background: transparent; | |||
| color: var(--ai-text-dark, #243b5d); | |||
| font-family: 'Montserrat', sans-serif; | |||
| margin-bottom: 0.75rem; | |||
| resize: none; | |||
| min-height: 24px; | |||
| max-height: 150px; | |||
| } | |||
| .input-wrapper.has-files { | |||
| border-radius: 0 0 16px 16px; | |||
| border-top: none; | |||
| padding-top: 12px; | |||
| } | |||
| .chatroom-input::placeholder { | |||
| color: #a0a0a0; | |||
| .input-wrapper:focus-within { | |||
| border-color: var(--secondary); | |||
| box-shadow: 0 4px 12px rgba(41, 132, 161, 0.12); | |||
| } | |||
| .chatroom-input:disabled { | |||
| opacity: 0.6; | |||
| cursor: not-allowed; | |||
| .input-wrapper textarea { | |||
| width: 100%; | |||
| border: none; | |||
| outline: none; | |||
| resize: none; | |||
| font-size: 15px; | |||
| font-family: inherit; | |||
| color: #1f2937; | |||
| background: transparent; | |||
| min-height: 24px; | |||
| max-height: 200px; | |||
| line-height: 1.5; | |||
| padding: 0; | |||
| } | |||
| .chatroom-input-actions { | |||
| .input-wrapper textarea::placeholder { | |||
| color: #9ca3af; | |||
| } | |||
| .input-wrapper textarea:disabled { | |||
| opacity: 0.6; | |||
| cursor: not-allowed; | |||
| } | |||
| /* Actions row */ | |||
| .actions-row { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| align-items: center; | |||
| gap: 1rem; | |||
| padding-top: 4px; | |||
| } | |||
| .btn-chat-action { | |||
| .attach-button { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 0.5rem; | |||
| padding: 0.5rem 0.75rem; | |||
| border-radius: 8px; | |||
| border: none; | |||
| gap: 8px; | |||
| padding: 0; | |||
| background: transparent; | |||
| color: var(--ai-text-light, #6c757d); | |||
| border: none; | |||
| color: #6b7280; | |||
| cursor: pointer; | |||
| font-size: 0.9rem; | |||
| transition: all 0.2s ease; | |||
| font-family: 'Montserrat', sans-serif; | |||
| font-size: 13px; | |||
| font-weight: 500; | |||
| transition: color 0.2s ease; | |||
| } | |||
| .btn-chat-action:hover:not(:disabled) { | |||
| background: white; | |||
| color: var(--ai-primary, #243b5d); | |||
| .attach-button:hover { | |||
| color: var(--secondary); | |||
| } | |||
| .btn-chat-action:disabled { | |||
| opacity: 0.5; | |||
| cursor: not-allowed; | |||
| .attach-button span { | |||
| display: none; | |||
| } | |||
| .btn-chat-label { | |||
| display: none; | |||
| } | |||
| @media (min-width: 768px) { | |||
| .btn-chat-label { | |||
| @media (min-width: 640px) { | |||
| .attach-button span { | |||
| display: inline; | |||
| } | |||
| } | |||
| .btn-chat-submit { | |||
| width: 40px; | |||
| height: 40px; | |||
| border-radius: 50%; | |||
| .right-actions { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| } | |||
| .domain-select { | |||
| padding: 6px 12px; | |||
| border: 1px solid var(--border); | |||
| border-radius: 8px; | |||
| background: white; | |||
| color: var(--text-dark); | |||
| font-size: 13px; | |||
| cursor: pointer; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .domain-select:hover { | |||
| border-color: var(--secondary); | |||
| } | |||
| .domain-select:focus { | |||
| outline: none; | |||
| border-color: var(--secondary); | |||
| box-shadow: 0 0 0 3px rgba(41, 132, 161, 0.1); | |||
| } | |||
| .voice-button { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 36px; | |||
| height: 36px; | |||
| background: transparent; | |||
| border: none; | |||
| background: var(--ai-accent, #f63e63); | |||
| color: white; | |||
| color: #6b7280; | |||
| cursor: pointer; | |||
| border-radius: 8px; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .voice-button:hover { | |||
| background: #f3f4f6; | |||
| color: var(--text-dark); | |||
| } | |||
| .send-button { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| font-size: 1.2rem; | |||
| transition: all 0.3s ease; | |||
| flex-shrink: 0; | |||
| width: 36px; | |||
| height: 36px; | |||
| background: var(--accent); | |||
| border: none; | |||
| color: #ffffff; | |||
| cursor: pointer; | |||
| border-radius: 8px; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .btn-chat-submit:hover:not(:disabled) { | |||
| background: #e02851; | |||
| .send-button:hover:not(:disabled) { | |||
| transform: scale(1.05); | |||
| box-shadow: 0 4px 12px rgba(246, 62, 99, 0.3); | |||
| } | |||
| .btn-chat-submit:disabled { | |||
| opacity: 0.5; | |||
| cursor: not-allowed; | |||
| transform: none; | |||
| .send-button:active:not(:disabled) { | |||
| transform: scale(0.98); | |||
| } | |||
| /* ======================================== | |||
| SCROLLBAR | |||
| ======================================== */ | |||
| .send-button:disabled { | |||
| background: #e5e7eb; | |||
| cursor: not-allowed; | |||
| opacity: 0.6; | |||
| } | |||
| .chatroom-messages::-webkit-scrollbar { | |||
| width: 8px; | |||
| } | |||
| .send-button:disabled svg { | |||
| color: #9ca3af; | |||
| } | |||
| .chatroom-messages::-webkit-scrollbar-track { | |||
| background: transparent; | |||
| } | |||
| .chatroom-messages::-webkit-scrollbar-thumb { | |||
| background: var(--ai-secondary, #2984a1); | |||
| border-radius: 4px; | |||
| } | |||
| @media (max-width: 768px) { | |||
| .chatroom-messages { | |||
| padding: 1rem; | |||
| } | |||
| .chatroom-messages::-webkit-scrollbar-thumb:hover { | |||
| background: var(--ai-primary, #243b5d); | |||
| .message-content-wrapper { | |||
| max-width: 85%; | |||
| } | |||
| .uploaded-docs-preview::-webkit-scrollbar { | |||
| height: 6px; | |||
| } | |||
| .chat-input-container { | |||
| padding: 12px 16px; | |||
| } | |||
| .uploaded-docs-preview::-webkit-scrollbar-track { | |||
| background: transparent; | |||
| } | |||
| .attached-files { | |||
| padding: 10px 12px; | |||
| gap: 6px; | |||
| } | |||
| .uploaded-docs-preview::-webkit-scrollbar-thumb { | |||
| background: rgba(0, 0, 0, 0.2); | |||
| border-radius: 3px; | |||
| } | |||
| .file-item { | |||
| padding: 6px 10px; | |||
| max-width: 200px; | |||
| } | |||
| /* ======================================== | |||
| RESPONSIVE | |||
| ======================================== */ | |||
| .file-preview { | |||
| width: 36px; | |||
| height: 36px; | |||
| } | |||
| @media (max-width: 768px) { | |||
| .chatroom-header { | |||
| padding: 1rem; | |||
| .file-name { | |||
| font-size: 12px; | |||
| } | |||
| .chatroom-title { | |||
| font-size: 1.25rem; | |||
| .file-size { | |||
| font-size: 10px; | |||
| } | |||
| .chatroom-messages { | |||
| padding: 1rem; | |||
| .remove-file-button { | |||
| width: 20px; | |||
| height: 20px; | |||
| } | |||
| .message-content-wrapper { | |||
| max-width: 85%; | |||
| .input-wrapper { | |||
| padding: 12px 16px; | |||
| border-radius: 14px; | |||
| } | |||
| .chatroom-input-container { | |||
| padding: 1rem; | |||
| .input-wrapper.has-files { | |||
| border-radius: 0 0 14px 14px; | |||
| } | |||
| .input-wrapper textarea { | |||
| font-size: 14px; | |||
| } | |||
| .actions-row .right-actions { | |||
| gap: 6px; | |||
| } | |||
| .chatroom-input-wrapper { | |||
| padding: 0.75rem 1rem; | |||
| .voice-button, | |||
| .send-button { | |||
| width: 32px; | |||
| height: 32px; | |||
| } | |||
| .domain-selector { | |||
| flex-direction: column; | |||
| align-items: flex-start; | |||
| gap: 0.25rem; | |||
| .domain-select { | |||
| font-size: 12px; | |||
| padding: 4px 8px; | |||
| } | |||
| } | |||
| @@ -521,16 +566,5 @@ | |||
| .avatar { | |||
| width: 36px; | |||
| height: 36px; | |||
| font-size: 1.1rem; | |||
| } | |||
| .chatroom-header-right { | |||
| gap: 0.5rem; | |||
| } | |||
| .btn-icon { | |||
| width: 36px; | |||
| height: 36px; | |||
| font-size: 1rem; | |||
| } | |||
| } | |||
| @@ -1664,7 +1664,35 @@ details[open] .nav-section-content { | |||
| align-items: center; | |||
| gap: 8px; | |||
| } | |||
| .history-button { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 36px; | |||
| height: 36px; | |||
| background: transparent; | |||
| border: none; | |||
| color: #6b7280; | |||
| cursor: pointer; | |||
| border-radius: 8px; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .history-button:hover { | |||
| background: #f3f4f6; | |||
| color: var(--text-dark); | |||
| } | |||
| .active { | |||
| transform:scale(1.05); | |||
| background-color:var(--main-color); | |||
| color:white; | |||
| } | |||
| .desactive { | |||
| transform: scale(0.95); | |||
| } | |||
| .voice-button { | |||
| display: flex; | |||
| align-items: center; | |||
| @@ -1919,376 +1947,312 @@ details[open] .nav-section-content { | |||
| .chatroom-container { | |||
| display: flex; | |||
| flex-direction: column; | |||
| height: 100%; | |||
| background: var(--bg-light); | |||
| } | |||
| .chat-input-container { | |||
| width: 100%; | |||
| padding: 16px 24px; | |||
| background: transparent; | |||
| .chatroom-messages { | |||
| flex: 1; | |||
| overflow-y: auto; | |||
| padding: 2rem; | |||
| display: flex; | |||
| flex-direction: column; | |||
| justify-content: center; | |||
| position: relative; | |||
| gap: 1.5rem; | |||
| } | |||
| .attached-files { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 8px; | |||
| padding: 12px 16px; | |||
| max-width: 1000px; | |||
| margin: 0 auto; | |||
| width: 100%; | |||
| .chatroom-messages::-webkit-scrollbar { | |||
| width: 8px; | |||
| } | |||
| .file-item { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| padding: 8px 12px; | |||
| background: #ffffff; | |||
| border: 1px solid #e5e7eb; | |||
| border-radius: 8px; | |||
| max-width: 32%; | |||
| transition: all 0.2s ease; | |||
| &:hover { | |||
| border-color: #d1d5db; | |||
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); | |||
| } | |||
| .file-preview { | |||
| flex-shrink: 0; | |||
| width: 40px; | |||
| height: 40px; | |||
| border-radius: 6px; | |||
| overflow: hidden; | |||
| background: #ffffff; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| border: 1px solid #e5e7eb; | |||
| img { | |||
| width: 100%; | |||
| height: 100%; | |||
| object-fit: cover; | |||
| } | |||
| .file-icon { | |||
| font-size: 20px; | |||
| } | |||
| } | |||
| .file-info { | |||
| flex: 1; | |||
| min-width: 0; | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 2px; | |||
| .file-name { | |||
| font-size: 13px; | |||
| font-weight: 500; | |||
| color: #374151; | |||
| white-space: nowrap; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| } | |||
| .file-size { | |||
| font-size: 11px; | |||
| color: #9ca3af; | |||
| } | |||
| } | |||
| .remove-file-button { | |||
| flex-shrink: 0; | |||
| width: 24px; | |||
| height: 24px; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| background: transparent; | |||
| border: none; | |||
| border-radius: 4px; | |||
| color: #9ca3af; | |||
| cursor: pointer; | |||
| transition: all 0.2s ease; | |||
| &:hover { | |||
| background: #ffffff; | |||
| color: #ef4444; | |||
| } | |||
| fa-icon { | |||
| font-size: 14px; | |||
| } | |||
| } | |||
| } | |||
| .chatroom-messages::-webkit-scrollbar-track { | |||
| background: transparent; | |||
| } | |||
| .input-wrapper { | |||
| width: 100%; | |||
| max-width: 1000px; | |||
| margin: 0 auto; | |||
| background: #ffffff; | |||
| border: 1px solid #e5e7eb; | |||
| border-radius: 16px; | |||
| padding: 16px 20px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 12px; | |||
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); | |||
| transition: all 0.2s ease; | |||
| &.has-files { | |||
| border-radius: 0 0 16px 16px; | |||
| border-top: none; | |||
| padding-top: 12px; | |||
| } | |||
| .chatroom-messages::-webkit-scrollbar-thumb { | |||
| background: var(--secondary); | |||
| border-radius: 4px; | |||
| } | |||
| &:focus-within { | |||
| border-color: #3498db; | |||
| box-shadow: 0 4px 12px rgba(52, 152, 219, 0.12); | |||
| .chatroom-messages::-webkit-scrollbar-thumb:hover { | |||
| background: var(--primary); | |||
| } | |||
| textarea { | |||
| width: 100%; | |||
| border: none; | |||
| outline: none; | |||
| resize: none; | |||
| font-size: 15px; | |||
| font-family: inherit; | |||
| color: #1f2937; | |||
| background: transparent; | |||
| min-height: 24px; | |||
| max-height: 200px; | |||
| line-height: 1.5; | |||
| padding: 0; | |||
| &::placeholder { | |||
| color: #9ca3af; | |||
| } | |||
| &:disabled { | |||
| opacity: 0.6; | |||
| cursor: not-allowed; | |||
| } | |||
| } | |||
| .chatroom-empty { | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| justify-content: center; | |||
| height: 100%; | |||
| color: var(--text-light); | |||
| text-align: center; | |||
| } | |||
| .actions-row { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| align-items: center; | |||
| padding-top: 4px; | |||
| .attach-button { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| padding: 0; | |||
| background: transparent; | |||
| border: none; | |||
| color: #6b7280; | |||
| cursor: pointer; | |||
| font-size: 13px; | |||
| font-weight: 500; | |||
| transition: color 0.2s ease; | |||
| &:hover { | |||
| color: #3498db; | |||
| } | |||
| fa-icon { | |||
| font-size: 16px; | |||
| } | |||
| span { | |||
| @media (max-width: 480px) { | |||
| display: none; | |||
| } | |||
| } | |||
| } | |||
| .right-actions { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| .voice-button { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 36px; | |||
| height: 36px; | |||
| background: transparent; | |||
| border: none; | |||
| color: #6b7280; | |||
| cursor: pointer; | |||
| border-radius: 8px; | |||
| transition: all 0.2s ease; | |||
| &:hover { | |||
| background: #f3f4f6; | |||
| color: #1f2937; | |||
| } | |||
| svg { | |||
| width: 18px; | |||
| height: 18px; | |||
| } | |||
| } | |||
| .send-button { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 36px; | |||
| height: 36px; | |||
| background: #f63e63; | |||
| border: none; | |||
| color: #ffffff; | |||
| cursor: pointer; | |||
| border-radius: 8px; | |||
| transition: all 0.2s ease; | |||
| &:hover:not(:disabled) { | |||
| transform: scale(1.05); | |||
| box-shadow: 0 4px 12px rgba(246, 62, 99, 0.3); | |||
| } | |||
| &:active:not(:disabled) { | |||
| transform: scale(0.98); | |||
| } | |||
| &:disabled { | |||
| background: #e5e7eb; | |||
| cursor: not-allowed; | |||
| opacity: 0.6; | |||
| svg { | |||
| color: #9ca3af; | |||
| } | |||
| } | |||
| svg { | |||
| width: 16px; | |||
| height: 16px; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .empty-icon { | |||
| font-size: 4rem; | |||
| color: var(--secondary); | |||
| margin-bottom: 1rem; | |||
| opacity: 0.5; | |||
| } | |||
| .chatroom-empty h3 { | |||
| font-size: 1.5rem; | |||
| color: var(--text-dark); | |||
| margin-bottom: 0.5rem; | |||
| } | |||
| .chatroom-empty p { | |||
| font-size: 1rem; | |||
| margin: 0; | |||
| } | |||
| /* Messages */ | |||
| .message-row { | |||
| display: flex; | |||
| align-items: flex-start; | |||
| gap: 0.75rem; | |||
| animation: fadeIn 0.3s ease; | |||
| } | |||
| @keyframes fadeIn { | |||
| from { | |||
| opacity: 0; | |||
| transform: translateY(10px); | |||
| } | |||
| input[type="file"] { | |||
| display: none; | |||
| to { | |||
| opacity: 1; | |||
| transform: translateY(0); | |||
| } | |||
| } | |||
| .chat-input-container.drag-over { | |||
| &::before { | |||
| content: 'Déposez vos fichiers ici'; | |||
| position: absolute; | |||
| inset: 0; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| border: 2px dashed #3498db; | |||
| border-radius: 16px; | |||
| background: rgba(52, 152, 219, 0.05); | |||
| color: #3498db; | |||
| font-size: 16px; | |||
| font-weight: 600; | |||
| pointer-events: none; | |||
| z-index: 10; | |||
| } | |||
| .message-row.user { | |||
| flex-direction: row-reverse; | |||
| justify-content: flex-start; | |||
| } | |||
| .message-row.assistant { | |||
| flex-direction: row; | |||
| justify-content: flex-start; | |||
| } | |||
| .avatar { | |||
| width: 40px; | |||
| height: 40px; | |||
| border-radius: 50%; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| flex-shrink: 0; | |||
| } | |||
| .avatar-user { | |||
| background: linear-gradient(135deg, var(--secondary) 0%, var(--primary) 100%); | |||
| color: white; | |||
| } | |||
| .avatar-assistant { | |||
| background: linear-gradient(135deg, var(--accent) 0%, #e02851 100%); | |||
| color: white; | |||
| } | |||
| .message-content-wrapper { | |||
| display: flex; | |||
| flex-direction: column; | |||
| max-width: 70%; | |||
| gap: 0.5rem; | |||
| } | |||
| .message-row.user .message-content-wrapper { | |||
| align-items: flex-end; | |||
| } | |||
| .message-row.assistant .message-content-wrapper { | |||
| align-items: flex-start; | |||
| } | |||
| .bubble { | |||
| padding: 1rem 1.25rem; | |||
| border-radius: 16px; | |||
| font-size: 1rem; | |||
| line-height: 1.6; | |||
| word-wrap: break-word; | |||
| white-space: pre-wrap; | |||
| } | |||
| .bubble-user { | |||
| background: linear-gradient(135deg, var(--secondary) 0%, var(--primary) 100%); | |||
| color: white; | |||
| border-bottom-right-radius: 4px; | |||
| } | |||
| .bubble-assistant { | |||
| background: white; | |||
| color: var(--text-dark); | |||
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); | |||
| border-bottom-left-radius: 4px; | |||
| } | |||
| .message-actions { | |||
| display: flex; | |||
| gap: 0.5rem; | |||
| } | |||
| .btn-message-action { | |||
| width: 32px; | |||
| height: 32px; | |||
| border-radius: 6px; | |||
| border: none; | |||
| background: transparent; | |||
| color: var(--text-light); | |||
| cursor: pointer; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .btn-message-action:hover { | |||
| background: var(--bg-light); | |||
| color: var(--primary); | |||
| } | |||
| // Responsive | |||
| @media (max-width: 768px) { | |||
| .chat-input-container { | |||
| padding: 12px 16px; | |||
| .typing-indicator { | |||
| display: flex; | |||
| gap: 0.4rem; | |||
| padding: 0.5rem 0; | |||
| } | |||
| .typing-indicator span { | |||
| width: 8px; | |||
| height: 8px; | |||
| border-radius: 50%; | |||
| background-color: var(--secondary); | |||
| animation: typing 1.4s infinite; | |||
| } | |||
| .attached-files { | |||
| padding: 10px 12px; | |||
| border-radius: 10px 10px 0 0; | |||
| gap: 6px; | |||
| .file-item { | |||
| padding: 6px 10px; | |||
| max-width: 200px; | |||
| .file-preview { | |||
| width: 36px; | |||
| height: 36px; | |||
| } | |||
| .file-info { | |||
| .file-name { | |||
| font-size: 12px; | |||
| } | |||
| .file-size { | |||
| font-size: 10px; | |||
| } | |||
| } | |||
| .remove-file-button { | |||
| width: 20px; | |||
| height: 20px; | |||
| fa-icon { | |||
| font-size: 12px; | |||
| } | |||
| } | |||
| } | |||
| .typing-indicator span:nth-child(2) { | |||
| animation-delay: 0.2s; | |||
| } | |||
| .input-wrapper { | |||
| padding: 12px 16px; | |||
| border-radius: 14px; | |||
| &.has-files { | |||
| border-radius: 0 0 14px 14px; | |||
| } | |||
| textarea { | |||
| font-size: 14px; | |||
| } | |||
| .actions-row { | |||
| .attach-button { | |||
| font-size: 12px; | |||
| fa-icon { | |||
| font-size: 14px; | |||
| } | |||
| } | |||
| .right-actions { | |||
| gap: 6px; | |||
| .voice-button, | |||
| .send-button { | |||
| width: 32px; | |||
| height: 32px; | |||
| svg { | |||
| width: 15px; | |||
| height: 15px; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .typing-indicator span:nth-child(3) { | |||
| animation-delay: 0.4s; | |||
| } | |||
| @keyframes typing { | |||
| 0%, 60%, 100% { | |||
| transform: translateY(0); | |||
| opacity: 0.7; | |||
| } | |||
| 30% { | |||
| transform: translateY(-10px); | |||
| opacity: 1; | |||
| } | |||
| } | |||
| // Auto-expand textarea | |||
| @media (min-width: 769px) { | |||
| .chat-input-container .input-wrapper textarea { | |||
| overflow-y: auto; | |||
| .chat-input-container { | |||
| width: 100%; | |||
| padding: 16px 24px; | |||
| background: transparent; | |||
| display: flex; | |||
| flex-direction: column; | |||
| justify-content: center; | |||
| position: fixed; | |||
| } | |||
| /* Fichiers attachés */ | |||
| .attached-files { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 8px; | |||
| padding: 12px 16px; | |||
| max-width: 1000px; | |||
| margin: 0 auto; | |||
| width: 100%; | |||
| } | |||
| .file-item { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| padding: 8px 12px; | |||
| background: #ffffff; | |||
| border: 1px solid var(--border); | |||
| border-radius: 8px; | |||
| max-width: 32%; | |||
| transition: all 0.2s ease; | |||
| animation: fileSlideIn 0.3s ease; | |||
| } | |||
| .file-item:hover { | |||
| border-color: #d1d5db; | |||
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); | |||
| } | |||
| .file-preview { | |||
| flex-shrink: 0; | |||
| width: 40px; | |||
| height: 40px; | |||
| border-radius: 6px; | |||
| overflow: hidden; | |||
| background: #ffffff; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| border: 1px solid var(--border); | |||
| } | |||
| .file-icon { | |||
| font-size: 20px; | |||
| color: var(--secondary); | |||
| } | |||
| .file-info { | |||
| flex: 1; | |||
| min-width: 0; | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 2px; | |||
| } | |||
| .file-name { | |||
| font-size: 13px; | |||
| font-weight: 500; | |||
| color: #374151; | |||
| white-space: nowrap; | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| } | |||
| .file-size { | |||
| font-size: 11px; | |||
| color: #9ca3af; | |||
| } | |||
| .remove-file-button { | |||
| flex-shrink: 0; | |||
| width: 24px; | |||
| height: 24px; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| background: transparent; | |||
| border: none; | |||
| border-radius: 4px; | |||
| color: #9ca3af; | |||
| cursor: pointer; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .remove-file-button:hover { | |||
| background: #fee2e2; | |||
| color: #ef4444; | |||
| } | |||
| @keyframes fileSlideIn { | |||
| from { | |||
| opacity: 0; | |||
| @@ -2301,10 +2265,255 @@ details[open] .nav-section-content { | |||
| } | |||
| } | |||
| .attached-files .file-item { | |||
| animation: fileSlideIn 0.3s ease; | |||
| /* Input wrapper */ | |||
| .input-wrapper { | |||
| width: 100%; | |||
| max-width: 1000px; | |||
| margin: 0 auto; | |||
| background: #ffffff; | |||
| border: 1px solid var(--border); | |||
| border-radius: 16px; | |||
| padding: 16px 20px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 12px; | |||
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); | |||
| transition: all 0.2s ease; | |||
| } | |||
| .input-wrapper.has-files { | |||
| border-radius: 0 0 16px 16px; | |||
| border-top: none; | |||
| padding-top: 12px; | |||
| } | |||
| .input-wrapper:focus-within { | |||
| border-color: var(--secondary); | |||
| box-shadow: 0 4px 12px rgba(41, 132, 161, 0.12); | |||
| } | |||
| .input-wrapper textarea { | |||
| width: 100%; | |||
| border: none; | |||
| outline: none; | |||
| resize: none; | |||
| font-size: 15px; | |||
| font-family: inherit; | |||
| color: #1f2937; | |||
| background: transparent; | |||
| min-height: 24px; | |||
| max-height: 200px; | |||
| line-height: 1.5; | |||
| padding: 0; | |||
| } | |||
| .input-wrapper textarea::placeholder { | |||
| color: #9ca3af; | |||
| } | |||
| .input-wrapper textarea:disabled { | |||
| opacity: 0.6; | |||
| cursor: not-allowed; | |||
| } | |||
| /* Actions row */ | |||
| .actions-row { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| align-items: center; | |||
| padding-top: 4px; | |||
| } | |||
| .attach-button { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| padding: 0; | |||
| background: transparent; | |||
| border: none; | |||
| color: #6b7280; | |||
| cursor: pointer; | |||
| font-size: 13px; | |||
| font-weight: 500; | |||
| transition: color 0.2s ease; | |||
| } | |||
| .attach-button:hover { | |||
| color: var(--secondary); | |||
| } | |||
| .attach-button span { | |||
| display: none; | |||
| } | |||
| @media (min-width: 640px) { | |||
| .attach-button span { | |||
| display: inline; | |||
| } | |||
| } | |||
| .right-actions { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| } | |||
| .domain-select { | |||
| padding: 6px 12px; | |||
| border: 1px solid var(--border); | |||
| border-radius: 8px; | |||
| background: white; | |||
| color: var(--text-dark); | |||
| font-size: 13px; | |||
| cursor: pointer; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .domain-select:hover { | |||
| border-color: var(--secondary); | |||
| } | |||
| .domain-select:focus { | |||
| outline: none; | |||
| border-color: var(--secondary); | |||
| box-shadow: 0 0 0 3px rgba(41, 132, 161, 0.1); | |||
| } | |||
| .voice-button { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 36px; | |||
| height: 36px; | |||
| background: transparent; | |||
| border: none; | |||
| color: #6b7280; | |||
| cursor: pointer; | |||
| border-radius: 8px; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .voice-button:hover { | |||
| background: #f3f4f6; | |||
| color: var(--text-dark); | |||
| } | |||
| .send-button { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 36px; | |||
| height: 36px; | |||
| background: var(--accent); | |||
| border: none; | |||
| color: #ffffff; | |||
| cursor: pointer; | |||
| border-radius: 8px; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .send-button:hover:not(:disabled) { | |||
| transform: scale(1.05); | |||
| box-shadow: 0 4px 12px rgba(246, 62, 99, 0.3); | |||
| } | |||
| .send-button:active:not(:disabled) { | |||
| transform: scale(0.98); | |||
| } | |||
| .send-button:disabled { | |||
| background: #e5e7eb; | |||
| cursor: not-allowed; | |||
| opacity: 0.6; | |||
| } | |||
| .send-button:disabled svg { | |||
| color: #9ca3af; | |||
| } | |||
| @media (max-width: 768px) { | |||
| .chatroom-messages { | |||
| padding: 1rem; | |||
| } | |||
| .message-content-wrapper { | |||
| max-width: 85%; | |||
| } | |||
| .chat-input-container { | |||
| padding: 12px 16px; | |||
| } | |||
| .attached-files { | |||
| padding: 10px 12px; | |||
| gap: 6px; | |||
| } | |||
| .file-item { | |||
| padding: 6px 10px; | |||
| max-width: 200px; | |||
| } | |||
| .file-preview { | |||
| width: 36px; | |||
| height: 36px; | |||
| } | |||
| .file-name { | |||
| font-size: 12px; | |||
| } | |||
| .file-size { | |||
| font-size: 10px; | |||
| } | |||
| .remove-file-button { | |||
| width: 20px; | |||
| height: 20px; | |||
| } | |||
| .input-wrapper { | |||
| padding: 12px 16px; | |||
| border-radius: 14px; | |||
| } | |||
| .input-wrapper.has-files { | |||
| border-radius: 0 0 14px 14px; | |||
| } | |||
| .input-wrapper textarea { | |||
| font-size: 14px; | |||
| } | |||
| .actions-row .right-actions { | |||
| gap: 6px; | |||
| } | |||
| .voice-button, | |||
| .send-button { | |||
| width: 32px; | |||
| height: 32px; | |||
| } | |||
| .domain-select { | |||
| font-size: 12px; | |||
| padding: 4px 8px; | |||
| } | |||
| } | |||
| @media (max-width: 480px) { | |||
| .bubble { | |||
| padding: 0.875rem 1rem; | |||
| font-size: 0.95rem; | |||
| } | |||
| .avatar { | |||
| width: 36px; | |||
| height: 36px; | |||
| } | |||
| } | |||