Sfoglia il codice sorgente

modification de la "sidebar" afin d'y inclure l'historique des conversations et des rag

déplacement des sections mail dans la partie "profile"
modification de la page home logique à mettre en place
WebUI
maela 1 settimana fa
parent
commit
5826eccc6a
7 ha cambiato i file con 2488 aggiunte e 558 eliminazioni
  1. +104
    -72
      WebUI/ReAct_PME.WebUI/Layout/MainLayout.razor
  2. +79
    -172
      WebUI/ReAct_PME.WebUI/Layout/MainLayout.razor.css
  3. +289
    -49
      WebUI/ReAct_PME.WebUI/Layout/NavMenu.razor
  4. +0
    -130
      WebUI/ReAct_PME.WebUI/Layout/NavMenu.razor.css
  5. +446
    -123
      WebUI/ReAct_PME.WebUI/Pages/ChatRoom/ChatRoom_base.razor.css
  6. +182
    -11
      WebUI/ReAct_PME.WebUI/Pages/Home.razor
  7. +1388
    -1
      WebUI/ReAct_PME.WebUI/wwwroot/css/app.css

+ 104
- 72
WebUI/ReAct_PME.WebUI/Layout/MainLayout.razor Vedi File

@@ -5,81 +5,115 @@
<BlazoredToasts />

<div class="page">
<!-- Barre supérieure fixe -->
<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 class="top-bar-right">
<div class="user-menu">
<button class="user-menu-button" onclick="toggleUserMenu(event)">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="currentColor" class="bi bi-person-circle" 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>
</button>
<div class="user-menu-dropdown" id="userMenuDropdown">
<AuthorizeView Roles="Admin">
<Authorized>
<a href="administration" class="dropdown-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-tools dropdown-icon " viewBox="0 0 16 16">
<path d="M1 0 0 1l2.2 3.081a1 1 0 0 0 .815.419h.07a1 1 0 0 1 .708.293l2.675 2.675-2.617 2.654A3.003 3.003 0 0 0 0 13a3 3 0 1 0 5.878-.851l2.654-2.617.968.968-.305.914a1 1 0 0 0 .242 1.023l3.27 3.27a.997.997 0 0 0 1.414 0l1.586-1.586a.997.997 0 0 0 0-1.414l-3.27-3.27a1 1 0 0 0-1.023-.242L10.5 9.5l-.96-.96 2.68-2.643A3.005 3.005 0 0 0 16 3q0-.405-.102-.777l-2.14 2.141L12 4l-.364-1.757L13.777.102a3 3 0 0 0-3.675 3.68L7.462 6.46 4.793 3.793a1 1 0 0 1-.293-.707v-.071a1 1 0 0 0-.419-.814zm9.646 10.646a.5.5 0 0 1 .708 0l2.914 2.915a.5.5 0 0 1-.707.707l-2.915-2.914a.5.5 0 0 1 0-.708M3 11l.471.242.529.026.287.445.445.287.026.529L5 13l-.242.471-.026.529-.445.287-.287.445-.529.026L3 15l-.471-.242L2 14.732l-.287-.445L1.268 14l-.026-.529L1 13l.242-.471.026-.529.445-.287.287-.445.529-.026z" />
<div class="main-container">
<div class="">
<NavMenu />
</div>

<div class="content-area">
<!-- Barre supérieure (au-dessus du contenu uniquement) -->
<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 class="top-bar-right">
<div class="user-menu">
<button class="user-menu-button" onclick="toggleUserMenu(event)">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="currentColor" class="bi bi-person-circle" 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>
</button>
<div class="user-menu-dropdown" id="userMenuDropdown">
<AuthorizeView Roles="Admin">
<Authorized>
<a href="administration" class="dropdown-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-tools dropdown-icon" viewBox="0 0 16 16">
<path d="M1 0 0 1l2.2 3.081a1 1 0 0 0 .815.419h.07a1 1 0 0 1 .708.293l2.675 2.675-2.617 2.654A3.003 3.003 0 0 0 0 13a3 3 0 1 0 5.878-.851l2.654-2.617.968.968-.305.914a1 1 0 0 0 .242 1.023l3.27 3.27a.997.997 0 0 0 1.414 0l1.586-1.586a.997.997 0 0 0 0-1.414l-3.27-3.27a1 1 0 0 0-1.023-.242L10.5 9.5l-.96-.96 2.68-2.643A3.005 3.005 0 0 0 16 3q0-.405-.102-.777l-2.14 2.141L12 4l-.364-1.757L13.777.102a3 3 0 0 0-3.675 3.68L7.462 6.46 4.793 3.793a1 1 0 0 1-.293-.707v-.071a1 1 0 0 0-.419-.814zm9.646 10.646a.5.5 0 0 1 .708 0l2.914 2.915a.5.5 0 0 1-.707.707l-2.915-2.914a.5.5 0 0 1 0-.708M3 11l.471.242.529.026.287.445.445.287.026.529L5 13l-.242.471-.026.529-.445.287-.287.445-.529.026L3 15l-.471-.242L2 14.732l-.287-.445L1.268 14l-.026-.529L1 13l.242-.471.026-.529.445-.287.287-.445.529-.026z" />
</svg>
<span>Administration</span>
</a>
</Authorized>
</AuthorizeView>

<a href="dt_work_force_list" class="dropdown-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person dropdown-icon" viewBox="0 0 16 16">
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10s-3.516.68-4.168 1.332c-.678.678-.83 1.418-.832 1.664z" />
</svg>
<span>Administration</span>
<span>Mon compte</span>
</a>
</Authorized>
</AuthorizeView>

