Bläddra i källkod

chatRoom et home fusionner

WebUI
maela 5 dagar sedan
förälder
incheckning
68436a4a88
10 ändrade filer med 1077 tillägg och 638 borttagningar
  1. +1
    -22
      WebUI/ReAct_PME.WebUI/Layout/MainLayout.razor
  2. +38
    -131
      WebUI/ReAct_PME.WebUI/Layout/MainLayout.razor.css
  3. +64
    -6
      WebUI/ReAct_PME.WebUI/Layout/NavMenu.razor
  4. +4
    -0
      WebUI/ReAct_PME.WebUI/Layout/NavMenu.razor.css
  5. +1
    -1
      WebUI/ReAct_PME.WebUI/Pages/AuthLogin/AuthLogin.razor.cs
  6. +325
    -128
      WebUI/ReAct_PME.WebUI/Pages/ChatRoom/ChatRoom_base.razor
  7. +143
    -82
      WebUI/ReAct_PME.WebUI/Pages/ChatRoom/ChatRoom_base.razor.cs
  8. +501
    -244
      WebUI/ReAct_PME.WebUI/Pages/ChatRoom/ChatRoom_base.razor.css
  9. +0
    -24
      WebUI/ReAct_PME.WebUI/wwwroot/css/app.css
  10. Binär
      WebUI/ReAct_PME.WebUI/wwwroot/images/textAIT.png

+ 1
- 22
WebUI/ReAct_PME.WebUI/Layout/MainLayout.razor Visa fil

@@ -22,18 +22,13 @@
<div class="top-bar">
<div class="top-bar-content">
<div class="top-bar-left">
<button class="burger-menu" onclick="toggleSidebar()" aria-label="Toggle menu">
<span></span>
<span></span>
<span></span>
</button>
<!--
<a href="https://www.allin-it.fr/" target="_blank" class="logo-link">
<img src="images/logo.png" alt="Logo All in IT" width="50" height="50" />
</a>
<a class="app-title" href="">AI-100-SaaS</a>
-->
</div>
</div>

<div class="top-bar-right">
<div class="user-menu">
@@ -125,22 +120,6 @@
</div>

<script>
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
const burger = document.querySelector('.burger-menu');

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

function closeSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
const burger = document.querySelector('.burger-menu');
}

function toggleUserMenu(event) {
event.stopPropagation();
const dropdown = document.getElementById('userMenuDropdown');

+ 38
- 131
WebUI/ReAct_PME.WebUI/Layout/MainLayout.razor.css Visa fil

@@ -1,4 +1,3 @@
/* Page principale */
.page {
display: flex;
height: 100vh;
@@ -8,23 +7,20 @@
background-size: cover;
}



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


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

.sidebar::-webkit-scrollbar {
@@ -45,7 +41,6 @@
}



.content-area {
flex: 1;
display: flex;
@@ -53,6 +48,34 @@
overflow: hidden;
}

.content-wrapper {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}

.main-content {
flex: 1;
overflow-y: auto;
}

.main-content::-webkit-scrollbar {
width: 8px;
}

.main-content::-webkit-scrollbar-track {
background: transparent;
}

.main-content::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}

.main-content::-webkit-scrollbar-thumb:hover {
background: #555;
}


.top-bar {
@@ -72,48 +95,12 @@
align-items: center;
}

/* Partie gauche avec logo + titre */
.top-bar-left {
display: flex;
align-items: center;
gap: 1rem;
}

/* Menu Burger */
.burger-menu {
display: none;
flex-direction: column;
justify-content: space-around;
width: 30px;
height: 25px;
background: transparent;
border: none;
cursor: pointer;
padding: 0;
z-index: 1001;
}

.burger-menu span {
width: 100%;
height: 3px;
background-color: #212529;
border-radius: 10px;
transition: all 0.3s ease;
}

.burger-menu.active span:nth-child(1) {
transform: rotate(45deg) translate(8px, 8px);
}

.burger-menu.active span:nth-child(2) {
opacity: 0;
}

.burger-menu.active span:nth-child(3) {
transform: rotate(-45deg) translate(7px, -7px);
}

/* Logo */
.logo-link {
display: flex;
align-items: center;
@@ -124,7 +111,6 @@
opacity: 0.8;
}

/* Titre de l'application */
.app-title {
font-size: 1.5rem;
font-weight: 600;
@@ -137,7 +123,7 @@
color: #0056b3;
}

/* Partie droite - Menu utilisateur */
.top-bar-right {
display: flex;
align-items: center;
@@ -147,7 +133,6 @@
position: relative;
}

/* Bouton du menu utilisateur */
.user-menu-button {
background: none;
border: none;
@@ -165,7 +150,6 @@
transform: scale(1.05);
}

/* Menu déroulant utilisateur */
.user-menu-dropdown {
position: absolute;
top: calc(100% + 10px);
@@ -188,7 +172,6 @@
transform: translateY(0);
}

/* Items du menu déroulant */
.dropdown-item {
display: flex;
align-items: center;
@@ -216,51 +199,17 @@
border-radius: 0 0 8px 8px;
}

/* Icône dans le menu déroulant */
.dropdown-icon {
margin-right: 0.75rem;
font-size: 1.1rem;
}

/* Séparateur dans le menu déroulant */
.dropdown-divider {
height: 1px;
background-color: #dee2e6;
margin: 0.5rem 0;
}

/* Bouton de déconnexion */

.content-wrapper {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}

.main-content {
flex: 1;
overflow-y: auto;
}

.main-content::-webkit-scrollbar {
width: 8px;
}

.main-content::-webkit-scrollbar-track {
/* Conteneur de droite qui héberge le contenu principal et le footer */
}

.main-content::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}

/* Contenu principal : scrollable si trop long */
background: #555;
}



.footer {
padding: 1rem;
@@ -276,60 +225,18 @@
}



.sidebar-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 998;
opacity: 0;
transition: opacity 0.3s ease;
}

.sidebar-overlay.active {
display: block;
opacity: 1;
}



