Docfile commited on
Commit
e0c9b0b
1 Parent(s): 9c37263

Create templates/svt.html

Browse files
Files changed (1) hide show
  1. templates/svt.html +413 -0
templates/svt.html ADDED
@@ -0,0 +1,413 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mariam AI - SVT</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css">
10
+ <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
11
+ <style>
12
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
13
+
14
+ body {
15
+ font-family: 'Poppins', sans-serif;
16
+ background: linear-gradient(135deg, #f6f8fc 0%, #e9f0f7 100%);
17
+ }
18
+
19
+ .animate-fade-in {
20
+ animation: fadeIn 0.5s ease-in;
21
+ }
22
+
23
+ .animate-slide-up {
24
+ animation: slideUp 0.5s ease-out;
25
+ }
26
+
27
+ @keyframes fadeIn {
28
+ from { opacity: 0; }
29
+ to { opacity: 1; }
30
+ }
31
+
32
+ @keyframes slideUp {
33
+ from { transform: translateY(20px); opacity: 0; }
34
+ to { transform: translateY(0); opacity: 1; }
35
+ }
36
+
37
+ .glass-effect {
38
+ background: rgba(255, 255, 255, 0.95);
39
+ backdrop-filter: blur(10px);
40
+ border: 1px solid rgba(255, 255, 255, 0.2);
41
+ }
42
+
43
+ .hover-scale {
44
+ transition: transform 0.2s;
45
+ }
46
+
47
+ .hover-scale:hover {
48
+ transform: scale(1.02);
49
+ }
50
+
51
+ .image-preview {
52
+ transition: all 0.3s ease;
53
+ }
54
+
55
+ .image-preview:hover .image-overlay {
56
+ opacity: 1;
57
+ }
58
+
59
+ .image-overlay {
60
+ opacity: 0;
61
+ transition: opacity 0.3s ease;
62
+ background: rgba(0, 0, 0, 0.5);
63
+ }
64
+
65
+ .preview-container {
66
+ display: grid;
67
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
68
+ gap: 1rem;
69
+ }
70
+ </style>
71
+ </head>
72
+ <body class="min-h-screen flex items-center justify-center p-4 md:p-8">
73
+ <div class="container mx-auto glass-effect rounded-2xl shadow-xl max-w-4xl animate-fade-in p-6 md:p-8">
74
+ <header class="flex flex-col md:flex-row justify-between items-center mb-8">
75
+ <div class="text-center md:text-left mb-4 md:mb-0">
76
+ <h1 class="text-4xl font-bold text-blue-900">Mariam AI</h1>
77
+ <p class="text-gray-600 mt-2">Assistant SVT Intelligent</p>
78
+ </div>
79
+ <button onclick="showInfo()" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-xl hover-scale transition-all duration-300 flex items-center gap-2">
80
+ <i class="fas fa-info-circle"></i>
81
+ <span>Guide d'utilisation</span>
82
+ </button>
83
+ </header>
84
+
85
+ <div class="space-y-8 animate-slide-up">
86
+ <div class="relative">
87
+ <label for="svtOption" class="block mb-3 text-lg font-medium text-gray-700">Type d'exercice :</label>
88
+ <select id="svtOption" class="w-full p-4 border border-gray-200 rounded-xl bg-white shadow-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-300">
89
+ <option value="Restitution organisée des connaissances">Restitution Organisée des Connaissances</option>
90
+ <option value="Exploitation du document">Exploitation du Document</option>
91
+ <option value="Synthèse">Synthèse</option>
92
+ </select>
93
+ </div>
94
+
95
+ <div class="relative">
96
+ <label class="block mb-3 text-lg font-medium text-gray-700">Images du sujet :</label>
97
+ <div class="border-2 border-dashed border-gray-300 rounded-xl p-8 text-center hover:border-blue-500 transition-all duration-300">
98
+ <input type="file" id="imageUpload" class="hidden" multiple accept="image/*" onchange="handleImageUpload(event)">
99
+ <label for="imageUpload" class="cursor-pointer">
100
+ <i class="fas fa-cloud-upload-alt text-4xl text-blue-500 mb-4"></i>
101
+ <p class="text-gray-600">Glissez vos images ici ou cliquez pour sélectionner</p>
102
+ <p class="text-sm text-gray-500 mt-2">Format acceptés : JPG, PNG, GIF</p>
103
+ </label>
104
+ </div>
105
+
106
+ <!-- Conteneur des prévisualisations -->
107
+ <div id="previewContainer" class="preview-container mt-4">
108
+ <!-- Les prévisualisations seront ajoutées ici -->
109
+ </div>
110
+ </div>
111
+
112
+ <button onclick="submitQuestion()" class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white py-4 rounded-xl hover-scale transition-all duration-300 font-semibold text-lg flex items-center justify-center gap-2">
113
+ <i class="fas fa-paper-plane"></i>
114
+ Analyser
115
+ </button>
116
+
117
+ <div id="loader" class="hidden">
118
+ <div class="flex flex-col items-center space-y-4 p-8">
119
+ <div class="animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"></div>
120
+ <p class="text-gray-600 text-lg">Analyse en cours...</p>
121
+ </div>
122
+ </div>
123
+
124
+ <div id="response" class="mt-6 p-6 bg-white rounded-xl shadow-sm prose max-w-none"></div>
125
+
126
+ <div id="copyResponseContainer" class="hidden">
127
+ <button onclick="copyResponse()" class="w-full bg-gray-800 hover:bg-gray-900 text-white px-6 py-3 rounded-xl hover-scale transition-all duration-300 flex items-center justify-center gap-2">
128
+ <i class="fas fa-copy"></i>
129
+ Copier la réponse
130
+ </button>
131
+ </div>
132
+ </div>
133
+ </div>
134
+
135
+ <div class="container mx-auto glass-effect rounded-2xl shadow-xl max-w-4xl animate-fade-in p-6 md:p-8 mt-6">
136
+ <div class="space-y-8 animate-slide-up">
137
+ <h2 class="text-2xl font-bold text-blue-900 mb-4">Historique des Réponses</h2>
138
+ <button onclick="clearLocalStorage()" class="bg-red-500 hover:bg-red-700 text-white px-4 py-2 rounded-lg">
139
+ Effacer l'historique
140
+ </button>
141
+ <div id="historyContainer">
142
+ <!-- L'historique des réponses sera affiché ici -->
143
+ </div>
144
+ </div>
145
+ </div>
146
+
147
+ <script>
148
+ let uploadedFiles = [];
149
+
150
+ function handleImageUpload(event) {
151
+ const files = event.target.files;
152
+ const previewContainer = document.getElementById('previewContainer');
153
+
154
+ for (let i = 0; i < files.length; i++) {
155
+ const file = files[i];
156
+ uploadedFiles.push(file);
157
+
158
+ const reader = new FileReader();
159
+ reader.onload = function(e) {
160
+ const imageId = `img-${Date.now()}-${i}`;
161
+ const previewDiv = document.createElement('div');
162
+ previewDiv.className = 'image-preview relative rounded-lg overflow-hidden';
163
+ previewDiv.id = imageId;
164
+ previewDiv.innerHTML = `
165
+ <img src="${e.target.result}" alt="${file.name}" class="w-full h-40 object-cover">
166
+ <div class="image-overlay absolute inset-0 flex items-center justify-center">
167
+ <button onclick="removeImage('${imageId}')" class="bg-red-500 hover:bg-red-600 text-white p-2 rounded-full transition-all duration-300">
168
+ <i class="fas fa-trash"></i>
169
+ </button>
170
+ <button onclick="previewImage('${e.target.result}')" class="bg-blue-500 hover:bg-blue-600 text-white p-2 rounded-full ml-2 transition-all duration-300">
171
+ <i class="fas fa-eye"></i>
172
+ </button>
173
+ </div>
174
+ <div class="bg-black bg-opacity-50 text-white text-xs p-1 absolute bottom-0 left-0 right-0">
175
+ ${file.name.substring(0, 15)}${file.name.length > 15 ? '...' : ''}
176
+ </div>
177
+ `;
178
+ previewContainer.appendChild(previewDiv);
179
+ };
180
+ reader.readAsDataURL(file);
181
+ }
182
+ }
183
+
184
+ function removeImage(imageId) {
185
+ const imageIndex = uploadedFiles.findIndex(file => {
186
+ const fileId = `img-${file.lastModified}-${uploadedFiles.indexOf(file)}`;
187
+ return fileId === imageId;
188
+ });
189
+
190
+ if (imageIndex !== -1) {
191
+ uploadedFiles.splice(imageIndex, 1);
192
+ }
193
+
194
+ const element = document.getElementById(imageId);
195
+ if (element) {
196
+ element.remove();
197
+ }
198
+ }
199
+
200
+ function previewImage(src) {
201
+ Swal.fire({
202
+ imageUrl: src,
203
+ imageAlt: 'Prévisualisation',
204
+ width: '80%',
205
+ showConfirmButton: false,
206
+ showCloseButton: true,
207
+ customClass: {
208
+ image: 'max-h-[80vh] object-contain'
209
+ }
210
+ });
211
+ }
212
+
213
+ function showInfo() {
214
+ Swal.fire({
215
+ title: 'Guide d\'utilisation',
216
+ html: `
217
+ <div class="text-left space-y-4">
218
+ <div class="flex items-start gap-3">
219
+ <i class="fas fa-check-circle text-green-500 mt-1"></i>
220
+ <p>Sélectionnez le type d'exercice correspondant à votre sujet.</p>
221
+ </div>
222
+ <div class="flex items-start gap-3">
223
+ <i class="fas fa-image text-blue-500 mt-1"></i>
224
+ <p>Assurez-vous que vos images sont nettes et bien cadrées.</p>
225
+ </div>
226
+ <div class="flex items-start gap-3">
227
+ <i class="fas fa-crop-alt text-purple-500 mt-1"></i>
228
+ <p>Rognez vos images pour ne garder que l'essentiel du sujet.</p>
229
+ </div>
230
+ </div>
231
+ `,
232
+ icon: 'info',
233
+ confirmButtonText: 'Compris',
234
+ confirmButtonColor: '#2563eb',
235
+ customClass: {
236
+ container: 'font-sans'
237
+ }
238
+ });
239
+ }
240
+
241
+ function copyResponse() {
242
+ const responseDiv = document.getElementById('response');
243
+ const range = document.createRange();
244
+ range.selectNode(responseDiv);
245
+ window.getSelection().removeAllRanges();
246
+ window.getSelection().addRange(range);
247
+ document.execCommand('copy');
248
+ window.getSelection().removeAllRanges();
249
+
250
+ Swal.fire({
251
+ icon: 'success',
252
+ title: 'Copié !',
253
+ text: 'La réponse a été copiée dans le presse-papiers.',
254
+ showConfirmButton: false,
255
+ timer: 1500,
256
+ customClass: {
257
+ popup: 'animate-fade-in'
258
+ }
259
+ });
260
+ }
261
+
262
+ async function submitQuestion() {
263
+ if (uploadedFiles.length === 0) {
264
+ Swal.fire({
265
+ icon: 'error',
266
+ title: 'Images manquantes',
267
+ text: 'Veuillez sélectionner au moins une image du sujet.',
268
+ confirmButtonColor: '#2563eb'
269
+ });
270
+ return;
271
+ }
272
+
273
+ const option = document.getElementById('svtOption').value;
274
+ const loader = document.getElementById('loader');
275
+ const responseDiv = document.getElementById('response');
276
+ const copyResponseContainer = document.getElementById('copyResponseContainer');
277
+
278
+ loader.classList.remove('hidden');
279
+ responseDiv.innerHTML = '';
280
+ copyResponseContainer.classList.add('hidden');
281
+
282
+ const formData = new FormData();
283
+ formData.append('option', option);
284
+
285
+ for (let i = 0; i < uploadedFiles.length; i++) {
286
+ formData.append('images', uploadedFiles[i]);
287
+ }
288
+
289
+ const response = await fetch('/svt_submit', {
290
+ method: 'POST',
291
+ body: formData
292
+ });
293
+
294
+ const data = await response.json();
295
+ loader.classList.add('hidden');
296
+
297
+ if (data.error) {
298
+ responseDiv.innerHTML = `
299
+ <div class="bg-red-50 border-l-4 border-red-500 p-4 rounded">
300
+ <p class="text-red-700">Erreur : ${data.error}</p>
301
+ </div>
302
+ `;
303
+ } else {
304
+ const htmlContent = marked.parse(data.response);
305
+ responseDiv.innerHTML = htmlContent;
306
+ responseDiv.classList.add('animate-fade-in');
307
+ copyResponseContainer.classList.remove('hidden');
308
+
309
+ // Convertir les images en base64 avant de les sauvegarder
310
+ const imagesData = await Promise.all(uploadedFiles.map(file => {
311
+ return new Promise((resolve) => {
312
+ const reader = new FileReader();
313
+ reader.onload = (e) => resolve(e.target.result);
314
+ reader.readAsDataURL(file);
315
+ });
316
+ }));
317
+
318
+ saveResponseToLocalStorage(option, imagesData, data.response);
319
+ displayHistory();
320
+ }
321
+ }
322
+
323
+ // Fonctions pour la gestion du localStorage
324
+ function saveResponseToLocalStorage(option, images, response) {
325
+ const timestamp = new Date().toISOString();
326
+ const data = { option, images, response, timestamp };
327
+ localStorage.setItem('svt_response_' + timestamp, JSON.stringify(data));
328
+ }
329
+
330
+ function loadResponsesFromLocalStorage() {
331
+ const responses = [];
332
+ for (let i = 0; i < localStorage.length; i++) {
333
+ const key = localStorage.key(i);
334
+ if (key.startsWith('svt_response_')) {
335
+ const data = JSON.parse(localStorage.getItem(key));
336
+ responses.push(data);
337
+ }
338
+ }
339
+ return responses.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
340
+ }
341
+
342
+ function clearLocalStorage() {
343
+ const keysToRemove = [];
344
+ for (let i = 0; i < localStorage.length; i++) {
345
+ const key = localStorage.key(i);
346
+ if (key.startsWith('svt_response_')) {
347
+ keysToRemove.push(key);
348
+ }
349
+ }
350
+ for (const key of keysToRemove) {
351
+ localStorage.removeItem(key);
352
+ }
353
+ displayHistory();
354
+ }
355
+
356
+ function displayHistory() {
357
+ const responses = loadResponsesFromLocalStorage();
358
+ const historyContainer = document.getElementById('historyContainer');
359
+ historyContainer.innerHTML = '';
360
+
361
+ if (responses.length === 0) {
362
+ historyContainer.innerHTML = '<p class="text-gray-500">Aucun historique disponible.</p>';
363
+ return;
364
+ }
365
+
366
+ const responseList = document.createElement('ul');
367
+ responseList.className = 'space-y-4';
368
+
369
+ responses.forEach(response => {
370
+ const listItem = document.createElement('li');
371
+ listItem.className = 'bg-white p-4 rounded-lg shadow-md hover:shadow-lg transition duration-300';
372
+
373
+ const title = document.createElement('h4');
374
+ title.className = 'text-lg font-semibold text-blue-800 mb-2';
375
+ title.textContent = `${response.option} - ${new Date(response.timestamp).toLocaleString()}`;
376
+ listItem.appendChild(title);
377
+
378
+ const previewContainer = document.createElement('div');
379
+ previewContainer.className = 'flex gap-2 mb-2';
380
+ response.images.forEach(imageData => {
381
+ const img = document.createElement('img');
382
+ img.src = imageData;
383
+ img.className = 'h-16 w-16 object-cover rounded-md cursor-pointer';
384
+ img.onclick = () => previewImage(imageData);
385
+ previewContainer.appendChild(img);
386
+ });
387
+ listItem.appendChild(previewContainer);
388
+
389
+ const responsePreview = document.createElement('p');
390
+ responsePreview.className = 'text-gray-600';
391
+ responsePreview.textContent = response.response.substring(0, 200) + (response.response.length > 200 ? '...' : '');
392
+ listItem.appendChild(responsePreview);
393
+
394
+ const viewButton = document.createElement('button');
395
+ viewButton.className = 'bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg mt-2';
396
+ viewButton.textContent = 'Voir';
397
+ viewButton.onclick = () => {
398
+ document.getElementById('response').innerHTML = marked.parse(response.response);
399
+ document.getElementById('copyResponseContainer').classList.remove('hidden');
400
+ window.scrollTo({ top: document.getElementById('response').offsetTop, behavior: 'smooth' });
401
+ };
402
+ listItem.appendChild(viewButton);
403
+
404
+ responseList.appendChild(listItem);
405
+ });
406
+
407
+ historyContainer.appendChild(responseList);
408
+ }
409
+
410
+ displayHistory();
411
+ </script>
412
+ </body>
413
+ </html>