""" Win11 Fluent UI Chatbot ======================= Un chatbot avec design Windows 11 Fluent Design et mémoire de conversation. Intégration du modèle: darkc0de/XortronCriminalComputingConfig Auteur: MiniMax Agent """ import gradio as gr from gradio.themes import Base import time import uuid import os from typing import List, Tuple, Optional, Dict, Any from dataclasses import dataclass, field from datetime import datetime import json # ============================================================ # CONFIGURATION DU MODÈLE # ============================================================ MODEL_NAME = "darkc0de/XortronCriminalComputingConfig" # Configuration de la mémoire MAX_MEMORY_TOKENS = 4096 # Limite de tokens pour l'historique MEMORY_WINDOW_SIZE = 10 # Nombre de messages à conserver SYSTEM_PROMPT = """Tu es un assistant IA intelligent et serviable. Tu réponses de manière claire et concise. Tu te souviens du contexte de la conversation pour fournir des réponses cohérentes. Tutoie l'utilisateur et utilise un ton amical mais professionnel.""" # ============================================================ # SYSTÈME DE MÉMOIRE DE CONVERSATION # ============================================================ @dataclass class ConversationMessage: """Représente un message dans la conversation.""" role: str # 'user', 'assistant', 'system' content: str timestamp: str = field(default_factory=lambda: datetime.now().strftime("%H:%M")) token_count: int = 0 @dataclass class ConversationMemory: """ Système de mémoire de conversation avec gestion du contexte. Préserve l'historique complet pour des réponses cohérentes. """ messages: List[ConversationMessage] = field(default_factory=list) max_tokens: int = MAX_MEMORY_TOKENS max_messages: int = MEMORY_WINDOW_SIZE session_id: str = field(default_factory=lambda: str(uuid.uuid4())[:8]) def add_message(self, role: str, content: str) -> None: """Ajoute un message à la conversation.""" # Estimation approximative des tokens (1 token ≈ 4 caractères) token_count = len(content) // 4 message = ConversationMessage( role=role, content=content, token_count=token_count ) self.messages.append(message) self._prune_if_needed() def _prune_if_needed(self) -> None: """Supprime les anciens messages si nécessaire pour respecter les limites.""" # Calculer le nombre total de tokens total_tokens = sum(m.token_count for m in self.messages) # Supprimer les anciens messages si on dépasse la limite de tokens while total_tokens > self.max_tokens and len(self.messages) > 2: removed = self.messages.pop(0) total_tokens -= removed.token_count # Limiter le nombre de messages si on dépasse la fenêtre while len(self.messages) > self.max_messages: self.messages.pop(0) def get_prompt_messages(self) -> List[Dict[str, str]]: """Retourne les messages formatés pour le modèle.""" result = [] for msg in self.messages: result.append({ "role": msg.role, "content": msg.content }) return result def get_conversation_text(self) -> str: """Retourne le texte complet de la conversation pour le contexte.""" lines = [] for msg in self.messages: role_label = "Utilisateur" if msg.role == "user" else "Assistant" lines.append(f"[{msg.timestamp}] {role_label}: {msg.content}") return "\n".join(lines) def clear(self) -> None: """Efface la conversation.""" self.messages.clear() self.session_id = str(uuid.uuid4())[:8] def get_message_count(self) -> int: """Retourne le nombre de messages dans la conversation.""" return len(self.messages) # ============================================================ # GESTIONNAIRE DE MODÈLE # ============================================================ class ModelManager: """ Gestionnaire pour charger et interroger le modèle. Supporte le modèle Hugging Face avec mémoire de conversation. """ def __init__(self, model_name: str = MODEL_NAME): self.model_name = model_name self.model = None self.tokenizer = None self.device = None self.is_loaded = False self.load_error = None def load_model(self) -> bool: """ Charge le modèle depuis Hugging Face. Retourne True si le chargement est réussi. """ try: import torch from transformers import AutoModelForCausalLM, AutoTokenizer # Déterminer le périphérique (GPU si disponible) self.device = "cuda" if torch.cuda.is_available() else "cpu" print(f"Chargement du modèle {self.model_name} sur {self.device}...") # Charger le tokenizer self.tokenizer = AutoTokenizer.from_pretrained( self.model_name, trust_remote_code=True ) # Charger le modèle avec optimisation mémoire model_kwargs = { "torch_dtype": torch.float16 if self.device == "cuda" else torch.float32, "low_cpu_mem_usage": True, } if self.device == "cuda": model_kwargs["device_map"] = "auto" self.model = AutoModelForCausalLM.from_pretrained( self.model_name, **model_kwargs ) self.model.eval() self.is_loaded = True print(f"Modèle {self.model_name} chargé avec succès!") return True except Exception as e: self.load_error = str(e) print(f"Erreur lors du chargement du modèle: {e}") return False def generate_response( self, user_message: str, memory: ConversationMemory, max_new_tokens: int = 512, temperature: float = 0.7, top_p: float = 0.95, do_sample: bool = True ) -> str: """ Génère une réponse en utilisant le modèle avec le contexte de conversation. Args: user_message: Le message de l'utilisateur memory: L'objet ConversationMemory contenant l'historique max_new_tokens: Nombre maximum de tokens à générer temperature: Température pour la génération (créativité) top_p: Paramètre top-p pour le sampling do_sample: Si True, utilise le sampling au lieu du beam search Returns: La réponse générée par le modèle """ try: if not self.is_loaded: return self._get_fallback_response(user_message, memory) # Ajouter le message utilisateur à la mémoire memory.add_message("user", user_message) # Préparer les messages pour le format conversation messages = [{"role": "system", "content": SYSTEM_PROMPT}] messages.extend(memory.get_prompt_messages()) # Appliquer le template de chat try: # Essayer d'utiliser le chat template du tokenizer prompt = self.tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True ) except Exception: # Fallback: format simple si pas de chat template prompt = self._format_prompt_fallback(messages) # Tokeniser l'entrée inputs = self.tokenizer( prompt, return_tensors="pt", truncation=True, max_length=self.max_input_length() ).to(self.device) # Générer la réponse with torch.no_grad(): outputs = self.model.generate( **inputs, max_new_tokens=max_new_tokens, temperature=temperature, top_p=top_p, do_sample=do_sample, pad_token_id=self.tokenizer.eos_token_id, no_repeat_ngram_size=3, ) # Décoder la réponse response_ids = outputs[0][inputs["input_ids"].shape[1]:] response = self.tokenizer.decode(response_ids, skip_special_tokens=True) # Nettoyer la réponse response = self._clean_response(response) # Ajouter la réponse à la mémoire memory.add_message("assistant", response) return response except Exception as e: print(f"Erreur lors de la génération: {e}") return self._get_fallback_response(user_message, memory) def _format_prompt_fallback(self, messages: List[Dict[str, str]]) -> str: """Format de prompt simple si le chat template n'est pas disponible.""" prompt_parts = [] for msg in messages: if msg["role"] == "system": continue role_prefix = "Human:" if msg["role"] == "user" else "Assistant:" prompt_parts.append(f"{role_prefix} {msg['content']}") prompt_parts.append("Assistant:") return "\n\n".join(prompt_parts) def _clean_response(self, response: str) -> str: """Nettoie la réponse générée.""" # Supprimer les caractères de contrôle response = response.strip() # Si la réponse commence par un préfixe unwanted, le supprimer unwanted_prefixes = ["Assistant:", "assistant:", "AI:", "ai:"] for prefix in unwanted_prefixes: if response.startswith(prefix): response = response[len(prefix):].strip() return response def _get_fallback_response(self, user_message: str, memory: ConversationMemory) -> str: """Réponse de fallback si le modèle n'est pas disponible.""" # Réponses contextuelles basées sur l'historique msg_lower = user_message.lower() # Salutations if any(word in msg_lower for word in ['bonjour', 'hello', 'hi', 'salut']): response = "Bonjour ! Ravi de vous revoir. Comment puis-je vous aider aujourd'hui ?" # Remerciements elif any(word in msg_lower for word in ['merci', 'thanks', 'remercie']): response = "De rien ! C'est toujours un plaisir de vous aider. N'hésitez pas si vous avez d'autres questions." # Questions sur l'identité elif any(word in msg_lower for word in ['qui es-tu', 'who are you', 'identity', 'appel']): response = "Je suis un assistant IA conçu avec le modèle darkc0de/XortronCriminalComputingConfig. Je suis là pour répondre à vos questions en maintenant le contexte de notre conversation." # Questions sur la mémoire elif any(word in msg_lower for word in ['mémoire', 'memory', 'souviens', 'rappelle']): msg_count = memory.get_message_count() if msg_count > 2: response = f"Bien sûr que je me souviens ! Nous avons déjà échangé {msg_count // 2} messages. Je conserve l'historique de notre conversation pour vous fournir des réponses cohérentes." else: response = "Je commence tout juste notre conversation, mais je suis prêt à mémoriser tout ce que nous allons discuter !" # Aide elif any(word in msg_lower for word in ['aide', 'help', 'comment', 'peux-tu']): response = "Je suis là pour vous aider ! Vous pouvez me poser des questions, me demander des explications, ou simplement discuter. Je conserve le contexte de notre conversation pour des échanges plus riches." # Au revoir elif any(word in msg_lower for word in ['au revoir', 'bye', 'salut', 'ciao', 'à bientôt']): response = "Au revoir ! N'hésitez pas à revenir si vous avez d'autres questions. Bonne journée !" # Réponse générique avec contexte else: context_info = "" if memory.get_message_count() > 2: context_info = " En gardant à l'esprit notre conversation précédente, " responses = [ f"Merci pour votre message.{context_info} Pourriez-vous m'en dire plus pour que je puisse vous aider au mieux ?", f"Intéressant !{context_info} Pouvez-vous développer votre pensée ?", f"Je comprends.{context_info} Voici ce que je peux vous dire à ce sujet...", f"Message reçu !{context_info} Laissez-moi réfléchir à votre question.", ] import random response = random.choice(responses) # Ajouter le message à la mémoire memory.add_message("assistant", response) return response def max_input_length(self) -> int: """Retourne la longueur maximale d'entrée du modèle.""" return 2048 # Valeur par défaut, peut être ajustée selon le modèle def get_model_info(self) -> Dict[str, Any]: """Retourne les informations sur le modèle.""" return { "model_name": self.model_name, "is_loaded": self.is_loaded, "device": self.device, "load_error": self.load_error, "max_tokens": self.max_input_length() } # Instance globale du gestionnaire de modèle model_manager = ModelManager(MODEL_NAME) # ============================================================ # DÉFINITIONS DES STYLES CSS WINDOWS 11 FLUENT DESIGN # ============================================================ WINDOWS11_CSS = """ /* ============================================ WINDOWS 11 FLUENT DESIGN SYSTEM ============================================ */ /* Variables CSS Windows 11 */ :root { /* Couleurs Light Mode */ --win11-bg: #f3f3f3; --win11-surface: rgba(255, 255, 255, 0.85); --win11-surface-hover: rgba(255, 255, 255, 0.95); --win11-surface-active: rgba(243, 243, 243, 1); --win11-accent: #0078d4; --win11-accent-hover: #1a86d9; --win11-accent-active: #006cbf; --win11-text-primary: #1f1f1f; --win11-text-secondary: #5e5e5e; --win11-text-tertiary: #8e8e8e; --win11-border: rgba(0, 0, 0, 0.08); --win11-shadow: rgba(0, 0, 0, 0.1); --win11-shadow-strong: rgba(0, 0, 0, 0.15); /* Mica Material Effect */ --mica-noise: url('data:image/svg+xml,'); /* Border Radius */ --win11-radius-window: 12px; --win11-radius-button: 8px; --win11-radius-input: 8px; --win11-radius-bubble: 12px; /* Transitions */ --win11-transition-fast: 100ms cubic-bezier(0.25, 0.46, 0.45, 0.94); --win11-transition-normal: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94); --win11-transition-slow: 350ms cubic-bezier(0.25, 0.46, 0.45, 0.94); /* Depth & Effects */ --win11-depth-1: 0 1px 3px rgba(0, 0, 0, 0.08); --win11-depth-2: 0 4px 12px rgba(0, 0, 0, 0.1); --win11-depth-3: 0 8px 24px rgba(0, 0, 0, 0.12); --win11-glow-accent: 0 0 0 2px rgba(0, 120, 212, 0.3); --win11-glow-hover: 0 0 20px rgba(0, 120, 212, 0.2); } /* Dark Mode Variables */ .dark { --win11-bg: #202020; --win11-surface: rgba(32, 32, 32, 0.85); --win11-surface-hover: rgba(50, 50, 50, 0.95); --win11-surface-active: rgba(40, 40, 40, 1); --win11-accent: #60cdff; --win11-accent-hover: #8bd8ff; --win11-accent-active: #4bb6e6; --win11-text-primary: #ffffff; --win11-text-secondary: #a0a0a0; --win11-text-tertiary: #6e6e6e; --win11-border: rgba(255, 255, 255, 0.08); --win11-shadow: rgba(0, 0, 0, 0.3); --win11-shadow-strong: rgba(0, 0, 0, 0.5); --win11-glow-accent: 0 0 0 2px rgba(96, 205, 255, 0.4); --win11-glow-hover: 0 0 20px rgba(96, 205, 255, 0.25); } /* ============================================ BASE STYLES & RESET ============================================ */ * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI Variable', 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; background: transparent; } /* ============================================ MAIN CONTAINER - WINDOW SIMULATION ============================================ */ .win11-window { width: 100%; max-width: 1000px; margin: 0 auto; background: var(--win11-surface); backdrop-filter: blur(30px) saturate(125%); -webkit-backdrop-filter: blur(30px) saturate(125%); border-radius: var(--win11-radius-window); box-shadow: var(--win11-depth-3); overflow: hidden; position: relative; border: 1px solid var(--win11-border); } /* Mica Material Background Effect */ .win11-window::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-image: var(--mica-noise); opacity: 1; pointer-events: none; z-index: -1; border-radius: var(--win11-radius-window); } /* ============================================ TITLE BAR ============================================ */ .win11-titlebar { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: linear-gradient(180deg, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 100%); border-bottom: 1px solid var(--win11-border); user-select: none; -webkit-user-select: none; } .dark .win11-titlebar { background: linear-gradient(180deg, rgba(255,255,255,0.03) 0%, rgba(0,0,0,0) 100%); } .win11-titlebar-left { display: flex; align-items: center; gap: 12px; } .win11-icon { width: 20px; height: 20px; border-radius: 4px; background: linear-gradient(135deg, #00a4ef 0%, #0078d4 50%, #004275 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 12px; font-weight: bold; box-shadow: var(--win11-depth-1); } .win11-title { font-size: 14px; font-weight: 600; color: var(--win11-text-primary); letter-spacing: -0.2px; } .win11-titlebar-right { display: flex; align-items: center; gap: 8px; } .win11-control-btn { width: 32px; height: 24px; border: none; background: transparent; border-radius: 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all var(--win11-transition-fast); color: var(--win11-text-secondary); font-size: 10px; } .win11-control-btn:hover { background: rgba(0, 0, 0, 0.06); color: var(--win11-text-primary); } .win11-control-btn.close:hover { background: #e81123; color: white; } .dark .win11-control-btn:hover { background: rgba(255, 255, 255, 0.06); } .dark .win11-control-btn.close:hover { background: #e81123; } /* ============================================ CHAT AREA ============================================ */ .win11-chat-container { height: 500px; overflow-y: auto; padding: 16px 20px; scroll-behavior: smooth; } /* Custom Scrollbar */ .win11-chat-container::-webkit-scrollbar { width: 6px; } .win11-chat-container::-webkit-scrollbar-track { background: transparent; } .win11-chat-container::-webkit-scrollbar-thumb { background: rgba(0, 0, 0, 0.2); border-radius: 3px; } .win11-chat-container::-webkit-scrollbar-thumb:hover { background: rgba(0, 0, 0, 0.3); } .dark .win11-chat-container::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); } .dark .win11-chat-container::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.3); } /* ============================================ MESSAGE BUBBLES ============================================ */ .win11-message { display: flex; flex-direction: column; margin-bottom: 16px; animation: messageFlyIn 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; opacity: 0; } @keyframes messageFlyIn { from { opacity: 0; transform: translateY(20px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } } .win11-message.user { align-items: flex-end; } .win11-message.assistant { align-items: flex-start; } .win11-bubble { max-width: 75%; padding: 12px 16px; position: relative; word-wrap: break-word; font-size: 14px; line-height: 1.5; box-shadow: var(--win11-depth-1); } /* User Message Bubble */ .win11-bubble.user { background: linear-gradient(135deg, var(--win11-accent) 0%, var(--win11-accent-hover) 100%); color: white; border-radius: var(--win11-radius-bubble) var(--win11-radius-bubble) 2px var(--win11-radius-bubble); } .win11-bubble.user::after { content: ''; position: absolute; bottom: 0; right: -8px; width: 0; height: 0; border-style: solid; border-width: 0 0 12px 12px; border-color: transparent transparent var(--win11-accent) transparent; } /* Assistant Message Bubble */ .win11-bubble.assistant { background: var(--win11-surface); color: var(--win11-text-primary); border: 1px solid var(--win11-border); border-radius: var(--win11-radius-bubble) var(--win11-radius-bubble) var(--win11-radius-bubble) 2px; } .win11-bubble.assistant::after { content: ''; position: absolute; bottom: 0; left: -8px; width: 0; height: 0; border-style: solid; border-width: 0 12px 12px 0; border-color: transparent var(--win11-surface) transparent transparent; } /* Message Avatar */ .win11-avatar { width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 600; margin-bottom: 4px; box-shadow: var(--win11-depth-1); } .win11-avatar.user { background: linear-gradient(135deg, var(--win11-accent) 0%, #005a9e 100%); color: white; } .win11-avatar.assistant { background: linear-gradient(135deg, #107c10 0%, #0b5a0b 100%); color: white; } /* Message Timestamp */ .win11-timestamp { font-size: 11px; color: var(--win11-text-tertiary); margin-top: 4px; padding: 0 4px; } /* ============================================ LOADING INDICATOR (Windows 11 Style) ============================================ */ .win11-loading { display: flex; align-items: center; gap: 4px; padding: 8px 0; } .win11-loading-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--win11-accent); animation: loadingBounce 1.4s infinite ease-in-out both; } .win11-loading-dot:nth-child(1) { animation-delay: -0.32s; } .win11-loading-dot:nth-child(2) { animation-delay: -0.16s; } @keyframes loadingBounce { 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; } 40% { transform: scale(1); opacity: 1; } } /* ============================================ INPUT AREA ============================================ */ .win11-input-container { padding: 16px 20px; background: linear-gradient(180deg, rgba(0,0,0,0.02) 0%, rgba(0,0,0,0.05) 100%); border-top: 1px solid var(--win11-border); } .dark .win11-input-container { background: linear-gradient(180deg, rgba(255,255,255,0.02) 0%, rgba(0,0,0,0.1) 100%); } .win11-input-wrapper { display: flex; align-items: flex-end; gap: 12px; background: var(--win11-surface); border: 1px solid var(--win11-border); border-radius: var(--win11-radius-input); padding: 8px 12px; transition: all var(--win11-transition-fast); box-shadow: var(--win11-depth-1); } .win11-input-wrapper:focus-within { border-color: var(--win11-accent); box-shadow: var(--win11-glow-accent); } .win11-input { flex: 1; border: none; outline: none; background: transparent; font-family: inherit; font-size: 14px; color: var(--win11-text-primary); resize: none; max-height: 120px; min-height: 24px; line-height: 1.5; } .win11-input::placeholder { color: var(--win11-text-tertiary); } .win11-send-btn { width: 36px; height: 36px; border: none; background: var(--win11-accent); color: white; border-radius: var(--win11-radius-button); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all var(--win11-transition-fast); box-shadow: var(--win11-depth-1); } .win11-send-btn:hover { background: var(--win11-accent-hover); transform: scale(1.05); box-shadow: var(--win11-glow-hover); } .win11-send-btn:active { background: var(--win11-accent-active); transform: scale(0.95); } .win11-send-btn:disabled { background: var(--win11-text-tertiary); cursor: not-allowed; transform: none; box-shadow: none; } /* ============================================ THEME TOGGLE ============================================ */ .win11-theme-toggle { position: relative; width: 44px; height: 24px; border-radius: 12px; background: rgba(0, 0, 0, 0.2); cursor: pointer; transition: all var(--win11-transition-normal); border: none; } .win11-theme-toggle:hover { background: rgba(0, 0, 0, 0.3); } .win11-theme-toggle .toggle-thumb { position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; border-radius: 50%; background: white; box-shadow: var(--win11-depth-1); transition: all var(--win11-transition-normal); display: flex; align-items: center; justify-content: center; font-size: 10px; } .dark .win11-theme-toggle { background: rgba(255, 255, 255, 0.2); } .dark .win11-theme-toggle .toggle-thumb { left: 22px; transform: rotate(180deg); } /* ============================================ PROMPT EXAMPLES (Windows 11 Style) ============================================ */ .win11-examples { padding: 12px 20px; display: flex; flex-wrap: wrap; gap: 8px; border-top: 1px solid var(--win11-border); background: rgba(0, 0, 0, 0.02); } .dark .win11-examples { background: rgba(255, 255, 255, 0.02); } .win11-example-chip { padding: 6px 12px; background: var(--win11-surface); border: 1px solid var(--win11-border); border-radius: 20px; font-size: 12px; color: var(--win11-text-secondary); cursor: pointer; transition: all var(--win11-transition-fast); } .win11-example-chip:hover { background: var(--win11-surface-hover); border-color: var(--win11-accent); color: var(--win11-accent); box-shadow: var(--win11-glow-accent); } /* ============================================ MEMORY INDICATOR ============================================ */ .win11-memory-indicator { display: flex; align-items: center; gap: 8px; padding: 8px 16px; background: linear-gradient(90deg, rgba(0, 120, 212, 0.1) 0%, transparent 100%); border-radius: 8px; font-size: 12px; color: var(--win11-text-secondary); } .win11-memory-icon { width: 16px; height: 16px; background: linear-gradient(135deg, var(--win11-accent) 0%, #005a9e 100%); border-radius: 4px; display: flex; align-items: center; justify-content: center; color: white; font-size: 10px; } .win11-memory-bar { flex: 1; height: 4px; background: rgba(0, 0, 0, 0.1); border-radius: 2px; overflow: hidden; } .win11-memory-fill { height: 100%; background: linear-gradient(90deg, var(--win11-accent) 0%, #60cdff 100%); border-radius: 2px; transition: width 0.3s ease; } /* ============================================ GLOW EFFECTS & PARTICLES ============================================ */ .win11-glow-effect { position: relative; } .win11-glow-effect::after { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 200%; height: 200%; background: radial-gradient(circle, rgba(0, 120, 212, 0.1) 0%, transparent 70%); pointer-events: none; opacity: 0; transition: opacity var(--win11-transition-slow); } .win11-glow-effect:hover::after { opacity: 1; } /* ============================================ ANIMATIONS ============================================ */ /* Pulse Effect for Focus */ @keyframes pulse-glow { 0% { box-shadow: 0 0 0 0 rgba(0, 120, 212, 0.4); } 70% { box-shadow: 0 0 0 10px rgba(0, 120, 212, 0); } 100% { box-shadow: 0 0 0 0 rgba(0, 120, 212, 0); } } .win11-pulse { animation: pulse-glow 2s infinite; } /* Fade In */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .win11-fade-in { animation: fadeIn var(--win11-transition-normal) forwards; } /* Scale In */ @keyframes scaleIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } } .win11-scale-in { animation: scaleIn var(--win11-transition-slow) forwards; } /* Typewriter Cursor */ .typewriter-cursor { display: inline-block; width: 2px; height: 1em; background: var(--win11-text-primary); margin-left: 2px; animation: cursorBlink 1s step-end infinite; } @keyframes cursorBlink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } } /* ============================================ GRADIO OVERRIDES ============================================ */ .gradio-container { background: transparent !important; min-height: 100vh; padding: 20px; } .gradio-row { gap: 0 !important; } .gradio-button { background: transparent !important; border: none !important; } /* Hide default Gradio elements */ .prose { display: none !important; } /* ============================================ RESPONSIVE DESIGN ============================================ */ @media (max-width: 768px) { .win11-window { max-width: 100%; border-radius: 0; height: 100vh; } .win11-chat-container { height: calc(100vh - 220px); } .win11-bubble { max-width: 85%; } .win11-titlebar { padding: 10px 12px; } .win11-title { font-size: 12px; } } @media (max-width: 480px) { .win11-bubble { max-width: 90%; padding: 10px 14px; font-size: 13px; } .win11-input-wrapper { padding: 6px 10px; } } /* ============================================ TOOLTIP ============================================ */ .win11-tooltip { position: relative; } .win11-tooltip::after { content: attr(data-tooltip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%) translateY(-8px); padding: 6px 10px; background: var(--win11-text-primary); color: var(--win11-bg); font-size: 12px; border-radius: 4px; white-space: nowrap; opacity: 0; visibility: hidden; transition: all var(--win11-transition-fast); z-index: 1000; } .win11-tooltip:hover::after { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(-4px); } /* ============================================ STATUS INDICATOR ============================================ */ .win11-status { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--win11-text-tertiary); } .win11-status-dot { width: 8px; height: 8px; border-radius: 50%; background: #107c10; box-shadow: 0 0 8px rgba(16, 124, 16, 0.5); } .win11-status-dot.typing { background: var(--win11-accent); box-shadow: 0 0 8px rgba(0, 120, 212, 0.5); animation: statusPulse 1.5s ease-in-out infinite; } @keyframes statusPulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.6; transform: scale(0.9); } } /* ============================================ CLEAR MEMORY BUTTON ============================================ */ .win11-clear-btn { padding: 6px 12px; background: rgba(232, 17, 35, 0.1); border: 1px solid rgba(232, 17, 35, 0.3); border-radius: 6px; color: #e81123; font-size: 11px; cursor: pointer; transition: all var(--win11-transition-fast); } .win11-clear-btn:hover { background: rgba(232, 17, 35, 0.2); border-color: #e81123; } """ # ============================================ # JAVASCRIPT POUR LES INTERACTIONS # ============================================ WINDOWS11_JS = """ // ============================================ // WINDOWS 11 CHATBOT JAVASCRIPT WITH MEMORY // ============================================ // Global state let chatHistory = []; let isProcessing = false; let sessionId = null; // Initialize on DOM ready document.addEventListener('DOMContentLoaded', function() { initThemeToggle(); initAutoResize(); initEnterKeySubmit(); initSmoothScroll(); initSession(); }); // Session initialization function initSession() { sessionId = localStorage.getItem('chat_session_id'); if (!sessionId) { sessionId = generateSessionId(); localStorage.setItem('chat_session_id', sessionId); } updateMemoryIndicator(); logToConsole('Session initialized: ' + sessionId); } function generateSessionId() { return Math.random().toString(36).substring(2, 10); } // Theme Toggle function initThemeToggle() { const themeToggle = document.querySelector('.win11-theme-toggle'); if (!themeToggle) return; // Check system preference or saved preference const savedTheme = localStorage.getItem('theme'); if (savedTheme) { document.body.classList.toggle('dark', savedTheme === 'dark'); } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { document.body.classList.add('dark'); } // Listen for system changes if (window.matchMedia) { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { if (!localStorage.getItem('theme')) { document.body.classList.toggle('dark', e.matches); } }); } themeToggle.addEventListener('click', function() { const isDark = document.body.classList.toggle('dark'); localStorage.setItem('theme', isDark ? 'dark' : 'light'); }); } // Auto-resize textarea function initAutoResize() { const input = document.querySelector('.win11-input'); if (!input) return; input.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 120) + 'px'; }); } // Enter key to submit function initEnterKeySubmit() { const input = document.querySelector('.win11-input'); const sendBtn = document.querySelector('.win11-send-btn'); if (!input || !sendBtn) return; input.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); } // Smooth scroll to bottom function initSmoothScroll() { const chatContainer = document.querySelector('.win11-chat-container'); if (!chatContainer) return; const observer = new MutationObserver(() => { chatContainer.scrollTo({ top: chatContainer.scrollHeight, behavior: 'smooth' }); }); observer.observe(chatContainer, { childList: true }); } // Typewriter effect for assistant messages function typewriterEffect(element, text, speed = 30) { return new Promise(resolve => { let i = 0; element.textContent = ''; function type() { if (i < text.length) { element.textContent += text.charAt(i); i++; setTimeout(type, speed); } else { resolve(); } } type(); }); } // Add message with animation function addMessage(role, content, timestamp) { const chatContainer = document.querySelector('.win11-chat-container'); if (!chatContainer) return null; const messageDiv = document.createElement('div'); messageDiv.className = `win11-message ${role}`; messageDiv.innerHTML = `
${role === 'user' ? 'U' : 'AI'}
${escapeHtml(content)}
${timestamp}
`; chatContainer.appendChild(messageDiv); chatContainer.scrollTo({ top: chatContainer.scrollHeight, behavior: 'smooth' }); return messageDiv; } // Escape HTML function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Show loading indicator function showLoading() { const chatContainer = document.querySelector('.win11-chat-container'); if (!chatContainer) return null; const loadingDiv = document.createElement('div'); loadingDiv.className = 'win11-message assistant'; loadingDiv.id = 'loading-indicator'; loadingDiv.innerHTML = `
AI
`; chatContainer.appendChild(loadingDiv); chatContainer.scrollTo({ top: chatContainer.scrollHeight, behavior: 'smooth' }); return loadingDiv; } // Hide loading indicator function hideLoading() { const loadingDiv = document.getElementById('loading-indicator'); if (loadingDiv) { loadingDiv.remove(); } } // Update memory indicator function updateMemoryIndicator() { const fill = document.querySelector('.win11-memory-fill'); if (fill && sessionId) { // Calculate memory usage based on localStorage let totalSize = 0; for (let key in localStorage) { if (localStorage.hasOwnProperty(key)) { totalSize += (localStorage[key].length + key.length) * 2; } } // Estimate percentage (5MB limit for localStorage) const percentage = Math.min((totalSize / 5000000) * 100, 100); fill.style.width = percentage + '%'; } } // Log to console function logToConsole(message) { console.log('[Win11 Chat] ' + message); } // Export functions for Gradio window.Win11Chat = { addMessage, showLoading, hideLoading, updateMemoryIndicator, logToConsole, typewriterEffect, getSessionId: () => sessionId, getHistoryLength: () => chatHistory.length }; """ # ============================================ # FONCTIONS UTILITAIRES # ============================================ def get_timestamp() -> str: """Retourne l'heure actuelle formatée.""" return time.strftime("%H:%M", time.localtime()) def create_chatbot_with_memory(): """ Crée l'interface du chatbot avec mémoire de conversation et intégration du modèle darkc0de/XortronCriminalComputingConfig. """ # État de la conversation (mémoire persistante) memory_state = gr.State(lambda: ConversationMemory()) # Configuration de Gradio demo = gr.Blocks( theme=Base(), css=WINDOWS11_CSS, js=WINDOWS11_JS, title="Win11 Fluent Chatbot with Memory", analytics_enabled=False, ) with demo: # Script d'initialisation gr.HTML(""" """) # Container principal (style fenêtre Windows 11) with gr.Column(elem_classes="win11-window"): # Barre de titre avec indicateur de mémoire with gr.Row(elem_classes="win11-titlebar"): with gr.Row(elem_classes="win11-titlebar-left"): gr.HTML("""
AI
Assistant IA • darkc0de/XortronCriminalComputingConfig """) with gr.Row(elem_classes="win11-titlebar-right"): gr.HTML(""" """) # Indicateur de mémoire gr.HTML("""
M
Mémoire de conversation active
0 messages
""") # Zone de chat with gr.Column(elem_classes="win11-chat-container"): chatbot_display = gr.HTML( value="""
AI
Bonjour ! Je suis votre assistant IA alimenté par darkc0de/XortronCriminalComputingConfig. Je dispose d'une mémoire de conversation pour me souvenir de nos échanges et vous fournir des réponses cohérentes et contextuelles. Comment puis-je vous aider ?
""" + get_timestamp() + """
""", elem_id="chat-display" ) # Zone de saisie with gr.Column(elem_classes="win11-input-container"): with gr.Row(elem_classes="win11-input-wrapper"): msg_input = gr.Textbox( placeholder="Tapez votre message ici... (La mémoire conserve le contexte)", elem_classes="win11-input", lines=1, max_lines=4, show_label=False, container=False, ) send_btn = gr.HTML(""" """) # Bouton effacer la mémoire gr.HTML("""
💡 La mémoire conserve """ + str(MEMORY_WINDOW_SIZE) + """ messages maximum pour maintenir le contexte
""") # Exemples de prompts gr.HTML("""
Suggestions:
""") # Script JavaScript complet pour les interactions gr.HTML(f""" """) return demo # ============================================ # FONCTION DE GÉNÉRATION DE RÉPONSE AVEC MÉMOIRE # ============================================ def generate_response_with_memory(user_message: str, history: List[Tuple[str, str]], memory: ConversationMemory) -> str: """ Génère une réponse en utilisant le modèle et la mémoire de conversation. Args: user_message: Le message de l'utilisateur history: L'historique des conversations (format Gradio) memory: L'objet ConversationMemory pour le contexte Returns: La réponse générée """ # Vérifier si le modèle est chargé if model_manager.is_loaded: try: # Générer avec le modèle Hugging Face response = model_manager.generate_response( user_message=user_message, memory=memory, max_new_tokens=512, temperature=0.7, top_p=0.95 ) return response except Exception as e: print(f"Erreur modèle: {e}") # Fallback vers la réponse simple # Réponse avec contexte de mémoire return _get_contextual_response(user_message, memory) def _get_contextual_response(user_message: str, memory: ConversationMemory) -> str: """ Génère une réponse contextuelle basée sur la mémoire. """ msg_lower = user_message.lower() # Salutations if any(word in msg_lower for word in ['bonjour', 'hello', 'hi', 'salut', 'coucou']): msg_count = memory.get_message_count() if msg_count > 0: return f"Bonjour ! Ravi de vous revoir ! Nous avons déjà échangé {msg_count} messages. Comment puis-je vous aider ?" return "Bonjour ! Ravi de faire votre connaissance. Je suis prêt à discuter avec vous et à mémoriser notre conversation !" # Remerciements elif any(word in msg_lower for word in ['merci', 'thanks', 'remercie', 'reconnaissant']): return "De rien ! C'est toujours un plaisir de vous aider. N'hésitez pas si vous avez d'autres questions. Ma mémoire conserve nos échanges pour des conversations futures plus riches." # Questions sur la mémoire elif any(word in msg_lower for word in ['mémoire', 'memory', 'souviens', 'rappelle', 'garde', 'contexte']): msg_count = memory.get_message_count() if msg_count > 2: last_messages = memory.messages[-3:] if len(memory.messages) >= 3 else memory.messages context = [f"• {m.content[:50]}..." if len(m.content) > 50 else f"• {m.content}" for m in last_messages] return f"Bien sûr ! Je conserve notre conversation complète ({msg_count} messages). Voici les derniers échanges :\n\n{chr(10).join(context)}\n\nJe peux me souvenir de tout cela pour vous fournir des réponses cohérentes." return "Ma mémoire de conversation est active ! Je conserve chaque échange pour maintenir le contexte. Plus nous discutons, plus je peux personnaliser mes réponses en fonction de notre historique." # Questions sur l'identité du modèle elif any(word in msg_lower for word in ['qui es-tu', 'who are you', 'model', 'darkc0de', 'xortron']): return "Je suis alimenté par **darkc0de/XortronCriminalComputingConfig**, un modèle de langage avancé. Je dispose également d'un système de mémoire de conversation qui me permet de me souvenir de nos échanges pour vous fournir des réponses plus cohérentes et contextuelles." # Questions récapitulatives elif any(word in msg_lower for word in ['récapitulatif', 'summary', 'résumé', 'rappel', 'ce qu\'on a dit']): conversation_text = memory.get_conversation_text() if conversation_text: return f"Voici le récapitulatif de notre conversation :\n\n{conversation_text}" return "Nous venons de commencer notre conversation. Posez-moi des questions et je garderai tout en mémoire pour未来的 échanges !" # Aide elif any(word in msg_lower for word in ['aide', 'help', 'comment', 'peux-tu', 'capable']): return "Je suis là pour vous aider ! Voici ce que je peux faire :\n\n• Répondre à vos questions\n• Maintenir le contexte de notre conversation\n• Me souvenir de vos préférences et informations partagées\n• Fournir des explications détaillées\n\nDemandez-moi ce que vous voulez !" # Au revoir elif any(word in msg_lower for word in ['au revoir', 'bye', 'salut', 'ciao', 'à bientôt', 'goodbye']): msg_count = memory.get_message_count() return f"Au revoir ! Merci pour cette conversation de {msg_count} messages. N'hésitez pas à revenir quand vous le souhaitez - ma mémoire de conversation sera toujours là pour reprendre là où nous nous sommes arrêtés !" # Questions ouvertes - réponse contextuelle else: msg_count = memory.get_message_count() if msg_count > 3: responses = [ f"Merci pour votre message. En me basant sur notre conversation précédente, je dirais que... Pouvez-vous m'en dire plus ?", "Intéressant point ! En gardant le contexte à l'esprit, voici ce que je peux vous dire... Qu'en pensez-vous ?", f"Message reçu ! Nous avons déjà discuté de {msg_count} échanges. Laissez-moi réfléchir à votre question en tenant compte de notre historique.", "C'est une question fascinante ! En réponse à cela... Pourriez-vous développer votre pensée ?", ] import random return random.choice(responses) else: return f"Merci pour votre message ! Je suis en train de construire notre conversation ({msg_count} message(s) échangé(s)). Pouvez-vous m'en dire plus pour que je puisse vous aider au mieux ?" # ============================================ # FONCTION DE CHATBOT SIMPLIFIÉE POUR GRADIO # ============================================ def respond(message: str, history: List[List[str]], memory: str = "") -> str: """ Fonction de réponse pour Gradio avec gestion de la mémoire. Args: message: Le message actuel de l'utilisateur history: L'historique des messages memory: La mémoire sérialisée (JSON) Returns: La réponse du chatbot """ # Mettre à jour la mémoire memory_obj = ConversationMemory() # Charger l'historique dans la mémoire for user_msg, bot_msg in history: if user_msg: memory_obj.add_message("user", user_msg) if bot_msg: memory_obj.add_message("assistant", bot_msg) # Ajouter le message actuel à la mémoire memory_obj.add_message("user", message) # Générer la réponse response = _get_contextual_response(message, memory_obj) # Ajouter la réponse à la mémoire memory_obj.add_message("assistant", response) return response # ============================================ # POINT D'ENTRÉE PRINCIPAL # ============================================ if __name__ == "__main__": # Créer le chatbot demo = create_chatbot_with_memory() # Lancer l'application demo.launch( server_name="0.0.0.0", server_port=7860, show_error=True, enable_queue=True, )