@media (max-width: 768px) {
/* Afficher le bouton burger sur mobile */
.burger-menu {
display: flex;
}

/* Cacher le titre sur très petit écran */
@media (max-width: 480px) {
.app-title {
display: none;
}
.top-bar-content {
padding: 0 1rem;
}

.sidebar {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 280px;
z-index: 999;
transform: translateX(-100%);
}

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

.main-content {
padding: 1rem;
}
}

.top-bar-content {
padding: 0 1rem;
/* Très petits écrans */
@media (max-width: 480px) {
.app-title {
display: none;
}
}

+ 64
- 6
WebUI/ReAct_PME.WebUI/Layout/NavMenu.razor Visa fil

@@ -6,13 +6,14 @@
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject NavigationManager Navigation
@inject IToastService ToastService
@inject IJSRuntime JSRuntime

<div class="sidebar @(IsCollapsed ? "collapsed" : "")">
<div class="sidebar-header">
@if (!IsCollapsed)
{
<div class="logo">
<h1>AIT</h1>
<img src="/images/textAIT.png" width="80" height="40" alt="ait" />
</div>
}
<button class="toggle-button" @onclick="ToggleSidebar"
@@ -92,8 +93,9 @@
<h3 class="section-title">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-folder" viewBox="0 0 16 16">
<path d="M.54 3.87.5 3a2 2 0 0 1 2-2h3.672a2 2 0 0 1 1.414.586l.828.828A2 2 0 0 0 9.828 3h3.982a2 2 0 0 1 1.992 2.181l-.637 7A2 2 0 0 1 13.174 14H2.826a2 2 0 0 1-1.991-1.819l-.637-7a2 2 0 0 1 .342-1.31zM2.19 4a1 1 0 0 0-.996 1.09l.637 7a1 1 0 0 0 .995.91h10.348a1 1 0 0 0 .995-.91l.637-7A1 1 0 0 0 13.81 4zm4.69-1.707A1 1 0 0 0 6.172 2H2.5a1 1 0 0 0-1 .981l.006.139q.323-.119.684-.12h5.396z" />
</svg>
RAG</h3>
</svg>
RAG
</h3>

<div class="conversations-section">
<button class="new-chat-button" @onclick="OnNewRagChat" aria-label="Nouveau RAG">
@@ -152,6 +154,12 @@

</div>

<script>
window.getWindowWidth = function () {
return window.innerWidth;
};
</script>

@code {
private bool IsCollapsed = false;
private string CurrentConversationId = "";
@@ -171,10 +179,33 @@
if (!(tok == null || tok.Length == 0))
AuthService.SetToken(tok);

await LoadChatConversations();
UpdateCurrentConversationFromUrl();

Navigation.LocationChanged += OnLocationChanged;

await LoadChatConversations();
await LoadRagConversations();
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await CheckScreenSize();
}
}

private async Task CheckScreenSize()
{
try
{
var width = await JSRuntime.InvokeAsync<int>("getWindowWidth");
IsCollapsed = width <= 768;
StateHasChanged();
}
catch
{
}
}

private void ToggleSidebar()
@@ -182,6 +213,32 @@
IsCollapsed = !IsCollapsed;
}

private void UpdateCurrentConversationFromUrl()
{
var uri = new Uri(Navigation.Uri);
var segments = uri.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);

if (segments.Length >= 3 && segments[0] == "chatroom_base")
{
CurrentConversationId = segments[2];
}
else
{
CurrentConversationId = "";
}
}

private void OnLocationChanged(object? sender, Microsoft.AspNetCore.Components.Routing.LocationChangedEventArgs e)
{
UpdateCurrentConversationFromUrl();
StateHasChanged();
}

public void Dispose()
{
Navigation.LocationChanged -= OnLocationChanged;
}

