Abmacode12 commited on
Commit
3132285
·
verified ·
1 Parent(s): ce4176c

from pathlib import Path

Browse files

# Créer le dossier pour les fichiers du thème complet
theme_path = Path("/mnt/data/espace_codage_theme")
theme_path.mkdir(parents=True, exist_ok=True)

# 1. index.html
html_code = """<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Espace Codage</title>
<link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"/>
</head>
<body>
<div class="container">

<!-- Colonne 1 : Navigation -->
<aside class="sidebar">
<div class="header">
<a href="#" class="logo">Espace Codage</a>
</div>
<nav class="menu">
<button>Nouvelle tâche</button>
<button>Rechercher</button>
<button>Bibliothèque</button>
</nav>
<div class="projects">
<h3>Projets</h3>
<ul id="projectList">
<!-- Projets ajoutés ici -->
</ul>
</div>
</aside>

<!-- Colonne 2 : Chat IA Rosalinda -->
<main class="chat">
<div class="chat-header">
<h2>Espace Codage – Rosalinda</h2>
<div class="activity-monitor">[ Activité IA en cours... ]</div>
</div>
<div class="chat-messages" id="chatMessages">
<!-- Messages Rosalinda ici -->
</div>
<div class="chat-input">
<button title="Ajouter un fichier"><i class="fas fa-plus"></i></button>
<button title="Connexion"><i class="fas fa-plug"></i></button>
<input type="text" placeholder="Écrire à Rosalinda..." />
<button title="Micro"><i class="fas fa-microphone"></i></button>
<button title="Envoyer"><i class="fas fa-paper-plane"></i></button>
</div>
</main>

<!-- Colonne 3 : Résultat & Code -->
<section class="output">
<div class="output-tabs">
<button><i class="fas fa-code"></i> Code</button>
<button><i class="fas fa-chart-pie"></i> Tableau de bord</button>
<button><i class="fas fa-database"></i> Base de données</button>
<button><i class="fas fa-folder"></i> Stockage</button>
<button><i class="fas fa-cog"></i> Paramètres</button>
</div>
<div class="code-output" id="codeOutput">
<pre><code>// Code généré s'affichera ici...</code></pre>
</div>
<div class="result-output">
<p>Résultat final ici (image, vidéo ou page)...</p>
</div>
</section>

</div>
<script src="script.js"></script>
</body>
</html>
"""

# 2. styles.css
css_code = """
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
body, html {
height: 100%;
width: 100%;
}
.container {
display: flex;
height: 100vh;
}
/* Colonne 1 */
.sidebar {
width: 20%;
background: #f1f1f1;
border-right: 1px solid #ccc;
padding: 20px;
display: flex;
flex-direction: column;
}
.sidebar .logo {
font-weight: bold;
font-size: 18px;
text-decoration: none;
color: #222;
margin-bottom: 20px;
display: block;
}
.menu button {
display: block;
width: 100%;
margin-bottom: 10px;
padding: 10px;
background: #e0e0e0;
border: none;
cursor: pointer;
}
.projects h3 {
margin-top: 20px;
margin-bottom: 10px;
}
.projects ul {
list-style: none;
}
.projects li {
padding: 5px 0;
border-bottom: 1px solid #ccc;
}
/* Colonne 2 */
.chat {
width: 45%;
display: flex;
flex-direction: column;
padding: 20px;
}
.chat-header h2 {
font-size: 18px;
margin-bottom: 5px;
}
.activity-monitor {
font-size: 12px;
color: gray;
margin-bottom: 15px;
}
.chat-messages {
flex: 1;
background: #fafafa;
padding: 10px;
border: 1px solid #ddd;
overflow-y: auto;
}
.chat-input {
display: flex;
align-items: center;
gap: 8px;
margin-top: 10px;
}
.chat-input input {
flex: 1;
padding: 10px;
}
.chat-input button {
background: #ddd;
border: none;
padding: 10px;
cursor: pointer;
}
/* Colonne 3 */
.output {
width: 35%;
background: #fcfcfc;
border-left: 1px solid #ccc;
display: flex;
flex-direction: column;
padding: 20px;
}
.output-tabs {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.output-tabs button {
flex: 1;
padding: 8px;
font-size: 12px;
background: #e8e8e8;
border: none;
cursor: pointer;
display: flex;
align-items: center;
gap: 5px;
}
.code-output {
flex: 1;
background: #000;
color: #0f0;
padding: 10px;
font-family: monospace;
overflow-y: auto;
margin-bottom: 10px;
}
.result-output {
background: #fff;
border: 1px solid #ccc;
padding: 10px;
text-align: center;
}
"""

# 3. script.js
js_code = """console.log("Interface Espace Codage chargée.");

// Tu pourras ajouter ici les fonctions pour Rosalinda plus tard
"""

# Sauvegarder les fichiers
(theme_path / "index.html").write_text(html_code, encoding="utf-8")
(theme_path / "styles.css").write_text(css_code, encoding="utf-8")
(theme_path / "script.js").write_text(js_code, encoding="utf-8")

# Créer le ZIP pour téléchargement
import shutil
zip_file = "/mnt/data/espace_codage_theme.zip"
shutil.make_archive(str(theme_path), 'zip', str(theme_path))

zip_file
Résultat
'/mnt/data/espace_codage_theme.zip'
✅ Le thème complet Espace Codage que tu m’as décrit est prêt !

👉 Clique ici pour télécharger le projet complet

Le dossier contient :

index.html — structure HTML en 3 colonnes avec tous les éléments que tu as demandés

styles.css — design avec icônes et mise en page

script.js — fichier JavaScript prêt à être enrichi

Intégration de Font Awesome pour toutes les icônes personnalisées

Tu peux :

Extraire le .zip sur ton ordinateur

Ouvrir index.html dans ton navigateur pour voir le rendu

Modifier les fichiers à volonté

Si tu veux maintenant ajouter :

Des actions à Rosalinda (texte / image / vidéo)

Des appels API (OpenAI, Whisper, DALL·E, etc.)

L’enregistrement des projets dans une base de données

Files changed (4) hide show
  1. components/ai-assistant.js +60 -0
  2. index.html +54 -1702
  3. script.js +2 -68
  4. style.css +129 -216
