| @@ -22,18 +22,13 @@ | |||
| <div class="top-bar"> | |||
| <div class="top-bar-content"> | |||
| <div class="top-bar-left"> | |||
| <button class="burger-menu" onclick="toggleSidebar()" aria-label="Toggle menu"> | |||
| <span></span> | |||
| <span></span> | |||
| <span></span> | |||
| </button> | |||
| <!-- | |||
| <a href="https://www.allin-it.fr/" target="_blank" class="logo-link"> | |||
| <img src="images/logo.png" alt="Logo All in IT" width="50" height="50" /> | |||
| </a> | |||
| <a class="app-title" href="">AI-100-SaaS</a> | |||
| --> | |||
| </div> | |||
| </div> | |||
| <div class="top-bar-right"> | |||
| <div class="user-menu"> | |||
| @@ -125,22 +120,6 @@ | |||
| </div> | |||
| <script> | |||
| function toggleSidebar() { | |||
| const sidebar = document.getElementById('sidebar'); | |||
| const overlay = document.getElementById('sidebarOverlay'); | |||
| const burger = document.querySelector('.burger-menu'); | |||
| sidebar.classList.toggle('active'); | |||
| overlay.classList.toggle('active'); | |||
| burger.classList.toggle('active'); | |||
| } | |||
| function closeSidebar() { | |||
| const sidebar = document.getElementById('sidebar'); | |||
| const overlay = document.getElementById('sidebarOverlay'); | |||
| const burger = document.querySelector('.burger-menu'); | |||
| } | |||
| function toggleUserMenu(event) { | |||
| event.stopPropagation(); | |||
| const dropdown = document.getElementById('userMenuDropdown'); | |||
| @@ -1,4 +1,3 @@ | |||
| /* Page principale */ | |||
| .page { | |||
| display: flex; | |||
| height: 100vh; | |||
| @@ -8,23 +7,20 @@ | |||
| background-size: cover; | |||
| } | |||
| .main-container { | |||
| display: flex; | |||
| flex: 1; | |||
| overflow: hidden; | |||
| } | |||
| .sidebar { | |||
| width: 280px; | |||
| background: linear-gradient(180deg, var(--secondary-color), var(--main-color)); | |||
| height: 100vh; /* Prend toute la hauteur de l'écran */ | |||
| height: 100vh; | |||
| overflow-y: auto; | |||
| overflow-x: hidden; | |||
| flex-shrink: 0; | |||
| transition: transform 0.3s ease; | |||
| transition: width 0.3s ease; | |||
| } | |||
| .sidebar::-webkit-scrollbar { | |||
| @@ -45,7 +41,6 @@ | |||
| } | |||
| .content-area { | |||
| flex: 1; | |||
| display: flex; | |||
| @@ -53,6 +48,34 @@ | |||
| overflow: hidden; | |||
| } | |||
| .content-wrapper { | |||
| flex: 1; | |||
| display: flex; | |||
| flex-direction: column; | |||
| overflow: hidden; | |||
| } | |||
| .main-content { | |||
| flex: 1; | |||
| overflow-y: auto; | |||
| } | |||
| .main-content::-webkit-scrollbar { | |||
| width: 8px; | |||
| } | |||
| .main-content::-webkit-scrollbar-track { | |||
| background: transparent; | |||
| } | |||
| .main-content::-webkit-scrollbar-thumb { | |||
| background: #888; | |||
| border-radius: 4px; | |||
| } | |||
| .main-content::-webkit-scrollbar-thumb:hover { | |||
| background: #555; | |||
| } | |||
| .top-bar { | |||
| @@ -72,48 +95,12 @@ | |||
| align-items: center; | |||
| } | |||
| /* Partie gauche avec logo + titre */ | |||
| .top-bar-left { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 1rem; | |||
| } | |||
| /* Menu Burger */ | |||
| .burger-menu { | |||
| display: none; | |||
| flex-direction: column; | |||
| justify-content: space-around; | |||
| width: 30px; | |||
| height: 25px; | |||
| background: transparent; | |||
| border: none; | |||
| cursor: pointer; | |||
| padding: 0; | |||
| z-index: 1001; | |||
| } | |||
| .burger-menu span { | |||
| width: 100%; | |||
| height: 3px; | |||
| background-color: #212529; | |||
| border-radius: 10px; | |||
| transition: all 0.3s ease; | |||
| } | |||
| .burger-menu.active span:nth-child(1) { | |||
| transform: rotate(45deg) translate(8px, 8px); | |||
| } | |||
| .burger-menu.active span:nth-child(2) { | |||
| opacity: 0; | |||
| } | |||
| .burger-menu.active span:nth-child(3) { | |||
| transform: rotate(-45deg) translate(7px, -7px); | |||
| } | |||
| /* Logo */ | |||
| .logo-link { | |||
| display: flex; | |||
| align-items: center; | |||
| @@ -124,7 +111,6 @@ | |||
| opacity: 0.8; | |||
| } | |||
| /* Titre de l'application */ | |||
| .app-title { | |||
| font-size: 1.5rem; | |||
| font-weight: 600; | |||
| @@ -137,7 +123,7 @@ | |||
| color: #0056b3; | |||
| } | |||
| /* Partie droite - Menu utilisateur */ | |||
| .top-bar-right { | |||
| display: flex; | |||
| align-items: center; | |||
| @@ -147,7 +133,6 @@ | |||
| position: relative; | |||
| } | |||
| /* Bouton du menu utilisateur */ | |||
| .user-menu-button { | |||
| background: none; | |||
| border: none; | |||
| @@ -165,7 +150,6 @@ | |||
| transform: scale(1.05); | |||
| } | |||
| /* Menu déroulant utilisateur */ | |||
| .user-menu-dropdown { | |||
| position: absolute; | |||
| top: calc(100% + 10px); | |||
| @@ -188,7 +172,6 @@ | |||
| transform: translateY(0); | |||
| } | |||
| /* Items du menu déroulant */ | |||
| .dropdown-item { | |||
| display: flex; | |||
| align-items: center; | |||
| @@ -216,51 +199,17 @@ | |||
| border-radius: 0 0 8px 8px; | |||
| } | |||
| /* Icône dans le menu déroulant */ | |||
| .dropdown-icon { | |||
| margin-right: 0.75rem; | |||
| font-size: 1.1rem; | |||
| } | |||
| /* Séparateur dans le menu déroulant */ | |||
| .dropdown-divider { | |||
| height: 1px; | |||
| background-color: #dee2e6; | |||
| margin: 0.5rem 0; | |||
| } | |||
| /* Bouton de déconnexion */ | |||
| .content-wrapper { | |||
| flex: 1; | |||
| display: flex; | |||
| flex-direction: column; | |||
| overflow: hidden; | |||
| } | |||
| .main-content { | |||
| flex: 1; | |||
| overflow-y: auto; | |||
| } | |||
| .main-content::-webkit-scrollbar { | |||
| width: 8px; | |||
| } | |||
| .main-content::-webkit-scrollbar-track { | |||
| /* Conteneur de droite qui héberge le contenu principal et le footer */ | |||
| } | |||
| .main-content::-webkit-scrollbar-thumb { | |||
| background: #888; | |||
| border-radius: 4px; | |||
| } | |||
| /* Contenu principal : scrollable si trop long */ | |||
| background: #555; | |||
| } | |||
| .footer { | |||
| padding: 1rem; | |||
| @@ -276,60 +225,18 @@ | |||
| } | |||
| .sidebar-overlay { | |||
| display: none; | |||
| position: fixed; | |||
| top: 0; | |||
| left: 0; | |||
| right: 0; | |||
| bottom: 0; | |||
| background-color: rgba(0, 0, 0, 0.5); | |||
| z-index: 998; | |||
| opacity: 0; | |||
| transition: opacity 0.3s ease; | |||
| } | |||
| .sidebar-overlay.active { | |||
| display: block; | |||
| opacity: 1; | |||
| } | |||
| @media (max-width: 768px) { | |||
| /* Afficher le bouton burger sur mobile */ | |||
| .burger-menu { | |||
| display: flex; | |||
| } | |||
| /* Cacher le titre sur très petit écran */ | |||
| @media (max-width: 480px) { | |||
| .app-title { | |||
| display: none; | |||
| } | |||
| .top-bar-content { | |||
| padding: 0 1rem; | |||
| } | |||
| .sidebar { | |||
| position: fixed; | |||
| top: 0; | |||
| left: 0; | |||
| bottom: 0; | |||
| width: 280px; | |||
| z-index: 999; | |||
| transform: translateX(-100%); | |||
| } | |||
| .sidebar.active { | |||
| transform: translateX(0); | |||
| box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3); | |||
| } | |||
| .main-content { | |||
| padding: 1rem; | |||
| } | |||
| } | |||
| .top-bar-content { | |||
| padding: 0 1rem; | |||
| /* Très petits écrans */ | |||
| @media (max-width: 480px) { | |||
| .app-title { | |||
| display: none; | |||
| } | |||
| } | |||
| @@ -6,13 +6,14 @@ | |||
| @inject AuthenticationStateProvider AuthenticationStateProvider | |||
| @inject NavigationManager Navigation | |||
| @inject IToastService ToastService | |||
| @inject IJSRuntime JSRuntime | |||
| <div class="sidebar @(IsCollapsed ? "collapsed" : "")"> | |||
| <div class="sidebar-header"> | |||
| @if (!IsCollapsed) | |||
| { | |||
| <div class="logo"> | |||
| <h1>AIT</h1> | |||
| <img src="/images/textAIT.png" width="80" height="40" alt="ait" /> | |||
| </div> | |||
| } | |||
| <button class="toggle-button" @onclick="ToggleSidebar" | |||
| @@ -92,8 +93,9 @@ | |||
| <h3 class="section-title"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-folder" viewBox="0 0 16 16"> | |||
| <path d="M.54 3.87.5 3a2 2 0 0 1 2-2h3.672a2 2 0 0 1 1.414.586l.828.828A2 2 0 0 0 9.828 3h3.982a2 2 0 0 1 1.992 2.181l-.637 7A2 2 0 0 1 13.174 14H2.826a2 2 0 0 1-1.991-1.819l-.637-7a2 2 0 0 1 .342-1.31zM2.19 4a1 1 0 0 0-.996 1.09l.637 7a1 1 0 0 0 .995.91h10.348a1 1 0 0 0 .995-.91l.637-7A1 1 0 0 0 13.81 4zm4.69-1.707A1 1 0 0 0 6.172 2H2.5a1 1 0 0 0-1 .981l.006.139q.323-.119.684-.12h5.396z" /> | |||
| </svg> | |||
| RAG</h3> | |||
| </svg> | |||
| RAG | |||
| </h3> | |||
| <div class="conversations-section"> | |||
| <button class="new-chat-button" @onclick="OnNewRagChat" aria-label="Nouveau RAG"> | |||
| @@ -152,6 +154,12 @@ | |||
| </div> | |||
| <script> | |||
| window.getWindowWidth = function () { | |||
| return window.innerWidth; | |||
| }; | |||
| </script> | |||
| @code { | |||
| private bool IsCollapsed = false; | |||
| private string CurrentConversationId = ""; | |||
| @@ -171,10 +179,33 @@ | |||
| if (!(tok == null || tok.Length == 0)) | |||
| AuthService.SetToken(tok); | |||
| await LoadChatConversations(); | |||
| UpdateCurrentConversationFromUrl(); | |||
| Navigation.LocationChanged += OnLocationChanged; | |||
| await LoadChatConversations(); | |||
| await LoadRagConversations(); | |||
| } | |||
| protected override async Task OnAfterRenderAsync(bool firstRender) | |||
| { | |||
| if (firstRender) | |||
| { | |||
| await CheckScreenSize(); | |||
| } | |||
| } | |||
| private async Task CheckScreenSize() | |||
| { | |||
| try | |||
| { | |||
| var width = await JSRuntime.InvokeAsync<int>("getWindowWidth"); | |||
| IsCollapsed = width <= 768; | |||
| StateHasChanged(); | |||
| } | |||
| catch | |||
| { | |||
| } | |||
| } | |||
| private void ToggleSidebar() | |||
| @@ -182,6 +213,32 @@ | |||
| IsCollapsed = !IsCollapsed; | |||
| } | |||
| private void UpdateCurrentConversationFromUrl() | |||
| { | |||
| var uri = new Uri(Navigation.Uri); | |||
| var segments = uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries); | |||
| if (segments.Length >= 3 && segments[0] == "chatroom_base") | |||
| { | |||
| CurrentConversationId = segments[2]; | |||
| } | |||
| else | |||
| { | |||
| CurrentConversationId = ""; | |||
| } | |||
| } | |||
| private void OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e) | |||
| { | |||
| UpdateCurrentConversationFromUrl(); | |||
| StateHasChanged(); | |||
| } | |||
| public void Dispose() | |||
| { | |||
| Navigation.LocationChanged -= OnLocationChanged; | |||
| } | |||
| private async Task LoadChatConversations() | |||
| { | |||
| @@ -260,16 +317,17 @@ | |||
| private void OnNewChat() | |||
| { | |||
| Navigation.NavigateTo(""); | |||
| Navigation.NavigateTo("chatroom_base/0"); | |||
| } | |||
| private void OnNewRagChat() | |||
| { | |||
| Navigation.NavigateTo("chatroom_nav/2"); | |||
| Navigation.NavigateTo("chatroom_base/2"); | |||
| } | |||
| private void OnSelectConversation(string conversationId, int typeLLM) | |||
| { | |||
| CurrentConversationId = conversationId; | |||
| Navigation.NavigateTo($"chatroom_base/{typeLLM}/{conversationId}"); | |||
| } | |||
| @@ -0,0 +1,4 @@ | |||
| active { | |||
| background-color: rgba(52, 152, 219, 0.3); | |||
| border-left: 3px solid #3498db; | |||
| } | |||
| @@ -93,7 +93,7 @@ namespace ReAct_PME.WebUI.Pages.AuthLogin | |||
| if (IsAuthorized == true) | |||
| { | |||
| authProvider.NotifyUserAuthentication(loginResponse.Token); | |||
| Navigation.NavigateTo("/"); | |||
| Navigation.NavigateTo("/chatroom_base/0"); | |||
| } | |||
| else | |||
| { | |||
| @@ -1,167 +1,364 @@ | |||
| @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> | |||
| @if (TypeLLM == 0) | |||
| { | |||
| <!-- Vue Home --> | |||
| <div class="home-container"> | |||
| <div class="home-content"> | |||
| <!-- Section bienvenue --> | |||
| <div class="welcome-section"> | |||
| <h1 class="welcome-title">Bonjour !</h1> | |||
| <p class="welcome-subtitle">Qu'est-ce qu'on fait aujourd'hui ?</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> | |||
| <div class="message-content-wrapper"> | |||
| <div class="bubble @(msg.IsUser ? "bubble-user" : "bubble-assistant")"> | |||
| @msg.Text | |||
| </div> | |||
| @if (!msg.IsUser) | |||
| <!-- Zone de saisie --> | |||
| <div class="input-container"> | |||
| @if (UploadedDocuments.Any()) | |||
| { | |||
| <div class="input-files"> | |||
| @foreach (var doc in UploadedDocuments) | |||
| { | |||
| <div class="message-actions"> | |||
| <button class="btn-message-action" @onclick="() => CopyMessage(msg.Text)" title="Copier"> | |||
| <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="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="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" /> | |||
| <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="input-wrapper @(UploadedDocuments.Any() ? "has-files" : "")"> | |||
| <textarea @bind="CurrentInput" | |||
| @bind:event="oninput" | |||
| placeholder="Nouvelle conversation..." | |||
| rows="1" | |||
| disabled="@isDisabled"></textarea> | |||
| <div class="input-actions"> | |||
| <label class="attach-button" for="fileInputHome"> | |||
| <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="fileInputHome" OnChange="HandleFileSelection" multiple style="display: none;" /> | |||
| </label> | |||
| <div class="input-right-actions"> | |||
| @if (SelectedAgentType == 2 && RagDomains.Any()) | |||
| { | |||
| <select class="domain-select" @bind="SelectedDomain"> | |||
| @foreach (var domain in RagDomains) | |||
| { | |||
| <option value="@domain">@domain</option> | |||
| } | |||
| </select> | |||
| } | |||
| <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> | |||
| <button class="send-button" | |||
| @onclick="SendMessageFromHome" | |||
| disabled="@((string.IsNullOrWhiteSpace(CurrentInput) && !UploadedDocuments.Any()) || isDisabled)" | |||
| 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> | |||
| </div> | |||
| </div> | |||
| } | |||
| @if (isTyping) | |||
| { | |||
| <div class="message-row assistant"> | |||
| <div class="avatar avatar-assistant"> | |||
| <!-- Cartes des agents --> | |||
| <div class="agents-grid"> | |||
| <label class="agent-card @(SelectedAgentType == 1 ? "selected" : "")" for="agent1"> | |||
| <input type="radio" | |||
| id="agent1" | |||
| name="agentType" | |||
| value="1" | |||
| checked="@(SelectedAgentType == 1)" | |||
| @onchange="() => SelectAgent(1)" /> | |||
| <div class="agent-icon"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325" /> | |||
| </svg> | |||
| </div> | |||
| <div class="agent-content"> | |||
| <h3 class="agent-name">Par défaut</h3> | |||
| <p class="agent-description"> | |||
| Discutez avec un agent IA ayant une base de données importante dans votre domaine | |||
| </p> | |||
| </div> | |||
| <div class="agent-check"> | |||
| <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" /> | |||
| <path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z"/> | |||
| </svg> | |||
| </div> | |||
| <div class="message-content-wrapper"> | |||
| <div class="bubble bubble-assistant"> | |||
| <div class="typing-indicator"> | |||
| <span></span> | |||
| <span></span> | |||
| <span></span> | |||
| </div> | |||
| </div> | |||
| </label> | |||
| <label class="agent-card @(SelectedAgentType == 2 ? "selected" : "")" for="agent2"> | |||
| <input type="radio" | |||
| id="agent2" | |||
| name="agentType" | |||
| value="2" | |||
| checked="@(SelectedAgentType == 2)" | |||
| @onchange="() => SelectAgent(2)" /> | |||
| <div class="agent-icon"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5M3.854 2.146a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 3.293l1.146-1.147a.5.5 0 0 1 .708 0m0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 7.293l1.146-1.147a.5.5 0 0 1 .708 0m0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0" /> | |||
| </svg> | |||
| </div> | |||
| </div> | |||
| } | |||
| } | |||
| <div class="agent-content"> | |||
| <h3 class="agent-name">Agent RAG</h3> | |||
| <p class="agent-description"> | |||
| Un agent IA entraîné dans la création de bilan comptable sur la base de vos comptes-rendus | |||
| </p> | |||
| </div> | |||
| <div class="agent-check"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z"/> | |||
| </svg> | |||
| </div> | |||
| </label> | |||
| <label class="agent-card @(SelectedAgentType == 3 ? "selected" : "")" for="agent3"> | |||
| <input type="radio" | |||
| id="agent3" | |||
| name="agentType" | |||
| value="3" | |||
| checked="@(SelectedAgentType == 3)" | |||
| @onchange="() => SelectAgent(3)" /> | |||
| <div class="agent-icon"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path fill-rule="evenodd" d="M10.854 6.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 8.793l2.646-2.647a.5.5 0 0 1 .708 0" /> | |||
| <path d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2" /> | |||
| <path d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1z" /> | |||
| </svg> | |||
| </div> | |||
| <div class="agent-content"> | |||
| <h3 class="agent-name">Agent Code</h3> | |||
| <p class="agent-description"> | |||
| Un agent IA spécialisé dans l'étude de vos documents et dans la réalisation d'audit financier | |||
| </p> | |||
| </div> | |||
| <div class="agent-check"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z"/> | |||
| </svg> | |||
| </div> | |||
| </label> | |||
| <label class="agent-card @(SelectedAgentType == 4 ? "selected" : "")" for="agent4"> | |||
| <input type="radio" | |||
| id="agent4" | |||
| name="agentType" | |||
| value="4" | |||
| checked="@(SelectedAgentType == 4)" | |||
| @onchange="() => SelectAgent(4)" /> | |||
| <div class="agent-icon"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M.05 3.555A2 2 0 0 1 2 2h12a2 2 0 0 1 1.95 1.555L8 8.414zM0 4.697v7.104l5.803-3.558zM6.761 8.83l-6.57 4.027A2 2 0 0 0 2 14h12a2 2 0 0 0 1.808-1.144l-6.57-4.027L8 9.586zm3.436-.586L16 11.801V4.697z" /> | |||
| </svg> | |||
| </div> | |||
| <div class="agent-content"> | |||
| <h3 class="agent-name">Agent Image</h3> | |||
| <p class="agent-description"> | |||
| Un agent IA spécialisé dans la rédaction de mail professionnel avec une approche humaine et experte | |||
| </p> | |||
| </div> | |||
| <div class="agent-check"> | |||
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"> | |||
| <path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z"/> | |||
| </svg> | |||
| </div> | |||
| </label> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <!-- Zone de saisie moderne --> | |||
| <div class="chat-input-container"> | |||
| <!-- Fichiers attachés --> | |||
| @if (UploadedDocuments.Any()) | |||
| { | |||
| <div class="attached-files"> | |||
| @foreach (var doc in UploadedDocuments) | |||
| } | |||
| else | |||
| { | |||
| <!-- Vue ChatRoom --> | |||
| <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="file-item"> | |||
| <div class="file-preview"> | |||
| <div class="file-icon"> | |||
| <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="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" /> | |||
| <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> | |||
| </div> | |||
| } | |||
| 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> | |||
| <div class="file-info"> | |||
| <span class="file-name">@doc.FileName</span> | |||
| <span class="file-size">@FormatFileSize(doc.Bytes.Length)</span> | |||
| <div class="message-content-wrapper"> | |||
| <div class="bubble @(msg.IsUser ? "bubble-user" : "bubble-assistant")"> | |||
| @msg.Text | |||
| </div> | |||
| @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> | |||
| <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" /> | |||
| </div> | |||
| } | |||
| @if (isTyping) | |||
| { | |||
| <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> | |||
| </button> | |||
| </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> | |||
| } | |||
| </div> | |||
| } | |||
| <!-- Input wrapper --> | |||
| <div class="input-wrapper @(UploadedDocuments.Any() ? "has-files" : "")"> | |||
| <textarea @bind="CurrentInput" | |||
| @bind:event="oninput" | |||
| @onkeydown="HandleKeyPress" | |||
| placeholder="Nouvelle conversation..." | |||
| rows="1"></textarea> | |||
| <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> | |||
| } | |||
| </div> | |||
| <div class="right-actions"> | |||
| <!-- Sélecteur de domaine RAG --> | |||
| @if (!hiddenDomain && RagDomains.Any()) | |||
| <!-- Zone de saisie moderne --> | |||
| <div class="chat-input-container"> | |||
| <!-- Fichiers attachés --> | |||
| @if (UploadedDocuments.Any()) | |||
| { | |||
| <div class="attached-files"> | |||
| @foreach (var doc in UploadedDocuments) | |||
| { | |||
| <select class="domain-select" @bind="SelectedDomain"> | |||
| @foreach (var domain in RagDomains) | |||
| { | |||
| <option value="@domain">@domain</option> | |||
| } | |||
| </select> | |||
| <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="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> | |||
| } | |||
| <!-- 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> | |||
| <!-- Input wrapper --> | |||
| <div class="input-wrapper @(UploadedDocuments.Any() ? "has-files" : "")"> | |||
| <textarea @bind="CurrentInput" | |||
| @bind:event="oninput" | |||
| @onkeydown="HandleKeyPress" | |||
| placeholder="Nouvelle conversation..." | |||
| rows="1"></textarea> | |||
| <!-- Bouton envoyer --> | |||
| <button class="send-button" | |||
| @onclick="SendMessage" | |||
| disabled="@(string.IsNullOrWhiteSpace(CurrentInput) && !UploadedDocuments.Any())" | |||
| aria-label="Envoyer le message"> | |||
| <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 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" /> | |||
| <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> | |||
| </button> | |||
| <span>Importer un fichier</span> | |||
| <InputFile id="fileUpload" OnChange="UploadDocuments" multiple style="display: none;" /> | |||
| </label> | |||
| <div class="right-actions"> | |||
| <!-- Sélecteur de domaine RAG --> | |||
| @if (!hiddenDomain && RagDomains.Any()) | |||
| { | |||
| <select class="domain-select" @bind="SelectedDomain"> | |||
| @foreach (var domain in RagDomains) | |||
| { | |||
| <option value="@domain">@domain</option> | |||
| } | |||
| </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> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| } | |||
| @@ -2,14 +2,11 @@ | |||
| using Microsoft.AspNetCore.Components; | |||
| using Microsoft.AspNetCore.Components.Authorization; | |||
| using Microsoft.AspNetCore.Components.Forms; | |||
| using Microsoft.AspNetCore.Components.Web; // ← AJOUT pour KeyboardEventArgs | |||
| using Microsoft.AspNetCore.Components.Web; | |||
| using Microsoft.JSInterop; | |||
| using ReAct_PME.Domain; | |||
| using ReAct_PME.WebUI.ServicesUI; | |||
| using System.Net.Http.Json; | |||
| using System.Runtime.CompilerServices; | |||
| using System.Text.Json; | |||
| namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| { | |||
| @@ -18,7 +15,6 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| [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!; | |||
| [Inject] private ReAct_PME.WebUI.ServicesUI.ApiService ApiService { get; set; } = default!; | |||
| @@ -27,12 +23,12 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| [Inject] private IToastService ToastService { get; set; } = default!; | |||
| [Inject] private IJSRuntime JSRuntime { get; set; } = default!; | |||
| public class UploadedDoc | |||
| { | |||
| public string FileName { get; set; } = ""; | |||
| public byte[] Bytes { get; set; } = Array.Empty<byte>(); | |||
| } | |||
| public List<UploadedDoc> UploadedDocuments { get; set; } = new(); | |||
| // État de l'UI | |||
| @@ -41,9 +37,11 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| public string SelectedConversationId { get; set; } = ""; | |||
| public List<MessageDto> Messages { get; set; } = new(); | |||
| private bool IsWithHistorique { get; set; } | |||
| private bool isTyping { get; set; } = false; | |||
| private bool isTyping { get; set; } = false; | |||
| private bool isDisabled { get; set; } = false; | |||
| public string CurrentInput { get; set; } = ""; | |||
| public int SelectedAgentType { get; set; } = 1; // Agent par défaut sélectionné | |||
| // Options | |||
| public List<string> RagDomains { get; set; } = new(); | |||
| @@ -54,9 +52,17 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| private bool IsNewConversation = false; | |||
| public bool hiddenDomain = true; | |||
| protected override async Task OnParametersSetAsync() | |||
| protected override async Task OnParametersSetAsync() | |||
| { | |||
| // Configuration selon le type de LLM | |||
| if (TypeLLM == 0) | |||
| { | |||
| // Mode Home - pas de configuration particulière | |||
| return; | |||
| } | |||
| await LoadConversations(); | |||
| if (TypeLLM == 1) | |||
| { | |||
| SelectedMode = "llm"; | |||
| @@ -68,7 +74,8 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| IsWithHistorique = false; | |||
| hiddenDomain = false; | |||
| await LoadRagDomains(); | |||
| SelectedDomain = RagDomains[0]; | |||
| if (RagDomains.Any()) | |||
| SelectedDomain = RagDomains[0]; | |||
| } | |||
| else if (TypeLLM == 3) | |||
| { | |||
| @@ -86,77 +93,34 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| 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 | |||
| } | |||
| StateHasChanged(); | |||
| } | |||
| protected override async Task OnInitializedAsync() | |||
| { | |||
| #region Vérification si utilisateur est connecté et s'il a les droits | |||
| var tok = await PagesInitializer.VerifConnexionAndRules(AuthenticationStateProvider, Navigation, Http, ToastService, "/api/ChatRoom/llm/test"); | |||
| var tok = await PagesInitializer.VerifConnexionAndRules( | |||
| AuthenticationStateProvider, | |||
| Navigation, | |||
| Http, | |||
| ToastService, | |||
| "/api/ChatRoom/llm/test" | |||
| ); | |||
| if (tok == null || tok.Length == 0) | |||
| return; | |||
| else | |||
| AuthService.SetToken(tok); | |||
| #endregion | |||
| //await LoadConversations(); | |||
| //await LoadOllamaModels(); | |||
| /* | |||
| if (TypeLLM == 1) | |||
| { | |||
| SelectedMode = "llm"; | |||
| IsWithHistorique = true; | |||
| } | |||
| else if (TypeLLM == 2) | |||
| { | |||
| SelectedMode = "rag"; | |||
| IsWithHistorique = false; | |||
| hiddenDomain = false; | |||
| await LoadRagDomains(); | |||
| SelectedDomain = RagDomains[0]; | |||
| } | |||
| else if (TypeLLM == 3) | |||
| { | |||
| SelectedMode = "code"; | |||
| IsWithHistorique = true; | |||
| } | |||
| else if (TypeLLM == 4) | |||
| { | |||
| SelectedMode = "image"; | |||
| IsWithHistorique = false; | |||
| } | |||
| 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) | |||
| @@ -180,22 +144,19 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| private void RemoveDocument(UploadedDoc doc) | |||
| { | |||
| UploadedDocuments.Remove(doc); | |||
| StateHasChanged(); | |||
| } | |||
| private async Task LoadConversations() | |||
| { | |||
| if (string.IsNullOrEmpty(ConversationId)) | |||
| return; | |||
| var oneConversation = await Http.GetFromJsonAsync<ConversationDto>($"/api/ChatRoom/conversation/{ConversationId}") | |||
| ?? new(); | |||
| Conversations = new(); | |||
| Conversations.Add(oneConversation); | |||
| /* | |||
| Conversations = await Http.GetFromJsonAsync<List<ConversationDto>>($"/api/ChatRoom/conversations/{AuthService.ID}/{TypeLLM}") | |||
| ?? new(); | |||
| Conversations = Conversations.OrderByDescending(c => c.Messages.Any() ? c.Messages.Max(m => m.CreatedAt) : DateTime.MinValue).ToList(); | |||
| */ | |||
| StateHasChanged(); | |||
| } | |||
| @@ -217,10 +178,6 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| public void CreateConversation() | |||
| { | |||
| //var newConv = await Http.PostAsJsonAsync("/api/ChatRoom/conversation/create", new { }); | |||
| //var created = await newConv.Content.ReadFromJsonAsync<ConversationDto>(); | |||
| ConversationDto newConversation = new(); | |||
| if (newConversation != null) | |||
| @@ -234,20 +191,112 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| public void SelectConversation(string id) | |||
| { | |||
| SelectedConversationId = id; | |||
| SelectedConversation = Conversations.Where(c => c.Id == id).FirstOrDefault(); | |||
| Messages = SelectedConversation?.Messages.OrderBy(m => m.CreatedAt).ToList() ?? new(); | |||
| StateHasChanged(); | |||
| } | |||
| // Méthode pour envoyer un message depuis la page Home | |||
| private async Task SendMessageFromHome() | |||
| { | |||
| if (string.IsNullOrWhiteSpace(CurrentInput) && !UploadedDocuments.Any()) | |||
| return; | |||
| try | |||
| { | |||
| isDisabled = true; | |||
| isTyping = true; | |||
| StateHasChanged(); | |||
| // Ajoute le message utilisateur dans l'UI | |||
| var userMsg = new MessageDto | |||
| { | |||
| IsUser = true, | |||
| Text = CurrentInput | |||
| }; | |||
| Messages.Add(userMsg); | |||
| var newConversationId = Guid.NewGuid().ToString(); | |||
| // Déterminer le mode selon l'agent sélectionné | |||
| string mode = SelectedAgentType switch | |||
| { | |||
| 1 => "LLM", | |||
| 2 => "RAG", | |||
| 3 => "CODE", | |||
| 4 => "IMAGE", | |||
| _ => "LLM" | |||
| }; | |||
| // Pour le mode RAG, charger les domaines si nécessaire | |||
| string domain = ""; | |||
| if (SelectedAgentType == 2) | |||
| { | |||
| if (!RagDomains.Any()) | |||
| await LoadRagDomains(); | |||
| domain = RagDomains.Any() ? RagDomains[0] : ""; | |||
| } | |||
| var payload = new SendMessageRequest | |||
| { | |||
| ConversationId = newConversationId, | |||
| IsNewConversation = true, | |||
| IdWorkForce = AuthService!.ID!, | |||
| Mode = mode, | |||
| Domain = domain, | |||
| Documents64 = UploadedDocuments.Select(d => Convert.ToBase64String(d.Bytes)).ToList(), | |||
| Documents = UploadedDocuments.Select(d => d.FileName).ToList(), | |||
| Role = "user", | |||
| Text = CurrentInput, | |||
| Context = "" | |||
| }; | |||
| CurrentInput = ""; | |||
| var assistantMsg = await ApiService.EnvoiRequete<MessageDto>("/api/ChatRoom/sendMessage", payload); | |||
| isTyping = false; | |||
| isDisabled = false; | |||
| if (assistantMsg != null) | |||
| { | |||
| Messages.Add(assistantMsg); | |||
| // Navigation vers la conversation créée avec le type d'agent sélectionné | |||
| Navigation.NavigateTo($"/chatroom_base/{SelectedAgentType}/{newConversationId}"); | |||
| } | |||
| UploadedDocuments.Clear(); | |||
| StateHasChanged(); | |||
| } | |||
| catch (Exception ex) | |||
| { | |||
| Console.WriteLine(ex.Message); | |||
| isTyping = false; | |||
| isDisabled = false; | |||
| ToastService.ShowError($"Erreur lors de l'envoi du message : {ex.Message}"); | |||
| StateHasChanged(); | |||
| } | |||
| } | |||
| public async Task SendMessage() | |||
| { | |||
| try | |||
| { | |||
| if (string.IsNullOrWhiteSpace(CurrentInput) || SelectedConversation == null) | |||
| return; | |||
| { | |||
| // Si on est en mode TypeLLM > 0 mais sans conversation, on la crée | |||
| if (TypeLLM > 0 && !string.IsNullOrWhiteSpace(CurrentInput)) | |||
| { | |||
| CreateConversation(); | |||
| if (SelectedConversation == null) | |||
| return; | |||
| } | |||
| else | |||
| { | |||
| return; | |||
| } | |||
| } | |||
| isTyping = true; | |||
| StateHasChanged(); | |||
| @@ -270,10 +319,8 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| Documents64 = UploadedDocuments.Select(d => Convert.ToBase64String(d.Bytes)).ToList(), | |||
| Documents = UploadedDocuments.Select(d => d.FileName).ToList(), | |||
| Role = "user", | |||
| //Model = SelectedModel, | |||
| Text = CurrentInput, | |||
| Context = IsWithHistorique ? string.Join("\n ", Messages.Select(m => m.Text)) : "" | |||
| }; | |||
| CurrentInput = ""; | |||
| @@ -292,6 +339,9 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| await LoadConversations(); | |||
| SelectConversation(payload.ConversationId); | |||
| IsNewConversation = false; | |||
| // Mettre à jour l'URL pour inclure l'ID de la conversation | |||
| Navigation.NavigateTo($"/chatroom_base/{TypeLLM}/{payload.ConversationId}", false); | |||
| } | |||
| } | |||
| @@ -306,7 +356,6 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| } | |||
| } | |||
| private async Task HandleKeyPress(KeyboardEventArgs e) | |||
| { | |||
| if (e.Key == "Enter" && !e.ShiftKey) | |||
| @@ -317,7 +366,7 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| private void GoBack() | |||
| { | |||
| Navigation.NavigateTo("/"); | |||
| Navigation.NavigateTo("/chatroom_base/0"); | |||
| } | |||
| private void ToggleHistory() | |||
| @@ -335,10 +384,10 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| } | |||
| 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"); | |||
| @@ -356,5 +405,17 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom | |||
| } | |||
| return $"{len:0.##} {sizes[order]}"; | |||
| } | |||
| // Méthodes spécifiques à la vue Home | |||
| private void SelectAgent(int agentId) | |||
| { | |||
| SelectedAgentType = agentId; | |||
| StateHasChanged(); | |||
| } | |||
| private async Task HandleFileSelection(InputFileChangeEventArgs e) | |||
| { | |||
| await UploadDocuments(e); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -1,9 +1,437 @@ | |||
| | |||
| @keyframes fadeIn { | |||
| from { | |||
| opacity: 0; | |||
| transform: translateY(10px); | |||
| } | |||
| to { | |||
| opacity: 1; | |||
| transform: translateY(0); | |||
| } | |||
| } | |||
| @keyframes fileSlideIn { | |||
| from { | |||
| opacity: 0; | |||
| transform: translateY(-10px); | |||
| } | |||
| to { | |||
| opacity: 1; | |||
| transform: translateY(0); | |||
| } | |||
| } | |||
| @keyframes typing { | |||
| 0%, 60%, 100% { | |||
| transform: translateY(0); | |||
| opacity: 0.7; | |||
| } | |||
| 30% { | |||
| transform: translateY(-10px); | |||
| opacity: 1; | |||
| } | |||
| } | |||
| @keyframes checkPop { | |||
| 0% { | |||
| transform: scale(0); | |||
| } | |||
| 50% { | |||
| transform: scale(1.2); | |||
| } | |||
| 100% { | |||
| transform: scale(1); | |||
| } | |||
| } | |||
| @keyframes pulse { | |||
| from { | |||
| opacity: 1; | |||
| } | |||
| to { | |||
| opacity: 0.6; | |||
| } | |||
| } | |||
| /* Boutons d'action */ | |||
| .attach-button, | |||
| .voice-button { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| background: transparent; | |||
| border: none; | |||
| color: #6b7280; | |||
| cursor: pointer; | |||
| border-radius: 8px; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .attach-button { | |||
| gap: 8px; | |||
| padding: 0; | |||
| font-size: 13px; | |||
| font-weight: 500; | |||
| } | |||
| .attach-button span { | |||
| display: none; | |||
| } | |||
| .attach-button:hover { | |||
| color: var(--secondary); | |||
| } | |||
| .voice-button { | |||
| width: 36px; | |||
| height: 36px; | |||
| } | |||
| .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(--contrast); | |||
| 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; | |||
| } | |||
| /* Gestion des fichiers */ | |||
| .file-item { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| padding: 8px 12px; | |||
| background: #ffffff; | |||
| border: 1px solid var(--border); | |||
| border-radius: 8px; | |||
| transition: all 0.2s 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; | |||
| } | |||
| /* Wrapper d'input universel */ | |||
| .input-wrapper { | |||
| background: white; | |||
| 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; | |||
| } | |||
| .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: var(--text-dark); | |||
| 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; | |||
| } | |||
| .home-container { | |||
| display: flex; | |||
| flex-direction: column; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| .home-content { | |||
| width: 100%; | |||
| max-width: 1200px; | |||
| display: flex; | |||
| flex-direction: column; | |||
| gap: 3rem; | |||
| } | |||
| /* Section bienvenue */ | |||
| .welcome-section { | |||
| text-align: center; | |||
| margin-bottom: 1rem; | |||
| } | |||
| .welcome-title { | |||
| font-size: 2.5rem; | |||
| font-weight: 700; | |||
| color: var(--text-dark); | |||
| margin-bottom: 0.5rem; | |||
| } | |||
| .welcome-subtitle { | |||
| font-size: 1.25rem; | |||
| color: var(--text-light); | |||
| margin: 0; | |||
| } | |||
| /* Zone de saisie Home */ | |||
| .input-container { | |||
| width: 100%; | |||
| margin: 0 auto; | |||
| } | |||
| .input-files { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| gap: 8px; | |||
| padding: 12px 16px; | |||
| background: white; | |||
| border: 1px solid var(--border); | |||
| border-radius: 16px 16px 0 0; | |||
| margin-bottom: -1px; | |||
| } | |||
| /* Variantes spécifiques pour Home */ | |||
| .input-files .file-item { | |||
| background: #f9fafb; | |||
| max-width: 45%; | |||
| animation: fileSlideIn 0.3s ease; | |||
| } | |||
| .input-files .file-preview { | |||
| width: 36px; | |||
| height: 36px; | |||
| } | |||
| .input-files .file-icon { | |||
| font-size: 18px; | |||
| } | |||
| .input-actions { | |||
| display: flex; | |||
| justify-content: space-between; | |||
| align-items: center; | |||
| padding-top: 4px; | |||
| } | |||
| .input-right-actions { | |||
| display: flex; | |||
| align-items: center; | |||
| gap: 8px; | |||
| } | |||
| /* Cartes des agents */ | |||
| .agents-grid { | |||
| display: grid; | |||
| grid-template-columns: repeat(auto-fit, minmax(480px, 1fr)); | |||
| gap: 1.5rem; | |||
| margin-top: 1rem; | |||
| } | |||
| .agent-card { | |||
| position: relative; | |||
| background: white; | |||
| border: 2px solid var(--border); | |||
| border-radius: 12px; | |||
| padding: 1.5rem; | |||
| display: flex; | |||
| gap: 1rem; | |||
| cursor: pointer; | |||
| transition: all 0.2s ease; | |||
| } | |||
| .agent-card input[type="radio"] { | |||
| position: absolute; | |||
| opacity: 0; | |||
| pointer-events: none; | |||
| } | |||
| .agent-card:hover { | |||
| border-color: var(--secondary); | |||
| box-shadow: 0 4px 12px rgba(41, 132, 161, 0.15); | |||
| transform: translateY(-2px); | |||
| } | |||
| .agent-card.selected { | |||
| border-color: var(--secondary); | |||
| background: linear-gradient(135deg, rgba(41, 132, 161, 0.05) 0%, rgba(57, 181, 221, 0.05) 100%); | |||
| box-shadow: 0 4px 12px rgba(41, 132, 161, 0.2); | |||
| } | |||
| .agent-card.selected .agent-icon { | |||
| background: linear-gradient(135deg, var(--secondary) 0%, var(--third-color) 100%); | |||
| box-shadow: 0 4px 12px rgba(41, 132, 161, 0.3); | |||
| } | |||
| .agent-card .agent-check { | |||
| position: absolute; | |||
| top: 1rem; | |||
| right: 1rem; | |||
| width: 24px; | |||
| height: 24px; | |||
| border-radius: 50%; | |||
| background: var(--secondary); | |||
| color: white; | |||
| display: none; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| .agent-card.selected .agent-check { | |||
| display: flex; | |||
| animation: checkPop 0.3s ease; | |||
| } | |||
| .agent-icon { | |||
| flex-shrink: 0; | |||
| width: 48px; | |||
| height: 48px; | |||
| border-radius: 10px; | |||
| background: linear-gradient(135deg, var(--secondary) 0%, var(--third-color) 100%); | |||
| color: white; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| .agent-content { | |||
| flex: 1; | |||
| } | |||
| .agent-name { | |||
| font-size: 1.125rem; | |||
| font-weight: 600; | |||
| color: var(--text-dark); | |||
| margin: 0 0 0.5rem 0; | |||
| } | |||
| .agent-description { | |||
| font-size: 0.9rem; | |||
| color: var(--text-light); | |||
| line-height: 1.5; | |||
| margin: 0; | |||
| } | |||
| .chatroom-container { | |||
| display: flex; | |||
| flex-direction: column; | |||
| height: 100%; | |||
| background: var(--bg-light); | |||
| } | |||
| .chatroom-messages { | |||
| @@ -29,7 +457,7 @@ | |||
| } | |||
| .chatroom-messages::-webkit-scrollbar-thumb:hover { | |||
| background: var( --third-color); | |||
| background: var(--third-color); | |||
| } | |||
| .chatroom-empty { | |||
| @@ -42,6 +470,17 @@ | |||
| text-align: center; | |||
| } | |||
| .chatroom-empty h3 { | |||
| font-size: 1.5rem; | |||
| color: var(--text-dark); | |||
| margin-bottom: 0.5rem; | |||
| } | |||
| .chatroom-empty p { | |||
| font-size: 1rem; | |||
| margin: 0; | |||
| } | |||
| .empty-icon { | |||
| font-size: 4rem; | |||
| color: var(--secondary); | |||
| @@ -49,17 +488,6 @@ | |||
| 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; | |||
| @@ -68,27 +496,15 @@ | |||
| animation: fadeIn 0.3s ease; | |||
| } | |||
| @keyframes fadeIn { | |||
| from { | |||
| opacity: 0; | |||
| transform: translateY(10px); | |||
| .message-row.user { | |||
| flex-direction: row-reverse; | |||
| justify-content: flex-start; | |||
| } | |||
| to { | |||
| opacity: 1; | |||
| transform: translateY(0); | |||
| .message-row.assistant { | |||
| flex-direction: row; | |||
| justify-content: flex-start; | |||
| } | |||
| } | |||
| .message-row.user { | |||
| flex-direction: row-reverse; | |||
| justify-content: flex-start; | |||
| } | |||
| .message-row.assistant { | |||
| flex-direction: row; | |||
| justify-content: flex-start; | |||
| } | |||
| .avatar { | |||
| width: 40px; | |||
| @@ -101,12 +517,12 @@ | |||
| } | |||
| .avatar-user { | |||
| background: linear-gradient(135deg, var(--secondary) 0%, var( --third-color) 100%); | |||
| background: linear-gradient(135deg, var(--secondary) 0%, var(--third-color) 100%); | |||
| color: white; | |||
| } | |||
| .avatar-assistant { | |||
| background: linear-gradient(135deg, var( --contrast) 0%, #e02851 100%); | |||
| background: linear-gradient(135deg, var(--contrast) 0%, #e02851 100%); | |||
| color: white; | |||
| } | |||
| @@ -135,7 +551,7 @@ | |||
| } | |||
| .bubble-user { | |||
| background: linear-gradient(135deg, var(--secondary) 0%, var( --third-color) 100%); | |||
| background: linear-gradient(135deg, var(--secondary) 0%, var(--third-color) 100%); | |||
| color: white; | |||
| border-bottom-right-radius: 4px; | |||
| } | |||
| @@ -168,9 +584,10 @@ | |||
| .btn-message-action:hover { | |||
| background: var(--bg-light); | |||
| color: var( --third-color); | |||
| color: var(--third-color); | |||
| } | |||
| /* Indicateur de frappe */ | |||
| .typing-indicator { | |||
| display: flex; | |||
| gap: 0.4rem; | |||
| @@ -193,19 +610,7 @@ | |||
| animation-delay: 0.4s; | |||
| } | |||
| @keyframes typing { | |||
| 0%, 60%, 100% { | |||
| transform: translateY(0); | |||
| opacity: 0.7; | |||
| } | |||
| 30% { | |||
| transform: translateY(-10px); | |||
| opacity: 1; | |||
| } | |||
| } | |||
| /* Zone de saisie ChatRoom */ | |||
| .chat-input-container { | |||
| width: 100%; | |||
| padding: 16px 24px; | |||
| @@ -216,7 +621,6 @@ | |||
| position: relative; | |||
| } | |||
| /* Fichiers attachés */ | |||
| .attached-files { | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| @@ -227,148 +631,12 @@ | |||
| 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; | |||
| transform: translateY(-10px); | |||
| } | |||
| to { | |||
| opacity: 1; | |||
| transform: translateY(0); | |||
| } | |||
| } | |||
| /* Input wrapper */ | |||
| .input-wrapper { | |||
| width: 100%; | |||
| .chat-input-container .input-wrapper { | |||
| 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; | |||
| width: 100%; | |||
| } | |||
| .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; | |||
| @@ -376,40 +644,13 @@ | |||
| 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; | |||
| } | |||
| /* Contrôles spécifiques ChatRoom */ | |||
| .domain-select { | |||
| padding: 6px 12px; | |||
| border: 1px solid var(--border); | |||
| @@ -431,7 +672,7 @@ | |||
| box-shadow: 0 0 0 3px rgba(41, 132, 161, 0.1); | |||
| } | |||
| .voice-button { | |||
| .history-button { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| @@ -445,49 +686,45 @@ | |||
| transition: all 0.2s ease; | |||
| } | |||
| .voice-button:hover { | |||
| .history-button:hover { | |||
| background: #f3f4f6; | |||
| color: var(--text-dark); | |||
| } | |||
| .history-button.active { | |||
| background: var(--secondary); | |||
| color: white; | |||
| } | |||
| .history-button.desactive { | |||
| opacity: 0.5; | |||
| } | |||
| .send-button { | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| width: 36px; | |||
| height: 36px; | |||
| background: var( --contrast); | |||
| border: none; | |||
| color: #ffffff; | |||
| cursor: pointer; | |||
| border-radius: 8px; | |||
| transition: all 0.2s ease; | |||
| @media (min-width: 640px) { | |||
| .attach-button span { | |||
| display: inline; | |||
| } | |||
| } | |||
| .send-button:hover:not(:disabled) { | |||
| transform: scale(1.05); | |||
| box-shadow: 0 4px 12px rgba(246, 62, 99, 0.3); | |||
| @media (max-width: 768px) { | |||
| /* Home */ | |||
| .home-content { | |||
| gap: 2rem; | |||
| } | |||
| .send-button:active:not(:disabled) { | |||
| transform: scale(0.98); | |||
| .welcome-title { | |||
| font-size: 2rem; | |||
| } | |||
| .send-button:disabled { | |||
| background: #e5e7eb; | |||
| cursor: not-allowed; | |||
| opacity: 0.6; | |||
| .welcome-subtitle { | |||
| font-size: 1.125rem; | |||
| } | |||
| .send-button:disabled svg { | |||
| color: #9ca3af; | |||
| } | |||
| .agents-grid { | |||
| grid-template-columns: 1fr; | |||
| } | |||
| @media (max-width: 768px) { | |||
| /* ChatRoom */ | |||
| .chatroom-messages { | |||
| padding: 1rem; | |||
| } | |||
| @@ -505,6 +742,7 @@ | |||
| gap: 6px; | |||
| } | |||
| /* Fichiers */ | |||
| .file-item { | |||
| padding: 6px 10px; | |||
| max-width: 200px; | |||
| @@ -528,6 +766,7 @@ | |||
| height: 20px; | |||
| } | |||
| /* Input wrapper */ | |||
| .input-wrapper { | |||
| padding: 12px 16px; | |||
| border-radius: 14px; | |||
| @@ -541,6 +780,7 @@ | |||
| font-size: 14px; | |||
| } | |||
| /* Actions */ | |||
| .actions-row .right-actions { | |||
| gap: 6px; | |||
| } | |||
| @@ -567,4 +807,21 @@ | |||
| width: 36px; | |||
| height: 36px; | |||
| } | |||
| .agent-card { | |||
| padding: 1rem; | |||
| } | |||
| .agent-icon { | |||
| width: 40px; | |||
| height: 40px; | |||
| } | |||
| .agent-name { | |||
| font-size: 1rem; | |||
| } | |||
| .agent-description { | |||
| font-size: 0.85rem; | |||
| } | |||
| } | |||
| @@ -868,7 +868,6 @@ code { | |||
| /* Responsive */ | |||
| @media (max-width: 768px) { | |||
| .home-container { | |||
| padding: 1rem; | |||
| } | |||
| .home-header { | |||
| @@ -3008,17 +3007,6 @@ color:white; | |||
| @media (max-width: 768px) { | |||
| .sidebar { | |||
| width: 64px; | |||
| } | |||
| .sidebar.collapsed { | |||
| width: 0; | |||
| } | |||
| } | |||
| @keyframes fadeIn { | |||
| from { | |||
| opacity: 0; | |||
| @@ -3037,15 +3025,3 @@ color:white; | |||
| } | |||
| .footer { | |||
| padding: 1rem; | |||
| background-color: #e9ecef; | |||
| border-top: 1px solid #dee2e6; | |||
| text-align: center; | |||
| flex-shrink: 0; | |||
| } | |||
| .footer p { | |||
| margin: 0; | |||
| color: #6c757d; | |||
| } | |||