private async Task LoadChatConversations()
{

@@ -260,16 +317,17 @@

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

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

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


+ 4
- 0
WebUI/ReAct_PME.WebUI/Layout/NavMenu.razor.css Visa fil

@@ -0,0 +1,4 @@
active {
background-color: rgba(52, 152, 219, 0.3);
border-left: 3px solid #3498db;
}

+ 1
- 1
WebUI/ReAct_PME.WebUI/Pages/AuthLogin/AuthLogin.razor.cs Visa fil

@@ -93,7 +93,7 @@ namespace ReAct_PME.WebUI.Pages.AuthLogin
if (IsAuthorized == true)
{
authProvider.NotifyUserAuthentication(loginResponse.Token);
Navigation.NavigateTo("/");
Navigation.NavigateTo("/chatroom_base/0");
}
else
{

+ 325
- 128
WebUI/ReAct_PME.WebUI/Pages/ChatRoom/ChatRoom_base.razor Visa fil

@@ -1,167 +1,364 @@
@page "/chatroom_base/{typellm:int}"
@page "/chatroom_base/{typellm:int}/{conversationid}"

<div class="chatroom-container">
<!-- Zone de messages -->
<div class="chatroom-messages">
@if (SelectedConversation == null || !Messages.Any())
{
<div class="chatroom-empty">
<div class="empty-icon">💬</div>
<h3>Commencez une conversation</h3>
<p>Posez une question ou envoyez un message pour démarrer</p>
@if (TypeLLM == 0)
{
<!-- Vue Home -->
<div class="home-container">
<div class="home-content">
<!-- Section bienvenue -->
<div class="welcome-section">
<h1 class="welcome-title">Bonjour !</h1>
<p class="welcome-subtitle">Qu'est-ce qu'on fait aujourd'hui ?</p>
</div>
}
else
{
@foreach (var msg in Messages)
{
<div class="message-row @(msg.IsUser ? "user" : "assistant")">
<div class="avatar @(msg.IsUser ? "avatar-user" : "avatar-assistant")">
@if (msg.IsUser)
{
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0" />
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1" />
</svg>
}
else
{
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.6 26.6 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.93.93 0 0 1-.765.935c-.845.147-2.34.346-4.235.346s-3.39-.2-4.235-.346A.93.93 0 0 1 3 9.219zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a25 25 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25 25 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135" />
<path d="M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2zM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5" />
</svg>
}
</div>

<div class="message-content-wrapper">
<div class="bubble @(msg.IsUser ? "bubble-user" : "bubble-assistant")">
@msg.Text
</div>
@if (!msg.IsUser)
<!-- Zone de saisie -->
<div class="input-container">
@if (UploadedDocuments.Any())
{
<div class="input-files">
@foreach (var doc in UploadedDocuments)
{
<div class="message-actions">
<button class="btn-message-action" @onclick="() => CopyMessage(msg.Text)" title="Copier">
<div class="file-item">
<div class="file-preview">
<div class="file-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5z" />
</svg>
</div>
</div>
<div class="file-info">
<span class="file-name">@doc.FileName</span>
<span class="file-size">@FormatFileSize(doc.Bytes.Length)</span>
</div>
<button class="remove-file-button" @onclick="() => RemoveDocument(doc)" aria-label="Supprimer le fichier">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1z" />
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0z" />
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z" />
</svg>
</button>
</div>
}
</div>
}

<div class="input-wrapper @(UploadedDocuments.Any() ? "has-files" : "")">
<textarea @bind="CurrentInput"
@bind:event="oninput"
placeholder="Nouvelle conversation..."
rows="1"
disabled="@isDisabled"></textarea>

<div class="input-actions">
<label class="attach-button" for="fileInputHome">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5a.5.5 0 0 1 1 0v7a.5.5 0 0 0 1 0V3a1.5 1.5 0 1 0-3 0v9a2.5 2.5 0 0 0 5 0V5a.5.5 0 0 1 1 0v7a3.5 3.5 0 1 1-7 0z" />
</svg>
<span>Importer un fichier</span>
<InputFile id="fileInputHome" OnChange="HandleFileSelection" multiple style="display: none;" />
</label>

<div class="input-right-actions">
@if (SelectedAgentType == 2 && RagDomains.Any())
{
<select class="domain-select" @bind="SelectedDomain">
@foreach (var domain in RagDomains)
{
<option value="@domain">@domain</option>
}
</select>
}
<button class="voice-button" @onclick="RecordVoice" aria-label="Message vocal">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8.5 2a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-1 0v-11a.5.5 0 0 1 .5-.5m-2 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m4 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m-6 1.5A.5.5 0 0 1 5 6v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m8 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m-10 1A.5.5 0 0 1 3 7v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5m12 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5" />
</svg>
</button>

<button class="send-button"
@onclick="SendMessageFromHome"
disabled="@((string.IsNullOrWhiteSpace(CurrentInput) && !UploadedDocuments.Any()) || isDisabled)"
aria-label="Envoyer le message">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5" />
</svg>
</button>
</div>
</div>
</div>
}

@if (isTyping)
{
<div class="message-row assistant">
<div class="avatar avatar-assistant">
<!-- Cartes des agents -->
<div class="agents-grid">
<label class="agent-card @(SelectedAgentType == 1 ? "selected" : "")" for="agent1">
<input type="radio"
id="agent1"
name="agentType"
value="1"
checked="@(SelectedAgentType == 1)"
@onchange="() => SelectAgent(1)" />
<div class="agent-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" fill="currentColor" viewBox="0 0 16 16">
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168zM11.207 2.5 13.5 4.793 14.793 3.5 12.5 1.207zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293zm-9.761 5.175-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325" />
</svg>
</div>
<div class="agent-content">
<h3 class="agent-name">Par défaut</h3>
<p class="agent-description">
Discutez avec un agent IA ayant une base de données importante dans votre domaine
</p>
</div>
<div class="agent-check">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.6 26.6 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.93.93 0 0 1-.765.935c-.845.147-2.34.346-4.235.346s-3.39-.2-4.235-.346A.93.93 0 0 1 3 9.219zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a25 25 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25 25 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135" />
<path d="M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2zM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5" />
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z"/>
</svg>
</div>
<div class="message-content-wrapper">
<div class="bubble bubble-assistant">
<div class="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
</label>

<label class="agent-card @(SelectedAgentType == 2 ? "selected" : "")" for="agent2">
<input type="radio"
id="agent2"
name="agentType"
value="2"
checked="@(SelectedAgentType == 2)"
@onchange="() => SelectAgent(2)" />
<div class="agent-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5M3.854 2.146a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 3.293l1.146-1.147a.5.5 0 0 1 .708 0m0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 7.293l1.146-1.147a.5.5 0 0 1 .708 0m0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147 1.146-1.147a.5.5 0 0 1 .708 0" />
</svg>
</div>
</div>
}
}
<div class="agent-content">
<h3 class="agent-name">Agent RAG</h3>
<p class="agent-description">
Un agent IA entraîné dans la création de bilan comptable sur la base de vos comptes-rendus
</p>
</div>
<div class="agent-check">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z"/>
</svg>
</div>
</label>

<label class="agent-card @(SelectedAgentType == 3 ? "selected" : "")" for="agent3">
<input type="radio"
id="agent3"
name="agentType"
value="3"
checked="@(SelectedAgentType == 3)"
@onchange="() => SelectAgent(3)" />
<div class="agent-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M10.854 6.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 8.793l2.646-2.647a.5.5 0 0 1 .708 0" />
<path d="M3 0h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2v-1h1v1a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v1H1V2a2 2 0 0 1 2-2" />
<path d="M1 5v-.5a.5.5 0 0 1 1 0V5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1zm0 3v-.5a.5.5 0 0 1 1 0V8h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1zm0 3v-.5a.5.5 0 0 1 1 0v.5h.5a.5.5 0 0 1 0 1h-2a.5.5 0 0 1 0-1z" />
</svg>
</div>
<div class="agent-content">
<h3 class="agent-name">Agent Code</h3>
<p class="agent-description">
Un agent IA spécialisé dans l'étude de vos documents et dans la réalisation d'audit financier
</p>
</div>
<div class="agent-check">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z"/>
</svg>
</div>
</label>

<label class="agent-card @(SelectedAgentType == 4 ? "selected" : "")" for="agent4">
<input type="radio"
id="agent4"
name="agentType"
value="4"
checked="@(SelectedAgentType == 4)"
@onchange="() => SelectAgent(4)" />
<div class="agent-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" fill="currentColor" viewBox="0 0 16 16">
<path d="M.05 3.555A2 2 0 0 1 2 2h12a2 2 0 0 1 1.95 1.555L8 8.414zM0 4.697v7.104l5.803-3.558zM6.761 8.83l-6.57 4.027A2 2 0 0 0 2 14h12a2 2 0 0 0 1.808-1.144l-6.57-4.027L8 9.586zm3.436-.586L16 11.801V4.697z" />
</svg>
</div>
<div class="agent-content">
<h3 class="agent-name">Agent Image</h3>
<p class="agent-description">
Un agent IA spécialisé dans la rédaction de mail professionnel avec une approche humaine et experte
</p>
</div>
<div class="agent-check">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425z"/>
</svg>
</div>
</label>
</div>
</div>
</div>
</div>

<!-- Zone de saisie moderne -->
<div class="chat-input-container">
<!-- Fichiers attachés -->
@if (UploadedDocuments.Any())
{
<div class="attached-files">
@foreach (var doc in UploadedDocuments)
}
else
{
<!-- Vue ChatRoom -->
<div class="chatroom-container">
<!-- Zone de messages -->
<div class="chatroom-messages">
@if (SelectedConversation == null || !Messages.Any())
{
<div class="chatroom-empty">
<div class="empty-icon">💬</div>
<h3>Commencez une conversation</h3>
<p>Posez une question ou envoyez un message pour démarrer</p>
</div>
}
else
{
@foreach (var msg in Messages)
{
<div class="file-item">
<div class="file-preview">
<div class="file-icon">
<div class="message-row @(msg.IsUser ? "user" : "assistant")">
<div class="avatar @(msg.IsUser ? "avatar-user" : "avatar-assistant")">
@if (msg.IsUser)
{
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5z" />
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0" />
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1" />
</svg>
</div>
}
else
{
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.6 26.6 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.93.93 0 0 1-.765.935c-.845.147-2.34.346-4.235.346s-3.39-.2-4.235-.346A.93.93 0 0 1 3 9.219zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a25 25 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25 25 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135" />
<path d="M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2zM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5" />
</svg>
}
</div>
<div class="file-info">
<span class="file-name">@doc.FileName</span>
<span class="file-size">@FormatFileSize(doc.Bytes.Length)</span>

<div class="message-content-wrapper">
<div class="bubble @(msg.IsUser ? "bubble-user" : "bubble-assistant")">
@msg.Text
</div>

@if (!msg.IsUser)
{
<div class="message-actions">
<button class="btn-message-action" @onclick="() => CopyMessage(msg.Text)" title="Copier">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1z" />
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0z" />
</svg>
</button>
</div>
}
</div>
<button class="remove-file-button" @onclick="() => RemoveDocument(doc)" aria-label="Supprimer le fichier">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z" />
</div>
}

@if (isTyping)
{
<div class="message-row assistant">
<div class="avatar avatar-assistant">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M6 12.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5M3 8.062C3 6.76 4.235 5.765 5.53 5.886a26.6 26.6 0 0 0 4.94 0C11.765 5.765 13 6.76 13 8.062v1.157a.93.93 0 0 1-.765.935c-.845.147-2.34.346-4.235.346s-3.39-.2-4.235-.346A.93.93 0 0 1 3 9.219zm4.542-.827a.25.25 0 0 0-.217.068l-.92.9a25 25 0 0 1-1.871-.183.25.25 0 0 0-.068.495c.55.076 1.232.149 2.02.193a.25.25 0 0 0 .189-.071l.754-.736.847 1.71a.25.25 0 0 0 .404.062l.932-.97a25 25 0 0 0 1.922-.188.25.25 0 0 0-.068-.495c-.538.074-1.207.145-1.98.189a.25.25 0 0 0-.166.076l-.754.785-.842-1.7a.25.25 0 0 0-.182-.135" />
<path d="M8.5 1.866a1 1 0 1 0-1 0V3h-2A4.5 4.5 0 0 0 1 7.5V8a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1v1a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-1a1 1 0 0 0 1-1V9a1 1 0 0 0-1-1v-.5A4.5 4.5 0 0 0 10.5 3h-2zM14 7.5V13a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V7.5A3.5 3.5 0 0 1 5.5 4h5A3.5 3.5 0 0 1 14 7.5" />
</svg>
</button>
</div>
<div class="message-content-wrapper">
<div class="bubble bubble-assistant">
<div class="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
}
</div>
}

<!-- Input wrapper -->
<div class="input-wrapper @(UploadedDocuments.Any() ? "has-files" : "")">
<textarea @bind="CurrentInput"
@bind:event="oninput"
@onkeydown="HandleKeyPress"
placeholder="Nouvelle conversation..."
rows="1"></textarea>

<div class="actions-row">
<!-- Bouton attachement -->
<label class="attach-button" for="fileUpload">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path d="M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5a.5.5 0 0 1 1 0v7a.5.5 0 0 0 1 0V3a1.5 1.5 0 1 0-3 0v9a2.5 2.5 0 0 0 5 0V5a.5.5 0 0 1 1 0v7a3.5 3.5 0 1 1-7 0z" />
</svg>
<span>Importer un fichier</span>
<InputFile id="fileUpload" OnChange="UploadDocuments" multiple style="display: none;" />
</label>
}
</div>

<div class="right-actions">
<!-- Sélecteur de domaine RAG -->
@if (!hiddenDomain && RagDomains.Any())
<!-- Zone de saisie moderne -->
<div class="chat-input-container">
<!-- Fichiers attachés -->
@if (UploadedDocuments.Any())
{
<div class="attached-files">
@foreach (var doc in UploadedDocuments)
{
<select class="domain-select" @bind="SelectedDomain">
@foreach (var domain in RagDomains)
{
<option value="@domain">@domain</option>
}
</select>
<div class="file-item">
<div class="file-preview">
<div class="file-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5z" />
</svg>
</div>
</div>
<div class="file-info">
<span class="file-name">@doc.FileName</span>
<span class="file-size">@FormatFileSize(doc.Bytes.Length)</span>
</div>
<button class="remove-file-button" @onclick="() => RemoveDocument(doc)" aria-label="Supprimer le fichier">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8z" />
</svg>
</button>
</div>
}
</div>
}

<!-- Bouton historique -->
<button class="history-button @(IsWithHistorique ? "active" : "desactive")" @onclick="ToggleHistory" title="@(IsWithHistorique ? "Historique activé" : "Historique désactivé")">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16">
<path d="M8.515 1.019A7 7 0 0 0 8 1V0a8 8 0 0 1 .589.022zm2.004.45a7 7 0 0 0-.985-.299l.219-.976q.576.129 1.126.342zm1.37.71a7 7 0 0 0-.439-.27l.493-.87a8 8 0 0 1 .979.654l-.615.789a7 7 0 0 0-.418-.302zm1.834 1.79a7 7 0 0 0-.653-.796l.724-.69q.406.429.747.91zm.744 1.352a7 7 0 0 0-.214-.468l.893-.45a8 8 0 0 1 .45 1.088l-.95.313a7 7 0 0 0-.179-.483m.53 2.507a7 7 0 0 0-.1-1.025l.985-.17q.1.58.116 1.17zm-.131 1.538q.05-.254.081-.51l.993.123a8 8 0 0 1-.23 1.155l-.964-.267q.069-.247.12-.501m-.952 2.379q.276-.436.486-.908l.914.405q-.24.54-.555 1.038zm-.964 1.205q.183-.183.35-.378l.758.653a8 8 0 0 1-.401.432z" />
<path d="M8 1a7 7 0 1 0 4.95 11.95l.707.707A8.001 8.001 0 1 1 8 0z" />
<path d="M7.5 3a.5.5 0 0 1 .5.5v5.21l3.248 1.856a.5.5 0 0 1-.496.868l-3.5-2A.5.5 0 0 1 7 9V3.5a.5.5 0 0 1 .5-.5" />
</svg>
</button>
<button class="voice-button" @onclick="RecordVoice" aria-label="Message vocal">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8.5 2a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-1 0v-11a.5.5 0 0 1 .5-.5m-2 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m4 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m-6 1.5A.5.5 0 0 1 5 6v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m8 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m-10 1A.5.5 0 0 1 3 7v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5m12 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5" />
</svg>
</button>
<!-- Input wrapper -->
<div class="input-wrapper @(UploadedDocuments.Any() ? "has-files" : "")">
<textarea @bind="CurrentInput"
@bind:event="oninput"
@onkeydown="HandleKeyPress"
placeholder="Nouvelle conversation..."
rows="1"></textarea>

<!-- Bouton envoyer -->
<button class="send-button"
@onclick="SendMessage"
disabled="@(string.IsNullOrWhiteSpace(CurrentInput) && !UploadedDocuments.Any())"
aria-label="Envoyer le message">
<div class="actions-row">
<!-- Bouton attachement -->
<label class="attach-button" for="fileUpload">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5" />
<path d="M4.5 3a2.5 2.5 0 0 1 5 0v9a1.5 1.5 0 0 1-3 0V5a.5.5 0 0 1 1 0v7a.5.5 0 0 0 1 0V3a1.5 1.5 0 1 0-3 0v9a2.5 2.5 0 0 0 5 0V5a.5.5 0 0 1 1 0v7a3.5 3.5 0 1 1-7 0z" />
</svg>
</button>
<span>Importer un fichier</span>
<InputFile id="fileUpload" OnChange="UploadDocuments" multiple style="display: none;" />
</label>

<div class="right-actions">
<!-- Sélecteur de domaine RAG -->
@if (!hiddenDomain && RagDomains.Any())
{
<select class="domain-select" @bind="SelectedDomain">
@foreach (var domain in RagDomains)
{
<option value="@domain">@domain</option>
}
</select>
}

<!-- Bouton historique -->
<button class="history-button @(IsWithHistorique ? "active" : "desactive")" @onclick="ToggleHistory" title="@(IsWithHistorique ? "Historique activé" : "Historique désactivé")">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16">
<path d="M8.515 1.019A7 7 0 0 0 8 1V0a8 8 0 0 1 .589.022zm2.004.45a7 7 0 0 0-.985-.299l.219-.976q.576.129 1.126.342zm1.37.71a7 7 0 0 0-.439-.27l.493-.87a8 8 0 0 1 .979.654l-.615.789a7 7 0 0 0-.418-.302zm1.834 1.79a7 7 0 0 0-.653-.796l.724-.69q.406.429.747.91zm.744 1.352a7 7 0 0 0-.214-.468l.893-.45a8 8 0 0 1 .45 1.088l-.95.313a7 7 0 0 0-.179-.483m.53 2.507a7 7 0 0 0-.1-1.025l.985-.17q.1.58.116 1.17zm-.131 1.538q.05-.254.081-.51l.993.123a8 8 0 0 1-.23 1.155l-.964-.267q.069-.247.12-.501m-.952 2.379q.276-.436.486-.908l.914.405q-.24.54-.555 1.038zm-.964 1.205q.183-.183.35-.378l.758.653a8 8 0 0 1-.401.432z" />
<path d="M8 1a7 7 0 1 0 4.95 11.95l.707.707A8.001 8.001 0 1 1 8 0z" />
<path d="M7.5 3a.5.5 0 0 1 .5.5v5.21l3.248 1.856a.5.5 0 0 1-.496.868l-3.5-2A.5.5 0 0 1 7 9V3.5a.5.5 0 0 1 .5-.5" />
</svg>
</button>
<button class="voice-button" @onclick="RecordVoice" aria-label="Message vocal">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8.5 2a.5.5 0 0 1 .5.5v11a.5.5 0 0 1-1 0v-11a.5.5 0 0 1 .5-.5m-2 2a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m4 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5m-6 1.5A.5.5 0 0 1 5 6v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m8 0a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m-10 1A.5.5 0 0 1 3 7v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5m12 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0V7a.5.5 0 0 1 .5-.5" />
</svg>
</button>

<!-- Bouton envoyer -->
<button class="send-button"
@onclick="SendMessage"
disabled="@(string.IsNullOrWhiteSpace(CurrentInput) && !UploadedDocuments.Any())"
aria-label="Envoyer le message">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 15a.5.5 0 0 0 .5-.5V2.707l3.146 3.147a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L7.5 2.707V14.5a.5.5 0 0 0 .5.5" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
</div>
}

+ 143
- 82
WebUI/ReAct_PME.WebUI/Pages/ChatRoom/ChatRoom_base.razor.cs Visa fil

@@ -2,14 +2,11 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Web; // ← AJOUT pour KeyboardEventArgs
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using ReAct_PME.Domain;
using ReAct_PME.WebUI.ServicesUI;
using System.Net.Http.Json;
using System.Runtime.CompilerServices;
using System.Text.Json;


namespace ReAct_PME.WebUI.Pages.ChatRoom
{
@@ -18,7 +15,6 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
[Parameter] public int TypeLLM { get; set; }
[Parameter] public string? ConversationId { get; set; }


[Inject] public HttpClient Http { get; set; } = default!;
[Inject] private ReAct_PME.WebUI.ServicesUI.AuthService AuthService { get; set; } = default!;
[Inject] private ReAct_PME.WebUI.ServicesUI.ApiService ApiService { get; set; } = default!;
@@ -27,12 +23,12 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
[Inject] private IToastService ToastService { get; set; } = default!;
[Inject] private IJSRuntime JSRuntime { get; set; } = default!;


public class UploadedDoc
{
public string FileName { get; set; } = "";
public byte[] Bytes { get; set; } = Array.Empty<byte>();
}

public List<UploadedDoc> UploadedDocuments { get; set; } = new();

// État de l'UI
@@ -41,9 +37,11 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
public string SelectedConversationId { get; set; } = "";
public List<MessageDto> Messages { get; set; } = new();
private bool IsWithHistorique { get; set; }
private bool isTyping { get; set; } = false;
private bool isTyping { get; set; } = false;
private bool isDisabled { get; set; } = false;

public string CurrentInput { get; set; } = "";
public int SelectedAgentType { get; set; } = 1; // Agent par défaut sélectionné

// Options
public List<string> RagDomains { get; set; } = new();
@@ -54,9 +52,17 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
private bool IsNewConversation = false;
public bool hiddenDomain = true;

protected override async Task OnParametersSetAsync()
protected override async Task OnParametersSetAsync()
{
// Configuration selon le type de LLM
if (TypeLLM == 0)
{
// Mode Home - pas de configuration particulière
return;
}

await LoadConversations();

if (TypeLLM == 1)
{
SelectedMode = "llm";
@@ -68,7 +74,8 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
IsWithHistorique = false;
hiddenDomain = false;
await LoadRagDomains();
SelectedDomain = RagDomains[0];
if (RagDomains.Any())
SelectedDomain = RagDomains[0];
}
else if (TypeLLM == 3)
{
@@ -86,77 +93,34 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
IsWithHistorique = true;
}

var uri = new Uri(Navigation.Uri);
if (!string.IsNullOrEmpty(ConversationId))
{
SelectConversation(ConversationId);

}
else
{
Console.WriteLine("No conversationId in URL");
// IsNewConversation reste false par défaut
}

StateHasChanged();
}

protected override async Task OnInitializedAsync()
{
#region Vérification si utilisateur est connecté et s'il a les droits
var tok = await PagesInitializer.VerifConnexionAndRules(AuthenticationStateProvider, Navigation, Http, ToastService, "/api/ChatRoom/llm/test");
var tok = await PagesInitializer.VerifConnexionAndRules(
AuthenticationStateProvider,
Navigation,
Http,
ToastService,
"/api/ChatRoom/llm/test"
);

if (tok == null || tok.Length == 0)
return;
else
AuthService.SetToken(tok);

#endregion


//await LoadConversations();

//await LoadOllamaModels();
/*
if (TypeLLM == 1)
{
SelectedMode = "llm";
IsWithHistorique = true;
}
else if (TypeLLM == 2)
{
SelectedMode = "rag";
IsWithHistorique = false;
hiddenDomain = false;
await LoadRagDomains();
SelectedDomain = RagDomains[0];
}
else if (TypeLLM == 3)
{
SelectedMode = "code";
IsWithHistorique = true;
}
else if (TypeLLM == 4)
{
SelectedMode = "image";
IsWithHistorique = false;
}
else
{
SelectedMode = "llm";
IsWithHistorique = true;
}

var uri = new Uri(Navigation.Uri);
if (!string.IsNullOrEmpty(ConversationId))
{
SelectConversation(ConversationId);
}
else
{
Console.WriteLine("No conversationId in URL");
// IsNewConversation reste false par défaut
}
*/
}

private async Task UploadDocuments(InputFileChangeEventArgs e)
@@ -180,22 +144,19 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
private void RemoveDocument(UploadedDoc doc)
{
UploadedDocuments.Remove(doc);
StateHasChanged();
}

private async Task LoadConversations()
{
if (string.IsNullOrEmpty(ConversationId))
return;

var oneConversation = await Http.GetFromJsonAsync<ConversationDto>($"/api/ChatRoom/conversation/{ConversationId}")
?? new();

Conversations = new();
Conversations.Add(oneConversation);
/*
Conversations = await Http.GetFromJsonAsync<List<ConversationDto>>($"/api/ChatRoom/conversations/{AuthService.ID}/{TypeLLM}")
?? new();

Conversations = Conversations.OrderByDescending(c => c.Messages.Any() ? c.Messages.Max(m => m.CreatedAt) : DateTime.MinValue).ToList();
*/

StateHasChanged();
}
@@ -217,10 +178,6 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom

public void CreateConversation()
{
//var newConv = await Http.PostAsJsonAsync("/api/ChatRoom/conversation/create", new { });

//var created = await newConv.Content.ReadFromJsonAsync<ConversationDto>();

ConversationDto newConversation = new();

if (newConversation != null)
@@ -234,20 +191,112 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
public void SelectConversation(string id)
{
SelectedConversationId = id;

SelectedConversation = Conversations.Where(c => c.Id == id).FirstOrDefault();

Messages = SelectedConversation?.Messages.OrderBy(m => m.CreatedAt).ToList() ?? new();

StateHasChanged();
}

// Méthode pour envoyer un message depuis la page Home
private async Task SendMessageFromHome()
{
if (string.IsNullOrWhiteSpace(CurrentInput) && !UploadedDocuments.Any())
return;

try
{
isDisabled = true;
isTyping = true;
StateHasChanged();

// Ajoute le message utilisateur dans l'UI
var userMsg = new MessageDto
{
IsUser = true,
Text = CurrentInput
};
Messages.Add(userMsg);

var newConversationId = Guid.NewGuid().ToString();

// Déterminer le mode selon l'agent sélectionné
string mode = SelectedAgentType switch
{
1 => "LLM",
2 => "RAG",
3 => "CODE",
4 => "IMAGE",
_ => "LLM"
};

// Pour le mode RAG, charger les domaines si nécessaire
string domain = "";
if (SelectedAgentType == 2)
{
if (!RagDomains.Any())
await LoadRagDomains();
domain = RagDomains.Any() ? RagDomains[0] : "";
}

var payload = new SendMessageRequest
{
ConversationId = newConversationId,
IsNewConversation = true,
IdWorkForce = AuthService!.ID!,
Mode = mode,
Domain = domain,
Documents64 = UploadedDocuments.Select(d => Convert.ToBase64String(d.Bytes)).ToList(),
Documents = UploadedDocuments.Select(d => d.FileName).ToList(),
Role = "user",
Text = CurrentInput,
Context = ""
};

CurrentInput = "";

var assistantMsg = await ApiService.EnvoiRequete<MessageDto>("/api/ChatRoom/sendMessage", payload);

isTyping = false;
isDisabled = false;

if (assistantMsg != null)
{
Messages.Add(assistantMsg);

// Navigation vers la conversation créée avec le type d'agent sélectionné
Navigation.NavigateTo($"/chatroom_base/{SelectedAgentType}/{newConversationId}");
}

UploadedDocuments.Clear();
StateHasChanged();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
isTyping = false;
isDisabled = false;
ToastService.ShowError($"Erreur lors de l'envoi du message : {ex.Message}");
StateHasChanged();
}
}

public async Task SendMessage()
{
try
{
if (string.IsNullOrWhiteSpace(CurrentInput) || SelectedConversation == null)
return;
{
// Si on est en mode TypeLLM > 0 mais sans conversation, on la crée
if (TypeLLM > 0 && !string.IsNullOrWhiteSpace(CurrentInput))
{
CreateConversation();
if (SelectedConversation == null)
return;
}
else
{
return;
}
}

isTyping = true;
StateHasChanged();
@@ -270,10 +319,8 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
Documents64 = UploadedDocuments.Select(d => Convert.ToBase64String(d.Bytes)).ToList(),
Documents = UploadedDocuments.Select(d => d.FileName).ToList(),
Role = "user",
//Model = SelectedModel,
Text = CurrentInput,
Context = IsWithHistorique ? string.Join("\n ", Messages.Select(m => m.Text)) : ""

};

CurrentInput = "";
@@ -292,6 +339,9 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
await LoadConversations();
SelectConversation(payload.ConversationId);
IsNewConversation = false;

// Mettre à jour l'URL pour inclure l'ID de la conversation
Navigation.NavigateTo($"/chatroom_base/{TypeLLM}/{payload.ConversationId}", false);
}
}

@@ -306,7 +356,6 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
}
}


private async Task HandleKeyPress(KeyboardEventArgs e)
{
if (e.Key == "Enter" && !e.ShiftKey)
@@ -317,7 +366,7 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom

private void GoBack()
{
Navigation.NavigateTo("/");
Navigation.NavigateTo("/chatroom_base/0");
}

private void ToggleHistory()
@@ -335,10 +384,10 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
}
catch (Exception)
{
// Fallback si clipboard API n'est pas disponible
ToastService.ShowWarning("Impossible de copier le message");
}
}

