Bläddra i källkod

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 vecka sedan
förälder
incheckning
5826eccc6a
7 ändrade filer med 2488 tillägg och 558 borttagningar
  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 Visa fil

@@ -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 Visa fil

@@ -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 Visa fil

@@ -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 Visa fil

@@ -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 Visa fil

@@ -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 Visa fil

@@ -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
Filskillnaden har hållits tillbaka eftersom den är för stor
Visa fil


Laddar…
Avbryt
Spara