|
|
|
|
|
|
| |
| 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'); |
| |
| 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'); |
| } |
| |
| 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'); |
| }); |
|
|
| |
| 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(); |
| |
| |
| const typingIndicator = appendTypingIndicator(); |
| |
| try { |
| |
| 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}` }); |
| } |
| }); |
|
|
| |
| async function generateCode(prompt) { |
| const model = modelSelect.value; |
| const includeComments = includeCommentsCheckbox.checked; |
| const highReasoning = highReasoningCheckbox.checked; |
| |
| |
| 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) { |
| |
| console.log('API not available, using fallback:', error.message); |
| return generateFallbackCode(prompt); |
| } |
| } |
|
|
| |
| 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; |
| } |
| }`; |
| } |
| |
| |
| 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();`; |
| } |
|
|
| |
| 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; |
| } |
|
|
| |
| function updateCodePreview(code) { |
| codePreview.innerHTML = `<code class="language-javascript">${escapeHtml(code)}</code>`; |
| } |
|
|
| |
| 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); |
| } |
|
|
| |
| 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); |
| } |
| }); |
|
|
| |
| 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); |
| }); |
|
|
| |
| document.querySelectorAll('.code-template').forEach(btn => { |
| btn.addEventListener('click', () => { |
| const prompt = btn.dataset.prompt; |
| chatInput.value = prompt; |
| updateCharCount(); |
| chatInput.focus(); |
| }); |
| }); |
|
|
| |
| function updateCharCount() { |
| const count = chatInput.value.length; |
| charCount.textContent = `${count} caractères`; |
| } |
|
|
| chatInput?.addEventListener('input', updateCharCount); |
|
|
| |
| updateCharCount(); |
|
|
| |
| 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(); |
| }); |
|
|
| |
| 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(); |
| }); |
|
|