private void RecordVoice()
{
ToastService.ShowInfo("Fonctionnalité d'enregistrement vocal en cours de développement");
@@ -356,5 +405,17 @@ namespace ReAct_PME.WebUI.Pages.ChatRoom
}
return $"{len:0.##} {sizes[order]}";
}

// Méthodes spécifiques à la vue Home
private void SelectAgent(int agentId)
{
SelectedAgentType = agentId;
StateHasChanged();
}

private async Task HandleFileSelection(InputFileChangeEventArgs e)
{
await UploadDocuments(e);
}
}
}
}

+ 501
- 244
WebUI/ReAct_PME.WebUI/Pages/ChatRoom/ChatRoom_base.razor.css Visa fil

@@ -1,9 +1,437 @@

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

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

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

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

@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
opacity: 0.7;
}

30% {
transform: translateY(-10px);
opacity: 1;
}
}

@keyframes checkPop {
0% {
transform: scale(0);
}

50% {
transform: scale(1.2);
}

100% {
transform: scale(1);
}
}

@keyframes pulse {
from {
opacity: 1;
}

to {
opacity: 0.6;
}
}

/* Boutons d'action */
.attach-button,
.voice-button {
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
color: #6b7280;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
}

.attach-button {
gap: 8px;
padding: 0;
font-size: 13px;
font-weight: 500;
}