<a href="dt_work_force_list" class="dropdown-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person dropdown-icon" viewBox="0 0 16 16">
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6m2-3a2 2 0 1 1-4 0 2 2 0 0 1 4 0m4 8c0 1-1 1-1 1H3s-1 0-1-1 1-4 6-4 6 3 6 4m-1-.004c-.001-.246-.154-.986-.832-1.664C11.516 10.68 10.289 10 8 10s-3.516.68-4.168 1.332c-.678.678-.83 1.418-.832 1.664z" />
</svg>
<span>Mon compte</span>
</a>

<div class="dropdown-divider"></div>
<div class="dropdown-divider"></div>

<a href="logout" class="dropdown-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-door-closed dropdown-icon" viewBox="0 0 16 16">
<path d="M3 2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v13h1.5a.5.5 0 0 1 0 1h-13a.5.5 0 0 1 0-1H3zm1 13h8V2H4z" />
<path d="M9 9a1 1 0 1 0 2 0 1 1 0 0 0-2 0" />
</svg>
<span>Déconnexion</span>
</a>
<a href="listemails_card" class="dropdown-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-envelope dropdown-icon" viewBox="0 0 16 16">
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1zm13 2.383-4.708 2.825L15 11.105zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741M1 11.105l4.708-2.897L1 5.383z" />
</svg>
<span>Emails non lus</span>
</a>

<a href="listemailssend_card" class="dropdown-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-mailbox dropdown-icon" viewBox="0 0 16 16">
<path d="M4 4a3 3 0 0 0-3 3v6h6V7a3 3 0 0 0-3-3m0-1h8a4 4 0 0 1 4 4v6a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V7a4 4 0 0 1 4-4m2.646 1A4 4 0 0 1 8 7v6h7V7a3 3 0 0 0-3-3z" />
<path d="M11.793 8.5H9v-1h5a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.354-.146zM5 7c0 .552-.448 0-1 0s-1 .552-1 0a1 1 0 0 1 2 0" />
</svg>
<span>Emails à suivre</span>
</a>

<a href="editmail_redaction" class="dropdown-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square dropdown-icon" viewBox="0 0 16 16">
<path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z" />
<path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5z" />
</svg>
<span>Rédiger un email</span>
</a>

<a href="parametres_mail" class="dropdown-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear dropdown-icon" viewBox="0 0 16 16">
<path d="M8 4.754a3.246 3.246 0 1 0 0 6.492 3.246 3.246 0 0 0 0-6.492M5.754 8a2.246 2.246 0 1 1 4.492 0 2.246 2.246 0 0 1-4.492 0" />
<path d="M9.796 1.343c-.527-1.79-3.065-1.79-3.592 0l-.094.319a.873.873 0 0 1-1.255.52l-.292-.16c-1.64-.892-3.433.902-2.54 2.541l.159.292a.873.873 0 0 1-.52 1.255l-.319.094c-1.79.527-1.79 3.065 0 3.592l.319.094a.873.873 0 0 1 .52 1.255l-.16.292c-.892 1.64.901 3.434 2.541 2.54l.292-.159a.873.873 0 0 1 1.255.52l.094.319c.527 1.79 3.065 1.79 3.592 0l.094-.319a.873.873 0 0 1 1.255-.52l.292.16c1.64.893 3.434-.902 2.54-2.541l-.159-.292a.873.873 0 0 1 .52-1.255l.319-.094c1.79-.527 1.79-3.065 0-3.592l-.319-.094a.873.873 0 0 1-.52-1.255l.16-.292c.893-1.64-.902-3.433-2.541-2.54l-.292.159a.873.873 0 0 1-1.255-.52zm-2.633.283c.246-.835 1.428-.835 1.674 0l.094.319a1.873 1.873 0 0 0 2.693 1.115l.291-.16c.764-.415 1.6.42 1.184 1.185l-.159.292a1.873 1.873 0 0 0 1.116 2.692l.318.094c.835.246.835 1.428 0 1.674l-.319.094a1.873 1.873 0 0 0-1.115 2.693l.16.291c.415.764-.42 1.6-1.185 1.184l-.291-.159a1.873 1.873 0 0 0-2.693 1.116l-.094.318c-.246.835-1.428.835-1.674 0l-.094-.319a1.873 1.873 0 0 0-2.692-1.115l-.292.16c-.764.415-1.6-.42-1.184-1.185l.159-.291A1.873 1.873 0 0 0 1.945 8.93l-.319-.094c-.835-.246-.835-1.428 0-1.674l.319-.094A1.873 1.873 0 0 0 3.06 4.377l-.16-.292c-.415-.764.42-1.6 1.185-1.184l.292.159a1.873 1.873 0 0 0 2.692-1.115z" />
</svg>
<span>Paramètres mails</span>
</a>

<div class="dropdown-divider"></div>

<a href="logout" class="dropdown-item">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-door-closed dropdown-icon" viewBox="0 0 16 16">
<path d="M3 2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v13h1.5a.5.5 0 0 1 0 1h-13a.5.5 0 0 1 0-1H3zm1 13h8V2H4z" />
<path d="M9 9a1 1 0 1 0 2 0 1 1 0 0 0-2 0" />
</svg>
<span>Déconnexion</span>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

<div class="sidebar-overlay" id="sidebarOverlay" onclick="closeSidebar()"></div>

