Spaces:
Running
Running
| // OpenAI Codex integration for code generation | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const chatForm = document.getElementById('chat-form'); | |
| const chatInput = document.getElementById('chat-input'); | |
| const chatLog = document.getElementById('chat-log'); | |
| const newTaskBtn = document.getElementById('newTaskBtn'); | |
| const projectList = document.getElementById('projectList'); | |
| const codePreview = document.getElementById('code-preview'); | |
| const codeHistory = document.getElementById('code-history'); | |
| const copyCodeBtn = document.getElementById('copy-code'); | |
| const downloadCodeBtn = document.getElementById('download-code'); | |
| const modelSelect = document.getElementById('model-select'); | |
| const highReasoningCheckbox = document.getElementById('high-reasoning'); | |
| const includeCommentsCheckbox = document.getElementById('include-comments'); | |
| const charCount = document.getElementById('char-count'); | |
| const themeToggle = document.getElementById('themeToggle'); | |
| // Theme: respect system, allow toggle | |
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)'); | |
| function applyTheme() { | |
| document.documentElement.classList.toggle('dark', prefersDark.matches); | |
| localStorage.setItem('rosalinda.theme', prefersDark.matches ? 'dark' : 'light'); | |
| } | |
| // Load saved theme or default to system | |
| const savedTheme = localStorage.getItem('rosalinda.theme'); | |
| if (savedTheme) { | |
| document.documentElement.classList.toggle('dark', savedTheme === 'dark'); | |
| } else { | |
| applyTheme(); | |
| } | |
| prefersDark.addEventListener('change', applyTheme); | |
| themeToggle?.addEventListener('click', () => { | |
| document.documentElement.classList.toggle('dark'); | |
| const isDark = document.documentElement.classList.contains('dark'); | |
| localStorage.setItem('rosalinda.theme', isDark ? 'dark' : 'light'); | |
| }); | |
| // Chat handling | |
| function appendMessage({ role, text }) { | |
| const wrapper = document.createElement('div'); | |
| wrapper.className = `message ${role} flex items-start gap-3`; | |
| if (role === 'user') { | |
| wrapper.classList.add('justify-end'); | |
| wrapper.innerHTML = ` | |
| <div class="bubble max-w-[80%] rounded-xl bg-slate-100 px-4 py-2 text-sm dark:bg-slate-700"> | |
| ${escapeHtml(text)} | |
| </div> | |
| <div class="avatar flex h-8 w-8 flex-none items-center justify-center rounded-full bg-secondary/10 text-secondary"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor"> | |
| <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/> | |
| </svg> | |
| </div> | |
| `; | |
| } else { | |
| wrapper.innerHTML = ` | |
| <div class="avatar flex h-8 w-8 flex-none items-center justify-center rounded-full bg-primary/10 text-primary"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor"> | |
| <path d="M2 5a2 2 0 012-2h7a2 2 0 012 2v4a2 2 0 01-2 2H9l-3 3v-3H4a2 2 0 01-2-2V5z" /> | |
| </svg> | |
| </div> | |
| <div class="rounded-xl bg-slate-100 px-4 py-2 text-sm text-slate-800 dark:bg-slate-700 dark:text-slate-100"> | |
| ${escapeHtml(text)} | |
| </div> | |
| `; | |
| } | |
| chatLog.appendChild(wrapper); | |
| chatLog.scrollTop = chatLog.scrollHeight; | |
| } | |
| function escapeHtml(str) { | |
| return str | |
| .replaceAll('&', '&') | |
| .replaceAll('<', '<') | |
| .replaceAll('>', '>') | |
| .replaceAll('"', '"') | |
| .replaceAll("'", '''); | |
| } | |
| chatForm?.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const value = chatInput.value.trim(); | |
| if (!value) return; | |
| appendMessage({ role: 'user', text: value }); | |
| chatInput.value = ''; | |
| updateCharCount(); | |
| // Show typing indicator | |
| const typingIndicator = appendTypingIndicator(); | |
| try { | |
| // Generate code using OpenAI Codex | |
| const code = await generateCode(value); | |
| typingIndicator.remove(); | |
| appendMessage({ role: 'bot', text: "Voici le code généré :", code }); | |
| updateCodePreview(code); | |
| addToHistory(value, code); | |
| } catch (error) { | |
| typingIndicator.remove(); | |
| appendMessage({ role: 'bot', text: `Erreur: ${error.message}` }); | |
| } | |
| }); | |
| // Generate code using OpenAI Codex | |
| async function generateCode(prompt) { | |
| const model = modelSelect.value; | |
| const includeComments = includeCommentsCheckbox.checked; | |
| const highReasoning = highReasoningCheckbox.checked; | |
| // Enhanced prompt for better code generation | |
| let enhancedPrompt = prompt; | |
| if (includeComments) { | |
| enhancedPrompt += "\n\nInclus des commentaires détaillés en français pour expliquer le code."; | |
| } | |
| const requestBody = { | |
| model: model, | |
| input: enhancedPrompt, | |
| max_tokens: 2000, | |
| temperature: 0.7, | |
| }; | |
| if (highReasoning && model.includes('codex')) { | |
| requestBody.reasoning = { effort: "high" }; | |
| } | |
| try { | |
| const response = await fetch('/api/generate-code', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify(requestBody) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP ${response.status}: ${response.statusText}`); | |
| } | |
| const data = await response.json(); | |
| return data.output_text || data.choices[0].text || "Aucune réponse reçue."; | |
| } catch (error) { | |
| // Fallback to local simulation if API is not available | |
| console.log('API not available, using fallback:', error.message); | |
| return generateFallbackCode(prompt); | |
| } | |
| } | |
| // Fallback code generation when API is not available | |
| function generateFallbackCode(prompt) { | |
| const lc = prompt.toLowerCase(); | |
| if (lc.includes('function') || lc.includes('fonction')) { | |
| return `// Fonction générée | |
| function exemple() { | |
| console.log("Hello World!"); | |
| return "Résultat"; | |
| } | |
| // Utilisation | |
| const resultat = exemple(); | |
| console.log(resultat);`; | |
| } | |
| if (lc.includes('react') || lc.includes('composant')) { | |
| return `// Composant React | |
| import React, { useState } from 'react'; | |
| const MonComposant = () => { | |
| const [count, setCount] = useState(0); | |
| return ( | |
| <div className="p-4"> | |
| <h1>Compteur: {count}</h1> | |
| <button | |
| onClick={() => setCount(count + 1)} | |
| className="px-4 py-2 bg-blue-500 text-white rounded" | |
| > | |
| Increment | |
| </button> | |
| </div> | |
| ); | |
| }; | |
| export default MonComposant;`; | |
| } | |
| if (lc.includes('sql') || lc.includes('base de données')) { | |
| return `-- Requête SQL générée | |
| SELECT | |
| id, | |
| nom, | |
| email, | |
| date_creation | |
| FROM utilisateurs | |
| WHERE date_creation >= '2024-01-01' | |
| ORDER BY date_creation DESC | |
| LIMIT 100;`; | |
| } | |
| if (lc.includes('api') || lc.includes('express')) { | |
| return `// API Express.js | |
| const express = require('express'); | |
| const app = express(); | |
| app.use(express.json()); | |
| // Route GET | |
| app.get('/api/users', (req, res) => { | |
| res.json([ | |
| { id: 1, nom: 'Jean', email: 'jean@example.com' }, | |
| { id: 2, nom: 'Marie', email: 'marie@example.com' } | |
| ]); | |
| }); | |
| // Route POST | |
| app.post('/api/users', (req, res) => { | |
| const user = { | |
| id: Date.now(), | |
| ...req.body | |
| }; | |
| res.status(201).json(user); | |
| }); | |
| app.listen(3000, () => { | |
| console.log('Serveur démarré sur le port 3000'); | |
| });`; | |
| } | |
| if (lc.includes('css') || lc.includes('style')) { | |
| return `/* Styles CSS générés */ | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 2rem; | |
| } | |
| .card { | |
| background: white; | |
| border-radius: 8px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| padding: 1.5rem; | |
| margin-bottom: 1rem; | |
| } | |
| .button { | |
| background: #3b82f6; | |
| color: white; | |
| border: none; | |
| padding: 0.75rem 1.5rem; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-weight: 500; | |
| } | |
| .button:hover { | |
| background: #2563eb; | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 1rem; | |
| } | |
| }`; | |
| } | |
| // Default generic code | |
| return `// Code généré pour: ${prompt} | |
| // Exemple d'implémentation | |
| function genererCode() { | |
| console.log("Code généré avec succès!"); | |
| // Votre logique ici | |
| const donnees = [ | |
| { id: 1, nom: "Exemple 1" }, | |
| { id: 2, nom: "Exemple 2" } | |
| ]; | |
| // Traitement des données | |
| donnees.forEach(item => { | |
| console.log(\`ID: \${item.id}, Nom: \${item.nom}\`); | |
| }); | |
| return donnees; | |
| } | |
| // Exécution | |
| genererCode();`; | |
| } | |
| // Append typing indicator | |
| function appendTypingIndicator() { | |
| const wrapper = document.createElement('div'); | |
| wrapper.className = 'message bot flex items-start gap-3'; | |
| wrapper.innerHTML = ` | |
| <div class="flex h-8 w-8 flex-none items-center justify-center rounded-full bg-primary/10 text-primary"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 animate-spin" viewBox="0 0 20 20" fill="currentColor"> | |
| <path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd" /> | |
| </svg> | |
| </div> | |
| <div class="rounded-xl bg-slate-100 px-4 py-2 text-sm text-slate-800 dark:bg-slate-700 dark:text-slate-100"> | |
| Génération du code en cours... | |
| </div> | |
| `; | |
| chatLog.appendChild(wrapper); | |
| chatLog.scrollTop = chatLog.scrollHeight; | |
| return wrapper; | |
| } | |
| // Update code preview | |
| function updateCodePreview(code) { | |
| codePreview.innerHTML = `<code class="language-javascript">${escapeHtml(code)}</code>`; | |
| } | |
| // Add to history | |
| function addToHistory(prompt, code) { | |
| if (codeHistory.querySelector('.text-center')) { | |
| codeHistory.innerHTML = ''; | |
| } | |
| const historyItem = document.createElement('div'); | |
| historyItem.className = 'text-xs p-2 rounded border border-slate-200 dark:border-slate-600 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-700'; | |
| historyItem.innerHTML = ` | |
| <div class="font-medium text-slate-700 dark:text-slate-300 truncate">${escapeHtml(prompt)}</div> | |
| <div class="text-slate-500 dark:text-slate-400 mt-1 text-xs">${escapeHtml(code.substring(0, 50))}...</div> | |
| `; | |
| historyItem.addEventListener('click', () => { | |
| updateCodePreview(code); | |
| chatInput.value = prompt; | |
| updateCharCount(); | |
| }); | |
| codeHistory.insertBefore(historyItem, codeHistory.firstChild); | |
| } | |
| // Copy code to clipboard | |
| copyCodeBtn?.addEventListener('click', async () => { | |
| const code = codePreview.textContent; | |
| try { | |
| await navigator.clipboard.writeText(code); | |
| copyCodeBtn.textContent = 'Copié!'; | |
| setTimeout(() => { | |
| copyCodeBtn.textContent = 'Copier'; | |
| }, 2000); | |
| } catch (error) { | |
| console.error('Erreur lors de la copie:', error); | |
| } | |
| }); | |
| // Download code as file | |
| downloadCodeBtn?.addEventListener('click', () => { | |
| const code = codePreview.textContent; | |
| const blob = new Blob([code], { type: 'text/plain' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `code-${Date.now()}.txt`; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| }); | |
| // Code template buttons | |
| document.querySelectorAll('.code-template').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const prompt = btn.dataset.prompt; | |
| chatInput.value = prompt; | |
| updateCharCount(); | |
| chatInput.focus(); | |
| }); | |
| }); | |
| // Character counter | |
| function updateCharCount() { | |
| const count = chatInput.value.length; | |
| charCount.textContent = `${count} caractères`; | |
| } | |
| chatInput?.addEventListener('input', updateCharCount); | |
| // Initialize character count | |
| updateCharCount(); | |
| // "Nouvelle tâche" quick prompts | |
| newTaskBtn?.addEventListener('click', () => { | |
| const samples = [ | |
| "Crée une fonction JavaScript pour trier un tableau d'objets par date", | |
| "Génère un composant React avec useState pour un formulaire de contact", | |
| "Crée une API REST Express avec authentification JWT", | |
| "Génère du CSS pour une carte responsive avec animations", | |
| "Crée une requête SQL pour récupérer les ventes par mois" | |
| ]; | |
| const pick = samples[Math.floor(Math.random() * samples.length)]; | |
| chatInput.value = pick; | |
| updateCharCount(); | |
| chatInput.focus(); | |
| }); | |
| // Seed a couple of example "projects" | |
| const demoProjects = [ | |
| { name: "API Utilisateurs", status: "Terminé", type: "API" }, | |
| { name: "Composant React", status: "En cours", type: "Frontend" }, | |
| { name: "Base de données", status: "Brouillon", type: "SQL" }, | |
| ]; | |
| function renderProjects() { | |
| if (!projectList) return; | |
| projectList.innerHTML = ""; | |
| const list = document.createElement('div'); | |
| list.className = "space-y-2"; | |
| demoProjects.forEach(p => { | |
| const row = document.createElement('div'); | |
| row.className = "flex items-center justify-between rounded-lg border border-slate-200 p-3 text-sm dark:border-slate-700"; | |
| row.innerHTML = ` | |
| <div class="flex items-center gap-3"> | |
| <span class="inline-flex h-2.5 w-2.5 rounded-full bg-secondary"></span> | |
| <div> | |
| <div class="font-medium">${escapeHtml(p.name)}</div> | |
| <div class="text-xs text-slate-500 dark:text-slate-400">${escapeHtml(p.type)} • ${escapeHtml(p.status)}</div> | |
| </div> | |
| </div> | |
| <button class="rounded-md border border-slate-200 bg-white px-2 py-1 text-xs hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-800 dark:hover:bg-slate-700">Ouvrir</button> | |
| `; | |
| list.appendChild(row); | |
| }); | |
| projectList.appendChild(list); | |
| } | |
| renderProjects(); | |
| }); | |