.attach-button span {
display: none;
}

.attach-button:hover {
color: var(--secondary);
}

.voice-button {
width: 36px;
height: 36px;
}

.voice-button:hover {
background: #f3f4f6;
color: var(--text-dark);
}

.send-button {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: var(--contrast);
border: none;
color: #ffffff;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
}

.send-button:hover:not(:disabled) {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(246, 62, 99, 0.3);
}

.send-button:active:not(:disabled) {
transform: scale(0.98);
}

.send-button:disabled {
background: #e5e7eb;
cursor: not-allowed;
opacity: 0.6;
}

.send-button:disabled svg {
color: #9ca3af;
}

/* Gestion des fichiers */
.file-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #ffffff;
border: 1px solid var(--border);
border-radius: 8px;
transition: all 0.2s ease;
}

.file-item:hover {
border-color: #d1d5db;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}

.file-preview {
flex-shrink: 0;
width: 40px;
height: 40px;
border-radius: 6px;
overflow: hidden;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border);
}

.file-icon {
font-size: 20px;
color: var(--secondary);
}

.file-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}

.file-name {
font-size: 13px;
font-weight: 500;
color: #374151;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.file-size {
font-size: 11px;
color: #9ca3af;
}

.remove-file-button {
flex-shrink: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: 4px;
color: #9ca3af;
cursor: pointer;
transition: all 0.2s ease;
}

