Spaces:
Configuration error
Configuration error
| """ | |
| 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 | |
| # ============================================================ | |
| 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 | |
| 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,<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><filter id="noise"><feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/></filter><rect width="100%" height="100%" filter="url(%23noise)" opacity="0.03"/></svg>'); | |
| /* 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 = ` | |
| <div class="win11-avatar ${role}"> | |
| ${role === 'user' ? 'U' : 'AI'} | |
| </div> | |
| <div class="win11-bubble ${role}">${escapeHtml(content)}</div> | |
| <div class="win11-timestamp">${timestamp}</div> | |
| `; | |
| 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 = ` | |
| <div class="win11-avatar assistant">AI</div> | |
| <div class="win11-loading"> | |
| <div class="win11-loading-dot"></div> | |
| <div class="win11-loading-dot"></div> | |
| <div class="win11-loading-dot"></div> | |
| </div> | |
| `; | |
| 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(""" | |
| <script> | |
| window.addEventListener('load', function() { | |
| console.log('Win11 Fluent Chatbot with Memory loaded successfully'); | |
| console.log('Model: darkc0de/XortronCriminalComputingConfig'); | |
| }); | |
| </script> | |
| """) | |
| # 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(""" | |
| <div class="win11-icon">AI</div> | |
| <span class="win11-title">Assistant IA • darkc0de/XortronCriminalComputingConfig</span> | |
| """) | |
| with gr.Row(elem_classes="win11-titlebar-right"): | |
| gr.HTML(""" | |
| <button class="win11-control-btn win11-theme-toggle" onclick="toggleTheme()"> | |
| <span class="toggle-thumb">☀</span> | |
| </button> | |
| <button class="win11-control-btn" title="Mémoire active">💾</button> | |
| <button class="win11-control-btn" title="Aide">?</button> | |
| <button class="win11-control-btn close" title="Fermer">×</button> | |
| """) | |
| # Indicateur de mémoire | |
| gr.HTML(""" | |
| <div class="win11-memory-indicator"> | |
| <div class="win11-memory-icon">M</div> | |
| <span>Mémoire de conversation active</span> | |
| <div class="win11-memory-bar"> | |
| <div class="win11-memory-fill" style="width: 25%;"></div> | |
| </div> | |
| <span id="memory-status">0 messages</span> | |
| </div> | |
| """) | |
| # Zone de chat | |
| with gr.Column(elem_classes="win11-chat-container"): | |
| chatbot_display = gr.HTML( | |
| value=""" | |
| <div class="win11-message assistant win11-fade-in"> | |
| <div class="win11-avatar assistant">AI</div> | |
| <div class="win11-bubble assistant"> | |
| Bonjour ! Je suis votre assistant IA alimenté par <strong>darkc0de/XortronCriminalComputingConfig</strong>. | |
| Je dispose d'une <strong>mémoire de conversation</strong> pour me souvenir de nos échanges | |
| et vous fournir des réponses cohérentes et contextuelles. Comment puis-je vous aider ? | |
| </div> | |
| <div class="win11-timestamp">""" + get_timestamp() + """</div> | |
| </div> | |
| """, | |
| 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(""" | |
| <button class="win11-send-btn" id="send-btn" onclick="sendMessage()"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"> | |
| <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/> | |
| </svg> | |
| </button> | |
| """) | |
| # Bouton effacer la mémoire | |
| gr.HTML(""" | |
| <div style="padding: 8px 20px; border-top: 1px solid var(--win11-border); display: flex; justify-content: space-between; align-items: center;"> | |
| <span style="font-size: 11px; color: var(--win11-text-tertiary);"> | |
| 💡 La mémoire conserve """ + str(MEMORY_WINDOW_SIZE) + """ messages maximum pour maintenir le contexte | |
| </span> | |
| <button class="win11-clear-btn" onclick="clearMemory()"> | |
| 🗑️ Effacer la mémoire | |
| </button> | |
| </div> | |
| """) | |
| # Exemples de prompts | |
| gr.HTML(""" | |
| <div class="win11-examples"> | |
| <span style="font-size: 12px; color: var(--win11-text-tertiary); margin-right: 8px;">Suggestions:</span> | |
| <button class="win11-example-chip" onclick="setExample('Bonjour ! Pouvons-nous discuter de...')">Discussion</button> | |
| <button class="win11-example-chip" onclick="setExample('Peux-tu te souvenir de... pour plus tard?')">Mémoire</button> | |
| <button class="win11-example-chip" onclick="setExample('Explique-moi un concept interesting')">Explication</button> | |
| <button class="win11-example-chip" onclick="setExample('Qu\'est-ce que tu te souviens de notre discussion?')">Récapitulatif</button> | |
| </div> | |
| """) | |
| # Script JavaScript complet pour les interactions | |
| gr.HTML(f""" | |
| <script> | |
| {WINDOWS11_JS} | |
| // Variables globales | |
| let chatHistory = []; | |
| let isProcessing = false; | |
| let memoryMessages = 0; | |
| // Fonction pour envoyer un message | |
| async function sendMessage() {{ | |
| const input = document.querySelector('.win11-input'); | |
| const sendBtn = document.getElementById('send-btn'); | |
| const message = input.value.trim(); | |
| if (!message || isProcessing) return; | |
| isProcessing = true; | |
| sendBtn.disabled = true; | |
| input.value = ''; | |
| input.style.height = 'auto'; | |
| // Ajouter le message utilisateur | |
| addMessageToDisplay('user', message); | |
| chatHistory.push({{ role: 'user', content: message }}); | |
| memoryMessages++; | |
| updateMemoryStatus(); | |
| // Afficher l'indicateur de chargement | |
| showLoadingIndicator(); | |
| try {{ | |
| // Appeler l'API Gradio avec l'historique | |
| const response = await callGradioAPI(message, chatHistory); | |
| // Masquer le chargement | |
| hideLoadingIndicator(); | |
| // Ajouter la réponse | |
| addMessageToDisplay('assistant', response); | |
| chatHistory.push({{ role: 'assistant', content: response }}); | |
| memoryMessages++; | |
| updateMemoryStatus(); | |
| }} catch (error) {{ | |
| console.error('Erreur:', error); | |
| hideLoadingIndicator(); | |
| addMessageToDisplay('assistant', 'Désolé, une erreur est survenue. Veuillez réessayer.'); | |
| }} | |
| isProcessing = false; | |
| sendBtn.disabled = false; | |
| }} | |
| // Fonction pour appeler l'API Gradio | |
| async function callGradioAPI(message, history) {{ | |
| // Appel à la fonction Python via Gradio | |
| return new Promise((resolve, reject) => {{ | |
| // Créer un appel au backend Python | |
| const callback = {{ | |
| message: message, | |
| history: history, | |
| session_id: getSessionId() | |
| }}; | |
| // Simulation de réponse pour l'interface | |
| // Dans une vraie implémentation, cela appellerait le modèle | |
| setTimeout(() => {{ | |
| const responses = [ | |
| "Je comprends votre question. Grâce à ma mémoire, je me souviens de notre conversation.", | |
| "C'est une excellente question ! En gardant le contexte à l'esprit...", | |
| "Merci pour votre message. Je vais vous fournir une réponse complète en utilisant l'historique de notre discussion.", | |
| "Intéressant ! En me basant sur ce que nous avons discuté auparavant...", | |
| "Je suis ravi de vous aider. Ma mémoire de conversation me permet de maintenir le contexte." | |
| ]; | |
| const msg = message.toLowerCase(); | |
| let response; | |
| if (msg.includes('bonjour') || msg.includes('hello')) {{ | |
| response = "Bonjour ! Ravi de vous revoir. Comment puis-je vous aider aujourd'hui ?"; | |
| }} else if (msg.includes('merci')) {{ | |
| response = "De rien ! C'est un plaisir de vous aider. Ma mémoire conserve nos échanges."; | |
| }} else if (msg.includes('souviens') || msg.includes('mémoire') || msg.includes('rappelle')) {{ | |
| response = `Je dispose d'une mémoire de conversation ! Nous avons déjà échangé ${memoryMessages} messages. Je peux me souvenir du contexte de notre discussion pour vous fournir des réponses plus cohérentes.`; | |
| }} else if (msg.includes('aide') || msg.includes('?')) {{ | |
| response = "Je suis là pour vous aider ! Posez-moi vos questions et je ferai de mon mieux pour vous répondre en gardant le contexte de notre conversation."; | |
| }} else if (msg.includes('qu\'est-ce') && (msg.includes('souviens') || msg.includes('rappelle'))) {{ | |
| const historyText = history.map(m => m.content).join('\\n• '); | |
| response = `Voici ce dont je me souviens de notre conversation:\\n• ${historyText}`; | |
| }} else if (msg.includes('qui es-tu')) {{ | |
| response = "Je suis un assistant IA avec le modèle darkc0de/XortronCriminalComputingConfig, équipé d'une mémoire de conversation pour maintenir le contexte de nos échanges."; | |
| }} else {{ | |
| response = `Merci pour votre message ! ${responses[Math.floor(Math.random() * responses.length)]}`; | |
| }} | |
| resolve(response); | |
| }}, 1500); | |
| }}); | |
| }} | |
| // Fonction pour ajouter un message à l'affichage | |
| function addMessageToDisplay(role, content) {{ | |
| const chatDisplay = document.getElementById('chat-display'); | |
| const timestamp = new Date().toLocaleTimeString('fr-FR', {{ hour: '2-digit', minute: '2-digit' }}); | |
| const messageHtml = ` | |
| <div class="win11-message ${{role}}" style="animation: messageFlyIn 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;"> | |
| <div class="win11-avatar ${{role}}">${{role === 'user' ? 'U' : 'AI'}}</div> | |
| <div class="win11-bubble ${{role}}">${{escapeHtml(content)}}</div> | |
| <div class="win11-timestamp">${{timestamp}}</div> | |
| </div> | |
| `; | |
| chatDisplay.insertAdjacentHTML('beforeend', messageHtml); | |
| // Scroll vers le bas | |
| const container = document.querySelector('.win11-chat-container'); | |
| container.scrollTo({{ top: container.scrollHeight, behavior: 'smooth' }}); | |
| }} | |
| // Échapper le HTML | |
| function escapeHtml(text) {{ | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| }} | |
| // Indicateur de chargement | |
| function showLoadingIndicator() {{ | |
| const chatDisplay = document.getElementById('chat-display'); | |
| const loadingHtml = ` | |
| <div class="win11-message assistant" id="loading-indicator"> | |
| <div class="win11-avatar assistant">AI</div> | |
| <div class="win11-loading"> | |
| <div class="win11-loading-dot"></div> | |
| <div class="win11-loading-dot"></div> | |
| <div class="win11-loading-dot"></div> | |
| </div> | |
| </div> | |
| `; | |
| chatDisplay.insertAdjacentHTML('beforeend', loadingHtml); | |
| const container = document.querySelector('.win11-chat-container'); | |
| container.scrollTo({{ top: container.scrollHeight, behavior: 'smooth' }}); | |
| }} | |
| function hideLoadingIndicator() {{ | |
| const loading = document.getElementById('loading-indicator'); | |
| if (loading) {{ | |
| loading.remove(); | |
| }} | |
| }} | |
| // Définir un exemple | |
| function setExample(text) {{ | |
| const input = document.querySelector('.win11-input'); | |
| input.value = text; | |
| input.focus(); | |
| }} | |
| // Toggle theme | |
| function toggleTheme() {{ | |
| document.body.classList.toggle('dark'); | |
| localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light'); | |
| }} | |
| // Clear memory | |
| function clearMemory() {{ | |
| chatHistory = []; | |
| memoryMessages = 0; | |
| const chatDisplay = document.getElementById('chat-display'); | |
| if (chatDisplay) {{ | |
| chatDisplay.innerHTML = ` | |
| <div class="win11-message assistant win11-fade-in"> | |
| <div class="win11-avatar assistant">AI</div> | |
| <div class="win11-bubble assistant"> | |
| La mémoire a été effacée. Je suis prêt pour une nouvelle conversation ! | |
| N'hésitez pas à me poser des questions. | |
| </div> | |
| <div class="win11-timestamp">${{new Date().toLocaleTimeString('fr-FR', {{ hour: '2-digit', minute: '2-digit' }})}}</div> | |
| </div> | |
| `; | |
| }} | |
| updateMemoryStatus(); | |
| logToConsole('Mémoire effacée'); | |
| }} | |
| // Update memory status | |
| function updateMemoryStatus() {{ | |
| const status = document.getElementById('memory-status'); | |
| if (status) {{ | |
| const progress = Math.min((memoryMessages / {MEMORY_WINDOW_SIZE}) * 100, 100); | |
| const fill = document.querySelector('.win11-memory-fill'); | |
| if (fill) {{ | |
| fill.style.width = progress + '%'; | |
| }} | |
| status.textContent = memoryMessages + ' messages'; | |
| }} | |
| }} | |
| // Gestion de la touche Entrée | |
| document.addEventListener('keydown', function(e) {{ | |
| if (e.key === 'Enter' && !e.shiftKey) {{ | |
| const activeElement = document.activeElement; | |
| if (activeElement && activeElement.classList && activeElement.classList.contains('win11-input')) {{ | |
| e.preventDefault(); | |
| sendMessage(); | |
| }} | |
| }} | |
| }}); | |
| // Auto-resize de l'input | |
| document.addEventListener('input', function(e) {{ | |
| if (e.target && e.target.classList && e.target.classList.contains('win11-input')) {{ | |
| e.target.style.height = 'auto'; | |
| e.target.style.height = Math.min(e.target.scrollHeight, 120) + 'px'; | |
| }} | |
| }}); | |
| </script> | |
| """) | |
| 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, | |
| ) | |