File size: 14,076 Bytes
f5f1561
 
 
 
37af68a
 
 
 
 
 
 
 
f5f1561
 
 
 
 
 
 
 
37af68a
 
f5f1561
37af68a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f5f1561
37af68a
 
 
f5f1561
37af68a
 
f5f1561
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37af68a
 
f5f1561
 
 
 
 
 
 
 
 
 
37af68a
f5f1561
 
 
 
 
 
 
 
 
 
37af68a
f5f1561
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37af68a
f5f1561
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37af68a
f5f1561
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37af68a
 
f5f1561
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37af68a
 
 
f5f1561
 
 
 
 
37af68a
 
 
f5f1561
37af68a
 
 
 
 
f5f1561
 
 
37af68a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329b28c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437



// 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('&', '&amp;')
      .replaceAll('<', '&lt;')
      .replaceAll('>', '&gt;')
      .replaceAll('"', '&quot;')
      .replaceAll("'", '&#39;');
  }
  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();
});