.remove-file-button:hover {
background: #fee2e2;
color: #ef4444;
}

/* Wrapper d'input universel */
.input-wrapper {
background: white;
border: 1px solid var(--border);
border-radius: 16px;
padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: all 0.2s ease;
}

.input-wrapper.has-files {
border-radius: 0 0 16px 16px;
border-top: none;
}

.input-wrapper:focus-within {
border-color: var(--secondary);
box-shadow: 0 4px 12px rgba(41, 132, 161, 0.12);
}

.input-wrapper textarea {
width: 100%;
border: none;
outline: none;
resize: none;
font-size: 15px;
font-family: inherit;
color: var(--text-dark);
background: transparent;
min-height: 24px;
max-height: 200px;
line-height: 1.5;
padding: 0;
}

.input-wrapper textarea::placeholder {
color: #9ca3af;
}

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

.home-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.home-content {
width: 100%;
max-width: 1200px;
display: flex;
flex-direction: column;
gap: 3rem;
}

/* Section bienvenue */
.welcome-section {
text-align: center;
margin-bottom: 1rem;
}

.welcome-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-dark);
margin-bottom: 0.5rem;
}

.welcome-subtitle {
font-size: 1.25rem;
color: var(--text-light);
margin: 0;
}

/* Zone de saisie Home */
.input-container {
width: 100%;
margin: 0 auto;
}