<!-- Container principal avec sidebar et contenu -->
<div class="main-container">
<!-- Sidebar avec le menu -->
<div class="sidebar" id="sidebar">
<NavMenu />
</div>

<div class="content-wrapper">
<main class="main-content">
@Body
</main>
<footer class="footer">
<small>
<p>&copy; 2025 Make with love - Tous droits réservés - Version 1.0.0</p>
</small>
</footer>
<div class="content-wrapper">
<main class="main-content">
@Body
</main>
<footer class="footer">
<small>
<p>&copy; 2025 Make with love - Tous droits réservés - Version 1.0.0</p>
</small>
</footer>
</div>
</div>
</div>
</div>
@@ -99,16 +133,14 @@
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
const burger = document.querySelector('.burger-menu');

sidebar.classList.remove('active');
overlay.classList.remove('active');
burger.classList.remove('active');
}

function toggleUserMenu(event) {
event.stopPropagation();
const dropdown = document.getElementById('userMenuDropdown');
dropdown.classList.toggle('show');
if (dropdown) {
dropdown.classList.toggle('show');
}
}

document.addEventListener('click', function(event) {
@@ -129,4 +161,4 @@
authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
user = authState.User;
}
}
}

+ 79
- 172
WebUI/ReAct_PME.WebUI/Layout/MainLayout.razor.css Vedi File

@@ -1,46 +1,65 @@
/* Page principale */
.page {
display: flex;
flex-direction: column;
height: 100vh;
width: 100%;
width: 100vw;
overflow: hidden;
}

main {


.main-container {
display: flex;
flex: 1;
overflow: hidden;
}

/* Conteneur de layout principal */
.layout-container {
display: flex;
flex-direction: column;
height: 100%; /* Prend toute la hauteur de la fenêtre (viewport height) */
width: 100%; /* Prend toute la largeur de la fenêtre (viewport width) */
overflow: hidden; /* Empêche les barres de défilement, utile pour un layout sans débordement */

.sidebar {
width: 280px;
background: linear-gradient(180deg, var(--secondary-color), var(--main-color));
height: 100vh; /* Prend toute la hauteur de l'écran */
overflow-y: auto;
overflow-x: hidden;
flex-shrink: 0;
transition: transform 0.3s ease;
}

.sidebar::-webkit-scrollbar {
width: 6px;
}

/* Conteneur principal avec sidebar et contenu */
.main-container {
display: flex;
.sidebar::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
}

.sidebar::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 3px;
}

.sidebar::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.5);
}



.content-area {
flex: 1;
margin-top: 60px; /* Hauteur de la top-bar */
height: calc(100vh - 60px);
display: flex;
flex-direction: column;
overflow: hidden;
}


/* Barre supérieure fixe */
.top-bar {
height: 60px;
background-color: #e9ecef;
border-bottom: 1px solid #dee2e6;
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
z-index: 100;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
flex-shrink: 0;
}

