Spaces:
Configuration error
Configuration error
| class CustomChat extends HTMLElement { | |
| constructor() { | |
| super(); | |
| this._logs = [ | |
| "Rosalinda • Démarrage autonome...", | |
| "Chargement des services locaux...", | |
| "Initialisation sécurisée...", | |
| "Prêt à aider ✅" | |
| ]; | |
| this._messages = [ | |
| { role: "assistant", content: "Bonjour ! Je suis Rosalinda, votre IA de création. Je peux vous aider avec des projets, du code, des images et des vidéos. Que souhaitez-vous créer aujourd'hui ?" } | |
| ]; | |
| this._files = []; | |
| this._speechState = "idle"; // idle, listening, unsupported, denied | |
| } | |
| connectedCallback() { | |
| this.attachShadow({ mode: 'open' }); | |
| this.shadowRoot.innerHTML = ` | |
| <style> | |
| :host { | |
| display: none; | |
| flex-direction: column; | |
| height: 100%; | |
| background: #0b0f19; | |
| color: white; | |
| position: relative; | |
| width: 100%; | |
| } | |
| :host(.active) { | |
| display: flex; | |
| } | |
| .chat-container { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 1rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| } | |
| .message { | |
| margin-bottom: 0.5rem; | |
| padding: 0.75rem 1rem; | |
| border-radius: 0.75rem; | |
| max-width: 80%; | |
| font-size: 0.875rem; | |
| line-height: 1.4; | |
| } | |
| .user-message { | |
| background: #3b82f6; | |
| margin-left: auto; | |
| border: 1px solid #2563eb; | |
| } | |
| .ai-message { | |
| background: #1e293b; | |
| margin-right: auto; | |
| border: 1px solid #334155; | |
| } | |
| .computer-screen { | |
| background: rgba(255, 255, 255, 0.05); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 0.5rem; | |
| margin: 0.5rem; | |
| overflow: hidden; | |
| } | |
| .screen-header { | |
| display: flex; | |
| justify-content: space-between; | |
| padding: 0.5rem 1rem; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); | |
| font-size: 0.75rem; | |
| font-weight: 500; | |
| } | |
| .logs-container { | |
| height: 7rem; | |
| overflow-y: auto; | |
| padding: 0.5rem 1rem; | |
| font-family: monospace; | |
| font-size: 0.7rem; | |
| line-height: 1.5; | |
| color: rgba(255, 255, 255, 0.7); | |
| } | |
| .log-line { | |
| white-space: pre-wrap; | |
| margin-bottom: 0.25rem; | |
| } | |
| .input-container { | |
| padding: 1rem; | |
| background: #0f172a; | |
| border-top: 1px solid #1e293b; | |
| position: sticky; | |
| bottom: 0; | |
| } | |
| .input-area { | |
| display: flex; | |
| gap: 0.5rem; | |
| align-items: center; | |
| } | |
| input { | |
| flex: 1; | |
| padding: 0.75rem; | |
| border-radius: 0.5rem; | |
| border: 1px solid #334155; | |
| background: #1e293b; | |
| color: white; | |
| font-size: 0.875rem; | |
| } | |
| .action-button { | |
| width: 2.25rem; | |
| height: 2.25rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 0.5rem; | |
| background: transparent; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| color: white; | |
| cursor: pointer; | |
| } | |
| .action-button:hover { | |
| background: rgba(255, 255, 255, 0.1); | |
| } | |
| .send-button { | |
| background: #3b82f6; | |
| border: none; | |
| } | |
| .send-button:hover { | |
| background: #2563eb; | |
| } | |
| .mic-button.listening { | |
| background: white; | |
| color: black; | |
| } | |
| .file-input { | |
| display: none; | |
| } | |
| .files-info { | |
| font-size: 0.7rem; | |
| color: rgba(255, 255, 255, 0.6); | |
| padding: 0.25rem 0.5rem; | |
| } | |
| </style> | |
| <div class="chat-container" id="chat"> | |
| ${this._messages.map(msg => ` | |
| <div class="message ${msg.role === 'user' ? 'user-message' : 'ai-message'}"> | |
| ${msg.content} | |
| </div> | |
| `).join('')} | |
| </div> | |
| <div class="computer-screen"> | |
| <div class="screen-header"> | |
| <span>Voir l'ordinateur de Espace Codage</span> | |
| <span id="micStatus">En ligne</span> | |
| </div> | |
| <div class="logs-container" id="logsContainer"> | |
| ${this._logs.map(log => `<div class="log-line">${log}</div>`).join('')} | |
| </div> | |
| </div> | |
| <div class="input-container"> | |
| <div class="input-area"> | |
| <button class="action-button" title="Ajouter des fichiers" id="fileButton"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none"> | |
| <path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round" /> | |
| </svg> | |
| </button> | |
| <button class="action-button" title="Connecter des applications" id="connectButton"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none"> | |
| <path d="M9 7v5M15 7v5M7 12h10M10 12v4a4 4 0 0 0 4 4h1" stroke="currentColor" stroke-width="2" stroke-linecap="round" /> | |
| <path d="M6 7h12" stroke="currentColor" stroke-width="2" stroke-linecap="round" /> | |
| </svg> | |
| </button> | |
| <input type="text" class="message-input" placeholder="Envoyer un message à Rosalinda" id="messageInput"> | |
| <button class="action-button mic-button" title="Saisie vocale" id="micButton"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none"> | |
| <path d="M12 14a3 3 0 0 0 3-3V7a3 3 0 0 0-6 0v4a3 3 0 0 0 3 3Z" stroke="currentColor" stroke-width="2" /> | |
| <path d="M19 11a7 7 0 0 1-14 0" stroke="currentColor" stroke-width="2" stroke-linecap="round" /> | |
| <path d="M12 18v3" stroke="currentColor" stroke-width="2" stroke-linecap="round" /> | |
| <path d="M8 21h8" stroke="currentColor" stroke-width="2" stroke-linecap="round" /> | |
| </svg> | |
| </button> | |
| <button class="action-button send-button" title="Envoyer" id="sendButton"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none"> | |
| <path d="M5 12h12" stroke="currentColor" stroke-width="2" stroke-linecap="round" /> | |
| <path d="M13 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> | |
| </svg> | |
| </button> | |
| <input type="file" class="file-input" id="fileInput" multiple> | |
| </div> | |
| <div class="files-info" id="filesInfo"></div> | |
| </div> | |
| `; | |
| this._setupEventListeners(); | |
| this._startLogsSimulation(); | |
| } | |
| _setupEventListeners() { | |
| const shadow = this.shadowRoot; | |
| const fileButton = shadow.getElementById('fileButton'); | |
| const fileInput = shadow.getElementById('fileInput'); | |
| const connectButton = shadow.getElementById('connectButton'); | |
| const micButton = shadow.getElementById('micButton'); | |
| const sendButton = shadow.getElementById('sendButton'); | |
| const messageInput = shadow.getElementById('messageInput'); | |
| const filesInfo = shadow.getElementById('filesInfo'); | |
| const micStatus = shadow.getElementById('micStatus'); | |
| const chatContainer = shadow.getElementById('chat'); | |
| let recognition; | |
| let isListening = false; | |
| // Enhanced microphone functionality | |
| micButton.addEventListener('click', () => { | |
| if (isListening) { | |
| this._stopSpeechRecognition(recognition, micButton, micStatus); | |
| isListening = false; | |
| return; | |
| } | |
| if (!('webkitSpeechRecognition' in window || 'SpeechRecognition' in window)) { | |
| this._addMessage('assistant', 'La reconnaissance vocale n\'est pas supportée par votre navigateur. Essayez Chrome ou Edge.'); | |
| return; | |
| } | |
| recognition = this._startSpeechRecognition(micButton, micStatus, messageInput); | |
| isListening = true; | |
| // Add visual feedback | |
| micButton.classList.add('listening'); | |
| micStatus.textContent = '🎤 En écoute...'; | |
| this._addMessage('assistant', 'Je vous écoute... Parlez maintenant.'); | |
| }); | |
| // Enhanced file upload with drag & drop | |
| fileButton.addEventListener('click', () => fileInput.click()); | |
| fileInput.addEventListener('change', (e) => { | |
| this._handleFiles(e.target.files, filesInfo); | |
| }); | |
| // Drag & drop support | |
| chatContainer.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| chatContainer.style.border = '2px dashed #3b82f6'; | |
| }); | |
| chatContainer.addEventListener('dragleave', () => { | |
| chatContainer.style.border = 'none'; | |
| }); | |
| chatContainer.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| chatContainer.style.border = 'none'; | |
| if (e.dataTransfer.files.length) { | |
| this._handleFiles(e.dataTransfer.files, filesInfo); | |
| } | |
| }); | |
| // Connect apps with Rosalinda integration | |
| connectButton.addEventListener('click', () => { | |
| this._addMessage('assistant', | |
| `🔌 Rosalinda peut se connecter à: | |
| - Votre ordinateur (fichiers locaux) | |
| - Votre micro (dictée vocale) | |
| - Votre caméra (analyse visuelle) | |
| - Vos applications préférées | |
| Que souhaitez-vous connecter en premier ?`); | |
| }); | |
| // Enhanced send message with files | |
| const sendMessage = () => { | |
| const message = messageInput.value.trim(); | |
| if (message || this._files.length) { | |
| this._addMessage('user', message); | |
| messageInput.value = ''; | |
| // Simulate Rosalinda processing | |
| setTimeout(() => { | |
| let response; | |
| if (this._files.length > 0) { | |
| response = `J'ai bien reçu ${this._files.length} fichier(s). Je les analyse maintenant...`; | |
| this._files = []; | |
| filesInfo.innerHTML = ''; | |
| } else { | |
| response = this._generateRosalindaResponse(message); | |
| } | |
| this._addMessage('assistant', response); | |
| }, 1000); | |
| } | |
| }; | |
| // Helper methods | |
| _handleFiles(files, filesInfo) { | |
| this._files = Array.from(files); | |
| if (this._files.length) { | |
| filesInfo.innerHTML = ` | |
| <div style="display:flex; gap:0.5rem; align-items:center;"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"> | |
| <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /> | |
| <polyline points="14 2 14 8 20 8" /> | |
| </svg> | |
| ${this._files.length} fichier(s) sélectionné(s) | |
| <button id="clearFiles" style="margin-left:auto; background:none; border:none; color:#3b82f6; cursor:pointer;"> | |
| Effacer | |
| </button> | |
| </div> | |
| `; | |
| shadow.getElementById('clearFiles').addEventListener('click', () => { | |
| this._files = []; | |
| filesInfo.innerHTML = ''; | |
| fileInput.value = ''; | |
| }); | |
| } else { | |
| filesInfo.textContent = ''; | |
| } | |
| } | |
| _generateRosalindaResponse(message) { | |
| if (message.toLowerCase().includes('projet')) { | |
| return `Pour votre projet, voici ce que je propose: | |
| 1. Structure claire et modulaire | |
| 2. Design cohérent avec votre identité | |
| 3. Tests automatisés | |
| 4. Documentation complète | |
| Par où commençons-nous ?`; | |
| } else if (message.toLowerCase().includes('image')) { | |
| return "Je peux générer des images personnalisées. Décrivez-moi ce que vous imaginez (couleurs, style, éléments)."; | |
| } else if (message.toLowerCase().includes('vidéo')) { | |
| return "Pour la vidéo, je peux vous aider avec:\n- Scénarisation\n- Montage\n- Effets\n- Sous-titres\nQuel aspect souhaitez-vous développer ?"; | |
| } else { | |
| const responses = [ | |
| "J'ai bien compris votre demande. Voici mes suggestions...", | |
| "Analyse terminée. Voici ce que je propose:", | |
| "Excellente idée ! Voici comment nous pourrions procéder:", | |
| "Je peux vous aider avec cela. Voici un plan d'action:" | |
| ]; | |
| return responses[Math.floor(Math.random() * responses.length)]; | |
| } | |
| } | |
| sendButton.addEventListener('click', sendMessage); | |
| messageInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') sendMessage(); | |
| }); | |
| } | |
| _addMessage(role, content) { | |
| this._messages.push({ role, content }); | |
| this._renderMessages(); | |
| } | |
| _renderMessages() { | |
| const chatContainer = this.shadowRoot.getElementById('chat'); | |
| chatContainer.innerHTML = this._messages.map(msg => ` | |
| <div class="message ${msg.role === 'user' ? 'user-message' : 'ai-message'}"> | |
| ${msg.content} | |
| </div> | |
| `).join(''); | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| } | |
| _startLogsSimulation() { | |
| const logsContainer = this.shadowRoot.getElementById('logsContainer'); | |
| setInterval(() => { | |
| const actions = [ | |
| 'Analyse du projet', | |
| 'Optimisation des ressources', | |
| 'Génération de contenu', | |
| 'Connexion aux services', | |
| 'Préparation des résultats' | |
| ]; | |
| const randomAction = actions[Math.floor(Math.random() * actions.length)]; | |
| this._logs.push(`Rosalinda • ${new Date().toLocaleTimeString()} • ${randomAction}...`); | |
| if (this._logs.length > 20) this._logs.shift(); | |
| logsContainer.innerHTML = this._logs.map(log => | |
| `<div class="log-line">${log}</div>` | |
| ).join(''); | |
| logsContainer.scrollTop = logsContainer.scrollHeight; | |
| }, 1400); | |
| } | |
| _startSpeechRecognition(micButton, micStatus, messageInput) { | |
| const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; | |
| const recognition = new SpeechRecognition(); | |
| recognition.lang = 'fr-FR'; | |
| recognition.interimResults = true; | |
| recognition.continuous = true; | |
| micButton.classList.add('listening'); | |
| micStatus.textContent = 'Micro actif...'; | |
| recognition.onresult = (event) => { | |
| let transcript = ''; | |
| for (let i = event.resultIndex; i < event.results.length; i++) { | |
| transcript += event.results[i][0].transcript; | |
| } | |
| messageInput.value = transcript; | |
| }; | |
| recognition.onerror = (event) => { | |
| console.error('Speech recognition error', event.error); | |
| micButton.classList.remove('listening'); | |
| micStatus.textContent = 'Erreur micro'; | |
| setTimeout(() => micStatus.textContent = 'En ligne', 2000); | |
| }; | |
| recognition.onend = () => { | |
| micButton.classList.remove('listening'); | |
| micStatus.textContent = 'En ligne'; | |
| }; | |
| recognition.start(); | |
| return recognition; | |
| } | |
| _stopSpeechRecognition(recognition, micButton, micStatus) { | |
| if (recognition) { | |
| recognition.stop(); | |
| } | |
| micButton.classList.remove('listening'); | |
| micStatus.textContent = 'En ligne'; | |
| } | |
| `; | |
| } | |
| } | |
| customElements.define('custom-chat', CustomChat); |