| | <!DOCTYPE html> |
| | <html lang="fr"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>Cygnis - Assistant IA Avancé</title> |
| | <script src="https://cdn.tailwindcss.com"></script> |
| | <script> |
| | tailwind.config = { |
| | theme: { |
| | extend: { |
| | colors: { |
| | primary: { |
| | 600: '#7c3aed', |
| | 700: '#6d28d9', |
| | }, |
| | dark: { |
| | 800: '#1e293b', |
| | 900: '#0f172a', |
| | } |
| | } |
| | } |
| | } |
| | } |
| | </script> |
| | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| | <link rel="icon" href="https://svgsilh.com/svg/2034730.svg" type="image/svg+xml"> |
| | <style> |
| | @keyframes pulse { |
| | 0%, 100% { opacity: 1; } |
| | 50% { opacity: 0.5; } |
| | } |
| | .animate-pulse { |
| | animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; |
| | } |
| | .typing-indicator { |
| | display: flex; |
| | padding: 8px 12px; |
| | } |
| | .typing-dot { |
| | width: 8px; |
| | height: 8px; |
| | margin: 0 2px; |
| | background-color: #9ca3af; |
| | border-radius: 50%; |
| | animation: typing-dot 1.5s infinite ease-in-out; |
| | } |
| | .typing-dot:nth-child(1) { animation-delay: 0s; } |
| | .typing-dot:nth-child(2) { animation-delay: 0.2s; } |
| | .typing-dot:nth-child(3) { animation-delay: 0.4s; } |
| | @keyframes typing-dot { |
| | 0%, 60%, 100% { transform: translateY(0); } |
| | 30% { transform: translateY(-5px); } |
| | } |
| | .message-transition { |
| | transition: all 0.3s ease; |
| | transform-origin: bottom; |
| | } |
| | .message-enter { |
| | opacity: 0; |
| | transform: scale(0.9); |
| | } |
| | .message-enter-active { |
| | opacity: 1; |
| | transform: scale(1); |
| | } |
| | .sidebar { |
| | scrollbar-width: thin; |
| | scrollbar-color: #4b5563 #1f2937; |
| | } |
| | .sidebar::-webkit-scrollbar { |
| | width: 6px; |
| | } |
| | .sidebar::-webkit-scrollbar-track { |
| | background: #1f2937; |
| | } |
| | .sidebar::-webkit-scrollbar-thumb { |
| | background-color: #4b5563; |
| | border-radius: 6px; |
| | } |
| | .chat-container { |
| | scrollbar-width: thin; |
| | scrollbar-color: #4b5563 #1f2937; |
| | } |
| | .chat-container::-webkit-scrollbar { |
| | width: 6px; |
| | } |
| | .chat-container::-webkit-scrollbar-track { |
| | background: #1f2937; |
| | } |
| | .chat-container::-webkit-scrollbar-thumb { |
| | background-color: #4b5563; |
| | border-radius: 6px; |
| | } |
| | .markdown-content h1, .markdown-content h2, .markdown-content h3 { |
| | font-weight: bold; |
| | margin-top: 1em; |
| | margin-bottom: 0.5em; |
| | } |
| | .markdown-content h1 { font-size: 1.5em; } |
| | .markdown-content h2 { font-size: 1.3em; } |
| | .markdown-content h3 { font-size: 1.1em; } |
| | .markdown-content p { margin-bottom: 1em; } |
| | .markdown-content ul, .markdown-content ol { |
| | margin-bottom: 1em; |
| | padding-left: 2em; |
| | } |
| | .markdown-content ul { list-style-type: disc; } |
| | .markdown-content ol { list-style-type: decimal; } |
| | .markdown-content li { margin-bottom: 0.5em; } |
| | .markdown-content code { |
| | background-color: rgba(124, 58, 237, 0.2); |
| | padding: 0.2em 0.4em; |
| | border-radius: 0.25em; |
| | font-family: monospace; |
| | color: #e2e8f0; |
| | } |
| | .markdown-content pre { |
| | background-color: rgba(15, 23, 42, 0.7); |
| | padding: 1em; |
| | border-radius: 0.5em; |
| | overflow-x: auto; |
| | margin-bottom: 1em; |
| | border: 1px solid rgba(124, 58, 237, 0.3); |
| | } |
| | .markdown-content pre code { |
| | background-color: transparent; |
| | padding: 0; |
| | } |
| | .markdown-content a { |
| | color: #818cf8; |
| | text-decoration: underline; |
| | } |
| | .markdown-content blockquote { |
| | border-left: 4px solid #4b5563; |
| | padding-left: 1em; |
| | margin-left: 0; |
| | color: #9ca3af; |
| | margin-bottom: 1em; |
| | } |
| | .markdown-content table { |
| | width: 100%; |
| | border-collapse: collapse; |
| | margin-bottom: 1em; |
| | } |
| | .markdown-content th, .markdown-content td { |
| | border: 1px solid #4b5563; |
| | padding: 0.5em; |
| | text-align: left; |
| | } |
| | .markdown-content th { |
| | background-color: rgba(124, 58, 237, 0.2); |
| | } |
| | .gradient-bg { |
| | background: linear-gradient(135deg, rgba(124, 58, 237, 0.1) 0%, rgba(15, 23, 42, 0.8) 100%); |
| | } |
| | .message-toolbar { |
| | opacity: 0; |
| | transition: opacity 0.2s ease; |
| | } |
| | .message-container:hover .message-toolbar { |
| | opacity: 1; |
| | } |
| | .suggestion-chip { |
| | transition: all 0.2s ease; |
| | } |
| | .suggestion-chip:hover { |
| | transform: translateY(-2px); |
| | background-color: rgba(124, 58, 237, 0.2); |
| | } |
| | .wave-animation { |
| | display: inline-block; |
| | position: relative; |
| | } |
| | .wave-animation span { |
| | display: inline-block; |
| | animation: wave 1.5s infinite; |
| | animation-delay: calc(0.1s * var(--i)); |
| | } |
| | @keyframes wave { |
| | 0%, 40%, 100% { transform: translateY(0); } |
| | 20% { transform: translateY(-5px); } |
| | } |
| | .blink { |
| | animation: blink 1s step-end infinite; |
| | } |
| | @keyframes blink { |
| | from, to { opacity: 1; } |
| | 50% { opacity: 0; } |
| | } |
| | </style> |
| | </head> |
| | <body class="bg-gray-900 text-gray-100 h-screen flex overflow-hidden"> |
| | |
| | <div class="w-64 bg-gray-800 border-r border-gray-700 flex flex-col h-full"> |
| | |
| | <div class="p-4 border-b border-gray-700"> |
| | <div class="flex items-center space-x-2"> |
| | <div class="w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center"> |
| | <i class="fas fa-robot text-white"></i> |
| | </div> |
| | <h1 class="text-xl font-bold">Cygnis AI</h1> |
| | </div> |
| | <button id="new-chat-btn" class="mt-4 w-full bg-primary-600 hover:bg-primary-700 text-white py-2 px-4 rounded-lg flex items-center justify-center transition transform hover:scale-[1.02] active:scale-95"> |
| | <i class="fas fa-plus mr-2"></i> Nouvelle conversation |
| | </button> |
| | </div> |
| |
|
| | |
| | <div class="p-2 border-b border-gray-700"> |
| | <div class="relative"> |
| | <input |
| | id="search-conversations" |
| | type="text" |
| | placeholder="Rechercher des conversations..." |
| | class="w-full bg-gray-700 text-white rounded-lg py-2 px-3 pl-8 text-sm focus:outline-none focus:ring-1 focus:ring-primary-600" |
| | > |
| | <i class="fas fa-search absolute left-3 top-2.5 text-gray-400 text-sm"></i> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="flex-1 overflow-y-auto sidebar"> |
| | <div id="conversation-list" class="p-2 space-y-1"></div> |
| | </div> |
| |
|
| | |
| | <div class="p-4 border-t border-gray-700"> |
| | <div id="user-profile" class="flex items-center space-x-3 cursor-pointer group"> |
| | <div id="user-avatar" class="w-10 h-10 rounded-full bg-gray-700 flex items-center justify-center group-hover:bg-gray-600 transition"> |
| | <i class="fas fa-user"></i> |
| | </div> |
| | <div> |
| | <div id="username" class="font-medium">Invité</div> |
| | <div class="text-xs text-gray-400 flex items-center"> |
| | <span class="inline-block w-2 h-2 rounded-full bg-green-500 mr-1"></span> |
| | Version Pro |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="flex-1 flex flex-col h-full"> |
| | |
| | <div class="bg-gray-800 p-4 border-b border-gray-700 flex items-center justify-between"> |
| | <div class="flex items-center space-x-2"> |
| | <h2 id="current-conversation-title" class="text-lg font-semibold">Nouvelle conversation</h2> |
| | <button id="edit-title-btn" class="text-gray-400 hover:text-white"> |
| | <i class="fas fa-pencil-alt text-xs"></i> |
| | </button> |
| | </div> |
| | <div class="flex items-center space-x-4"> |
| | <button id="suggestions-btn" class="text-gray-400 hover:text-white" title="Suggestions"> |
| | <i class="fas fa-lightbulb"></i> |
| | </button> |
| | <button id="settings-btn" class="text-gray-400 hover:text-white" title="Paramètres"> |
| | <i class="fas fa-cog"></i> |
| | </button> |
| | <button id="help-btn" class="text-gray-400 hover:text-white" title="Aide"> |
| | <i class="fas fa-question-circle"></i> |
| | </button> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="suggestions-panel" class="bg-gray-800 border-b border-gray-700 p-3 hidden"> |
| | <div class="max-w-4xl mx-auto"> |
| | <h3 class="text-sm font-medium text-gray-400 mb-2">SUGGESTIONS</h3> |
| | <div class="grid grid-cols-1 md:grid-cols-3 gap-2"> |
| | <button class="suggestion-chip bg-gray-700 hover:bg-gray-600 text-sm py-2 px-3 rounded-lg text-left transition"> |
| | Explique-moi les concepts de l'apprentissage automatique comme si j'avais 5 ans |
| | </button> |
| | <button class="suggestion-chip bg-gray-700 hover:bg-gray-600 text-sm py-2 px-3 rounded-lg text-left transition"> |
| | Rédige un email professionnel pour reporter une réunion |
| | </button> |
| | <button class="suggestion-chip bg-gray-700 hover:bg-gray-600 text-sm py-2 px-3 rounded-lg text-left transition"> |
| | Génère un poème sur l'intelligence artificielle |
| | </button> |
| | <button class="suggestion-chip bg-gray-700 hover:bg-gray-600 text-sm py-2 px-3 rounded-lg text-left transition"> |
| | Donne-moi des idées de projets en Python pour débutants |
| | </button> |
| | <button class="suggestion-chip bg-gray-700 hover:bg-gray-600 text-sm py-2 px-3 rounded-lg text-left transition"> |
| | Quelle est la différence entre React et Vue.js ? |
| | </button> |
| | <button class="suggestion-chip bg-gray-700 hover:bg-gray-600 text-sm py-2 px-3 rounded-lg text-left transition"> |
| | Planifie un programme d'entraînement de 4 semaines pour débutant |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="chat-messages" class="flex-1 overflow-y-auto chat-container p-4 space-y-4 gradient-bg"> |
| | |
| | <div class="max-w-3xl mx-auto text-center py-10"> |
| | <div class="w-16 h-16 mx-auto rounded-full bg-primary-600 flex items-center justify-center mb-4"> |
| | <i class="fas fa-robot text-2xl text-white"></i> |
| | </div> |
| | <h2 class="text-2xl font-bold mb-2"> |
| | <span class="wave-animation"> |
| | <span style="--i:1">B</span> |
| | <span style="--i:2">o</span> |
| | <span style="--i:3">n</span> |
| | <span style="--i:4">j</span> |
| | <span style="--i:5">o</span> |
| | <span style="--i:6">u</span> |
| | <span style="--i:7">r</span> |
| | <span style="--i:8">!</span> |
| | </span> |
| | </h2> |
| | <p class="text-gray-400 mb-6">Je suis Cygnis, votre assistant IA basé sur Nous-Hermes-2-Mistral-7B-DPO. Comment puis-je vous aider aujourd'hui ?</p> |
| | <div class="grid grid-cols-1 md:grid-cols-2 gap-3 max-w-xl mx-auto"> |
| | <button class="suggestion-chip bg-gray-800 hover:bg-gray-700 py-2 px-4 rounded-lg text-left transition"> |
| | <div class="font-medium">Expliquer un concept technique</div> |
| | <div class="text-xs text-gray-400">"Qu'est-ce qu'un réseau de neurones ?"</div> |
| | </button> |
| | <button class="suggestion-chip bg-gray-800 hover:bg-gray-700 py-2 px-4 rounded-lg text-left transition"> |
| | <div class="font-medium">Générer du code</div> |
| | <div class="text-xs text-gray-400">"Montre-moi un exemple de composant React"</div> |
| | </button> |
| | <button class="suggestion-chip bg-gray-800 hover:bg-gray-700 py-2 px-4 rounded-lg text-left transition"> |
| | <div class="font-medium">Rédiger du contenu</div> |
| | <div class="text-xs text-gray-400">"Écris un article sur les bienfaits du yoga"</div> |
| | </button> |
| | <button class="suggestion-chip bg-gray-800 hover:bg-gray-700 py-2 px-4 rounded-lg text-left transition"> |
| | <div class="font-medium">Résoudre un problème</div> |
| | <div class="text-xs text-gray-400">"Comment optimiser mes performances SQL ?"</div> |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="bg-gray-800 p-4 border-t border-gray-700"> |
| | <div class="max-w-3xl mx-auto"> |
| | <div class="relative"> |
| | <textarea |
| | id="message-input" |
| | rows="1" |
| | placeholder="Envoyez un message..." |
| | class="w-full bg-gray-700 text-white rounded-lg py-3 px-4 pr-12 focus:outline-none focus:ring-2 focus:ring-primary-600 resize-none" |
| | style="min-height: 44px; max-height: 200px;" |
| | ></textarea> |
| | <div class="absolute right-3 bottom-3 flex space-x-2"> |
| | <button id="attach-btn" class="text-gray-400 hover:text-white"> |
| | <i class="fas fa-paperclip"></i> |
| | </button> |
| | <button id="send-btn" class="text-gray-400 hover:text-white"> |
| | <i class="fas fa-paper-plane"></i> |
| | </button> |
| | </div> |
| | </div> |
| | <div class="mt-2 text-xs text-gray-500 text-center"> |
| | Cygnis peut faire des erreurs. Vérifiez les informations importantes. |
| | <span class="hidden md:inline-block ml-2"> |
| | <kbd class="bg-gray-700 px-1.5 py-0.5 rounded">Shift</kbd> + <kbd class="bg-gray-700 px-1.5 py-0.5 rounded">Enter</kbd> pour aller à la ligne |
| | </span> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="settings-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
| | <div class="bg-gray-800 rounded-lg w-full max-w-md max-h-[90vh] overflow-y-auto"> |
| | <div class="p-4 border-b border-gray-700 flex justify-between items-center"> |
| | <h3 class="text-lg font-semibold">Paramètres</h3> |
| | <button id="close-settings" class="text-gray-400 hover:text-white"> |
| | <i class="fas fa-times"></i> |
| | </button> |
| | </div> |
| | <div class="p-4 space-y-6"> |
| | <div> |
| | <h4 class="font-medium mb-3 text-primary-400">Préférences d'affichage</h4> |
| | <div class="space-y-4"> |
| | <div> |
| | <label class="block text-sm font-medium mb-1">Thème</label> |
| | <select id="theme-select" class="w-full bg-gray-700 text-white rounded-lg py-2 px-3"> |
| | <option value="dark">Sombre</option> |
| | <option value="light">Clair</option> |
| | <option value="system">Système</option> |
| | </select> |
| | </div> |
| | <div> |
| | <label class="block text-sm font-medium mb-1">Langue</label> |
| | <select id="language-select" class="w-full bg-gray-700 text-white rounded-lg py-2 px-3"> |
| | <option value="fr">Français</option> |
| | <option value="en">Anglais</option> |
| | <option value="es">Espagnol</option> |
| | <option value="de">Allemand</option> |
| | <option value="it">Italien</option> |
| | </select> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <div> |
| | <h4 class="font-medium mb-3 text-primary-400">Expérience de chat</h4> |
| | <div class="space-y-3"> |
| | <div class="flex items-center justify-between"> |
| | <label class="flex items-center space-x-2"> |
| | <span>Indicateur de saisie</span> |
| | </label> |
| | <label class="relative inline-flex items-center cursor-pointer"> |
| | <input type="checkbox" id="typing-indicator" class="sr-only peer"> |
| | <div class="w-9 h-5 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-primary-600"></div> |
| | </label> |
| | </div> |
| | <div class="flex items-center justify-between"> |
| | <label class="flex items-center space-x-2"> |
| | <span>Sons des messages</span> |
| | </label> |
| | <label class="relative inline-flex items-center cursor-pointer"> |
| | <input type="checkbox" id="message-sounds" class="sr-only peer"> |
| | <div class="w-9 h-5 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-primary-600"></div> |
| | </label> |
| | </div> |
| | <div class="flex items-center justify-between"> |
| | <label class="flex items-center space-x-2"> |
| | <span>Suggestions automatiques</span> |
| | </label> |
| | <label class="relative inline-flex items-center cursor-pointer"> |
| | <input type="checkbox" id="auto-suggestions" class="sr-only peer"> |
| | <div class="w-9 h-5 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-primary-600"></div> |
| | </label> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <div> |
| | <h4 class="font-medium mb-3 text-primary-400">Paramètres avancés</h4> |
| | <div> |
| | <label class="block text-sm font-medium mb-1">Modèle IA</label> |
| | <select id="model-select" class="w-full bg-gray-700 text-white rounded-lg py-2 px-3"> |
| | <option value="nous-hermes-2-mistral-7b-dpo">Nous-Hermes-2-Mistral-7B-DPO</option> |
| | <option value="nous-hermes-2-mixtral-8x7b-dpo">Nous-Hermes-2-Mixtral-8x7B-DPO</option> |
| | <option value="mistral-7b-instruct">Mistral-7B-Instruct</option> |
| | <option value="mixtral-8x7b-instruct">Mixtral-8x7B-Instruct</option> |
| | </select> |
| | <p class="text-xs text-gray-500 mt-1">Le modèle affecte la qualité et la vitesse des réponses.</p> |
| | </div> |
| | </div> |
| | </div> |
| | <div class="p-4 border-t border-gray-700 flex justify-between"> |
| | <button id="reset-settings" class="text-gray-400 hover:text-white text-sm"> |
| | Réinitialiser les paramètres |
| | </button> |
| | <button id="save-settings" class="bg-primary-600 hover:bg-primary-700 text-white py-2 px-6 rounded-lg transition"> |
| | Enregistrer |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="help-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
| | <div class="bg-gray-800 rounded-lg w-full max-w-2xl max-h-[90vh] overflow-y-auto"> |
| | <div class="p-4 border-b border-gray-700 flex justify-between items-center"> |
| | <h3 class="text-lg font-semibold">Aide & Fonctionnalités</h3> |
| | <button id="close-help" class="text-gray-400 hover:text-white"> |
| | <i class="fas fa-times"></i> |
| | </button> |
| | </div> |
| | <div class="p-4 space-y-6"> |
| | <div class="bg-gray-700 rounded-lg p-4"> |
| | <h4 class="font-medium text-primary-400 mb-3">Comment utiliser Cygnis</h4> |
| | <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
| | <div class="flex items-start space-x-3"> |
| | <div class="w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center flex-shrink-0"> |
| | <i class="fas fa-comment text-white text-sm"></i> |
| | </div> |
| | <div> |
| | <h5 class="font-medium">Envoyer un message</h5> |
| | <p class="text-sm text-gray-400">Tapez dans la zone de texte et appuyez sur Entrée ou cliquez sur l'icône d'envoi.</p> |
| | </div> |
| | </div> |
| | <div class="flex items-start space-x-3"> |
| | <div class="w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center flex-shrink-0"> |
| | <i class="fas fa-plus text-white text-sm"></i> |
| | </div> |
| | <div> |
| | <h5 class="font-medium">Nouvelle conversation</h5> |
| | <p class="text-sm text-gray-400">Cliquez sur "Nouvelle conversation" dans la barre latérale.</p> |
| | </div> |
| | </div> |
| | <div class="flex items-start space-x-3"> |
| | <div class="w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center flex-shrink-0"> |
| | <i class="fas fa-history text-white text-sm"></i> |
| | </div> |
| | <div> |
| | <h5 class="font-medium">Historique</h5> |
| | <p class="text-sm text-gray-400">Accédez à vos conversations précédentes dans la liste à gauche.</p> |
| | </div> |
| | </div> |
| | <div class="flex items-start space-x-3"> |
| | <div class="w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center flex-shrink-0"> |
| | <i class="fas fa-trash text-white text-sm"></i> |
| | </div> |
| | <div> |
| | <h5 class="font-medium">Suppression</h5> |
| | <p class="text-sm text-gray-400">Supprimez une conversation en cliquant sur l'icône corbeille.</p> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <div> |
| | <h4 class="font-medium text-primary-400 mb-3">Commandes utiles</h4> |
| | <div class="grid grid-cols-1 md:grid-cols-3 gap-2"> |
| | <div class="bg-gray-700 rounded-lg p-3"> |
| | <div class="font-mono text-sm bg-gray-800 px-2 py-1 rounded mb-1">/reset</div> |
| | <p class="text-xs text-gray-400">Réinitialise la conversation actuelle</p> |
| | </div> |
| | <div class="bg-gray-700 rounded-lg p-3"> |
| | <div class="font-mono text-sm bg-gray-800 px-2 py-1 rounded mb-1">/code</div> |
| | <p class="text-xs text-gray-400">Formate la réponse en code</p> |
| | </div> |
| | <div class="bg-gray-700 rounded-lg p-3"> |
| | <div class="font-mono text-sm bg-gray-800 px-2 py-1 rounded mb-1">/explain</div> |
| | <p class="text-xs text-gray-400">Explication détaillée</p> |
| | </div> |
| | <div class="bg-gray-700 rounded-lg p-3"> |
| | <div class="font-mono text-sm bg-gray-800 px-2 py-1 rounded mb-1">/summarize</div> |
| | <p class="text-xs text-gray-400">Résume le texte fourni</p> |
| | </div> |
| | <div class="bg-gray-700 rounded-lg p-3"> |
| | <div class="font-mono text-sm bg-gray-800 px-2 py-1 rounded mb-1">/translate</div> |
| | <p class="text-xs text-gray-400">Traduit le texte</p> |
| | </div> |
| | <div class="bg-gray-700 rounded-lg p-3"> |
| | <div class="font-mono text-sm bg-gray-800 px-2 py-1 rounded mb-1">/help</div> |
| | <p class="text-xs text-gray-400">Affiche l'aide</p> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <div> |
| | <h4 class="font-medium text-primary-400 mb-3">Fonctionnalités avancées</h4> |
| | <div class="bg-gray-700 rounded-lg p-4"> |
| | <div class="flex items-start space-x-3 mb-4"> |
| | <div class="w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center flex-shrink-0"> |
| | <i class="fas fa-code text-white text-sm"></i> |
| | </div> |
| | <div> |
| | <h5 class="font-medium">Génération de code</h5> |
| | <p class="text-sm text-gray-400">Cygnis peut générer du code dans divers langages de programmation et expliquer son fonctionnement.</p> |
| | </div> |
| | </div> |
| | <div class="flex items-start space-x-3 mb-4"> |
| | <div class="w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center flex-shrink-0"> |
| | <i class="fas fa-book text-white text-sm"></i> |
| | </div> |
| | <div> |
| | <h5 class="font-medium">Rédaction de contenu</h5> |
| | <p class="text-sm text-gray-400">Créez des articles, des emails, des poèmes et bien plus avec des instructions précises.</p> |
| | </div> |
| | </div> |
| | <div class="flex items-start space-x-3"> |
| | <div class="w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center flex-shrink-0"> |
| | <i class="fas fa-brain text-white text-sm"></i> |
| | </div> |
| | <div> |
| | <h5 class="font-medium">Mémoire contextuelle</h5> |
| | <p class="text-sm text-gray-400">Cygnis conserve le contexte de la conversation pour des réponses cohérentes.</p> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | </div> |
| | <div class="p-4 border-t border-gray-700"> |
| | <button id="close-help-btn" class="w-full bg-primary-600 hover:bg-primary-700 text-white py-2 px-4 rounded-lg transition"> |
| | Fermer |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div id="edit-title-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> |
| | <div class="bg-gray-800 rounded-lg w-full max-w-md"> |
| | <div class="p-4 border-b border-gray-700 flex justify-between items-center"> |
| | <h3 class="text-lg font-semibold">Modifier le titre</h3> |
| | <button id="close-edit-title" class="text-gray-400 hover:text-white"> |
| | <i class="fas fa-times"></i> |
| | </button> |
| | </div> |
| | <div class="p-4"> |
| | <input |
| | type="text" |
| | id="conversation-title-input" |
| | class="w-full bg-gray-700 text-white rounded-lg py-2 px-3 focus:outline-none focus:ring-2 focus:ring-primary-600" |
| | placeholder="Entrez un nouveau titre" |
| | > |
| | </div> |
| | <div class="p-4 border-t border-gray-700 flex justify-end"> |
| | <button id="cancel-edit-title" class="text-gray-400 hover:text-white mr-3"> |
| | Annuler |
| | </button> |
| | <button id="save-title-btn" class="bg-primary-600 hover:bg-primary-700 text-white py-2 px-6 rounded-lg transition"> |
| | Enregistrer |
| | </button> |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | |
| | const MODEL_PATH = "mistralai/Mistral-7B-Instruct-v0.1"; |
| | const API_URL = 'https://needlessly-faithful-gopher.ngrok-free.app/generate'; |
| | |
| | |
| | const chatMessages = document.getElementById('chat-messages'); |
| | const messageInput = document.getElementById('message-input'); |
| | const sendBtn = document.getElementById('send-btn'); |
| | const newChatBtn = document.getElementById('new-chat-btn'); |
| | const conversationList = document.getElementById('conversation-list'); |
| | const currentConversationTitle = document.getElementById('current-conversation-title'); |
| | const editTitleBtn = document.getElementById('edit-title-btn'); |
| | const settingsBtn = document.getElementById('settings-btn'); |
| | const helpBtn = document.getElementById('help-btn'); |
| | const suggestionsBtn = document.getElementById('suggestions-btn'); |
| | const suggestionsPanel = document.getElementById('suggestions-panel'); |
| | const settingsModal = document.getElementById('settings-modal'); |
| | const helpModal = document.getElementById('help-modal'); |
| | const editTitleModal = document.getElementById('edit-title-modal'); |
| | const closeSettings = document.getElementById('close-settings'); |
| | const closeHelp = document.getElementById('close-help'); |
| | const closeHelpBtn = document.getElementById('close-help-btn'); |
| | const closeEditTitle = document.getElementById('close-edit-title'); |
| | const cancelEditTitle = document.getElementById('cancel-edit-title'); |
| | const saveTitleBtn = document.getElementById('save-title-btn'); |
| | const saveSettingsBtn = document.getElementById('save-settings'); |
| | const resetSettingsBtn = document.getElementById('reset-settings'); |
| | const searchConversations = document.getElementById('search-conversations'); |
| | const userAvatar = document.getElementById('user-avatar'); |
| | const usernameElement = document.getElementById('username'); |
| | const attachBtn = document.getElementById('attach-btn'); |
| | |
| | |
| | let conversations = JSON.parse(localStorage.getItem('conversations')) || []; |
| | let currentConversationId = localStorage.getItem('currentConversationId') || null; |
| | let settings = JSON.parse(localStorage.getItem('settings')) || { |
| | theme: 'dark', |
| | language: 'fr', |
| | typingIndicator: true, |
| | messageSounds: true, |
| | autoSuggestions: true, |
| | model: 'nous-hermes-2-mistral-7b-dpo' |
| | }; |
| | |
| | |
| | function init() { |
| | applySettings(); |
| | renderConversationList(); |
| | |
| | |
| | if (currentConversationId) { |
| | const conversation = conversations.find(c => c.id === currentConversationId); |
| | if (conversation) { |
| | loadConversation(conversation.id); |
| | } else { |
| | createNewConversation(); |
| | } |
| | } else { |
| | createNewConversation(); |
| | } |
| | |
| | |
| | const urlParams = new URLSearchParams(window.location.search); |
| | const user = urlParams.get('user') || 'Invité'; |
| | const token = urlParams.get('token'); |
| | |
| | usernameElement.textContent = user; |
| | if (token) { |
| | userAvatar.innerHTML = `<img src="https://api.dicebear.com/6.x/identicon/svg?seed=${user}" alt="avatar" class="w-full h-full rounded-full">`; |
| | } |
| | } |
| | |
| | |
| | function applySettings() { |
| | |
| | if (settings.theme === 'dark' || (settings.theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) { |
| | document.documentElement.classList.add('dark'); |
| | } else { |
| | document.documentElement.classList.remove('dark'); |
| | } |
| | |
| | |
| | document.documentElement.lang = settings.language; |
| | |
| | |
| | if (settings.autoSuggestions) { |
| | suggestionsPanel.classList.remove('hidden'); |
| | } else { |
| | suggestionsPanel.classList.add('hidden'); |
| | } |
| | } |
| | |
| | |
| | function saveSettings() { |
| | settings = { |
| | theme: document.getElementById('theme-select').value, |
| | language: document.getElementById('language-select').value, |
| | typingIndicator: document.getElementById('typing-indicator').checked, |
| | messageSounds: document.getElementById('message-sounds').checked, |
| | autoSuggestions: document.getElementById('auto-suggestions').checked, |
| | model: document.getElementById('model-select').value |
| | }; |
| | |
| | localStorage.setItem('settings', JSON.stringify(settings)); |
| | applySettings(); |
| | closeModal(settingsModal); |
| | |
| | |
| | showToast('Paramètres enregistrés avec succès'); |
| | } |
| | |
| | |
| | function resetSettings() { |
| | if (confirm('Voulez-vous vraiment réinitialiser tous les paramètres aux valeurs par défaut ?')) { |
| | settings = { |
| | theme: 'dark', |
| | language: 'fr', |
| | typingIndicator: true, |
| | messageSounds: true, |
| | autoSuggestions: true, |
| | model: 'nous-hermes-2-mistral-7b-dpo' |
| | }; |
| | |
| | localStorage.setItem('settings', JSON.stringify(settings)); |
| | applySettings(); |
| | |
| | |
| | document.getElementById('theme-select').value = settings.theme; |
| | document.getElementById('language-select').value = settings.language; |
| | document.getElementById('typing-indicator').checked = settings.typingIndicator; |
| | document.getElementById('message-sounds').checked = settings.messageSounds; |
| | document.getElementById('auto-suggestions').checked = settings.autoSuggestions; |
| | document.getElementById('model-select').value = settings.model; |
| | |
| | showToast('Paramètres réinitialisés'); |
| | } |
| | } |
| | |
| | |
| | function createNewConversation() { |
| | const id = Date.now().toString(); |
| | const newConvo = { |
| | id, |
| | title: `Conversation ${conversations.length + 1}`, |
| | messages: [], |
| | createdAt: new Date().toISOString() |
| | }; |
| | |
| | conversations.unshift(newConvo); |
| | currentConversationId = id; |
| | saveConversations(); |
| | renderConversationList(); |
| | loadConversation(id); |
| | clearChatMessages(); |
| | currentConversationTitle.textContent = newConvo.title; |
| | |
| | |
| | if (conversations.length === 1) { |
| | showWelcomeMessage(); |
| | } |
| | } |
| | |
| | |
| | function showWelcomeMessage() { |
| | const welcomeHTML = ` |
| | <div class="max-w-3xl mx-auto text-center py-10"> |
| | <div class="w-16 h-16 mx-auto rounded-full bg-primary-600 flex items-center justify-center mb-4"> |
| | <i class="fas fa-robot text-2xl text-white"></i> |
| | </div> |
| | <h2 class="text-2xl font-bold mb-2"> |
| | <span class="wave-animation"> |
| | <span style="--i:1">B</span> |
| | <span style="--i:2">o</span> |
| | <span style="--i:3">n</span> |
| | <span style="--i:4">j</span> |
| | <span style="--i:5">o</span> |
| | <span style="--i:6">u</span> |
| | <span style="--i:7">r</span> |
| | <span style="--i:8">!</span> |
| | </span> |
| | </h2> |
| | <p class="text-gray-400 mb-6">Je suis Cygnis, votre assistant IA basé sur Nous-Hermes-2-Mistral-7B-DPO. Comment puis-je vous aider aujourd'hui ?</p> |
| | <div class="grid grid-cols-1 md:grid-cols-2 gap-3 max-w-xl mx-auto"> |
| | <button class="suggestion-chip bg-gray-800 hover:bg-gray-700 py-2 px-4 rounded-lg text-left transition"> |
| | <div class="font-medium">Expliquer un concept technique</div> |
| | <div class="text-xs text-gray-400">"Qu'est-ce qu'un réseau de neurones ?"</div> |
| | </button> |
| | <button class="suggestion-chip bg-gray-800 hover:bg-gray-700 py-2 px-4 rounded-lg text-left transition"> |
| | <div class="font-medium">Générer du code</div> |
| | <div class="text-xs text-gray-400">"Montre-moi un exemple de composant React"</div> |
| | </button> |
| | <button class="suggestion-chip bg-gray-800 hover:bg-gray-700 py-2 px-4 rounded-lg text-left transition"> |
| | <div class="font-medium">Rédiger du contenu</div> |
| | <div class="text-xs text-gray-400">"Écris un article sur les bienfaits du yoga"</div> |
| | </button> |
| | <button class="suggestion-chip bg-gray-800 hover:bg-gray-700 py-2 px-4 rounded-lg text-left transition"> |
| | <div class="font-medium">Résoudre un problème</div> |
| | <div class="text-xs text-gray-400">"Comment optimiser mes performances SQL ?"</div> |
| | </button> |
| | </div> |
| | </div> |
| | `; |
| | |
| | chatMessages.innerHTML = welcomeHTML; |
| | |
| | |
| | document.querySelectorAll('.suggestion-chip').forEach(chip => { |
| | chip.addEventListener('click', () => { |
| | const question = chip.querySelector('.text-xs').textContent.replace(/^"|"$/g, ''); |
| | messageInput.value = question; |
| | messageInput.focus(); |
| | }); |
| | }); |
| | } |
| | |
| | |
| | function loadConversation(id) { |
| | const conversation = conversations.find(c => c.id === id); |
| | if (!conversation) return; |
| | |
| | currentConversationId = id; |
| | localStorage.setItem('currentConversationId', id); |
| | currentConversationTitle.textContent = conversation.title; |
| | |
| | clearChatMessages(); |
| | |
| | if (conversation.messages.length === 0) { |
| | showWelcomeMessage(); |
| | } else { |
| | conversation.messages.forEach(message => { |
| | appendMessage(message.role, message.content, false, true); |
| | }); |
| | } |
| | |
| | |
| | document.querySelectorAll('.conversation-item').forEach(item => { |
| | item.classList.toggle('bg-gray-700', item.dataset.id === id); |
| | }); |
| | |
| | |
| | setTimeout(() => { |
| | chatMessages.scrollTop = chatMessages.scrollHeight; |
| | }, 10); |
| | } |
| | |
| | |
| | function saveConversations() { |
| | localStorage.setItem('conversations', JSON.stringify(conversations)); |
| | localStorage.setItem('currentConversationId', currentConversationId); |
| | } |
| | |
| | |
| | function renderConversationList() { |
| | conversationList.innerHTML = ''; |
| | |
| | if (conversations.length === 0) { |
| | conversationList.innerHTML = '<p class="text-gray-500 text-sm p-2 text-center">Aucune conversation</p>'; |
| | return; |
| | } |
| | |
| | conversations.forEach(conversation => { |
| | const convoElement = document.createElement('div'); |
| | convoElement.className = `conversation-item p-2 rounded-lg cursor-pointer flex items-center justify-between ${conversation.id === currentConversationId ? 'bg-gray-700' : 'hover:bg-gray-700'}`; |
| | convoElement.dataset.id = conversation.id; |
| | |
| | |
| | const date = new Date(conversation.createdAt); |
| | const formattedDate = date.toLocaleDateString('fr-FR', { |
| | day: 'numeric', |
| | month: 'short', |
| | hour: '2-digit', |
| | minute: '2-digit' |
| | }); |
| | |
| | convoElement.innerHTML = ` |
| | <div class="flex items-center space-x-2 truncate w-full"> |
| | <i class="fas fa-comment text-gray-400 flex-shrink-0"></i> |
| | <div class="truncate flex-1"> |
| | <div class="truncate">${conversation.title}</div> |
| | <div class="text-xs text-gray-500">${formattedDate}</div> |
| | </div> |
| | </div> |
| | <button class="delete-convo text-gray-400 hover:text-red-400 p-1" data-id="${conversation.id}"> |
| | <i class="fas fa-trash text-xs"></i> |
| | </button> |
| | `; |
| | |
| | convoElement.addEventListener('click', () => loadConversation(conversation.id)); |
| | |
| | const deleteBtn = convoElement.querySelector('.delete-convo'); |
| | deleteBtn.addEventListener('click', (e) => { |
| | e.stopPropagation(); |
| | deleteConversation(conversation.id); |
| | }); |
| | |
| | conversationList.appendChild(convoElement); |
| | }); |
| | } |
| | |
| | |
| | function deleteConversation(id) { |
| | if (confirm('Voulez-vous vraiment supprimer cette conversation ?')) { |
| | conversations = conversations.filter(c => c.id !== id); |
| | |
| | if (currentConversationId === id) { |
| | if (conversations.length > 0) { |
| | loadConversation(conversations[0].id); |
| | } else { |
| | createNewConversation(); |
| | } |
| | } |
| | |
| | saveConversations(); |
| | renderConversationList(); |
| | showToast('Conversation supprimée'); |
| | } |
| | } |
| | |
| | |
| | function editConversationTitle() { |
| | const conversation = conversations.find(c => c.id === currentConversationId); |
| | if (!conversation) return; |
| | |
| | conversationTitleInput.value = conversation.title; |
| | openModal(editTitleModal); |
| | conversationTitleInput.focus(); |
| | } |
| | |
| | |
| | function saveConversationTitle() { |
| | const newTitle = conversationTitleInput.value.trim(); |
| | if (!newTitle) return; |
| | |
| | const conversation = conversations.find(c => c.id === currentConversationId); |
| | if (conversation) { |
| | conversation.title = newTitle; |
| | currentConversationTitle.textContent = newTitle; |
| | saveConversations(); |
| | renderConversationList(); |
| | closeModal(editTitleModal); |
| | showToast('Titre mis à jour'); |
| | } |
| | } |
| | |
| | |
| | function clearChatMessages() { |
| | chatMessages.innerHTML = ''; |
| | } |
| | |
| | |
| | function appendMessage(role, content, isTyping = false, noAnimation = false) { |
| | const messageElement = document.createElement('div'); |
| | messageElement.className = `message-transition flex ${role === 'user' ? 'justify-end' : 'justify-start'}`; |
| | |
| | if (!noAnimation) { |
| | messageElement.classList.add('message-enter'); |
| | setTimeout(() => { |
| | messageElement.classList.add('message-enter-active'); |
| | }, 10); |
| | } else { |
| | messageElement.classList.add('message-enter-active'); |
| | } |
| | |
| | if (isTyping) { |
| | messageElement.innerHTML = ` |
| | <div class="bg-gray-700 rounded-lg p-3 max-w-[80%]"> |
| | <div class="typing-indicator"> |
| | <div class="typing-dot"></div> |
| | <div class="typing-dot"></div> |
| | <div class="typing-dot"></div> |
| | </div> |
| | </div> |
| | `; |
| | } else { |
| | const messageContent = role === 'assistant' ? markdownToHtml(content) : content; |
| | const timestamp = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); |
| | |
| | messageElement.innerHTML = ` |
| | <div class="message-container relative ${role === 'user' ? 'bg-primary-600' : 'bg-gray-700'} rounded-lg p-3 max-w-[80%]"> |
| | <div class="markdown-content">${messageContent}</div> |
| | <div class="text-xs text-gray-400 mt-1 text-right">${timestamp}</div> |
| | <div class="message-toolbar absolute -top-3 right-2 bg-gray-800 rounded-full p-1 shadow-lg"> |
| | <button class="copy-btn text-gray-400 hover:text-white p-1" title="Copier"> |
| | <i class="fas fa-copy text-xs"></i> |
| | </button> |
| | ${role === 'assistant' ? ` |
| | <button class="regenerate-btn text-gray-400 hover:text-white p-1" title="Régénérer"> |
| | <i class="fas fa-sync-alt text-xs"></i> |
| | </button> |
| | ` : ''} |
| | </div> |
| | </div> |
| | `; |
| | } |
| | |
| | chatMessages.appendChild(messageElement); |
| | |
| | |
| | if (!isTyping) { |
| | const copyBtn = messageElement.querySelector('.copy-btn'); |
| | if (copyBtn) { |
| | copyBtn.addEventListener('click', () => { |
| | const textToCopy = role === 'assistant' ? content : messageElement.querySelector('.markdown-content').textContent; |
| | navigator.clipboard.writeText(textToCopy); |
| | showToast('Message copié'); |
| | }); |
| | } |
| | |
| | const regenerateBtn = messageElement.querySelector('.regenerate-btn'); |
| | if (regenerateBtn && role === 'assistant') { |
| | regenerateBtn.addEventListener('click', () => { |
| | const conversation = conversations.find(c => c.id === currentConversationId); |
| | if (conversation) { |
| | |
| | const messageIndex = conversation.messages.findIndex(m => m.content === content && m.role === 'assistant'); |
| | if (messageIndex > 0 && conversation.messages[messageIndex - 1].role === 'user') { |
| | const userMessage = conversation.messages[messageIndex - 1].content; |
| | |
| | |
| | conversation.messages.splice(messageIndex, 1); |
| | saveConversations(); |
| | |
| | |
| | messageElement.remove(); |
| | |
| | |
| | messageInput.value = userMessage; |
| | sendMessage(); |
| | } |
| | } |
| | }); |
| | } |
| | } |
| | |
| | |
| | setTimeout(() => { |
| | chatMessages.scrollTop = chatMessages.scrollHeight; |
| | }, 10); |
| | |
| | return messageElement; |
| | } |
| | |
| | |
| | function markdownToHtml(markdown) { |
| | if (!markdown) return ''; |
| | |
| | |
| | markdown = markdown.replace(/^# (.*$)/gm, '<h1>$1</h1>'); |
| | markdown = markdown.replace(/^## (.*$)/gm, '<h2>$1</h2>'); |
| | markdown = markdown.replace(/^### (.*$)/gm, '<h3>$1</h3>'); |
| | |
| | |
| | markdown = markdown.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); |
| | markdown = markdown.replace(/\*(.*?)\*/g, '<em>$1</em>'); |
| | markdown = markdown.replace(/_(.*?)_/g, '<em>$1</em>'); |
| | |
| | |
| | markdown = markdown.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'); |
| | |
| | |
| | markdown = markdown.replace(/```([a-z]*)\n([\s\S]*?)```/g, function(match, lang, code) { |
| | return `<pre><code class="language-${lang}">${code.trim()}</code></pre>`; |
| | }); |
| | markdown = markdown.replace(/`(.*?)`/g, '<code>$1</code>'); |
| | |
| | |
| | markdown = markdown.replace(/^\s*\*\s(.*$)/gm, '<li>$1</li>'); |
| | markdown = markdown.replace(/^\s*-\s(.*$)/gm, '<li>$1</li>'); |
| | markdown = markdown.replace(/^\s*\+\s(.*$)/gm, '<li>$1</li>'); |
| | markdown = markdown.replace(/^\s*\d+\.\s(.*$)/gm, '<li>$1</li>'); |
| | markdown = markdown.replace(/(<li>.*<\/li>)+/g, '<ul>$&</ul>'); |
| | |
| | |
| | markdown = markdown.replace(/!\[(.*?)\]\((.*?)\)/g, '<img src="$2" alt="$1" class="max-w-full rounded-lg my-2">'); |
| | |
| | |
| | markdown = markdown.replace(/^\|(.+)\|$\n^\|([\-: ]+\|)+$\n((?:^\|.+\|$\n?)+)/gm, function(match, headers, align, rows) { |
| | headers = headers.split('|').map(h => h.trim()); |
| | align = align.split('|').map(a => a.trim()); |
| | rows = rows.split('\n').filter(r => r.trim()); |
| | |
| | let html = '<table class="markdown-table"><thead><tr>'; |
| | |
| | |
| | headers.forEach(h => { |
| | if (h) html += `<th>${h}</th>`; |
| | }); |
| | |
| | html += '</tr></thead><tbody>'; |
| | |
| | |
| | rows.forEach(r => { |
| | const cells = r.split('|').map(c => c.trim()); |
| | html += '<tr>'; |
| | cells.forEach(c => { |
| | if (c) html += `<td>${c}</td>`; |
| | }); |
| | html += '</tr>'; |
| | }); |
| | |
| | html += '</tbody></table>'; |
| | return html; |
| | }); |
| | |
| | |
| | markdown = markdown.replace(/^>\s(.*$)/gm, '<blockquote>$1</blockquote>'); |
| | |
| | |
| | markdown = markdown.replace(/^\-\-\-$/gm, '<hr class="border-gray-600 my-3">'); |
| | |
| | |
| | markdown = markdown.replace(/^(?!<[a-z])(.*$)/gm, function(m) { |
| | return m.trim() ? '<p>' + m + '</p>' : ''; |
| | }); |
| | |
| | |
| | markdown = markdown.replace(/\n/g, '<br>'); |
| | |
| | return markdown; |
| | } |
| | |
| | |
| | async function queryHuggingFace(prompt) { |
| | try { |
| | const response = await fetch(API_URL, { |
| | method: 'POST', |
| | headers: { |
| | 'Content-Type': 'application/json' |
| | }, |
| | body: JSON.stringify({ |
| | inputs: prompt, |
| | parameters: { |
| | max_new_tokens: 1000, |
| | temperature: 0.7, |
| | top_p: 0.9, |
| | repetition_penalty: 1.1, |
| | return_full_text: false |
| | } |
| | }) |
| | }); |
| | |
| | if (!response.ok) { |
| | throw new Error(`Erreur HTTP: ${response.status}`); |
| | } |
| | |
| | const result = await response.json(); |
| | return result.generated_text; |
| | |
| | } catch (error) { |
| | console.error('Erreur lors de la requête à l\'API locale Flask:', error); |
| | return "Désolé, une erreur s'est produite lors de la génération de la réponse. Veuillez réessayer."; |
| | } |
| | } |
| | |
| | |
| | async function sendMessage() { |
| | const message = messageInput.value.trim(); |
| | if (!message) return; |
| | |
| | |
| | appendMessage('user', message); |
| | |
| | |
| | const conversation = conversations.find(c => c.id === currentConversationId); |
| | if (conversation) { |
| | conversation.messages.push({ |
| | role: 'user', |
| | content: message, |
| | timestamp: new Date().toISOString() |
| | }); |
| | |
| | |
| | if (conversation.messages.length === 1) { |
| | conversation.title = message.length > 30 ? message.substring(0, 30) + '...' : message; |
| | currentConversationTitle.textContent = conversation.title; |
| | renderConversationList(); |
| | } |
| | } |
| | |
| | |
| | messageInput.value = ''; |
| | messageInput.style.height = 'auto'; |
| | |
| | |
| | const typingElement = appendMessage('assistant', '', true); |
| | |
| | try { |
| | |
| | const conversationHistory = conversation ? conversation.messages : []; |
| | |
| | |
| | let prompt = message; |
| | if (conversationHistory.length > 1) { |
| | prompt = conversationHistory.map(m => |
| | m.role === 'user' ? `[INST] ${m.content} [/INST]` : m.content |
| | ).join('\n') + `\n[INST] ${message} [/INST]`; |
| | } |
| | |
| | |
| | const responseText = await queryHuggingFace(prompt); |
| | |
| | |
| | typingElement.remove(); |
| | |
| | |
| | appendMessage('assistant', responseText); |
| | |
| | |
| | if (conversation) { |
| | conversation.messages.push({ |
| | role: 'assistant', |
| | content: responseText, |
| | timestamp: new Date().toISOString() |
| | }); |
| | } |
| | |
| | saveConversations(); |
| | |
| | |
| | if (settings.messageSounds) { |
| | const sound = new Audio('https://assets.mixkit.co/sfx/preview/mixkit-positive-interface-beep-221.mp3'); |
| | sound.volume = 0.3; |
| | sound.play(); |
| | } |
| | } catch (error) { |
| | console.error('Error:', error); |
| | typingElement.remove(); |
| | appendMessage('assistant', "Désolé, une erreur s'est produite. Veuillez réessayer."); |
| | } |
| | } |
| | |
| | |
| | function showToast(message) { |
| | const toast = document.createElement('div'); |
| | toast.className = 'fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-2 rounded-lg shadow-lg flex items-center animate-pulse'; |
| | toast.innerHTML = ` |
| | <i class="fas fa-check-circle text-green-500 mr-2"></i> |
| | <span>${message}</span> |
| | `; |
| | |
| | document.body.appendChild(toast); |
| | |
| | setTimeout(() => { |
| | toast.classList.remove('animate-pulse'); |
| | toast.style.opacity = '0'; |
| | toast.style.transition = 'opacity 0.5s ease'; |
| | |
| | setTimeout(() => { |
| | toast.remove(); |
| | }, 500); |
| | }, 2000); |
| | } |
| | |
| | |
| | function openModal(modal) { |
| | modal.classList.remove('hidden'); |
| | document.body.style.overflow = 'hidden'; |
| | } |
| | |
| | |
| | function closeModal(modal) { |
| | modal.classList.add('hidden'); |
| | document.body.style.overflow = ''; |
| | } |
| | |
| | |
| | function searchConversationsHandler() { |
| | const searchTerm = searchConversations.value.toLowerCase(); |
| | |
| | document.querySelectorAll('.conversation-item').forEach(item => { |
| | const title = item.textContent.toLowerCase(); |
| | if (title.includes(searchTerm)) { |
| | item.style.display = 'flex'; |
| | } else { |
| | item.style.display = 'none'; |
| | } |
| | }); |
| | } |
| | |
| | |
| | sendBtn.addEventListener('click', sendMessage); |
| | |
| | messageInput.addEventListener('keydown', (e) => { |
| | if (e.key === 'Enter' && !e.shiftKey) { |
| | e.preventDefault(); |
| | sendMessage(); |
| | } |
| | }); |
| | |
| | |
| | messageInput.addEventListener('input', () => { |
| | messageInput.style.height = 'auto'; |
| | messageInput.style.height = `${Math.min(messageInput.scrollHeight, 200)}px`; |
| | }); |
| | |
| | newChatBtn.addEventListener('click', createNewConversation); |
| | |
| | editTitleBtn.addEventListener('click', editConversationTitle); |
| | |
| | settingsBtn.addEventListener('click', () => { |
| | |
| | document.getElementById('theme-select').value = settings.theme; |
| | document.getElementById('language-select').value = settings.language; |
| | document.getElementById('typing-indicator').checked = settings.typingIndicator; |
| | document.getElementById('message-sounds').checked = settings.messageSounds; |
| | document.getElementById('auto-suggestions').checked = settings.autoSuggestions; |
| | document.getElementById('model-select').value = settings.model; |
| | |
| | openModal(settingsModal); |
| | }); |
| | |
| | helpBtn.addEventListener('click', () => { |
| | openModal(helpModal); |
| | }); |
| | |
| | suggestionsBtn.addEventListener('click', () => { |
| | suggestionsPanel.classList.toggle('hidden'); |
| | }); |
| | |
| | closeSettings.addEventListener('click', () => closeModal(settingsModal)); |
| | closeHelp.addEventListener('click', () => closeModal(helpModal)); |
| | closeHelpBtn.addEventListener('click', () => closeModal(helpModal)); |
| | closeEditTitle.addEventListener('click', () => closeModal(editTitleModal)); |
| | cancelEditTitle.addEventListener('click', () => closeModal(editTitleModal)); |
| | saveTitleBtn.addEventListener('click', saveConversationTitle); |
| | saveSettingsBtn.addEventListener('click', saveSettings); |
| | resetSettingsBtn.addEventListener('click', resetSettings); |
| | |
| | |
| | window.addEventListener('click', (e) => { |
| | if (e.target === settingsModal) closeModal(settingsModal); |
| | if (e.target === helpModal) closeModal(helpModal); |
| | if (e.target === editTitleModal) closeModal(editTitleModal); |
| | }); |
| | |
| | |
| | searchConversations.addEventListener('input', searchConversationsHandler); |
| | |
| | |
| | document.querySelectorAll('#suggestions-panel .suggestion-chip').forEach(chip => { |
| | chip.addEventListener('click', () => { |
| | messageInput.value = chip.textContent; |
| | messageInput.focus(); |
| | }); |
| | }); |
| | |
| | |
| | init(); |
| | </script> |
| | </body> |
| | </html> |