.input-files {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 12px 16px;
background: white;
border: 1px solid var(--border);
border-radius: 16px 16px 0 0;
margin-bottom: -1px;
}

/* Variantes spécifiques pour Home */
.input-files .file-item {
background: #f9fafb;
max-width: 45%;
animation: fileSlideIn 0.3s ease;
}

.input-files .file-preview {
width: 36px;
height: 36px;
}

.input-files .file-icon {
font-size: 18px;
}

.input-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 4px;
}

.input-right-actions {
display: flex;
align-items: center;
gap: 8px;
}

/* Cartes des agents */
.agents-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(480px, 1fr));
gap: 1.5rem;
margin-top: 1rem;
}

.agent-card {
position: relative;
background: white;
border: 2px solid var(--border);
border-radius: 12px;
padding: 1.5rem;
display: flex;
gap: 1rem;
cursor: pointer;
transition: all 0.2s ease;
}

.agent-card input[type="radio"] {
position: absolute;
opacity: 0;
pointer-events: none;
}

.agent-card:hover {
border-color: var(--secondary);
box-shadow: 0 4px 12px rgba(41, 132, 161, 0.15);
transform: translateY(-2px);
}

.agent-card.selected {
border-color: var(--secondary);
background: linear-gradient(135deg, rgba(41, 132, 161, 0.05) 0%, rgba(57, 181, 221, 0.05) 100%);
box-shadow: 0 4px 12px rgba(41, 132, 161, 0.2);
}

.agent-card.selected .agent-icon {
background: linear-gradient(135deg, var(--secondary) 0%, var(--third-color) 100%);
box-shadow: 0 4px 12px rgba(41, 132, 161, 0.3);
}

.agent-card .agent-check {
position: absolute;
top: 1rem;
right: 1rem;
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--secondary);
color: white;
display: none;
align-items: center;
justify-content: center;
}

.agent-card.selected .agent-check {
display: flex;
animation: checkPop 0.3s ease;
}

.agent-icon {
flex-shrink: 0;
width: 48px;
height: 48px;
border-radius: 10px;
background: linear-gradient(135deg, var(--secondary) 0%, var(--third-color) 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
}

.agent-content {
flex: 1;
}

.agent-name {
font-size: 1.125rem;
font-weight: 600;
color: var(--text-dark);
margin: 0 0 0.5rem 0;
}

.agent-description {
font-size: 0.9rem;
color: var(--text-light);
line-height: 1.5;
margin: 0;
}


.chatroom-container {
display: flex;
flex-direction: column;
height: 100%;
background: var(--bg-light);
}

.chatroom-messages {
@@ -29,7 +457,7 @@
}

.chatroom-messages::-webkit-scrollbar-thumb:hover {
background: var( --third-color);
background: var(--third-color);
}

.chatroom-empty {
@@ -42,6 +470,17 @@
text-align: center;
}

.chatroom-empty h3 {
font-size: 1.5rem;
color: var(--text-dark);
margin-bottom: 0.5rem;
}

.chatroom-empty p {
font-size: 1rem;
margin: 0;
}

.empty-icon {
font-size: 4rem;
color: var(--secondary);
@@ -49,17 +488,6 @@
opacity: 0.5;
}

.chatroom-empty h3 {
font-size: 1.5rem;
color: var(--text-dark);
margin-bottom: 0.5rem;
}

.chatroom-empty p {
font-size: 1rem;
margin: 0;
}

/* Messages */
.message-row {
display: flex;
@@ -68,27 +496,15 @@
animation: fadeIn 0.3s ease;
}

@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
.message-row.user {
flex-direction: row-reverse;
justify-content: flex-start;
}

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

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

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

.avatar {
width: 40px;
@@ -101,12 +517,12 @@
}

.avatar-user {
background: linear-gradient(135deg, var(--secondary) 0%, var( --third-color) 100%);
background: linear-gradient(135deg, var(--secondary) 0%, var(--third-color) 100%);
color: white;
}

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

@@ -135,7 +551,7 @@
}