components/ai-assistant.js ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class AIAssistant extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ .assistant-container {
7
+ background-color: #f8fafc;
8
+ border-radius: 0.5rem;
9
+ padding: 1rem;
10
+ margin-bottom: 1rem;
11
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
12
+ }
13
+ .assistant-header {
14
+ display: flex;
15
+ align-items: center;
16
+ margin-bottom: 0.5rem;
17
+ }
18
+ .assistant-avatar {
19
+ width: 2rem;
20
+ height: 2rem;
21
+ border-radius: 50%;
22
+ background-color: #6366f1;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: center;
26
+ margin-right: 0.5rem;
27
+ color: white;
28
+ }
29
+ .assistant-name {
30
+ font-weight: 600;
31
+ color: #1e293b;
32
+ }
33
+ .assistant-status {
34
+ font-size: 0.75rem;
35
+ color: #64748b;
36
+ margin-left: auto;
37
+ }
38
+ .assistant-message {
39
+ font-size: 0.875rem;
40
+ color: #334155;
41
+ line-height: 1.5;
42
+ }
43
+ </style>
44
+ <div class="assistant-container">
45
+ <div class="assistant-header">
46
+ <div class="assistant-avatar">
47
+ <i data-feather="cpu"></i>
48
+ </div>
49
+ <span class="assistant-name">Rosalinda</span>
50
+ <span class="assistant-status">Online</span>
51
+ </div>
52
+ <div class="assistant-message">
53
+ <slot></slot>
54
+ </div>
55
+ </div>
56
+ `;
57
+ }
58
+ }
59
+
60
+ customElements.define('ai-assistant', AIAssistant);
index.html CHANGED
@@ -4,1712 +4,64 @@
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Rosalinda IA - Espace Codage Indépendant</title>
8
  <link rel="stylesheet" href="style.css">
9
- <script src="https://cdn.tailwindcss.com"></script>
10
- <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
11
- <script src="https://unpkg.com/feather-icons"></script>
12
- </head>
13
- <body class="bg-gray-100 font-sans">
14
- <div class="flex h-screen">
15
- <custom-sidebar></custom-sidebar>
16
- <div class="flex-1 flex flex-col">
17
- <custom-header></custom-header>
18
- <main class="flex-1 overflow-hidden">
19
- <custom-chat id="rosalinda-chat"></custom-chat>
20
- <custom-code-editor></custom-code-editor>
21
- </main>
22
- <custom-footer></custom-footer>
23
- </div>
24
- <custom-code-preview></custom-code-preview>
25
- </div>
26
-
27
- <script>
28
- feather.replace();
29
- </script>
30
- <script src="components/sidebar.js"></script>
31
- <script src="components/header.js"></script>
32
- <script src="components/footer.js"></script>
33
- <script src="components/chat.js"></script>
34
- <script src="components/code-editor.js"></script>
35
- <script src="components/code-preview.js"></script>
36
- <script src="components/file-upload.js"></script>
37
- <script src="components/partnership-agreement.js"></script>
38
- <script src="script.js"></script>
39
- </body>
40
- </html>
41
- Composant complet : components/ChatFooterBar.tsx
42
-
43
- ✅ Fonctionnel :
44
-
45
- + = ouvrir un sélecteur de fichiers
46
-
47
- prise = bouton “connecter des applications”
48
-
49
- micro = dictée vocale (Web Speech API)
50
-
51
- flèche = envoyer
52
-
53
- Placeholder : “Envoyer un message à Espace Codage”
54
-
55
- Au-dessus : panneau “Voir l’ordinateur de Espace Codage” qui défile (logs)
56
-
57
- "use client";
58
-
59
- import React, { useEffect, useMemo, useRef, useState } from "react";
60
-
61
- type Props = {
62
- onSend: (text: string, files?: File[]) => Promise<void> | void;
63
- onConnectApps?: () => void;
64
- };
65
-
66
- type SpeechState = "idle" | "listening" | "unsupported" | "denied";
67
-
68
- export default function ChatFooterBar({ onSend, onConnectApps }: Props) {
69
- const [text, setText] = useState("");
70
- const [files, setFiles] = useState<File[]>([]);
71
- const [speech, setSpeech] = useState<SpeechState>("idle");
72
- const [logs, setLogs] = useState<string[]>([
73
- "Espace Codage • Démarrage…",
74
- "Chargement des services…",
75
- "Prêt ✅"
76
- ]);
77
-
78
- const fileInputRef = useRef<HTMLInputElement | null>(null);
79
- const logsRef = useRef<HTMLDivElement | null>(null);
80
-
81
- // ---- “Ordinateur” qui défile (simulation de flux)
82
- useEffect(() => {
83
- const t = setInterval(() => {
84
- setLogs((prev) => {
85
- const next = [...prev, `Console • ${new Date().toLocaleTimeString()} • activité…`];
86
- return next.length > 200 ? next.slice(-200) : next;
87
- });
88
- }, 1400);
89
- return () => clearInterval(t);
90
- }, []);
91
-
92
- useEffect(() => {
93
- if (!logsRef.current) return;
94
- logsRef.current.scrollTop = logsRef.current.scrollHeight;
95
- }, [logs]);
96
-
97
- // ---- Dictée vocale (Web Speech API)
98
- const SpeechRecognitionImpl = useMemo(() => {
99
- if (typeof window === "undefined") return null;
100
- const w = window as any;
101
- return w.SpeechRecognition || w.webkitSpeechRecognition || null;
102
- }, []);
103
-
104
- const startDictation = async () => {
105
- if (!SpeechRecognitionImpl) {
106
- setSpeech("unsupported");
107
- return;
108
- }
109
-
110
- // Certains navigateurs demandent une permission micro. On démarre simplement la reco.
111
- const recog = new SpeechRecognitionImpl();
112
- recog.lang = "fr-FR";
113
- recog.interimResults = true;
114
- recog.continuous = true;
115
-
116
- setSpeech("listening");
117
-
118
- let finalText = "";
119
-
120
- recog.onresult = (event: any) => {
121
- let interim = "";
122
- for (let i = event.resultIndex; i < event.results.length; i++) {
123
- const transcript = event.results[i][0].transcript;
124
- if (event.results[i].isFinal) finalText += transcript;
125
- else interim += transcript;
126
- }
127
- setText((prev) => {
128
- const base = prev.trim().length ? prev.trim() + " " : "";
129
- const merged = (base + (finalText + interim)).replace(/\s+/g, " ").trim();
130
- return merged;
131
- });
132
- };
133
-
134
- recog.onerror = (e: any) => {
135
- // not-allowed / service-not-allowed / audio-capture, etc.
136
- if (String(e?.error).includes("not-allowed") || String(e?.error).includes("service-not-allowed")) {
137
- setSpeech("denied");
138
- } else {
139
- setSpeech("idle");
140
- }
141
- try { recog.stop(); } catch {}
142
- };
143
-
144
- recog.onend = () => {
145
- setSpeech((s) => (s === "listening" ? "idle" : s));
146
- };
147
-
148
- try {
149
- recog.start();
150
- } catch {
151
- setSpeech("idle");
152
- }
153
- };
154
-
155
- const stopDictation = () => {
156
- // On ne garde pas l’instance globale ici. UX simple : re-cliquer micro stoppe via rechargement de state.
157
- // Si tu veux un stop “dur”, on peut stocker l’instance dans un ref.
158
- setSpeech("idle");
159
- };
160
-
161
- const handlePickFiles = () => fileInputRef.current?.click();
162
-
163
- const handleFilesChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
164
- const list = Array.from(e.target.files ?? []);
165
- setFiles(list);
166
- };
167
-
168
- const handleSend = async () => {
169
- const msg = text.trim();
170
- if (!msg && files.length === 0) return;
171
-
172
- await onSend(msg, files);
173
-
174
- setText("");
175
- setFiles([]);
176
- if (fileInputRef.current) fileInputRef.current.value = "";
177
- };
178
-
179
- return (
180
- <div className="w-full">
181
- {/* --- Petit écran au-dessus (ordinateur) --- */}
182
- <div className="mb-2 rounded-xl border border-white/10 bg-white/5 overflow-hidden">
183
- <div className="flex items-center justify-between px-3 py-2 border-b border-white/10">
184
- <div className="text-xs font-semibold text-white/80">Voir l’ordinateur de Espace Codage</div>
185
- <div className="text-[11px] text-white/50">
186
- {speech === "listening" ? "Micro actif…" : "En ligne"}
187
- </div>
188
- </div>
189
-
190
- <div
191
- ref={logsRef}
192
- className="h-28 overflow-auto px-3 py-2 text-[11px] leading-5 text-white/70"
193
- >
194
- {logs.map((l, idx) => (
195
- <div key={idx} className="whitespace-pre-wrap">
196
- {l}
197
- </div>
198
- ))}
199
- </div>
200
- </div>
201
-
202
- {/* --- Barre du bas (comme la capture) --- */}
203
- <div className="flex items-center gap-2 rounded-xl border border-white/10 bg-white/5 px-2 py-2">
204
- {/* + Fichiers */}
205
- <button
206
- type="button"
207
- onClick={handlePickFiles}
208
- className="h-9 w-9 grid place-items-center rounded-lg hover:bg-white/10 border border-white/10"
209
- aria-label="Ajouter des fichiers"
210
- title="Ajouter des fichiers"
211
- >
212
- <PlusIcon />
213
- </button>
214
-
215
- {/* Connecter des apps (prise) */}
216
- <button
217
- type="button"
218
- onClick={onConnectApps}
219
- className="h-9 w-9 grid place-items-center rounded-lg hover:bg-white/10 border border-white/10"
220
- aria-label="Connecter des applications"
221
- title="Connecter des applications"
222
- >
223
- <PlugIcon />
224
- </button>
225
-
226
- {/* Champ texte */}
227
- <div className="flex-1">
228
- <input
229
- value={text}
230
- onChange={(e) => setText(e.target.value)}
231
- onKeyDown={(e) => {
232
- if (e.key === "Enter" && !e.shiftKey) {
233
- e.preventDefault();
234
- void handleSend();
235
- }
236
- }}
237
- className="w-full bg-transparent outline-none text-sm px-2 py-2 text-white placeholder:text-white/50"
238
- placeholder="Envoyer un message à Espace Codage"
239
- />
240
- {!!files.length && (
241
- <div className="px-2 pt-1 text-[11px] text-white/60">
242
- {files.length} fichier(s) ajouté(s)
243
- </div>
244
- )}
245
- </div>
246
-
247
- {/* Micro dictée */}
248
- <button
249
- type="button"
250
- onClick={() => (speech === "listening" ? stopDictation() : startDictation())}
251
- className={[
252
- "h-9 w-9 grid place-items-center rounded-lg border border-white/10",
253
- speech === "listening" ? "bg-white text-black" : "hover:bg-white/10"
254
- ].join(" ")}
255
- aria-label="Saisie vocale"
256
- title={
257
- speech === "unsupported"
258
- ? "Dictée vocale non supportée"
259
- : speech === "denied"
260
- ? "Permission micro refusée"
261
- : "Saisie vocale"
262
- }
263
- >
264
- <MicIcon active={speech === "listening"} />
265
- </button>
266
-
267
- {/* Envoyer */}
268
- <button
269
- type="button"
270
- onClick={() => void handleSend()}
271
- className="h-9 w-9 grid place-items-center rounded-lg bg-white/10 hover:bg-white/15 border border-white/10"
272
- aria-label="Envoyer"
273
- title="Envoyer"
274
- >
275
- <SendIcon />
276
- </button>
277
-
278
- <input
279
- ref={fileInputRef}
280
- type="file"
281
- multiple
282
- className="hidden"
283
- onChange={handleFilesChanged}
284
- />
285
- </div>
286
- </div>
287
- );
288
- }
289
-
290
- /* --- Icônes inline (pas de dépendances) --- */
291
-
292
- function PlusIcon() {
293
- return (
294
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
295
- <path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
296
- </svg>
297
- );
298
- }
299
-
300
- function PlugIcon() {
301
- return (
302
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
303
- <path
304
- d="M9 7v5M15 7v5M7 12h10M10 12v4a4 4 0 0 0 4 4h1"
305
- stroke="currentColor"
306
- strokeWidth="2"
307
- strokeLinecap="round"
308
- />
309
- <path
310
- d="M6 7h12"
311
- stroke="currentColor"
312
- strokeWidth="2"
313
- strokeLinecap="round"
314
- />
315
- </svg>
316
- );
317
- }
318
-
319
- function MicIcon({ active }: { active: boolean }) {
320
- return (
321
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
322
- <path
323
- d="M12 14a3 3 0 0 0 3-3V7a3 3 0 0 0-6 0v4a3 3 0 0 0 3 3Z"
324
- stroke="currentColor"
325
- strokeWidth="2"
326
- />
327
- <path
328
- d="M19 11a7 7 0 0 1-14 0"
329
- stroke="currentColor"
330
- strokeWidth="2"
331
- strokeLinecap="round"
332
- />
333
- <path
334
- d="M12 18v3"
335
- stroke="currentColor"
336
- strokeWidth="2"
337
- strokeLinecap="round"
338
- />
339
- <path
340
- d="M8 21h8"
341
- stroke="currentColor"
342
- strokeWidth="2"
343
- strokeLinecap="round"
344
- />
345
- {active ? (
346
- <path
347
- d="M4 4l16 16"
348
- stroke="currentColor"
349
- strokeWidth="2"
350
- strokeLinecap="round"
351
- opacity="0.25"
352
- />
353
- ) : null}
354
- </svg>
355
- );
356
- }
357
-
358
- function SendIcon() {
359
- return (
360
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none">
361
- <path
362
- d="M5 12h12"
363
- stroke="currentColor"
364
- strokeWidth="2"
365
- strokeLinecap="round"
366
- />
367
- <path
368
- d="M13 6l6 6-6 6"
369
- stroke="currentColor"
370
- strokeWidth="2"
371
- strokeLinecap="round"
372
- strokeLinejoin="round"
373
- />
374
- </svg>
375
- );
376
- }
377
-
378
- 2) Exemple d’intégration dans ton chat : components/ChatPanel.tsx
379
-
380
- Ici tu branches ton système existant (onSend).
381
- Le code ci-dessous montre comment récupérer texte + fichiers.
382
-
383
- "use client";
384
-
385
- import React, { useState } from "react";
386
- import ChatFooterBar from "./ChatFooterBar";
387
-
388
- export default function ChatPanel() {
389
- const [messages, setMessages] = useState<{ role: "user" | "assistant"; content: string }[]>([
390
- { role: "assistant", content: "Envoyez-moi un message." }
391
- ]);
392
-
393
- return (
394
- <div className="h-full flex flex-col">
395
- <div className="flex-1 overflow-auto p-3 space-y-2">
396
- {messages.map((m, i) => (
397
- <div
398
- key={i}
399
- className={`max-w-[92%] rounded-2xl px-3 py-2 text-sm border ${
400
- m.role === "user"
401
- ? "ml-auto bg-white text-black border-white/20"
402
- : "bg-white/5 text-white border-white/10"
403
- }`}
404
- >
405
- {m.content}
406
- </div>
407
- ))}
408
- </div>
409
-
410
- {/* ✅ EN DESSOUS DU TCHAT : barre identique à la capture */}
411
- <div className="p-3 border-t border-white/10 bg-[#0b0f19]">
412
- <ChatFooterBar
413
- onConnectApps={() => {
414
- // TODO: ouvrir un modal “Connecter des applications”
415
- setMessages((prev) => [...prev, { role: "assistant", content: "Ouverture: connecter des applications…" }]);
416
- }}
417
- onSend={async (text, files) => {
418
- const info = files?.length ? ` (${files.length} fichier(s))` : "";
419
- setMessages((prev) => [...prev, { role: "user", content: text + info }]);
420
-
421
- // TODO: envoyer au backend
422
- // await fetch("/api/chat", ...)
423
-
424
- setMessages((prev) => [...prev, { role: "assistant", content: "Reçu ✅" }]);
425
- }}
426
- />
427
- </div>
428
- </div>
429
- );
430
- }
431
-
432
- 3) Note importante (micro)
433
-
434
- La dictée vocale fonctionne si le navigateur supporte Web Speech API (souvent OK sur Chrome/Edge).
435
-
436
- Si permission refusée, le bouton micro affiche l’état et tu peux gérer une alerte.
437
-
438
- Si tu veux, je te donne aussi la version “pro” :
439
-
440
- le panneau “ordinateur” connecté à un WebSocket (vrai flux live),
441
-
442
- enregistrement audio (pas فقط dictée) + envoi au serveur,
443
-
444
- et upload fichiers vers ton backend (S3/stockage) avec barre de progression.
445
-
446
- C'est lui qui me crée une IA qui s'appelle Rosalinda. Peux-tu me créer une IA, donc un chat IA, Rosalinda, pour qu'on puisse parler avec, créer des projets, faire des plugins, des images, des vidéos, qu'on puisse lui envoyer des fichiers, n'importe quoi. Mais il doit faire noter Rosalinda. Sans code d'API, s'il vous plaît, 100% à moi. Tu vois, merci pour ton aide.
447
-
448
- Oui — je peux te créer “Rosalinda” en mode 100% à toi, sans clé API, mais il faut être clair sur un point :
449
-
450
- Un chat IA “intelligent” + images + vidéos sans aucune API, c’est possible si tu utilises des modèles IA en local (sur ton PC/serveur) ou sur ton propre serveur.
451
-
452
- Sinon, sans modèle (ni API, ni local), l’IA ne peut pas “inventer” de l’intelligence toute seule.
453
-
454
- Je te donne donc une base réelle et pro : Rosalinda Studio = chat + projets + fichiers + plugins + jobs (images/vidéos) + mémoire, avec connecteurs locaux (pas d’API).
455
-
456
- Architecture “Rosalinda” (100% propriétaire)
457
-
458
- Front (web)
459
-
460
- Chat (texte + micro dictée)
461
-
462
- Upload fichiers (drag&drop)
463
-
464
- Projets (dossiers, tâches, historique)
465
-
466
- “Ordinateur”/console live (logs)
467
-
468
- Back (ton serveur)
469
-
470
- rosalinda-core : orchestrateur (chat + outils + mémoire)
471
-
472
- plugins : système de plugins (tools) exécutés en sandbox
473
-
474
- files : stockage local (uploads)
475
-
476
- jobs : file d’attente (images/vidéos) + logs + statut
477
-
478
- models : connecteurs IA locaux
479
-
480
- LLM local (Ollama ou llama.cpp ou vLLM)
481
-
482
- Images (Stable Diffusion local)
483
-
484
- Vidéo (FFmpeg + option modèles vidéo locaux si tu veux)
485
-
486
- Option “sans API” la plus simple (recommandée)
487
- ✅ LLM local avec Ollama
488
-
489
- Pas d’API payante, pas de clé, tout tourne chez toi.
490
-
491
- Ton serveur parle à Ollama en local (HTTP local).
492
-
493
- ✅ Images en local
494
-
495
- Stable Diffusion (AUTOMATIC1111 ou ComfyUI) sur ta machine/serveur.
496
-
497
- Rosalinda appelle ton endpoint local.
498
-
499
- ✅ Vidéos
500
-
501
- FFmpeg pour assembler (images + texte + audio).
502
-
503
- Plus tard, tu peux brancher un modèle vidéo local si tu as un gros GPU.
504
-
505
- Codes : Base “Rosalinda” (fonctionnelle) – sans clé API
506
-
507
- Je te mets un socle Node.js (Nest/Express style simple) + plugin system + upload + jobs.
508
- Tu peux le donner à ton développeur tel quel.
509
-
510
- 1) Structure projet
511
- rosalinda/
512
- server/
513
- package.json
514
- src/
515
- index.ts
516
- core/
517
- agent.ts
518
- memory.ts
519
- tools.ts
520
- plugins/
521
- index.ts
522
- project.create.ts
523
- file.summarize.ts
524
- routes/
525
- chat.ts
526
- files.ts
527
- projects.ts
528
- jobs.ts
529
- services/
530
- ollama.ts
531
- ffmpeg.ts
532
- storage/
533
- uploads/.gitkeep
534
- projects.json
535
- jobs.json
536
- web/
537
- (ton front Next/React — tu as déjà la base)
538
-
539
- 2) Serveur : server/package.json
540
- {
541
- "name": "rosalinda-server",
542
- "private": true,
543
- "type": "module",
544
- "scripts": {
545
- "dev": "node --watch --loader ts-node/esm src/index.ts"
546
- },
547
- "dependencies": {
548
- "express": "^4.19.2",
549
- "multer": "^1.4.5-lts.1",
550
- "zod": "^3.24.1"
551
- },
552
- "devDependencies": {
553
- "ts-node": "^10.9.2",
554
- "typescript": "^5.6.3"
555
- }
556
- }
557
-
558
- 3) Serveur principal : server/src/index.ts
559
- import express from "express";
560
- import { chatRouter } from "./routes/chat.js";
561
- import { filesRouter } from "./routes/files.js";
562
- import { projectsRouter } from "./routes/projects.js";
563
- import { jobsRouter } from "./routes/jobs.js";
564
-
565
- const app = express();
566
- app.use(express.json({ limit: "20mb" }));
567
-
568
- app.get("/health", (_req, res) => res.json({ ok: true, name: "Rosalinda" }));
569
-
570
- app.use("/api/chat", chatRouter);
571
- app.use("/api/files", filesRouter);
572
- app.use("/api/projects", projectsRouter);
573
- app.use("/api/jobs", jobsRouter);
574
-
575
- const port = process.env.PORT ? Number(process.env.PORT) : 3333;
576
- app.listen(port, () => console.log(`Rosalinda server running on http://localhost:${port}`));
577
-
578
- 4) Orchestrateur IA (sans API) : server/src/core/agent.ts
579
-
580
- Rosalinda = un agent qui :
581
-
582
- lit le message
583
-
584
- choisit des tools (plugins) si nécessaire
585
-
586
- répond
587
-
588
- import { runTools } from "./tools.js";
589
- import { memoryAppend, memoryGet } from "./memory.js";
590
- import { llmChat } from "../services/ollama.js";
591
-
592
- export async function rosalindaRespond(input: {
593
- projectId: string;
594
- userText: string;
595
- }) {
596
- const { projectId, userText } = input;
597
-
598
- const history = memoryGet(projectId);
599
- memoryAppend(projectId, { role: "user", content: userText });
600
-
601
- // 1) tenter outils (plugins)
602
- const toolResult = await runTools({ projectId, userText });
603
-
604
- // 2) prompt Rosalinda (identité + contexte)
605
- const system = `
606
- Tu es Rosalinda, l'IA privée du propriétaire.
607
- Règles:
608
- - Tu proposes des actions concrètes.
609
- - Tu peux créer des projets, lancer des jobs, manipuler des fichiers via les tools.
610
- - Tu réponds en français, clair, professionnel.
611
- `;
612
-
613
- const messages = [
614
- { role: "system", content: system },
615
- ...history.slice(-12),
616
- ...(toolResult ? [{ role: "assistant", content: `Résultat outil: ${toolResult}` }] : [])
617
- ];
618
-
619
- // 3) réponse LLM local
620
- const reply = await llmChat(messages);
621
-
622
- memoryAppend(projectId, { role: "assistant", content: reply });
623
-
624
- return { reply, toolResult };
625
- }
626
-
627
- 5) Mémoire simple : server/src/core/memory.ts
628
- type Msg = { role: "user" | "assistant" | "system"; content: string };
629
-
630
- const mem = new Map<string, Msg[]>();
631
-
632
- export function memoryGet(projectId: string): Msg[] {
633
- return mem.get(projectId) ?? [];
634
- }
635
-
636
- export function memoryAppend(projectId: string, msg: Msg) {
637
- const arr = mem.get(projectId) ?? [];
638
- arr.push(msg);
639
- mem.set(projectId, arr);
640
- }
641
-
642
- 6) Tools + Plugins : server/src/core/tools.ts
643
- import { plugins } from "../plugins/index.js";
644
-
645
- export async function runTools(input: { projectId: string; userText: string }) {
646
- // Routage simple: si le texte contient certains mots, déclenche plugin
647
- const t = input.userText.toLowerCase();
648
-
649
- if (t.includes("crée un projet") || t.includes("creer un projet")) {
650
- return plugins.projectCreate.run({ projectId: input.projectId, text: input.userText });
651
- }
652
-
653
- if (t.includes("résume le fichier") || t.includes("resume le fichier")) {
654
- return plugins.fileSummarize.run({ projectId: input.projectId, text: input.userText });
655
- }
656
-
657
- return null;
658
- }
659
-
660
- Plugins : server/src/plugins/index.ts
661
- import { projectCreate } from "./project.create.js";
662
- import { fileSummarize } from "./file.summarize.js";
663
-
664
- export const plugins = {
665
- projectCreate,
666
- fileSummarize
667
- };
668
-
669
- Plugin “Créer projet” : server/src/plugins/project.create.ts
670
- import { readProjects, writeProjects } from "../routes/projects.store.js";
671
-
672
- export const projectCreate = {
673
- name: "project.create",
674
- async run(input: { projectId: string; text: string }) {
675
- const name = input.text.split(":")[1]?.trim() || `Projet ${new Date().toISOString()}`;
676
- const db = readProjects();
677
- const id = "p_" + Math.random().toString(16).slice(2);
678
- db.projects.push({ id, name, createdAt: Date.now() });
679
- writeProjects(db);
680
- return `Projet créé ✅ id=${id} name="${name}"`;
681
- }
682
- };
683
-
684
- Plugin “Résumer fichier” : server/src/plugins/file.summarize.ts
685
- import { llmChat } from "../services/ollama.js";
686
-
687
- export const fileSummarize = {
688
- name: "file.summarize",
689
- async run(_input: { projectId: string; text: string }) {
690
- // Ici tu peux aller lire un fichier uploadé et l’injecter au LLM local.
691
- // Pour l’instant: exemple.
692
- const reply = await llmChat([
693
- { role: "system", content: "Tu résumes un document de manière structurée." },
694
- { role: "user", content: "Résumé: ceci est un test de résumé." }
695
- ]);
696
- return `Résumé (exemple) ✅\n${reply}`;
697
- }
698
- };
699
-
700
- 7) Route Chat : server/src/routes/chat.ts
701
- import { Router } from "express";
702
- import { z } from "zod";
703
- import { rosalindaRespond } from "../core/agent.js";
704
-
705
- export const chatRouter = Router();
706
-
707
- chatRouter.post("/", async (req, res) => {
708
- const body = z.object({
709
- projectId: z.string().min(1),
710
- text: z.string().min(1)
711
- }).safeParse(req.body);
712
-
713
- if (!body.success) return res.status(400).json({ ok: false, error: body.error.message });
714
-
715
- const out = await rosalindaRespond({ projectId: body.data.projectId, userText: body.data.text });
716
- res.json({ ok: true, reply: out.reply, toolResult: out.toolResult });
717
- });
718
-
719
- 8) Upload fichiers : server/src/routes/files.ts
720
- import { Router } from "express";
721
- import multer from "multer";
722
- import path from "path";
723
- import fs from "fs";
724
-
725
- export const filesRouter = Router();
726
-
727
- const uploadDir = path.resolve("src/storage/uploads");
728
- fs.mkdirSync(uploadDir, { recursive: true });
729
-
730
- const storage = multer.diskStorage({
731
- destination: (_req, _file, cb) => cb(null, uploadDir),
732
- filename: (_req, file, cb) => cb(null, Date.now() + "_" + file.originalname)
733
- });
734
-
735
- const upload = multer({ storage });
736
-
737
- filesRouter.post("/upload", upload.array("files", 10), (req, res) => {
738
- const files = (req.files as Express.Multer.File[]) ?? [];
739
- res.json({
740
- ok: true,
741
- files: files.map(f => ({ name: f.originalname, path: f.filename, size: f.size }))
742
- });
743
- });
744
-
745
- 9) Projets (stockage json simple) : server/src/routes/projects.store.ts
746
- import fs from "fs";
747
- import path from "path";
748
-
749
- const dbPath = path.resolve("src/storage/projects.json");
750
-
751
- export type ProjectDB = { projects: { id: string; name: string; createdAt: number }[] };
752
-
753
- export function readProjects(): ProjectDB {
754
- if (!fs.existsSync(dbPath)) return { projects: [] };
755
- return JSON.parse(fs.readFileSync(dbPath, "utf-8"));
756
- }
757
-
758
- export function writeProjects(db: ProjectDB) {
759
- fs.writeFileSync(dbPath, JSON.stringify(db, null, 2), "utf-8");
760
- }
761
-
762
- server/src/routes/projects.ts
763
- import { Router } from "express";
764
- import { readProjects } from "./projects.store.js";
765
-
766
- export const projectsRouter = Router();
767
-
768
- projectsRouter.get("/", (_req, res) => {
769
- res.json({ ok: true, ...readProjects() });
770
- });
771
-
772
- 10) LLM local (sans clé) : server/src/services/ollama.ts
773
-
774
- Rosalinda utilise Ollama local : http://localhost:11434
775
-
776
- type Msg = { role: "system" | "user" | "assistant"; content: string };
777
-
778
- export async function llmChat(messages: Msg[]) {
779
- const res = await fetch("http://localhost:11434/api/chat", {
780
- method: "POST",
781
- headers: { "Content-Type": "application/json" },
782
- body: JSON.stringify({
783
- model: "llama3.1", // tu peux changer selon ton modèle local
784
- messages,
785
- stream: false
786
- })
787
- });
788
-
789
- if (!res.ok) {
790
- const txt = await res.text().catch(() => "");
791
- return `Rosalinda: erreur LLM local (${res.status}). ${txt}`;
792
- }
793
-
794
- const data: any = await res.json();
795
- return data?.message?.content ?? "Rosalinda: (réponse vide)";
796
- }
797
- html
798
- <!DOCTYPE html>
799
- <html lang="fr">
800
- <head>
801
- <meta charset="UTF-8">
802
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
803
- <title>Mini Constructeur de Site Web IA</title>
804
- <style>
805
- * {
806
- box-sizing: border-box;
807
- margin: 0;
808
- padding: 0;
809
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
810
- }
811
-
812
- body {
813
- display: flex;
814
- flex-direction: column;
815
- height: 100vh;
816
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
817
- color: #333;
818
- }
819
-
820
- header {
821
- background: linear-gradient(90deg, #4b6cb7 0%, #182848 100%);
822
- color: white;
823
- padding: 20px;
824
- text-align: center;
825
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
826
- }
827
-
828
- header h1 {
829
- font-size: 2.5rem;
830
- margin-bottom: 10px;
831
- }
832
-
833
- header p {
834
- font-size: 1.2rem;
835
- opacity: 0.9;
836
- }
837
-
838
- .container {
839
- display: flex;
840
- flex: 1;
841
- padding: 20px;
842
- gap: 20px;
843
- }
844
-
845
- .toolbox {
846
- width: 250px;
847
- background: white;
848
- border-radius: 15px;
849
- padding: 20px;
850
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
851
- display: flex;
852
- flex-direction: column;
853
- gap: 15px;
854
- }
855
-
856
- .toolbox h2 {
857
- color: #4b6cb7;
858
- margin-bottom: 10px;
859
- font-size: 1.8rem;
860
- }
861
-
862
- .toolbox button {
863
- padding: 15px;
864
- border: none;
865
- border-radius: 10px;
866
- background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
867
- color: white;
868
- font-size: 1.1rem;
869
- cursor: pointer;
870
- transition: transform 0.3s, box-shadow 0.3s;
871
- display: flex;
872
- align-items: center;
873
- justify-content: center;
874
- gap: 10px;
875
- }
876
-
877
- .toolbox button:hover {
878
- transform: translateY(-5px);
879
- box-shadow: 0 5px 15px rgba(106, 17, 203, 0.4);
880
- }
881
-
882
- .toolbox button i {
883
- font-size: 1.5rem;
884
- }
885
-
886
- .canvas {
887
- flex: 1;
888
- background: white;
889
- border-radius: 15px;
890
- padding: 20px;
891
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
892
- overflow-y: auto;
893
- position: relative;
894
- min-height: 600px;
895
- }
896
-
897
- .canvas h2 {
898
- color: #4b6cb7;
899
- margin-bottom: 20px;
900
- font-size: 1.8rem;
901
- }
902
-
903
- .element {
904
- border: 2px dashed #4b6cb7;
905
- border-radius: 10px;
906
- padding: 20px;
907
- margin-bottom: 20px;
908
- cursor: move;
909
- transition: all 0.3s;
910
- position: relative;
911
- background: white;
912
- }
913
-
914
- .element:hover {
915
- border-color: #2575fc;
916
- background: #f8f9ff;
917
- }
918
-
919
- .element.selected {
920
- border: 2px solid #2575fc;
921
- background: #f0f4ff;
922
- }
923
-
924
- .element .delete-btn {
925
- position: absolute;
926
- top: 10px;
927
- right: 10px;
928
- background: #ff4757;
929
- color: white;
930
- border: none;
931
- border-radius: 5px;
932
- padding: 5px 10px;
933
- cursor: pointer;
934
- display: none;
935
- }
936
-
937
- .element:hover .delete-btn {
938
- display: block;
939
- }
940
-
941
- .element .edit-btn {
942
- position: absolute;
943
- top: 10px;
944
- right: 60px;
945
- background: #2ed573;
946
- color: white;
947
- border: none;
948
- border-radius: 5px;
949
- padding: 5px 10px;
950
- cursor: pointer;
951
- display: none;
952
- }
953
-
954
- .element:hover .edit-btn {
955
- display: block;
956
- }
957
-
958
- .properties {
959
- width: 300px;
960
- background: white;
961
- border-radius: 15px;
962
- padding: 20px;
963
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
964
- display: flex;
965
- flex-direction: column;
966
- gap: 15px;
967
- }
968
-
969
- .properties h2 {
970
- color: #4b6cb7;
971
- margin-bottom: 10px;
972
- font-size: 1.8rem;
973
- }
974
-
975
- .properties input, .properties select, .properties textarea {
976
- padding: 12px;
977
- border: 2px solid #ddd;
978
- border-radius: 8px;
979
- font-size: 1rem;
980
- transition: border 0.3s;
981
- }
982
-
983
- .properties input:focus, .properties select:focus, .properties textarea:focus {
984
- border-color: #4b6cb7;
985
- outline: none;
986
- }
987
-
988
- .properties label {
989
- font-weight: bold;
990
- color: #555;
991
- margin-top: 10px;
992
- }
993
-
994
- .ai-panel {
995
- background: white;
996
- border-radius: 15px;
997
- padding: 20px;
998
- margin: 20px;
999
- box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
1000
- display: flex;
1001
- flex-direction: column;
1002
- gap: 15px;
1003
- }
1004
-
1005
- .ai-panel h2 {
1006
- color: #4b6cb7;
1007
- font-size: 1.8rem;
1008
- }
1009
-
1010
- .ai-panel textarea {
1011
- padding: 12px;
1012
- border: 2px solid #ddd;
1013
- border-radius: 8px;
1014
- font-size: 1rem;
1015
- min-height: 100px;
1016
- resize: vertical;
1017
- }
1018
-
1019
- .ai-panel button {
1020
- padding: 15px;
1021
- border: none;
1022
- border-radius: 10px;
1023
- background: linear-gradient(135deg, #ff7e5f 0%, #feb47b 100%);
1024
- color: white;
1025
- font-size: 1.1rem;
1026
- cursor: pointer;
1027
- transition: transform 0.3s, box-shadow 0.3s;
1028
- }
1029
-
1030
- .ai-panel button:hover {
1031
- transform: translateY(-5px);
1032
- box-shadow: 0 5px 15px rgba(255, 126, 95, 0.4);
1033
- }
1034
-
1035
- footer {
1036
- background: linear-gradient(90deg, #182848 0%, #4b6cb7 100%);
1037
- color: white;
1038
- text-align: center;
1039
- padding: 15px;
1040
- font-size: 1.1rem;
1041
- }
1042
-
1043
- .modal {
1044
- display: none;
1045
- position: fixed;
1046
- z-index: 1000;
1047
- left: 0;
1048
- top: 0;
1049
- width: 100%;
1050
- height: 100%;
1051
- background-color: rgba(0, 0, 0, 0.5);
1052
- justify-content: center;
1053
- align-items: center;
1054
- }
1055
-
1056
- .modal-content {
1057
- background: white;
1058
- padding: 30px;
1059
- border-radius: 15px;
1060
- width: 500px;
1061
- max-width: 90%;
1062
- box-shadow: 0 15px 50px rgba(0, 0, 0, 0.2);
1063
- position: relative;
1064
- }
1065
-
1066
- .close-modal {
1067
- position: absolute;
1068
- top: 20px;
1069
- right: 20px;
1070
- font-size: 2rem;
1071
- cursor: pointer;
1072
- color: #aaa;
1073
- }
1074
-
1075
- .close-modal:hover {
1076
- color: #000;
1077
- }
1078
-
1079
- .modal h2 {
1080
- color: #4b6cb7;
1081
- margin-bottom: 20px;
1082
- font-size: 2rem;
1083
- }
1084
-
1085
- .modal textarea {
1086
- width: 100%;
1087
- min-height: 200px;
1088
- padding: 15px;
1089
- border: 2px solid #ddd;
1090
- border-radius: 10px;
1091
- font-family: monospace;
1092
- font-size: 1rem;
1093
- margin-bottom: 20px;
1094
- resize: vertical;
1095
- }
1096
-
1097
- .modal button {
1098
- padding: 15px 30px;
1099
- border: none;
1100
- border-radius: 10px;
1101
- background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
1102
- color: white;
1103
- font-size: 1.2rem;
1104
- cursor: pointer;
1105
- transition: transform 0.3s, box-shadow 0.3s;
1106
- width: 100%;
1107
- }
1108
-
1109
- .modal button:hover {
1110
- transform: translateY(-5px);
1111
- box-shadow: 0 5px 15px rgba(106, 17, 203, 0.4);
1112
- }
1113
-
1114
- .generate-btn {
1115
- padding: 20px;
1116
- border: none;
1117
- border-radius: 15px;
1118
- background: linear-gradient(135deg, #00b09b 0%, #96c93d 100%);
1119
- color: white;
1120
- font-size: 1.5rem;
1121
- cursor: pointer;
1122
- transition: transform 0.3s, box-shadow 0.3s;
1123
- margin: 20px;
1124
- display: flex;
1125
- align-items: center;
1126
- justify-content: center;
1127
- gap: 15px;
1128
- }
1129
-
1130
- .generate-btn:hover {
1131
- transform: translateY(-5px);
1132
- box-shadow: 0 10px 25px rgba(0, 176, 155, 0.4);
1133
- }
1134
-
1135
- .generate-btn i {
1136
- font-size: 2rem;
1137
- }
1138
- </style>
1139
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
1140
  </head>
1141
  <body>
1142
- <header>
1143
- <h1><i class="fas fa-magic"></i> Mini Constructeur de Site Web IA</h1>
1144
- <p>Glissez-déposez des éléments, personnalisez et générez votre site web en quelques clics !</p>
1145
- </header>
1146
-
1147
  <div class="container">
1148
- <div class="toolbox">
1149
- <h2><i class="fas fa-tools"></i> Boîte à outils</h2>
1150
- <button id="add-title"><i class="fas fa-heading"></i> Ajouter un titre</button>
1151
- <button id="add-paragraph"><i class="fas fa-paragraph"></i> Ajouter un paragraphe</button>
1152
- <button id="add-image"><i class="fas fa-image"></i> Ajouter une image</button>
1153
- <button id="add-button"><i class="fas fa-square"></i> Ajouter un bouton</button>
1154
- <button id="add-section"><i class="fas fa-layer-group"></i> Ajouter une section</button>
1155
-
1156
- <h2 style="margin-top: 30px;"><i class="fas fa-palette"></i> Thèmes rapides</h2>
1157
- <button id="theme-modern"><i class="fas fa-star"></i> Moderne</button>
1158
- <button id="theme-classic"><i class="fas fa-landmark"></i> Classique</button>
1159
- <button id="theme-dark"><i class="fas fa-moon"></i> Sombre</button>
1160
- </div>
1161
-
1162
- <div class="canvas" id="canvas">
1163
- <h2><i class="fas fa-paint-brush"></i> Votre Canva</h2>
1164
- <p style="color: #666; margin-bottom: 20px;">Glissez les éléments ici et personnalisez-les.</p>
1165
- <!-- Les éléments ajoutés apparaîtront ici -->
1166
- </div>
1167
-
1168
- <div class="properties" id="properties-panel">
1169
- <h2><i class="fas fa-sliders-h"></i> Propriétés</h2>
1170
- <p style="color: #666;">Sélectionnez un élément pour le personnaliser.</p>
1171
- <!-- Les contrôles de propriétés seront ajoutés dynamiquement -->
1172
- </div>
1173
- </div>
1174
-
1175
- <div class="ai-panel">
1176
- <h2><i class="fas fa-robot"></i> Assistant IA</h2>
1177
- <p>Utilisez l'IA pour générer du contenu pour votre site. Entrez une description et choisissez le type de contenu.</p>
1178
- <textarea id="ai-prompt" placeholder="Exemple : Un titre accrocheur pour un site de restaurant italien..."></textarea>
1179
- <select id="ai-content-type">
1180
- <option value="title">Titre</option>
1181
- <option value="paragraph">Paragraphe</option>
1182
- <option value="button">Texte de bouton</option>
1183
- <option value="section">Section complète</option>
1184
- </select>
1185
- <button id="generate-ai-content"><i class="fas fa-bolt"></i> Générer avec l'IA</button>
1186
- <small>Remarque : Vous devez configurer votre propre clé API OpenAI dans le code pour utiliser cette fonctionnalité.</small>
1187
- </div>
1188
-
1189
- <button class="generate-btn" id="generate-site">
1190
- <i class="fas fa-rocket"></i> Générer et Télécharger le Site Web
1191
- </button>
1192
-
1193
- <footer>
1194
- <p>Mini Constructeur de Site Web IA &copy; 2024 - Développé avec <i class="fas fa-heart" style="color: #ff4757;"></i> pour démontrer les capacités de génération de code</p>
1195
- </footer>
1196
-
1197
- <!-- Modal pour afficher le code généré -->
1198
- <div class="modal" id="code-modal">
1199
- <div class="modal-content">
1200
- <span class="close-modal" id="close-modal">&times;</span>
1201
- <h2><i class="fas fa-code"></i> Code généré</h2>
1202
- <p>Voici le code HTML et CSS de votre site web. Copiez-le ou téléchargez-le.</p>
1203
- <textarea id="generated-code" readonly></textarea>
1204
- <button id="download-code"><i class="fas fa-download"></i> Télécharger le code</button>
1205
- </div>
1206
  </div>
1207
-
1208
- <script>
1209
- // État de l'application
1210
- let elements = [];
1211
- let selectedElementId = null;
1212
- let elementCounter = 0;
1213
-
1214
- // Références aux éléments DOM
1215
- const canvas = document.getElementById('canvas');
1216
- const propertiesPanel = document.getElementById('properties-panel');
1217
- const generateSiteBtn = document.getElementById('generate-site');
1218
- const codeModal = document.getElementById('code-modal');
1219
- const generatedCodeTextarea = document.getElementById('generated-code');
1220
- const closeModalBtn = document.getElementById('close-modal');
1221
- const downloadCodeBtn = document.getElementById('download-code');
1222
-
1223
- // Initialisation
1224
- document.addEventListener('DOMContentLoaded', function() {
1225
- // Ajouter les écouteurs d'événements pour les boutons d'ajout d'éléments
1226
- document.getElementById('add-title').addEventListener('click', () => addElement('title'));
1227
- document.getElementById('add-paragraph').addEventListener('click', () => addElement('paragraph'));
1228
- document.getElementById('add-image').addEventListener('click', () => addElement('image'));
1229
- document.getElementById('add-button').addEventListener('click', () => addElement('button'));
1230
- document.getElementById('add-section').addEventListener('click', () => addElement('section'));
1231
-
1232
- // Écouteurs pour les thèmes rapides
1233
- document.getElementById('theme-modern').addEventListener('click', () => applyTheme('modern'));
1234
- document.getElementById('theme-classic').addEventListener('click', () => applyTheme('classic'));
1235
- document.getElementById('theme-dark').addEventListener('click', () => applyTheme('dark'));
1236
-
1237
- // Écouteur pour générer le site
1238
- generateSiteBtn.addEventListener('click', generateAndDownloadSite);
1239
-
1240
- // Écouteurs pour la modal
1241
- closeModalBtn.addEventListener('click', () => codeModal.style.display = 'none');
1242
- downloadCodeBtn.addEventListener('click', downloadGeneratedCode);
1243
-
1244
- // Écouteur pour fermer la modal en cliquant en dehors
1245
- window.addEventListener('click', (event) => {
1246
- if (event.target === codeModal) {
1247
- codeModal.style.display = 'none';
1248
- }
1249
- });
1250
-
1251
- // Écouteur pour l'IA (fonctionnalité basique)
1252
- document.getElementById('generate-ai-content').addEventListener('click', generateAIContent);
1253
-
1254
- // Afficher les propriétés par défaut
1255
- updatePropertiesPanel();
1256
- });
1257
-
1258
- // Fonction pour ajouter un nouvel élément
1259
- function addElement(type) {
1260
- elementCounter++;
1261
- const id = `element-${elementCounter}`;
1262
- let content = '';
1263
- let tag = 'div';
1264
- let defaultStyles = {};
1265
-
1266
- switch (type) {
1267
- case 'title':
1268
- content = 'Mon Titre Accrocheur';
1269
- tag = 'h2';
1270
- defaultStyles = {
1271
- fontSize: '32px',
1272
- fontWeight: 'bold',
1273
- color: '#2c3e50',
1274
- marginBottom: '20px'
1275
- };
1276
- break;
1277
- case 'paragraph':
1278
- content = 'Ceci est un paragraphe de texte. Vous pouvez le modifier en double-cliquant dessus ou via le panneau de propriétés.';
1279
- tag = 'p';
1280
- defaultStyles = {
1281
- fontSize: '18px',
1282
- lineHeight: '1.6',
1283
- color: '#34495e',
1284
- marginBottom: '20px'
1285
- };
1286
- break;
1287
- case 'image':
1288
- content = '<img src="https://via.placeholder.com/400x250/4b6cb7/ffffff?text=Image+Exemple" alt="Exemple" style="width:100%; border-radius:10px;">';
1289
- tag = 'div';
1290
- defaultStyles = {
1291
- marginBottom: '20px'
1292
- };
1293
- break;
1294
- case 'button':
1295
- content = 'Cliquez ici';
1296
- tag = 'button';
1297
- defaultStyles = {
1298
- backgroundColor: '#4b6cb7',
1299
- color: 'white',
1300
- padding: '15px 30px',
1301
- border: 'none',
1302
- borderRadius: '8px',
1303
- fontSize: '18px',
1304
- cursor: 'pointer',
1305
- marginBottom: '20px'
1306
- };
1307
- break;
1308
- case 'section':
1309
- content = '<h3>Nouvelle Section</h3><p>Contenu de la section...</p>';
1310
- tag = 'section';
1311
- defaultStyles = {
1312
- backgroundColor: '#f8f9fa',
1313
- padding: '30px',
1314
- borderRadius: '15px',
1315
- marginBottom: '30px',
1316
- borderLeft: '5px solid #4b6cb7'
1317
- };
1318
- break;
1319
- }
1320
-
1321
- const element = {
1322
- id,
1323
- type,
1324
- tag,
1325
- content,
1326
- styles: defaultStyles,
1327
- classes: []
1328
- };
1329
-
1330
- elements.push(element);
1331
- renderElement(element);
1332
- selectElement(id);
1333
- }
1334
-
1335
- // Fonction pour rendre un élément sur le canevas
1336
- function renderElement(element) {
1337
- // Créer l'élément DOM
1338
- const el = document.createElement('div');
1339
- el.id = element.id;
1340
- el.className = 'element';
1341
- el.innerHTML = `
1342
- ${element.type === 'image' ? element.content : `<${element.tag}>${element.content}</${element.tag}>`}
1343
- <button class="delete-btn" data-id="${element.id}"><i class="fas fa-trash"></i></button>
1344
- <button class="edit-btn" data-id="${element.id}"><i class="fas fa-edit"></i></button>
1345
- `;
1346
-
1347
- // Appliquer les styles
1348
- Object.keys(element.styles).forEach(style => {
1349
- el.style[style] = element.styles[style];
1350
- });
1351
-
1352
- // Ajouter des écouteurs d'événements
1353
- el.addEventListener('click', (e) => {
1354
- if (!e.target.closest('.delete-btn') && !e.target.closest('.edit-btn')) {
1355
- selectElement(element.id);
1356
- }
1357
- });
1358
-
1359
- // Double-clic pour éditer le texte (si ce n'est pas une image)
1360
- if (element.type !== 'image') {
1361
- el.addEventListener('dblclick', () => {
1362
- const currentText = el.querySelector(`${element.tag}`).innerText;
1363
- const newText = prompt('Modifier le texte:', currentText);
1364
- if (newText !== null) {
1365
- element.content = newText;
1366
- el.querySelector(`${element.tag}`).innerText = newText;
1367
- updatePropertiesPanel();
1368
- }
1369
- });
1370
- }
1371
-
1372
- // Bouton supprimer
1373
- el.querySelector('.delete-btn').addEventListener('click', (e) => {
1374
- e.stopPropagation();
1375
- deleteElement(element.id);
1376
- });
1377
-
1378
- // Bouton éditer (ouvre le panneau de propriétés)
1379
- el.querySelector('.edit-btn').addEventListener('click', (e) => {
1380
- e.stopPropagation();
1381
- selectElement(element.id);
1382
- });
1383
-
1384
- // Rendre l'élément glissable
1385
- el.draggable = true;
1386
- el.addEventListener('dragstart', (e) => {
1387
- e.dataTransfer.setData('text/plain', element.id);
1388
- });
1389
-
1390
- // Ajouter au canevas
1391
- canvas.appendChild(el);
1392
-
1393
- // Zone de dépôt pour le glisser-déposer
1394
- canvas.addEventListener('dragover', (e) => e.preventDefault());
1395
- canvas.addEventListener('drop', (e) => {
1396
- e.preventDefault();
1397
- const id = e.dataTransfer.getData('text/plain');
1398
- const draggedEl = document.getElementById(id);
1399
- if (draggedEl) {
1400
- // On pourrait implémenter un repositionnement plus sophistiqué ici
1401
- // Pour l'exemple, on ne change pas la position
1402
- }
1403
- });
1404
- }
1405
-
1406
- // Fonction pour supprimer un élément
1407
- function deleteElement(id) {
1408
- elements = elements.filter(el => el.id !== id);
1409
- document.getElementById(id)?.remove();
1410
- if (selectedElementId === id) {
1411
- selectedElementId = null;
1412
- updatePropertiesPanel();
1413
- }
1414
- }
1415
-
1416
- // Fonction pour sélectionner un élément
1417
- function selectElement(id) {
1418
- // Désélectionner l'élément précédent
1419
- if (selectedElementId) {
1420
- const prevEl = document.getElementById(selectedElementId);
1421
- if (prevEl) prevEl.classList.remove('selected');
1422
- }
1423
-
1424
- // Sélectionner le nouvel élément
1425
- selectedElementId = id;
1426
- const currentEl = document.getElementById(id);
1427
- if (currentEl) {
1428
- currentEl.classList.add('selected');
1429
- }
1430
-
1431
- updatePropertiesPanel();
1432
- }
1433
-
1434
- // Fonction pour mettre à jour le panneau de propriétés
1435
- function updatePropertiesPanel() {
1436
- propertiesPanel.innerHTML = '<h2><i class="fas fa-sliders-h"></i> Propriétés</h2>';
1437
-
1438
- if (!selectedElementId) {
1439
- propertiesPanel.innerHTML += '<p style="color:#666;">Sélectionnez un élément pour le personnaliser.</p>';
1440
- return;
1441
- }
1442
-
1443
- const element = elements.find(el => el.id === selectedElementId);
1444
- if (!element) return;
1445
-
1446
- // Afficher les propriétés de l'élément sélectionné
1447
- propertiesPanel.innerHTML += `
1448
- <p><strong>Type:</strong> ${element.type}</p>
1449
- <label for="edit-content">Contenu:</label>
1450
- <textarea id="edit-content">${element.type === 'image' ? 'Image (non modifiable)' : element.content}</textarea>
1451
- <label for="edit-font-size">Taille de police:</label>
1452
- <input type="text" id="edit-font-size" value="${element.styles.fontSize || '16px'}">
1453
- <label for="edit-color">Couleur:</label>
1454
- <input type="color" id="edit-color" value="${element.styles.color || '#000000'}">
1455
- <label for="edit-bg-color">Couleur de fond:</label>
1456
- <input type="color" id="edit-bg-color" value="${element.styles.backgroundColor || '#ffffff'}">
1457
- <label for="edit-padding">Padding:</label>
1458
- <input type="text" id="edit-padding" value="${element.styles.padding || '0px'}">
1459
- <label for="edit-margin">Margin:</label>
1460
- <input type="text" id="edit-margin" value="${element.styles.margin || '0px'}">
1461
- <button id="apply-properties"><i class="fas fa-check"></i> Appliquer les modifications</button>
1462
- `;
1463
-
1464
- // Écouteur pour appliquer les modifications
1465
- document.getElementById('apply-properties').addEventListener('click', () => {
1466
- applyProperties(element);
1467
- });
1468
- }
1469
-
1470
- // Fonction pour appliquer les propriétés modifiées
1471
- function applyProperties(element) {
1472
- // Mettre à jour le contenu
1473
- if (element.type !== 'image') {
1474
- const newContent = document.getElementById('edit-content').value;
1475
- element.content = newContent;
1476
- }
1477
-
1478
- // Mettre à jour les styles
1479
- const styles = {
1480
- fontSize: document.getElementById('edit-font-size').value,
1481
- color: document.getElementById('edit-color').value,
1482
- backgroundColor: document.getElementById('edit-bg-color').value,
1483
- padding: document.getElementById('edit-padding').value,
1484
- margin: document.getElementById('edit-margin').value
1485
- };
1486
-
1487
- // Appliquer les styles non vides
1488
- Object.keys(styles).forEach(style => {
1489
- if (styles[style]) {
1490
- element.styles[style] = styles[style];
1491
- }
1492
- });
1493
-
1494
- // Re-rendre l'élément
1495
- const el = document.getElementById(element.id);
1496
- if (el) {
1497
- if (element.type !== 'image') {
1498
- el.querySelector(element.tag).innerText = element.content;
1499
- }
1500
-
1501
- Object.keys(element.styles).forEach(style => {
1502
- el.style[style] = element.styles[style];
1503
- });
1504
- }
1505
- }
1506
-
1507
- // Fonction pour appliquer un thème rapide
1508
- function applyTheme(themeName) {
1509
- const themes = {
1510
- modern: {
1511
- backgroundColor: '#ffffff',
1512
- primaryColor: '#4b6cb7',
1513
- secondaryColor: '#2575fc',
1514
- fontFamily: "'Segoe UI', sans-serif"
1515
- },
1516
- classic: {
1517
- backgroundColor: '#f8f4e9',
1518
- primaryColor: '#8b4513',
1519
- secondaryColor: '#d2691e',
1520
- fontFamily: "'Times New Roman', serif"
1521
- },
1522
- dark: {
1523
- backgroundColor: '#2c3e50',
1524
- primaryColor: '#1abc9c',
1525
- secondaryColor: '#3498db',
1526
- fontFamily: "'Arial', sans-serif"
1527
- }
1528
- };
1529
-
1530
- const theme = themes[themeName];
1531
-
1532
- // Appliquer le thème au body
1533
- document.body.style.backgroundColor = theme.backgroundColor;
1534
- document.body.style.color = theme.primaryColor;
1535
- document.body.style.fontFamily = theme.fontFamily;
1536
-
1537
- // Appliquer le thème aux éléments existants
1538
- elements.forEach(element => {
1539
- if (element.type === 'title' || element.type === 'paragraph') {
1540
- element.styles.color = theme.primaryColor;
1541
- }
1542
- if (element.type === 'button') {
1543
- element.styles.backgroundColor = theme.secondaryColor;
1544
- }
1545
- if (element.type === 'section') {
1546
- element.styles.backgroundColor = theme.backgroundColor;
1547
- element.styles.borderLeftColor = theme.secondaryColor;
1548
- }
1549
-
1550
- // Re-rendre l'élément
1551
- const el = document.getElementById(element.id);
1552
- if (el) {
1553
- Object.keys(element.styles).forEach(style => {
1554
- el.style[style] = element.styles[style];
1555
- });
1556
- }
1557
- });
1558
- }
1559
-
1560
- // Fonction pour générer et télécharger le site
1561
- function generateAndDownloadSite() {
1562
- // Générer le code HTML
1563
- let html = `<!DOCTYPE html>
1564
- <html lang="fr">
1565
- <head>
1566
- <meta charset="UTF-8">
1567
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1568
- <title>Site Généré avec le Mini Constructeur</title>
1569
- <style>
1570
- * {
1571
- margin: 0;
1572
- padding: 0;
1573
- box-sizing: border-box;
1574
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
1575
- }
1576
- body {
1577
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
1578
- color: #333;
1579
- line-height: 1.6;
1580
- padding: 20px;
1581
- }
1582
- .container {
1583
- max-width: 1200px;
1584
- margin: 0 auto;
1585
- padding: 20px;
1586
- }
1587
- ${generateCSS()}
1588
- </style>
1589
- </head>
1590
- <body>
1591
- <div class="container">
1592
- `;
1593
-
1594
- // Ajouter chaque élément
1595
- elements.forEach(element => {
1596
- html += ` <!-- Élément ${element.type} -->\n`;
1597
- html += ` <div class="generated-element ${element.type}">\n`;
1598
- if (element.type === 'image') {
1599
- html += ` ${element.content}\n`;
1600
- } else {
1601
- html += ` <${element.tag}>${element.content}</${element.tag}>\n`;
1602
- }
1603
- html += ` </div>\n\n`;
1604
- });
1605
-
1606
- html += ` </div>
1607
- </body>
1608
- </html>`;
1609
-
1610
- // Afficher le code dans la modal
1611
- generatedCodeTextarea.value = html;
1612
- codeModal.style.display = 'flex';
1613
- }
1614
-
1615
- // Fonction pour générer le CSS
1616
- function generateCSS() {
1617
- let css = '';
1618
- elements.forEach(element => {
1619
- css += `.${element.id} {\n`;
1620
- Object.keys(element.styles).forEach(style => {
1621
- const cssStyle = style.replace(/([A-Z])/g, '-$1').toLowerCase();
1622
- css += ` ${cssStyle}: ${element.styles[style]};\n`;
1623
- });
1624
- css += '}\n\n';
1625
- });
1626
- return css;
1627
- }
1628
-
1629
- // Fonction pour télécharger le code généré
1630
- function downloadGeneratedCode() {
1631
- const code = generatedCodeTextarea.value;
1632
- const blob = new Blob([code], { type: 'text/html' });
1633
- const url = URL.createObjectURL(blob);
1634
- const a = document.createElement('a');
1635
- a.href = url;
1636
- a.download = 'site-generé.html';
1637
- a.click();
1638
- URL.revokeObjectURL(url);
1639
- }
1640
-
1641
- // Fonction pour générer du contenu avec l'IA (version simulée car besoin d'une clé API)
1642
- function generateAIContent() {
1643
- const prompt = document.getElementById('ai-prompt').value;
1644
- const contentType = document.getElementById('ai-content-type').value;
1645
-
1646
- if (!prompt.trim()) {
1647
- alert('Veuillez entrer une description pour l\'IA.');
1648
- return;
1649
- }
1650
-
1651
- // Simulation de réponse IA (en production, il faudrait appeler l'API OpenAI)
1652
- const responses = {
1653
- title: [
1654
- "Découvrez un Monde d'Innovation",
1655
- "Votre Partenaire pour la Réussite Digitale",
1656
- "Excellence et Créativité au Service de Vos Projets"
1657
- ],
1658
- paragraph: [
1659
- "Nous sommes une équipe passionnée dédiée à la création de solutions web innovantes. Notre approche combine design élégant et technologie de pointe pour offrir des expériences utilisateur exceptionnelles.",
1660
- "Avec des années d'expérience dans le développement web, nous accompagnons nos clients dans la réalisation de leurs projets digitaux, de la conception à la mise en production.",
1661
- "Notre philosophie est simple : comprendre vos besoins, anticiper vos attentes et délivrer des solutions qui dépassent vos espérances."
1662
- ],
1663
- button: [
1664
- "Commencer l'aventure",
1665
- "En savoir plus",
1666
- "Contactez-nous"
1667
- ],
1668
- section: `
1669
- <section style="background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); color: white; padding: 60px 20px; border-radius: 20px; text-align: center;">
1670
- <h2 style="font-size: 2.5rem; margin-bottom: 20px;">Une Solution Sur Mesure</h2>
1671
- <p style="font-size: 1.2rem; max-width: 800px; margin: 0 auto 30px;">Notre équipe d'experts crée des solutions personnalisées adaptées à vos besoins spécifiques pour maximiser votre impact digital.</p>
1672
- <button style="background: white; color: #2575fc; border: none; padding: 15px 40px; font-size: 1.1rem; border-radius: 50px; cursor: pointer; font-weight: bold;">Découvrir nos réalisations</button>
1673
- </section>
1674
- `
1675
- };
1676
-
1677
- // Choisir une réponse aléatoire pour le type demandé
1678
- let response;
1679
- if (contentType === 'section') {
1680
- response = responses[contentType];
1681
- } else {
1682
- const options = responses[contentType];
1683
- response = options[Math.floor(Math.random() * options.length)];
1684
- }
1685
-
1686
- // Si un élément est sélectionné, mettre à jour son contenu
1687
- if (selectedElementId) {
1688
- const element = elements.find(el => el.id === selectedElementId);
1689
- if (element) {
1690
- element.content = response;
1691
- const el = document.getElementById(element.id);
1692
- if (el && element.type !== 'image') {
1693
- el.querySelector(element.tag).innerHTML = response;
1694
- updatePropertiesPanel();
1695
- }
1696
- }
1697
- } else {
1698
- // Sinon, ajouter un nouvel élément avec le contenu généré
1699
- let type = contentType;
1700
- if (contentType === 'section') type = 'section';
1701
- addElement(type);
1702
- // Le nouvel élément aura le contenu par défaut, donc on le met à jour
1703
- const newElement = elements[elements.length - 1];
1704
- newElement.content = response;
1705
- const el = document.getElementById(newElement.id);
1706
- if (el && newElement.type !== 'image') {
1707
- el.querySelector(newElement.tag).innerHTML = response;
1708
- }
1709
- }
1710
-
1711
- alert('Contenu généré avec l\'IA (simulation). En production, connectez-vous à l\'API OpenAI.');
1712
- }
1713
- </script>
1714
  </body>
1715
  </html>
 
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Espace Codage</title>
8
  <link rel="stylesheet" href="style.css">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"/>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  </head>
11
  <body>
 
 
 
 
 
12
  <div class="container">
13
+ <!-- Colonne 1 : Navigation -->
14
+ <aside class="sidebar">
15
+ <div class="header">
16
+ <a href="#" class="logo">Espace Codage</a>
17
+ </div>
18
+ <nav class="menu">
19
+ <button>Nouvelle tâche</button>
20
+ <button>Rechercher</button>
21
+ <button>Bibliothèque</button>
22
+ </nav>
23
+ <div class="projects">
24
+ <h3>Projets</h3>
25
+ <ul id="projectList">
26
+ <!-- Projets ajoutés ici -->
27
+ </ul>
28
+ </div>
29
+ </aside>
30
+ <!-- Colonne 2 : Chat IA Rosalinda -->
31
+ <main class="chat">
32
+ <div class="chat-header">
33
+ <h2>Espace Codage – Rosalinda</h2>
34
+ <div class="activity-monitor">[ Activité IA en cours... ]</div>
35
+ </div>
36
+ <div class="chat-messages" id="chatMessages">
37
+ <!-- Messages Rosalinda ici -->
38
+ </div>
39
+ <div class="chat-input">
40
+ <button title="Ajouter un fichier"><i class="fas fa-plus"></i></button>
41
+ <button title="Connexion"><i class="fas fa-plug"></i></button>
42
+ <input type="text" placeholder="Écrire à Rosalinda..." />
43
+ <button title="Micro"><i class="fas fa-microphone"></i></button>
44
+ <button title="Envoyer"><i class="fas fa-paper-plane"></i></button>
45
+ </div>
46
+ </main>
47
+
48
+ <!-- Colonne 3 : Résultat & Code -->
49
+ <section class="output">
50
+ <div class="output-tabs">
51
+ <button><i class="fas fa-code"></i> Code</button>
52
+ <button><i class="fas fa-chart-pie"></i> Tableau de bord</button>
53
+ <button><i class="fas fa-database"></i> Base de données</button>
54
+ <button><i class="fas fa-folder"></i> Stockage</button>
55
+ <button><i class="fas fa-cog"></i> Paramètres</button>
56
+ </div>
57
+ <div class="code-output" id="codeOutput">
58
+ <pre><code>// Code généré s'affichera ici...</code></pre>
59
+ </div>
60
+ <div class="result-output">
61
+ <p>Résultat final ici (image, vidéo ou page)...</p>
62
+ </div>
63
+ </section>
 
 
 
 
 
 
 
64
  </div>
65
+ <script src="script.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  </body>
67
  </html>
script.js CHANGED
@@ -1,70 +1,4 @@
1
 
2
- document.addEventListener('DOMContentLoaded', function() {
3
- // Initialize partnership agreement
4
- const partnershipModal = document.createElement('partnership-agreement');
5
- document.body.appendChild(partnershipModal);
6
-
7
- partnershipModal.addEventListener('agreement-accepted', () => {
8
- setTimeout(() => {
9
- partnershipModal.remove();
10
- // Add to chat history
11
- const chat = document.querySelector('custom-chat');
12
- if (chat) {
13
- const shadow = chat.shadowRoot;
14
- const chatContainer = shadow.getElementById('chat');
15
- const message = document.createElement('div');
16
- message.className = 'message ai-message';
17
- message.textContent = 'Accord de partenariat accepté. Prêt à collaborer !';
18
- chatContainer.appendChild(message);
19
- chatContainer.scrollTop = chatContainer.scrollHeight;
20
- }
21
- }, 2000);
22
- });
23
 
24
- // Navigation entre les vues
25
- const views = {
26
- 'rosalinda-chat': document.querySelector('custom-chat'),
27
- 'code-editor': document.querySelector('custom-code-editor')
28
- };
29
-
30
- // Fonction pour changer de vue
31
- function showView(targetId) {
32
- Object.keys(views).forEach(id => {
33
- if (views[id]) {
34
- if (id === targetId) {
35
- views[id].classList.add('active');
36
- } else {
37
- views[id].classList.remove('active');
38
- }
39
- }
40
- });
41
- }
42
-
43
- // Événements de navigation
44
- document.addEventListener('click', (e) => {
45
- const target = e.target.closest('[data-target]');
46
- if (target) {
47
- e.preventDefault();
48
- const targetId = target.getAttribute('data-target');
49
- showView(targetId);
50
- }
51
- });
52
-
53
- // Vue par défaut
54
- showView('rosalinda-chat');
55
-
56
- // Chargement des fichiers sauvegardés
57
- const savedFiles = localStorage.getItem('rosalinda-files');
58
- if (savedFiles) {
59
- try {
60
- const files = JSON.parse(savedFiles);
61
- const editor = document.querySelector('custom-code-editor');
62
- if (editor) {
63
- editor._files = files;
64
- editor._render();
65
- }
66
- } catch (e) {
67
- console.log('Impossible de charger les fichiers sauvegardés');
68
- }
69
- }
70
- });
 
1
 
2
+ console.log("Interface Espace Codage chargée.");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ // Tu pourras ajouter ici les fonctions pour Rosalinda plus tard
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
style.css CHANGED
@@ -1,218 +1,131 @@
1
 
2
- body {
3
- margin: 0;
4
- padding: 0;
5
- overflow: hidden;
6
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
7
- background: #f8fafc;
8
- }
9
-
10
- /* Protection contre les plateformes externes */
11
- .protection-banner {
12
- background: #ef4444;
13
- color: white;
14
- padding: 0.5rem;
15
- text-align: center;
16
- font-weight: 500;
17
- }
18
- /* Rosalinda IA Chat Styles */
19
- custom-chat {
20
- flex: 1;
21
- display: flex;
22
- flex-direction: column;
23
- background: #0b0f19;
24
- color: white;
25
- position: relative;
26
- }
27
-
28
- .user-message {
29
- background: #3b82f6;
30
- color: white;
31
- margin-left: auto;
32
- border-radius: 1rem 1rem 0 1rem;
33
- max-width: 80%;
34
- padding: 0.75rem 1rem;
35
- margin-bottom: 0.5rem;
36
- }
37
-
38
- .ai-message {
39
- background: #1e293b;
40
- color: white;
41
- margin-right: auto;
42
- border-radius: 1rem 1rem 1rem 0;
43
- max-width: 80%;
44
- padding: 0.75rem 1rem;
45
- margin-bottom: 0.5rem;
46
- }
47
- .mic-button.listening {
48
- background-color: #3b82f6 !important;
49
- color: white !important;
50
- animation: pulse 1.5s infinite;
51
- box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
52
- transition: all 0.3s ease;
53
- position: relative;
54
- }
55
-
56
- .mic-button.listening::after {
57
- content: '';
58
- position: absolute;
59
- top: -5px;
60
- left: -5px;
61
- right: -5px;
62
- bottom: -5px;
63
- border: 2px solid rgba(59, 130, 246, 0.3);
64
- border-radius: 50%;
65
- animation: ripple 1.5s infinite;
66
- }
67
- @keyframes pulse {
68
- 0% { opacity: 1; transform: scale(1); }
69
- 50% { opacity: 0.8; transform: scale(1.05); }
70
- 100% { opacity: 1; transform: scale(1); }
71
- }
72
-
73
- @keyframes ripple {
74
- 0% { transform: scale(0.8); opacity: 1; }
75
- 100% { transform: scale(1.3); opacity: 0; }
76
- }
77
-
78
- custom-chat {
79
- border: 1px solid rgba(255, 255, 255, 0.1);
80
- border-radius: 0.75rem;
81
- overflow: hidden;
82
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
83
- }
84
-
85
- .message-input {
86
- transition: all 0.3s ease;
87
- }
88
-
89
- .message-input:focus {
90
- border-color: #3b82f6;
91
- box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
92
- }
93
- .file-info {
94
- font-size: 0.75rem;
95
- color: #3b82f6;
96
- margin-top: 0.25rem;
97
- display: flex;
98
- align-items: center;
99
- gap: 0.25rem;
100
- padding: 0.25rem 0.5rem;
101
- background: rgba(59, 130, 246, 0.1);
102
- border-radius: 0.25rem;
103
- }
104
- custom-sidebar {
105
- width: 280px;
106
- flex-shrink: 0;
107
- background: #f8fafc;
108
- border-right: 1px solid #e2e8f0;
109
- }
110
-
111
- custom-chat {
112
- flex-grow: 1;
113
- width: calc(100% - 280px - 40%);
114
- background: white;
115
- border-right: 1px solid #e2e8f0;
116
- overflow-y: auto;
117
- }
118
-
119
- custom-code-preview {
120
- width: 40%;
121
- flex-shrink: 0;
122
- background: white;
123
- overflow-y: auto;
124
- }
125
-
126
- .back-arrow {
127
- margin-left: auto;
128
- cursor: pointer;
129
- }
130
-
131
- .favorites div {
132
- padding: 0.5rem;
133
- color: #475569;
134
- cursor: pointer;
135
- }
136
-
137
- .favorites div:hover {
138
- background: #f1f5f9;
139
- }
140
- .divider {
141
- text-align: center;
142
- color: #94a3b8;
143
- margin: 1rem 0;
144
- }
145
-
146
- .mic-active.listening {
147
- background-color: #3b82f6;
148
- color: white;
149
- }
150
-
151
- .logs-container {
152
- max-height: 120px;
153
- overflow-y: auto;
154
- background: #f8fafc;
155
- border: 1px solid #e2e8f0;
156
- border-radius: 0.5rem;
157
- padding: 0.5rem;
158
- margin-bottom: 0.5rem;
159
- font-family: monospace;
160
- font-size: 0.8rem;
161
- }
162
-
163
- .log-line {
164
- margin-bottom: 0.25rem;
165
- color: #64748b;
166
- }
167
- .conversation-title {
168
- font-weight: 600;
169
- margin-bottom: 1rem;
170
- color: #1e293b;
171
- }
172
-
173
- .code-scroll-title {
174
- text-align: center;
175
- color: #64748b;
176
- margin-bottom: 1rem;
177
- }
178
-
179
- .code-line {
180
- font-family: monospace;
181
- white-space: pre;
182
- margin-bottom: 0.25rem;
183
- }
184
-
185
- .features {
186
- margin-top: 1rem;
187
- padding-top: 1rem;
188
- border-top: 1px dashed #e2e8f0;
189
- }
190
-
191
- .features-title {
192
- font-weight: 600;
193
- margin-bottom: 0.5rem;
194
- }
195
- .integration-note {
196
- margin-top: 1rem;
197
- padding: 0.75rem;
198
- background: #f0fdf4;
199
- border-radius: 0.5rem;
200
- color: #166534;
201
- font-size: 0.875rem;
202
- }
203
-
204
- partnership-agreement {
205
- position: fixed;
206
- top: 50%;
207
- left: 50%;
208
- transform: translate(-50%, -50%);
209
- z-index: 1000;
210
- width: 90%;
211
- max-width: 600px;
212
- animation: fadeIn 0.3s ease-out;
213
- }
214
-
215
- @keyframes fadeIn {
216
- from { opacity: 0; transform: translate(-50%, -40%); }
217
- to { opacity: 1; transform: translate(-50%, -50%); }
218
  }
 
1
 
2
+ * {
3
+ box-sizing: border-box;
4
+ margin: 0;
5
+ padding: 0;
6
+ font-family: Arial, sans-serif;
7
+ }
8
+ body, html {
9
+ height: 100%;
10
+ width: 100%;
11
+ }
12
+ .container {
13
+ display: flex;
14
+ height: 100vh;
15
+ }
16
+ /* Colonne 1 */
17
+ .sidebar {
18
+ width: 20%;
19
+ background: #f1f1f1;
20
+ border-right: 1px solid #ccc;
21
+ padding: 20px;
22
+ display: flex;
23
+ flex-direction: column;
24
+ }
25
+ .sidebar .logo {
26
+ font-weight: bold;
27
+ font-size: 18px;
28
+ text-decoration: none;
29
+ color: #222;
30
+ margin-bottom: 20px;
31
+ display: block;
32
+ }
33
+ .menu button {
34
+ display: block;
35
+ width: 100%;
36
+ margin-bottom: 10px;
37
+ padding: 10px;
38
+ background: #e0e0e0;
39
+ border: none;
40
+ cursor: pointer;
41
+ }
42
+ .projects h3 {
43
+ margin-top: 20px;
44
+ margin-bottom: 10px;
45
+ }
46
+ .projects ul {
47
+ list-style: none;
48
+ }
49
+ .projects li {
50
+ padding: 5px 0;
51
+ border-bottom: 1px solid #ccc;
52
+ }
53
+ /* Colonne 2 */
54
+ .chat {
55
+ width: 45%;
56
+ display: flex;
57
+ flex-direction: column;
58
+ padding: 20px;
59
+ }
60
+ .chat-header h2 {
61
+ font-size: 18px;
62
+ margin-bottom: 5px;
63
+ }
64
+ .activity-monitor {
65
+ font-size: 12px;
66
+ color: gray;
67
+ margin-bottom: 15px;
68
+ }
69
+ .chat-messages {
70
+ flex: 1;
71
+ background: #fafafa;
72
+ padding: 10px;
73
+ border: 1px solid #ddd;
74
+ overflow-y: auto;
75
+ }
76
+ .chat-input {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 8px;
80
+ margin-top: 10px;
81
+ }
82
+ .chat-input input {
83
+ flex: 1;
84
+ padding: 10px;
85
+ }
86
+ .chat-input button {
87
+ background: #ddd;
88
+ border: none;
89
+ padding: 10px;
90
+ cursor: pointer;
91
+ }
92
+ /* Colonne 3 */
93
+ .output {
94
+ width: 35%;
95
+ background: #fcfcfc;
96
+ border-left: 1px solid #ccc;
97
+ display: flex;
98
+ flex-direction: column;
99
+ padding: 20px;
100
+ }
101
+ .output-tabs {
102
+ display: flex;
103
+ gap: 10px;
104
+ margin-bottom: 10px;
105
+ }
106
+ .output-tabs button {
107
+ flex: 1;
108
+ padding: 8px;
109
+ font-size: 12px;
110
+ background: #e8e8e8;
111
+ border: none;
112
+ cursor: pointer;
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 5px;
116
+ }
117
+ .code-output {
118
+ flex: 1;
119
+ background: #000;
120
+ color: #0f0;
121
+ padding: 10px;
122
+ font-family: monospace;
123
+ overflow-y: auto;
124
+ margin-bottom: 10px;
125
+ }
126
+ .result-output {
127
+ background: #fff;
128
+ border: 1px solid #ccc;
129
+ padding: 10px;
130
+ text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  }