Spaces:
Running
Running
Voici une version améliorée de ton code avec plus de fonctionnalités et un meilleur design :
c00a577 verified | console.log("Interface Espace Codage chargée."); | |
| // ===== Configuration API OpenAI ===== | |
| const OPENAI_API_KEY = "sk-votre_cle_api_ici"; // Remplacez par votre clé API | |
| // ===== Thème (light/dark) ===== | |
| const themeToggle = document.getElementById('themeToggle'); | |
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)'); | |
| const storedTheme = localStorage.getItem('theme'); // 'dark' | 'light' | null | |
| const setTheme = (mode) => { | |
| const html = document.documentElement; | |
| if (mode === 'dark') { | |
| html.classList.add('dark'); | |
| localStorage.setItem('theme', 'dark'); | |
| themeToggle.innerHTML = '<i class="fa-solid fa-sun"></i>'; | |
| } else { | |
| html.classList.remove('dark'); | |
| localStorage.setItem('theme', 'light'); | |
| themeToggle.innerHTML = '<i class="fa-solid fa-moon"></i>'; | |
| } | |
| }; | |
| setTheme(storedTheme ? storedTheme : (prefersDark.matches ? 'dark' : 'light')); | |
| themeToggle.addEventListener('click', () => { | |
| const current = document.documentElement.classList.contains('dark') ? 'dark' : 'light'; | |
| setTheme(current === 'dark' ? 'light' : 'dark'); | |
| }); | |
| prefersDark.addEventListener('change', (e) => { | |
| if (!localStorage.getItem('theme')) { | |
| setTheme(e.matches ? 'dark' : 'light'); | |
| } | |
| }); | |
| // ===== Éléments du DOM ===== | |
| const projectList = document.getElementById('projectList'); | |
| const chatMessages = document.getElementById('chatMessages'); | |
| const chatInput = document.getElementById('chatInput'); | |
| const sendBtn = document.getElementById('sendBtn'); | |
| const micBtn = document.getElementById('micBtn'); | |
| const connectBtn = document.getElementById('connectBtn'); | |
| const attachBtn = document.getElementById('attachBtn'); | |
| const codeOutput = document.getElementById('codeOutput').querySelector('code'); | |
| const aiActivityText = document.getElementById('aiActivityText'); | |
| const aiActivityDot = document.getElementById('aiActivityDot'); | |
| const tabButtons = document.querySelectorAll('.tab-btn'); | |
| // ===== Gestion des projets ===== | |
| let projects = JSON.parse(localStorage.getItem('projects') || '[]'); | |
| function saveProjects() { | |
| localStorage.setItem('projects', JSON.stringify(projects)); | |
| } | |
| function renderProjects() { | |
| projectList.innerHTML = ''; | |
| if (projects.length === 0) { | |
| const li = document.createElement('li'); | |
| li.className = 'text-slate-500'; | |
| li.textContent = 'Aucun projet. Cliquez sur "Nouvelle tâche" pour commencer.'; | |
| projectList.appendChild(li); | |
| return; | |
| } | |
| projects.forEach((p, idx) => { | |
| const li = document.createElement('li'); | |
| li.innerHTML = ` | |
| <span>${p}</span> | |
| <span class="actions"> | |
| <button title="Renommer" data-action="rename" data-index="${idx}"><i class="fa-solid fa-pen"></i></button> | |
| <button title="Supprimer" data-action="delete" data-index="${idx}"><i class="fa-solid fa-trash"></i></button> | |
| </span> | |
| `; | |
| projectList.appendChild(li); | |
| }); | |
| } | |
| function addProject(name) { | |
| if (!name) return; | |
| projects.push(name); | |
| saveProjects(); | |
| renderProjects(); | |
| } | |
| function renameProject(index) { | |
| const newName = prompt('Nouveau nom du projet:', projects[index]); | |
| if (newName && newName.trim()) { | |
| projects[index] = newName.trim(); | |
| saveProjects(); | |
| renderProjects(); | |
| } | |
| } | |
| function deleteProject(index) { | |
| if (confirm('Supprimer ce projet ?')) { | |
| projects.splice(index, 1); | |
| saveProjects(); | |
| renderProjects(); | |
| } | |
| } | |
| projectList.addEventListener('click', (e) => { | |
| const btn = e.target.closest('button[data-action]'); | |
| if (!btn) return; | |
| const idx = Number(btn.dataset.index); | |
| if (btn.dataset.action === 'rename') renameProject(idx); | |
| if (btn.dataset.action === 'delete') deleteProject(idx); | |
| }); | |
| document.getElementById('newTaskBtn').addEventListener('click', () => { | |
| const name = prompt('Nom du nouveau projet:'); | |
| if (name) addProject(name.trim()); | |
| }); | |
| document.getElementById('searchBtn').addEventListener('click', () => { | |
| alert('Fonction "Rechercher" à implémenter.'); | |
| }); | |
| document.getElementById('libraryBtn').addEventListener('click', () => { | |
| alert('Fonction "Bibliothèque" à implémenter.'); | |
| }); | |
| renderProjects(); | |
| // ===== Chat (Rosalinda) ===== | |
| function setAIActivity(text, active = true) { | |
| aiActivityText.textContent = text; | |
| aiActivityDot.style.background = active ? 'rgb(34,197,94)' : 'rgb(100,116,139)'; // emerald vs slate | |
| } | |
| function getChatHistory() { | |
| const messages = []; | |
| const nodes = chatMessages.querySelectorAll('.msg'); | |
| nodes.forEach(node => { | |
| const role = node.classList.contains('msg-user') ? 'user' : 'assistant'; | |
| const text = node.querySelector('.bubble').textContent.trim(); | |
| messages.push({ role, content: text }); | |
| }); | |
| return messages; | |
| } | |
| function addMessage(role, content) { | |
| const msg = document.createElement('div'); | |
| msg.className = 'msg ' + (role === 'user' ? 'msg-user' : 'msg-ai'); | |
| if (role === 'user') { | |
| msg.innerHTML = ` | |
| <div class="bubble">${escapeHTML(content)}</div> | |
| <div class="avatar"> | |
| <i class="fa-solid fa-user"></i> | |
| </div> | |
| `; | |
| } else { | |
| msg.innerHTML = ` | |
| <div class="avatar"> | |
| <i class="fa-solid fa-robot"></i> | |
| </div> | |
| <div class="bubble">${content}</div> | |
| `; | |
| } | |
| chatMessages.appendChild(msg); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| } | |
| function escapeHTML(str) { | |
| return str.replace(/[&<>"']/g, (m) => ({ | |
| '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' | |
| }[m])); | |
| } | |
| async function sendMessage() { | |
| const text = chatInput.value.trim(); | |
| if (!text) return; | |
| addMessage('user', text); | |
| chatInput.value = ''; | |
| setAIActivity('Rosalinda réfléchit...', true); | |
| try { | |
| const response = await fetch('https://api.openai.com/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Bearer ${OPENAI_API_KEY}` | |
| }, | |
| body: JSON.stringify({ | |
| model: 'gpt-3.5-turbo', | |
| messages: [ | |
| { role: 'system', content: 'Tu es Rosalinda, une assistante IA utiles et concise.' }, | |
| ...getChatHistory(), | |
| { role: 'user', content: text } | |
| ], | |
| temperature: 0.7 | |
| }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Erreur API: ${response.status} ${response.statusText}`); | |
| } | |
| const data = await response.json(); | |
| const assistantMessage = data.choices?.[0]?.message?.content ?? 'Désolé, aucune réponse.'; | |
| setAIActivity('IA en veille', false); | |
| addMessage('ai', assistantMessage); | |
| codeOutput.textContent = assistantMessage; | |
| } catch (error) { | |
| console.error(error); | |
| setAIActivity('IA en veille', false); | |
| addMessage('ai', 'Une erreur est survenue lors de la communication avec l\'IA.'); | |
| } | |
| } | |
| sendBtn.addEventListener('click', sendMessage); | |
| chatInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') sendMessage(); | |
| }); | |
| const micBtn = document.getElementById('micBtn'); | |
| const languageSelect = document.createElement('select'); | |
| languageSelect.id = 'languageSelect'; | |
| languageSelect.style.marginLeft = '8px'; | |
| languageSelect.style.padding = '6px 8px'; | |
| languageSelect.style.borderRadius = '6px'; | |
| languageSelect.style.border = '1px solid rgb(226 232 240)'; | |
| languageSelect.style.background = 'rgb(248 250 252)'; | |
| languageSelect.style.color = 'inherit'; | |
| languageSelect.innerHTML = ` | |
| <option value="fr-FR">Français (France)</option> | |
| <option value="fr-CA">Français (Canada)</option> | |
| <option value="fr-BE">Français (Belgique)</option> | |
| <option value="fr-CH">Français (Suisse)</option> | |
| <option value="en-US">English (US)</option> | |
| <option value="es-ES">Español (España)</option> | |
| `; | |
| const chatInputParent = chatInput.parentElement; | |
| if (chatInputParent && !document.getElementById('languageSelect')) { | |
| chatInputParent.insertBefore(languageSelect, chatInput); | |
| } | |
| let isListening = false; | |
| let recognition = null; | |
| let interimTranscript = ''; | |
| let lastFinalTranscript = ''; | |
| const SpeechRecognitionAPI = window.SpeechRecognition || window.webkitSpeechRecognition; | |
| function initSpeechRecognition() { | |
| if (!SpeechRecognitionAPI) { | |
| aiActivityText.textContent = 'Reconnaissance vocale non supportée'; | |
| aiActivityDot.style.background = 'rgb(239,68,68)'; | |
| micBtn.disabled = true; | |
| micBtn.title = 'Navigateur non compatible'; | |
| micBtn.style.opacity = '0.6'; | |
| micBtn.style.cursor = 'not-allowed'; | |
| return; | |
| } | |
| recognition = new SpeechRecognitionAPI(); | |
| recognition.lang = languageSelect.value; | |
| recognition.continuous = true; | |
| recognition.interimResults = true; | |
| recognition.maxAlternatives = 1; | |
| recognition.onstart = () => { | |
| isListening = true; | |
| micBtn.classList.add('listening'); | |
| micBtn.innerHTML = '<i class="fas fa-microphone-slash"></i>'; | |
| aiActivityText.textContent = 'Écoute en cours...'; | |
| aiActivityDot.style.background = 'rgb(234,179,8)'; | |
| setAIActivity('Écoute en cours...', true); | |
| }; | |
| recognition.onend = () => { | |
| isListening = false; | |
| micBtn.classList.remove('listening'); | |
| micBtn.innerHTML = '<i class="fas fa-microphone"></i>'; | |
| aiActivityText.textContent = 'IA en veille'; | |
| aiActivityDot.style.background = 'rgb(100,116,139)'; | |
| setAIActivity('IA en veille', false); | |
| interimTranscript = ''; | |
| }; | |
| recognition.onerror = (event) => { | |
| console.error('SpeechRecognition error:', event.error); | |
| let message = 'Erreur de reconnaissance vocale.'; | |
| switch (event.error) { | |
| case 'no-speech': | |
| message = 'Aucune parole détectée. Essayez de parler plus fort.'; | |
| break; | |
| case 'audio-capture': | |
| message = 'Aucun microphone détecté.'; | |
| break; | |
| case 'not-allowed': | |
| message = 'Accès au microphone refusé.'; | |
| break; | |
| case 'network': | |
| message = 'Erreur réseau.'; | |
| break; | |
| } | |
| addMessage('ai', message); | |
| recognition.stop(); | |
| }; | |
| recognition.onresult = (event) => { | |
| interimTranscript = ''; | |
| for (let i = event.resultIndex; i < event.results.length; i++) { | |
| const transcript = event.results[i][0].transcript.trim(); | |
| if (event.results[i].isFinal) { | |
| lastFinalTranscript = transcript; | |
| addMessage('user', transcript); | |
| setAIActivity('Rosalinda réfléchit...', true); | |
| sendToAI(transcript); | |
| } else { | |
| interimTranscript += transcript + ' '; | |
| } | |
| } | |
| if (interimTranscript) { | |
| codeOutput.textContent = `// En écoute...\n${interimTranscript}`; | |
| } | |
| }; | |
| } | |
| function sendToAI(text) { | |
| fetch('https://api.openai.com/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Bearer ${OPENAI_API_KEY}` | |
| }, | |
| body: JSON.stringify({ | |
| model: 'gpt-3.5-turbo', | |
| messages: [ | |
| { role: 'system', content: 'Tu es Rosalinda, une assistante IA utile et concise.' }, | |
| ...getChatHistory(), | |
| { role: 'user', content: text } | |
| ], | |
| temperature: 0.7 | |
| }) | |
| }) | |
| .then(response => { | |
| if (!response.ok) throw new Error(`Erreur API: ${response.status}`); | |
| return response.json(); | |
| }) | |
| .then(data => { | |
| const assistantMessage = data.choices?.[0]?.message?.content ?? 'Désolé, aucune réponse.'; | |
| addMessage('ai', assistantMessage); | |
| codeOutput.textContent = assistantMessage; | |
| }) | |
| .catch(error => { | |
| console.error(error); | |
| addMessage('ai', 'Une erreur est survenue lors de la communication avec l\'IA.'); | |
| }) | |
| .finally(() => { | |
| setAIActivity('IA en veille', false); | |
| }); | |
| } | |
| async function toggleListening() { | |
| if (!recognition) initSpeechRecognition(); | |
| if (!recognition) return; | |
| try { | |
| if (isListening) { | |
| recognition.stop(); | |
| } else { | |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }).catch(() => null); | |
| if (!stream) { | |
| addMessage('ai', 'Impossible d\'accéder au microphone. Vérifiez les permissions.'); | |
| return; | |
| } | |
| stream.getTracks().forEach(t => t.stop()); | |
| recognition.start(); | |
| } | |
| } catch (err) { | |
| console.error(err); | |
| addMessage('ai', 'Erreur lors du démarrage de la reconnaissance vocale.'); | |
| } | |
| } | |
| function stopListening() { | |
| if (recognition && isListening) recognition.stop(); | |
| } | |
| micBtn.addEventListener('click', toggleListening); | |
| languageSelect.addEventListener('change', () => { | |
| if (recognition) recognition.lang = languageSelect.value; | |
| }); | |
| document.addEventListener('keydown', (e) => { | |
| if (e.code === 'Space' && (e.ctrlKey || e.metaKey)) { | |
| e.preventDefault(); | |
| toggleListening(); | |
| } | |
| if (e.key === 'Escape' && isListening) { | |
| stopListening(); | |
| } | |
| }); | |
| // Stop listening on tab change to avoid dangling permissions | |
| document.addEventListener('visibilitychange', () => { | |
| if (document.hidden) stopListening(); | |
| }); | |
| connectBtn.addEventListener('click', () => { | |
| alert('Fonction "Connexion" à implémenter (OAuth, clés API, etc.).'); | |
| }); | |
| attachBtn.addEventListener('click', () => { | |
| alert('Fonction "Ajouter un fichier" à implémenter (FileReader, uploads, etc.).'); | |
| }); | |
| // ===== Onglets ===== | |
| tabButtons.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| tabButtons.forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| const tab = btn.dataset.tab; | |
| const demo = { | |
| code: '// Code généré s’affichera ici...', | |
| dashboard: 'Tableau de bord (graphiques, KPIs)...', | |
| db: 'Visualisation base de données (tables, requêtes)...', | |
| storage: 'Gestion du stockage (fichiers, objets)...', | |
| settings: 'Paramètres (thème, clés API, intégrations)...' | |
| }; | |
| codeOutput.textContent = demo[tab] || '// ...'; | |
| }); | |
| }); | |
| // Surveillance anti-modification non autorisée du résultat | |
| setInterval(() => { | |
| const codeActuel = document.getElementById('resultat').innerText; | |
| if (!codeActuel.includes('Résultat final')) { | |
| alert('⚠️ Alerte : Modification non autorisée détectée. Restauration...'); | |
| location.reload(); | |
| } | |
| }, 5000); | |