.bubble-user {
background: linear-gradient(135deg, var(--secondary) 0%, var( --third-color) 100%);
background: linear-gradient(135deg, var(--secondary) 0%, var(--third-color) 100%);
color: white;
border-bottom-right-radius: 4px;
}
@@ -168,9 +584,10 @@

.btn-message-action:hover {
background: var(--bg-light);
color: var( --third-color);
color: var(--third-color);
}

/* Indicateur de frappe */
.typing-indicator {
display: flex;
gap: 0.4rem;
@@ -193,19 +610,7 @@
animation-delay: 0.4s;
}

@keyframes typing {
0%, 60%, 100% {
transform: translateY(0);
opacity: 0.7;
}

30% {
transform: translateY(-10px);
opacity: 1;
}
}


/* Zone de saisie ChatRoom */
.chat-input-container {
width: 100%;
padding: 16px 24px;
@@ -216,7 +621,6 @@
position: relative;
}

/* Fichiers attachés */
.attached-files {
display: flex;
flex-wrap: wrap;
@@ -227,148 +631,12 @@
width: 100%;
}

.file-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #ffffff;
border: 1px solid var(--border);
border-radius: 8px;
max-width: 32%;
transition: all 0.2s ease;
animation: fileSlideIn 0.3s ease;
}

.file-item:hover {
border-color: #d1d5db;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}

.file-preview {
flex-shrink: 0;
width: 40px;
height: 40px;
border-radius: 6px;
overflow: hidden;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border);
}

.file-icon {
font-size: 20px;
color: var(--secondary);
}

.file-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}

.file-name {
font-size: 13px;
font-weight: 500;
color: #374151;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.file-size {
font-size: 11px;
color: #9ca3af;
}

.remove-file-button {
flex-shrink: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
border-radius: 4px;
color: #9ca3af;
cursor: pointer;
transition: all 0.2s ease;
}

.remove-file-button:hover {
background: #fee2e2;
color: #ef4444;
}

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

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

/* Input wrapper */
.input-wrapper {
width: 100%;
.chat-input-container .input-wrapper {
max-width: 1000px;
margin: 0 auto;
background: #ffffff;
border: 1px solid var(--border);
border-radius: 16px;
padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: all 0.2s ease;
width: 100%;
}

.input-wrapper.has-files {
border-radius: 0 0 16px 16px;
border-top: none;
padding-top: 12px;
}

.input-wrapper:focus-within {
border-color: var(--secondary);
box-shadow: 0 4px 12px rgba(41, 132, 161, 0.12);
}

.input-wrapper textarea {
width: 100%;
border: none;
outline: none;
resize: none;
font-size: 15px;
font-family: inherit;
color: #1f2937;
background: transparent;
min-height: 24px;
max-height: 200px;
line-height: 1.5;
padding: 0;
}

.input-wrapper textarea::placeholder {
color: #9ca3af;
}

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

/* Actions row */
.actions-row {
display: flex;
justify-content: space-between;
@@ -376,40 +644,13 @@
padding-top: 4px;
}

.attach-button {
display: flex;
align-items: center;
gap: 8px;
padding: 0;
background: transparent;
border: none;
color: #6b7280;
cursor: pointer;
font-size: 13px;
font-weight: 500;
transition: color 0.2s ease;
}

.attach-button:hover {
color: var(--secondary);
}

.attach-button span {
display: none;
}

@media (min-width: 640px) {
.attach-button span {
display: inline;
}
}

.right-actions {
display: flex;
align-items: center;
gap: 8px;
}

/* Contrôles spécifiques ChatRoom */
.domain-select {
padding: 6px 12px;
border: 1px solid var(--border);
@@ -431,7 +672,7 @@
box-shadow: 0 0 0 3px rgba(41, 132, 161, 0.1);
}

.voice-button {
.history-button {
display: flex;
align-items: center;
justify-content: center;
@@ -445,49 +686,45 @@
transition: all 0.2s ease;
}

.voice-button:hover {
.history-button:hover {
background: #f3f4f6;
color: var(--text-dark);
}

.history-button.active {
background: var(--secondary);
color: white;
}

.history-button.desactive {
opacity: 0.5;
}


.send-button {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: var( --contrast);
border: none;
color: #ffffff;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
@media (min-width: 640px) {
.attach-button span {
display: inline;
}
}

.send-button:hover:not(:disabled) {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(246, 62, 99, 0.3);
@media (max-width: 768px) {
/* Home */
.home-content {
gap: 2rem;
}

.send-button:active:not(:disabled) {
transform: scale(0.98);
.welcome-title {
font-size: 2rem;
}

.send-button:disabled {
background: #e5e7eb;
cursor: not-allowed;
opacity: 0.6;
.welcome-subtitle {
font-size: 1.125rem;
}

.send-button:disabled svg {
color: #9ca3af;
}

.agents-grid {
grid-template-columns: 1fr;
}

@media (max-width: 768px) {
/* ChatRoom */
.chatroom-messages {
padding: 1rem;
}
@@ -505,6 +742,7 @@
gap: 6px;
}

/* Fichiers */
.file-item {
padding: 6px 10px;
max-width: 200px;
@@ -528,6 +766,7 @@
height: 20px;
}

/* Input wrapper */
.input-wrapper {
padding: 12px 16px;
border-radius: 14px;
@@ -541,6 +780,7 @@
font-size: 14px;
}

/* Actions */
.actions-row .right-actions {
gap: 6px;
}
@@ -567,4 +807,21 @@
width: 36px;
height: 36px;
}

.agent-card {
padding: 1rem;
}

.agent-icon {
width: 40px;
height: 40px;
}

.agent-name {
font-size: 1rem;
}

.agent-description {
font-size: 0.85rem;
}
}

+ 0
- 24
WebUI/ReAct_PME.WebUI/wwwroot/css/app.css Visa fil

@@ -868,7 +868,6 @@ code {
/* Responsive */
@media (max-width: 768px) {
.home-container {
padding: 1rem;
}

.home-header {
@@ -3008,17 +3007,6 @@ color:white;



@media (max-width: 768px) {
.sidebar {
width: 64px;
}

.sidebar.collapsed {
width: 0;
}
}


@keyframes fadeIn {
from {
opacity: 0;
@@ -3037,15 +3025,3 @@ color:white;
}


.footer {
padding: 1rem;
background-color: #e9ecef;
border-top: 1px solid #dee2e6;
text-align: center;
flex-shrink: 0;
}

.footer p {
margin: 0;
color: #6c757d;
}

Binär
WebUI/ReAct_PME.WebUI/wwwroot/images/textAIT.png Visa fil

Before After
Width: 90  |  Height: 44  |  Size: 2.1KB

Laddar…
Avbryt
Spara