.top-bar-content {
@@ -92,25 +111,6 @@ main {
transform: rotate(-45deg) translate(7px, -7px);
}

/* Overlay pour fermer le menu */
.sidebar-overlay {
display: none;
position: fixed;
top: 60px;
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;
}

/* Logo */
.logo-link {
display: flex;
@@ -122,10 +122,6 @@ main {
opacity: 0.8;
}

.logo-link img {
vertical-align: middle;
}

/* Titre de l'application */
.app-title {
font-size: 1.5rem;
@@ -167,11 +163,6 @@ main {
transform: scale(1.05);
}

.user-menu-button img {
border-radius: 50%;
display: block;
}

/* Menu déroulant utilisateur */
.user-menu-dropdown {
position: absolute;
@@ -237,52 +228,36 @@ main {
}

/* Bouton de déconnexion */
.logout-btn {
color: #dc3545;
font-weight: 500;
}

.logout-btn:hover {
background-color: #fff5f5;
}



.sidebar {
width: 280px;
background: linear-gradient(180deg, var(--secondary-color) , var(--main-color) );
overflow-y: auto;
flex-shrink: 0;
transition: transform 0.3s ease;
}



/* Conteneur de droite qui héberge le contenu principal et le footer */
.content-area {
flex-grow: 1;
.content-wrapper {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
}

.content-wrapper {
.main-content {
flex: 1;
overflow: auto;
width: 100%;
flex-direction: column;
display:flex;

overflow-y: auto;
background-color: #f8f9fa;
}

/* Contenu principal : scrollable si trop long */
.main-content {
flex-grow: 1;
overflow: auto;
background-color: #f8f9fa;
.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;
}



@@ -291,6 +266,7 @@ main {
background-color: #e9ecef;
border-top: 1px solid #dee2e6;
text-align: center;
flex-shrink: 0;
}

.footer p {
@@ -300,43 +276,24 @@ main {



/* Bandeau supérieur (ancien style) */
.top-banner {
padding: 0.5rem 1rem;
background-color: #e9ecef;
border-bottom: 1px solid #ccc;
display: flex;
justify-content: flex-end;
align-items: center;
}

/* Ancienne barre supérieure */
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
.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;
}

.top-row ::deep a,
.top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
.sidebar-overlay.active {
display: block;
opacity: 1;
}

.top-row ::deep a:hover,
.top-row ::deep .btn-link:hover {
text-decoration: underline;
}

.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}



@media (max-width: 768px) {
@@ -352,76 +309,26 @@ main {
}
}

/* Sidebar en position fixe cachée par défaut */
.sidebar {
position: fixed;
top: 60px;
top: 0;
left: 0;
bottom: 0;
width: 240px;
width: 280px;
z-index: 999;
transform: translateX(-100%);
}

/* Sidebar visible quand active */
.sidebar.active {
transform: translateX(0);
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3);
}

/* Le contenu prend toute la largeur sur mobile */
.content-wrapper {
width: 100%;
.main-content {
padding: 1rem;
}

.top-bar-content {
padding: 0 1rem;
}

.top-row {
justify-content: space-between;
}

.top-row ::deep a,
.top-row ::deep .btn-link {
margin-left: 0;
}
}

/* Desktop */
@media (min-width: 769px) {
/* Cacher le bouton burger sur desktop */
.burger-menu {
display: none !important;
}

.page {
flex-direction: row;
}

.sidebar {
width: 240px;
height: 100vh;
position: sticky;
top: 0;
}

.top-row {
position: sticky;
top: 0;
z-index: 1;
}

.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}

.top-row,
article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

+ 289
- 49
WebUI/ReAct_PME.WebUI/Layout/NavMenu.razor Vedi File

@@ -1,69 +1,309 @@
@inject AuthenticationStateProvider AuthenticationStateProvider
@using Microsoft.AspNetCore.Components.Authorization;
@using ReAct_PME.WebUI.ServicesUI;
@using Microsoft.AspNetCore.Components.Authorization
@using ReAct_PME.Domain
@using ReAct_PME.WebUI.ServicesUI
@inject HttpClient Http
@inject AuthService AuthService
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject NavigationManager Navigation

<div>
<div class="sidebar @(IsCollapsed ? "collapsed" : "")">
<div class="sidebar-header">
@if (!IsCollapsed)
{
<div class="logo">
<h1>AIT</h1>
</div>
}
<button class="toggle-button" @onclick="ToggleSidebar"
aria-label="@(IsCollapsed ? "Ouvrir la barre latérale" : "Réduire la barre latérale")">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
class="bi bi-layout-sidebar" viewBox="0 0 16 16">
<path d="M0 3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm5-1v12h9a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1zM4 2H2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h2z" />
</svg>
</button>
</div>

<nav class="menu-section access-section">
<div class="sidebar-section">
@if (!IsCollapsed)
{
<h3 class="section-title"> Conversations</h3>

<div class="nav-item">
<NavLink class="nav-link2" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="conversations-section">
<button class="new-chat-button" @onclick="OnNewChat" aria-label="Nouvelle conversation">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-dots" viewBox="0 0 16 16">
<path d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0m4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0m3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2" />
<path d="m2.165 15.803.02-.004c1.83-.363 2.948-.842 3.468-1.105A9 9 0 0 0 8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6a10.4 10.4 0 0 1-.524 2.318l-.003.011a11 11 0 0 1-.244.637c-.079.186.074.394.273.362a22 22 0 0 0 .693-.125m.8-3.108a1 1 0 0 0-.287-.801C1.618 10.83 1 9.468 1 8c0-3.192 3.004-6 7-6s7 2.808 7 6-3.004 6-7 6a8 8 0 0 1-2.088-.272 1 1 0 0 0-.711.074c-.387.196-1.24.57-2.634.893a11 11 0 0 0 .398-2" />
</svg>

</span>
<span>Nouvelle conversation</span>
</button>

<div class="nav-item">
<details>
<summary>💬 ChatRoom</summary>
<div class="nav-item">
<NavLink class="nav-link2" href="chatroom_nav/1">Chat</NavLink>
<NavLink class="nav-link2" href="chatroom_nav/2">RAG</NavLink>
</div>
</details>
</div>
@if (IsLoadingChat)
{
<div class="loading">Chargement...</div>
}
else if (ChatConversations.Any())
{
<div class="conversations-list">
@foreach (var conversation in ChatConversations)
{
<div class="conversation-item @(CurrentConversationId == conversation.Id ? "active" : "")"
@onclick="() => OnSelectConversation(conversation.Id, 1)">
<span class="conversation-title">
@if (!string.IsNullOrEmpty(conversation.Title))
{
@conversation.Title
}
else
{
@($"Conversation du {conversation.Date:dd/MM/yyyy}")
}
</span>
</div>
}
</div>
}
else
{
<div class="empty-state">Aucune conversation</div>
}
</div>
}
else
{
<button class="new-chat-button" @onclick="OnNewChat" aria-label="Nouvelle conversation">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-dots" viewBox="0 0 16 16">
<path d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0m4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0m3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2" />
<path d="m2.165 15.803.02-.004c1.83-.363 2.948-.842 3.468-1.105A9 9 0 0 0 8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6a10.4 10.4 0 0 1-.524 2.318l-.003.011a11 11 0 0 1-.244.637c-.079.186.074.394.273.362a22 22 0 0 0 .693-.125m.8-3.108a1 1 0 0 0-.287-.801C1.618 10.83 1 9.468 1 8c0-3.192 3.004-6 7-6s7 2.808 7 6-3.004 6-7 6a8 8 0 0 1-2.088-.272 1 1 0 0 0-.711.074c-.387.196-1.24.57-2.634.893a11 11 0 0 0 .398-2" />
</svg>

<div class="nav-item">
<details>
<summary>📧 Emails</summary>
<div class="nav-item">
<NavLink class="nav-link2" href="listemails_card">Non lus</NavLink>
<NavLink class="nav-link2" href="listemailssend_card">A suivre</NavLink>
<NavLink class="nav-link2" href="editmail_redaction">Rédaction d'un mail</NavLink>
</div>
</details>
</div>
</span>
</button>
}
</div>

<div class="nav-item">
<details>
<summary>⚙️ Paramètres</summary>
<div class="nav-item">
<NavLink class="nav-link2" href="parametres_mail">Mails</NavLink>
</div>
</details>
</div>
<div class="sidebar-section">
@if (!IsCollapsed)
{
<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>

</nav>
<div class="conversations-section">
<button class="new-chat-button" @onclick="OnNewRagChat" aria-label="Nouveau RAG">
<span class="icon">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chat-dots" viewBox="0 0 16 16">
<path d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0m4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0m3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2" />
<path d="m2.165 15.803.02-.004c1.83-.363 2.948-.842 3.468-1.105A9 9 0 0 0 8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6a10.4 10.4 0 0 1-.524 2.318l-.003.011a11 11 0 0 1-.244.637c-.079.186.074.394.273.362a22 22 0 0 0 .693-.125m.8-3.108a1 1 0 0 0-.287-.801C1.618 10.83 1 9.468 1 8c0-3.192 3.004-6 7-6s7 2.808 7 6-3.004 6-7 6a8 8 0 0 1-2.088-.272 1 1 0 0 0-.711.074c-.387.196-1.24.57-2.634.893a11 11 0 0 0 .398-2" />
</svg>
</span>
<span>Nouveau RAG</span>
</button>

@if (IsLoadingRag)
{
<div class="loading">Chargement...</div>
}
else if (RagConversations.Any())
{
<div class="conversations-list">
@foreach (var conversation in RagConversations)
{
<div class="conversation-item @(CurrentConversationId == conversation.Id ? "active" : "")"
@onclick="() => OnSelectConversation(conversation.Id, 2)">
<span class="conversation-title">
@if (!string.IsNullOrEmpty(conversation.Title))
{
@conversation.Title
}
else
{
@($"RAG du {conversation.Date:dd/MM/yyyy}")
}
</span>
</div>
}
</div>
}
else
{
<div class="empty-state">Aucune conversation RAG</div>
}
</div>
}
else
{
<button class="sidebar-icon-button" aria-label="RAG">
<span class="icon">
<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>

</span>
</button>
}
</div>

@if (IsCollapsed)
{
<div class="sidebar-footer sidebar-footer-collapsed">
<button class="footer-button" aria-label="Aide">
<span class="icon">❓</span>
</button>
<button class="footer-button" @onclick='() => NavigateTo("parametres_mail")' aria-label="Paramètres">
<span class="icon">⚙️</span>
</button>
</div>
}
else
{
<div class="sidebar-footer">
<button class="footer-button" aria-label="Aide">
<span class="icon">❓</span>
<span>Aide</span>
</button>
<button class="footer-button" @onclick='() => NavigateTo("parametres_mail")' aria-label="Paramètres">
<span class="icon">⚙️</span>
<span>Paramètres</span>
</button>
</div>
}
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>

@code {
private bool collapseNavMenu = false;
private bool IsCollapsed = false;
private string CurrentConversationId = "";

private List<ConversationDto> ChatConversations { get; set; } = new();
private List<ConversationDto> RagConversations { get; set; } = new();

private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private bool IsLoadingChat = false;
private bool IsLoadingRag = false;

private void ToggleNavMenu()
protected override async Task OnInitializedAsync()
{
collapseNavMenu = !collapseNavMenu;


await LoadChatConversations();

await LoadRagConversations();

}

private AuthenticationState? authState;
private System.Security.Claims.ClaimsPrincipal? user;
private void ToggleSidebar()
{
IsCollapsed = !IsCollapsed;
}

protected override async Task OnInitializedAsync()
private async Task LoadChatConversations()
{

if (string.IsNullOrEmpty(AuthService.ID))
{
return;
}

IsLoadingChat = true;
StateHasChanged();

try
{
var url = $"/api/ChatRoom/conversations/{AuthService.ID}/1";

var allConversations = await Http.GetFromJsonAsync<List<ConversationDto>>(url) ?? new();


// Filtrer uniquement celles avec TypeConv = "LLM"
ChatConversations = allConversations
.Where(c => c.TypeConv == "LLM")
.OrderByDescending(c => c.Messages.Any()
? c.Messages.Max(m => m.CreatedAt)
: c.Date)
.ToList();

}
catch (Exception ex)
{
ChatConversations = new();
}
finally
{
IsLoadingChat = false;
StateHasChanged();
}
}

private async Task LoadRagConversations()
{

if (string.IsNullOrEmpty(AuthService.ID))
{
return;
}

IsLoadingRag = true;
StateHasChanged();

try
{
var url = $"/api/ChatRoom/conversations/{AuthService.ID}/2";

var allConversations = await Http.GetFromJsonAsync<List<ConversationDto>>(url) ?? new();


// Filtrer uniquement celles avec TypeConv = "RAG"
RagConversations = allConversations
.Where(c => c.TypeConv == "RAG")
.OrderByDescending(c => c.Messages.Any()
? c.Messages.Max(m => m.CreatedAt)
: c.Date)
.ToList();

}
catch (Exception ex)
{
RagConversations = new();
}
finally
{
IsLoadingRag = false;
StateHasChanged();
}
}

private void OnNewChat()
{
Navigation.NavigateTo("");
}

private void OnNewRagChat()
{
Navigation.NavigateTo("chatroom_nav/2");
}

private void OnSelectConversation(string conversationId, int typeLLM)
{
CurrentConversationId = conversationId;
Navigation.NavigateTo($"chatroom_nav/{typeLLM}?conversationId={conversationId}");
}

private void NavigateTo(string route)
{
Navigation.NavigateTo(route);
}

public async Task RefreshChatConversations()
{
await LoadChatConversations();
}

public async Task RefreshRagConversations()
{
authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
user = authState.User;
await LoadRagConversations();
}
}

+ 0
- 130
WebUI/ReAct_PME.WebUI/Layout/NavMenu.razor.css Vedi File

@@ -1,130 +0,0 @@
/* NavMenu.razor.css */
.nav-menu {
padding: 1rem;
height: 100%;
}

/* Éléments de navigation */
.nav-item {
margin-bottom: 0.5rem;
}

.nav-link2 {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
color: white !important;
text-decoration: none;
border-radius: 8px;
transition: all 0.2s ease;
font-weight: 500;
}

.nav-link2:hover {
background-color: rgba(255, 255, 255, 0.1);
color: white !important;
}

.nav-link2.active {
background-color: rgba(255, 255, 255, 0.2);
color: white !important;
font-weight: 600;
}

.nav-icon {
margin-right: 0.75rem;
font-size: 1.1rem;
}

/* Sections avec détails */
.nav-section {
margin-bottom: 0.5rem;
}

.nav-section details {
border-radius: 8px;
}

.nav-section details[open] {
background-color: rgba(0, 0, 0, 0.1);
}

.nav-section-header {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
color: white;
cursor: pointer;
user-select: none;
border-radius: 8px;
transition: all 0.2s ease;
font-weight: 500;
}

.nav-section-header::after {
content: '▶';
position: absolute;
right: 1rem;
font-size: 0.7rem;
transition: transform 0.2s ease;
opacity: 0.7;
}

details[open] .nav-section-header::after {
transform: rotate(90deg);
}

.nav-section-header:hover {
background-color: rgba(255, 255, 255, 0.1);
color: white;
}

.nav-section-content {
padding: 0.5rem 0;
}

/* Sous-liens - couleur ivoire */
.sub-link {
padding-left: 3rem;
font-size: 0.9rem;
color: ivory !important;
font-weight: 400;
}

.sub-link:hover {
background-color: rgba(255, 255, 255, 0.08);
color: white !important;
}

.sub-link.active {
background-color: rgba(255, 255, 255, 0.15);
color: white !important;
}

/* Remove default details marker */
details summary::-webkit-details-marker {
display: none;
}

details summary::marker {
display: none;
}

/* Animation pour l'ouverture/fermeture */
details[open] .nav-section-content {
animation: slideDown 0.2s ease-out;
}

@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}

to {
opacity: 1;
transform: translateY(0);
}
}


+ 446
- 123
WebUI/ReAct_PME.WebUI/Pages/ChatRoom/ChatRoom_base.razor.css Vedi File

@@ -1,213 +1,536 @@
.chatroom-container {
/* ========================================
CHATROOM - AI DASHBOARD
======================================== */

.chatroom-container {
display: flex;
flex-direction: column;
height: 100%;
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);
}

.top-bar {
.chatroom-header-left {
display: flex;
gap: 20px;
padding: 10px;
align-items: center;
gap: 1rem;
}

.main-grid {
display: grid;
grid-template-columns: 200px 1fr;
height: calc(100% - 60px);
.chatroom-title {
font-size: 1.5rem;
font-weight: 600;
color: var(--ai-text-dark, #243b5d);
margin: 0;
}

.left-panel {
.chatroom-header-right {
display: flex;
flex-direction: column;
padding: 10px;
align-items: center;
gap: 1rem;
}

.conv-list {
height: 350px;
overflow-y: auto;
border: 1px solid #ccc;
.domain-selector {
display: flex;
align-items: center;
gap: 0.5rem;
}

.conv-item {
padding: 5px;
.domain-selector label {
font-size: 0.9rem;
color: var(--ai-text-light, #6c757d);
margin: 0;
}

.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;
}

.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;
}

.conv-item.selected {
background-color: #cce5ff;
.btn-icon:hover {
background: var(--ai-bg, #eaf8ff);
}

.doc-list {
flex: 1;
overflow-y: auto;
border: 1px solid #ccc;
margin-top: 5px;
.btn-back:hover {
background: var(--ai-primary, #243b5d);
color: white;
}

.chat-panel {
display: flex;
flex-direction: column;
padding: 10px;
}
/* ========================================
MESSAGES AREA
======================================== */

.chat-history {
.chatroom-messages {
flex: 1;
overflow-y: auto;
padding: 2rem;
display: flex;
flex-direction: column;
gap: 1.5rem;
}

.chat-row {
.chatroom-empty {
display: flex;
padding: 5px;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--ai-text-light, #6c757d);
text-align: center;
}

.chat-row.user {
justify-content: flex-end;
}

.chat-row.assistant {
justify-content: flex-start;
}

.chat-bubble {
max-width: 700px;
padding: 10px;
border-radius: 10px;
margin: 5px;
white-space: pre-wrap;
.empty-icon {
font-size: 4rem;
color: var(--ai-secondary, #2984a1);
margin-bottom: 1rem;
opacity: 0.5;
}

.chat-bubble.user {
background: #0084ff;
color: white;
}

.chat-bubble.assistant {
background: #e5e5e5;
color: black;
}

.msg-input {
width: 100%;
height: 70px;
.chatroom-empty h3 {
font-size: 1.5rem;
color: var(--ai-text-dark, #243b5d);
margin-bottom: 0.5rem;
}

.toast {
position: fixed;
bottom: 20px;
right: 20px;
background: #323232;
color: white;
padding: 16px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,.4);
.chatroom-empty p {
font-size: 1rem;
margin: 0;
}

.messages {
flex: 1;
overflow-y: auto;
padding: 20px;
background: #f5f7fb;
}

/* Ligne message */
/* Messages */
.message-row {
display: flex;
align-items: flex-start;
margin-bottom: 18px;
gap: 10px;
gap: 0.75rem;
animation: fadeIn 0.3s ease;
}

/* Alignement User / Assistant */
.message-row.user {
justify-content: flex-end;
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}

.message-row.assistant {
justify-content: flex-start;
to {
opacity: 1;
transform: translateY(0);
}
}

.message-row.user {
flex-direction: row-reverse;
justify-content: flex-start;
}

.message-row.assistant {
flex-direction: row;
justify-content: flex-start;
}

/* Avatars */
.avatar {
width: 38px;
height: 38px;
font-size: 20px;
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
justify-content: center;
font-size: 1.2rem;
flex-shrink: 0;
}

.avatar-user {
background-color: #007bff;
background: linear-gradient(135deg, var(--ai-secondary, #2984a1) 0%, var(--ai-primary, #243b5d) 100%);
color: white;
}

.avatar-assistant {
background-color: #6c757d;
background: linear-gradient(135deg, var(--ai-accent, #f63e63) 0%, #e02851 100%);
color: white;
}

/* Bulles de messages */
/* Message content */
.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 {
max-width: 60%;
padding: 12px 18px;
border-radius: 18px;
padding: 1rem 1.25rem;
border-radius: 16px;
font-size: 1rem;
line-height: 1.6;
word-wrap: break-word;
white-space: pre-wrap;
box-shadow: 0 3px 8px rgba(0,0,0,0.15);
font-size: 15px;
}

.bubble-user {
background-color: #007bff;
background: linear-gradient(135deg, var(--ai-secondary, #2984a1) 0%, var(--ai-primary, #243b5d) 100%);
color: white;
border-bottom-right-radius: 4px;
}

.bubble-assistant {
background-color: white;
color: #333;
background: white;
color: var(--ai-text-dark, #243b5d);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border-bottom-left-radius: 4px;
}

/* Zone de saisie */
.chat-input {
padding: 15px;
border-top: 1px solid #ddd;
/* Message actions */
.message-actions {
display: flex;
gap: 0.5rem;
}

.btn-message-action {
width: 32px;
height: 32px;
border-radius: 6px;
border: none;
background: transparent;
color: var(--ai-text-light, #6c757d);
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);
}

/* Typing indicator */
.typing-indicator {
display: flex;
gap: 0.4rem;
padding: 0.5rem 0;
}

.typing-indicator span {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: var(--ai-secondary, #2984a1);
animation: typing 1.4s infinite;
}

.typing-indicator span:nth-child(2) {
animation-delay: 0.2s;
}

.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;
}
}

/* ========================================
UPLOADED DOCUMENTS PREVIEW
======================================== */

.uploaded-docs-preview {
display: flex;
gap: 0.75rem;
padding: 0.75rem 2rem;
background: white;
border-top: 1px solid var(--ai-border, #e0e0e0);
overflow-x: auto;
}

textarea {
.doc-preview-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: var(--ai-bg, #eaf8ff);
border-radius: 8px;
font-size: 0.9rem;
white-space: nowrap;
}

.doc-preview-item i {
color: var(--ai-secondary, #2984a1);
font-size: 1rem;
}

.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;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.9rem;
transition: all 0.2s ease;
padding: 0;
}

.btn-remove-doc:hover {
background: var(--ai-accent, #f63e63);
color: white;
}

/* ========================================
INPUT AREA
======================================== */

.chatroom-input-container {
padding: 1.5rem 2rem;
background: white;
border-top: 1px solid var(--ai-border, #e0e0e0);
}

.chatroom-input-wrapper {
max-width: 1200px;
margin: 0 auto;
background: var(--ai-bg, #eaf8ff);
border-radius: 16px;
padding: 1rem 1.25rem;
}

.chatroom-input {
width: 100%;
height: 80px;
border-radius: 12px;
padding: 12px;
border: 1px solid #ccc;
resize: none;
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;
}

button {
margin-top: 10px;
}
.document-upload {
margin-top: 10px;
.chatroom-input::placeholder {
color: #a0a0a0;
}

.chatroom-input:disabled {
opacity: 0.6;
cursor: not-allowed;
}

.chatroom-input-actions {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}

.doc-list {
margin-top: 10px;
padding: 10px;
background: #f1f1f1;
.btn-chat-action {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border-radius: 8px;
border: none;
background: transparent;
color: var(--ai-text-light, #6c757d);
cursor: pointer;
font-size: 0.9rem;
transition: all 0.2s ease;
font-family: 'Montserrat', sans-serif;
}

.doc-item {
.btn-chat-action:hover:not(:disabled) {
background: white;
color: var(--ai-primary, #243b5d);
}

.btn-chat-action:disabled {
opacity: 0.5;
cursor: not-allowed;
}

.btn-chat-label {
display: none;
}

@media (min-width: 768px) {
.btn-chat-label {
display: inline;
}
}

.btn-chat-submit {
width: 40px;
height: 40px;
border-radius: 50%;
border: none;
background: var(--ai-accent, #f63e63);
color: white;
cursor: pointer;
display: flex;
justify-content: space-between;
padding: 4px 0;
align-items: center;
justify-content: center;
font-size: 1.2rem;
transition: all 0.3s ease;
flex-shrink: 0;
}

.btn-chat-submit:hover:not(:disabled) {
background: #e02851;
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;
}

/* ========================================
SCROLLBAR
======================================== */

.chatroom-messages::-webkit-scrollbar {
width: 8px;
}

.chatroom-messages::-webkit-scrollbar-track {
background: transparent;
}

.chatroom-messages::-webkit-scrollbar-thumb {
background: var(--ai-secondary, #2984a1);
border-radius: 4px;
}

.chatroom-messages::-webkit-scrollbar-thumb:hover {
background: var(--ai-primary, #243b5d);
}

.uploaded-docs-preview::-webkit-scrollbar {
height: 6px;
}

.uploaded-docs-preview::-webkit-scrollbar-track {
background: transparent;
}

.uploaded-docs-preview::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
}

.doc-item button {
margin-left: 10px;
/* ========================================
RESPONSIVE
======================================== */

@media (max-width: 768px) {
.chatroom-header {
padding: 1rem;
}

.chatroom-title {
font-size: 1.25rem;
}

.chatroom-messages {
padding: 1rem;
}

.message-content-wrapper {
max-width: 85%;
}

.chatroom-input-container {
padding: 1rem;
}

.chatroom-input-wrapper {
padding: 0.75rem 1rem;
}

.domain-selector {
flex-direction: column;
align-items: flex-start;
gap: 0.25rem;
}
}

@media (max-width: 480px) {
.bubble {
padding: 0.875rem 1rem;
font-size: 0.95rem;
}

.avatar {
width: 36px;
height: 36px;
font-size: 1.1rem;
}

.chatroom-header-right {
gap: 0.5rem;
}

.btn-icon {
width: 36px;
height: 36px;
font-size: 1rem;
}
}

+ 182
- 11
WebUI/ReAct_PME.WebUI/Pages/Home.razor Vedi File

@@ -1,16 +1,187 @@
@page "/"
@using System.Text
@using ReAct_PME.WebUI.ServicesUI

<div class="page-container">
<!-- En-tête avec dégradé -->
<div class="home-header">
<p class="home-welcome">
Bienvenue, <strong>@AuthService.FirstName @AuthService.LastName</strong>
sur votre application AI-100-SaaS
</p>
@using ReAct_PME.Domain

<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>

<!-- Zone de saisie -->
<div class="input-container">
@if (UploadedDocuments.Any())
{
<div class="input-files">
@foreach (var doc in UploadedDocuments)
{
<div class="file-item">
<div class="file-info">
<span class="file-name">@doc.FileName</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="input-wrapper @(UploadedDocuments.Any() ? "has-files" : "")">
<textarea @bind="message"
@bind:event="oninput"
placeholder="Nouvelle conversation..."
rows="1"
disabled="@isDisabled"></textarea>

<div class="input-actions">
<button class="attach-button" @onclick="OpenFileDialog" aria-label="Importer un fichier">
<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>
</button>

<div class="input-right-actions">
<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="SendMessage"
disabled="@((string.IsNullOrWhiteSpace(message) && !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>

<InputFile id="fileInput" OnChange="HandleFileSelection" multiple style="display: none;" />
</div>

<!-- Cartes des agents -->
<div class="agents-grid">
<div class="agent-card" @onclick="() => 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>

<div class="agent-card" @onclick="() => 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 class="agent-content">
<h3 class="agent-name">Agent Audit</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>

<div class="agent-card" @onclick="() => 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">L'agent Audit</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>

<div class="agent-card" @onclick="() => 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">L'agent mail spécialisé</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>
</div>
</div>
</div>

@code {
private string message = "";
private bool isDisabled = false;
private List<UploadedDoc> UploadedDocuments = new();

private void OpenFileDialog()
{
// JavaScript interop pour ouvrir le sélecteur de fichiers
// await JSRuntime.InvokeVoidAsync("document.getElementById('fileInput').click()");
ToastService.ShowInfo("Sélection de fichiers");
}

private async Task HandleFileSelection(InputFileChangeEventArgs e)
{
foreach (var file in e.GetMultipleFiles())
{
using var stream = file.OpenReadStream(maxAllowedSize: 20 * 1024 * 1024);
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);

UploadedDocuments.Add(new UploadedDoc
{
FileName = file.Name,
Bytes = ms.ToArray()
});
}
StateHasChanged();
}

private void RemoveDocument(UploadedDoc doc)
{
UploadedDocuments.Remove(doc);
StateHasChanged();
}

private void SendMessage()
{
if (!string.IsNullOrWhiteSpace(message) || UploadedDocuments.Any())
{
ToastService.ShowInfo("Création d'une conversation");
// Navigation.NavigateTo($"/chatroom?message={Uri.EscapeDataString(message)}");
}
}

private void SelectAgent(int agentId)
{
ToastService.ShowInfo($"Agent {agentId} sélectionné");
Navigation.NavigateTo($"/chatroom_nav/{agentId}");
}

private void RecordVoice()
{
ToastService.ShowInfo("Fonctionnalité d'enregistrement vocal en cours de développement");
}
}

+ 1388
- 1
WebUI/ReAct_PME.WebUI/wwwroot/css/app.css
File diff soppresso perché troppo grande
Vedi File


Loading…
Annulla
Salva