Docfile commited on
Commit
2abfdf4
·
verified ·
1 Parent(s): 451d8eb

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +340 -384
templates/index.html CHANGED
@@ -1,413 +1,369 @@
1
-
2
-
3
  <!DOCTYPE html>
4
  <html lang="fr">
5
  <head>
6
- <meta charset="UTF-8">
7
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
- <title>Mariam M-0 | Solution Mathématique</title>
9
- <!-- Tailwind CSS -->
10
- <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
11
-
12
- <!-- SweetAlert2 -->
13
- <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
14
-
15
- <!-- Configuration de MathJax -->
16
- <script>
17
- window.MathJax = {
18
- tex: {
19
- inlineMath: [['$', '$']],
20
- displayMath: [['$$', '$$']],
21
- processEscapes: true,
22
- packages: {'[+]': ['autoload', 'ams']}
23
- },
24
- options: {
25
- enableMenu: false,
26
- messageStyle: 'none'
27
- },
28
- startup: {
29
- pageReady: () => { window.mathJaxReady = true; }
30
- }
31
- };
32
- </script>
33
- <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script" async></script>
34
- <script src="https://cdn.jsdelivr.net/npm/marked@15.0.7/lib/marked.umd.min.js"></script>
35
-
36
- <style>
37
- @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;700&display=swap');
38
- body { font-family: 'Space Grotesk', sans-serif; }
39
-
40
- .uploadArea {
41
- background: #f3f4f6;
42
- border: 2px dashed #d1d5db;
43
- transition: border-color 0.2s ease;
44
- }
45
- .uploadArea:hover { border-color: #3b82f6; }
46
-
47
- .blue-button { background: #3b82f6; transition: background-color 0.2s ease; }
48
- .blue-button:hover { background: #2563eb; }
49
-
50
- .loader {
51
- width: 48px;
52
- height: 48px;
53
- border: 3px solid #3b82f6;
54
- border-bottom-color: transparent;
55
- border-radius: 50%;
56
- display: inline-block;
57
- animation: rotation 1s linear infinite;
58
- }
59
- @keyframes rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
60
-
61
- .thought-box {
62
- transition: max-height 0.3s ease-out;
63
- max-height: 0;
64
- overflow: hidden;
65
- }
66
- .thought-box.open { max-height: 500px; }
67
-
68
- #thoughtsContent, #answerContent {
69
- max-height: 500px;
70
- overflow-y: auto;
71
- scroll-behavior: smooth;
72
- white-space: pre-wrap;
73
- }
74
-
75
- .preview-image { max-width: 300px; max-height: 300px; object-fit: contain; }
76
-
77
- .timestamp { color: #3b82f6; font-size: 0.9em; margin-left: 8px; }
78
-
79
- table {
80
- border-collapse: collapse;
81
- width: 100%;
82
- margin-bottom: 1rem;
83
- }
84
- th, td {
85
- border: 1px solid #d1d5db;
86
- padding: 0.5rem;
87
- text-align: left;
88
- }
89
- th { background-color: #f3f4f6; font-weight: 600; }
90
- .table-responsive { overflow-x: auto; }
91
-
92
- /* Style pour le bouton Sauvegarder afin de le mettre en évidence */
93
- #saveButton {
94
- background: #3b82f6;
95
- color: white;
96
- padding: 0.5rem 1rem;
97
- border-radius: 0.375rem;
98
- transition: background-color 0.2s ease;
99
- }
100
- #saveButton:hover { background: #2563eb; }
101
-
102
- /* Modal plein écran pour les sauvegardes */
103
- #savedModal {
104
- display: none;
105
- position: fixed;
106
- inset: 0;
107
- background: rgba(0,0,0,0.5);
108
- z-index: 50;
109
- }
110
- #savedModal.active { display: block; }
111
- #savedModalContent {
112
- background: #fff;
113
- width: 100%;
114
- height: 100%;
115
- overflow-y: auto;
116
- }
117
- </style>
 
 
 
 
 
 
 
 
 
118
  </head>
119
- <body class="p-4">
120
- <div class="max-w-4xl mx-auto">
121
- <header class="p-6 text-center mb-8">
122
- <h1 class="text-4xl font-bold text-blue-600">Mariam M-1</h1>
123
- <p class="text-gray-600">Solution Mathématique/Physique/Chimie Intelligente</p>
124
- <div class="mt-4 flex justify-end">
125
- <button id="openSaved" class="blue-button px-4 py-2 text-white rounded">Sauvegardes</button>
126
- </div>
127
- </header>
128
 
129
- <main id="mainContent">
130
- <form id="problemForm" class="space-y-6" novalidate>
131
- <!-- Zone de dépôt / sélection d'image -->
132
- <div class="uploadArea p-8 text-center relative" aria-label="Zone de dépôt d'image">
133
- <input type="file" id="imageInput" accept="image/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer" aria-label="Choisir une image">
134
- <div class="space-y-3">
135
- <div class="w-16 h-16 mx-auto border-2 border-blue-400 rounded-full flex items-center justify-center">
136
- <svg class="w-8 h-8 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
137
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
138
- d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
139
- </svg>
140
  </div>
141
- <p class="text-gray-700 font-medium">Déposez votre image ici</p>
142
- <p class="text-gray-500 text-sm">ou cliquez pour sélectionner</p>
143
- </div>
144
- </div>
145
- <!-- Aperçu de l'image -->
146
- <div id="imagePreview" class="hidden text-center">
147
- <img id="previewImage" class="preview-image mx-auto" alt="Prévisualisation de l'image">
148
- </div>
149
- <button type="submit" class="blue-button w-full py-3 text-white font-medium rounded-lg">
150
- Résoudre le problème
151
- </button>
152
- </form>
153
 
154
- <!-- Loader -->
155
- <div id="loader" class="hidden mt-8 text-center">
156
- <span class="loader"></span>
157
- <p class="mt-4 text-gray-600">Analyse en cours...</p>
158
- </div>
 
 
 
 
159
 
160
- <!-- Zone d'affichage de la solution -->
161
- <section id="solution" class="hidden mt-8 space-y-6 relative">
162
- <div class="border-t pt-4">
163
- <button id="thoughtsToggle" type="button" class="w-full flex justify-between items-center p-2">
164
- <span class="font-medium text-gray-700">Processus de Réflexion</span>
165
- <span id="timestamp" class="timestamp"></span>
166
- </button>
167
- <div id="thoughtsBox" class="thought-box">
168
- <div id="thoughtsContent" class="p-4 text-gray-600"></div>
169
- </div>
170
  </div>
171
- <div class="border-t pt-6">
172
- <div class="flex justify-between items-center">
173
- <h3 class="text-xl font-bold text-gray-800 mb-4">Solution</h3>
174
- <!-- Bouton Sauvegarder mis en évidence -->
175
- <button id="saveButton">Sauvegarder</button>
176
- </div>
177
- <div id="answerContent" class="text-gray-700 table-responsive"></div>
178
  </div>
179
- </section>
180
- </main>
181
- </div>
182
 
183
- <!-- Modal plein écran pour les sauvegardes -->
184
- <div id="savedModal">
185
- <div id="savedModalContent" class="p-6">
186
- <header class="flex justify-between items-center border-b pb-4">
187
- <h2 class="text-2xl font-bold">Sauvegardes</h2>
188
- <button id="closeSaved" class="text-3xl text-gray-600">&times;</button>
189
- </header>
190
- <div id="savedListContainer" class="mt-4">
191
- <ul id="savedList" class="space-y-4">
192
- <!-- Liste des sauvegardes insérée dynamiquement -->
193
- </ul>
194
- </div>
195
- <div class="mt-6">
196
- <button id="newExercise" class="blue-button w-full py-3 text-white font-medium rounded-lg">
197
- Résoudre un nouvel exercice
198
- </button>
199
- </div>
200
  </div>
201
- </div>
202
 
203
- <script>
204
- document.addEventListener('DOMContentLoaded', () => {
205
- // Récupération des éléments
206
- const form = document.getElementById('problemForm');
207
- const imageInput = document.getElementById('imageInput');
208
- const loader = document.getElementById('loader');
209
- const solutionSection = document.getElementById('solution');
210
- const thoughtsContent = document.getElementById('thoughtsContent');
211
- const answerContent = document.getElementById('answerContent');
212
- const thoughtsToggle = document.getElementById('thoughtsToggle');
213
- const thoughtsBox = document.getElementById('thoughtsBox');
214
- const imagePreview = document.getElementById('imagePreview');
215
- const previewImage = document.getElementById('previewImage');
216
- const timestamp = document.getElementById('timestamp');
217
- const saveButton = document.getElementById('saveButton');
218
- const openSaved = document.getElementById('openSaved');
219
- const closeSaved = document.getElementById('closeSaved');
220
- const savedModal = document.getElementById('savedModal');
221
- const savedList = document.getElementById('savedList');
222
- const newExercise = document.getElementById('newExercise');
223
- const mainContent = document.getElementById('mainContent');
224
 
225
- let startTime = null;
226
- let timerInterval = null;
227
- let thoughtsBuffer = '';
228
- let answerBuffer = '';
229
- let currentMode = null;
230
- let updateTimeout = null;
231
 
232
- // Mise à jour du temps écoulé
233
- const updateTimestamp = () => {
234
- if (startTime) {
235
- const seconds = Math.floor((Date.now() - startTime) / 1000);
236
- timestamp.textContent = `${seconds}s`;
237
- }
238
- };
239
- const startTimer = () => { startTime = Date.now(); timerInterval = setInterval(updateTimestamp, 1000); updateTimestamp(); };
240
- const stopTimer = () => { clearInterval(timerInterval); startTime = null; timestamp.textContent = ''; };
241
-
242
- // Affichage de l'image sélectionnée
243
- const handleFileSelect = file => {
244
- if (!file) return;
245
- const reader = new FileReader();
246
- reader.onload = e => {
247
- previewImage.src = e.target.result;
248
- imagePreview.classList.remove('hidden');
249
- };
250
- reader.readAsDataURL(file);
251
- };
252
 
253
- thoughtsToggle.addEventListener('click', () => { thoughtsBox.classList.toggle('open'); });
254
- imageInput.addEventListener('change', e => handleFileSelect(e.target.files[0]));
 
 
255
 
256
- // Gestion du glisser-déposer
257
- const dropZone = document.querySelector('.uploadArea');
258
- dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('border-blue-400'); });
259
- dropZone.addEventListener('dragleave', e => { e.preventDefault(); dropZone.classList.remove('border-blue-400'); });
260
- dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.classList.remove('border-blue-400'); handleFileSelect(e.dataTransfer.files[0]); });
261
 
262
- // Rendu MathJax et mise à jour de l'affichage
263
- const typesetAnswerIfReady = async () => {
264
- if (window.mathJaxReady) {
265
- MathJax.startup.document.elements = [document.getElementById('answerContent')];
266
- await MathJax.typesetPromise();
267
- answerContent.scrollTop = answerContent.scrollHeight;
268
- } else { setTimeout(typesetAnswerIfReady, 200); }
269
- };
270
- const updateDisplay = async () => {
271
- thoughtsContent.innerHTML = marked.parse(thoughtsBuffer);
272
- answerContent.innerHTML = marked.parse(answerBuffer);
273
- await typesetAnswerIfReady();
274
- updateTimeout = null;
275
- };
276
- const scheduleUpdate = () => { if (!updateTimeout) updateTimeout = setTimeout(updateDisplay, 200); };
277
 
278
- marked.setOptions({ gfm: true, breaks: true });
 
 
 
 
279
 
280
- // Envoi de l'image pour résolution
281
- form.addEventListener('submit', async e => {
282
- e.preventDefault();
283
- const file = imageInput.files[0];
284
- if (!file) { alert('Veuillez sélectionner une image.'); return; }
285
- startTimer();
286
- loader.classList.remove('hidden');
287
- solutionSection.classList.add('hidden');
288
- thoughtsContent.innerHTML = '';
289
- answerContent.innerHTML = '';
290
- thoughtsBuffer = '';
291
- answerBuffer = '';
292
- currentMode = null;
293
- thoughtsBox.classList.add('open');
294
 
295
- const formData = new FormData();
296
- formData.append('image', file);
297
- try {
298
- const response = await fetch('/solve', { method: 'POST', body: formData });
299
- const reader = response.body.getReader();
300
- const decoder = new TextDecoder();
301
- let buffer = '';
302
- const processChunk = async chunk => {
303
- buffer += decoder.decode(chunk, { stream: true });
304
- const lines = buffer.split('\n\n');
305
- buffer = lines.pop();
306
- for (const line of lines) {
307
- if (!line.startsWith('data:')) continue;
308
- const data = JSON.parse(line.slice(5));
309
- if (data.mode) {
310
- currentMode = data.mode;
311
- loader.classList.add('hidden');
312
- solutionSection.classList.remove('hidden');
313
- }
314
- if (data.content) {
315
- if (currentMode === 'thinking') { thoughtsBuffer += data.content; }
316
- else if (currentMode === 'answering') { answerBuffer += data.content; }
317
- }
318
  }
319
- scheduleUpdate();
320
- };
321
- while (true) {
322
- const { done, value } = await reader.read();
323
- if (done) {
324
- if (buffer) {
325
- const data = JSON.parse(buffer.slice(5));
326
- if (data.content) {
327
- if (currentMode === 'thinking') { thoughtsBuffer += data.content; }
328
- else if (currentMode === 'answering') { answerBuffer += data.content; }
 
 
 
 
 
 
 
 
329
  }
330
- }
331
- scheduleUpdate();
332
- break;
333
- }
334
- await processChunk(value);
335
- }
336
- stopTimer();
337
- } catch (error) {
338
- console.error('Erreur:', error);
339
- alert('Une erreur est survenue.');
340
- loader.classList.add('hidden');
341
- stopTimer();
342
- }
343
- });
344
 
345
- // Sauvegarde de la solution avec SweetAlert2
346
- saveButton.addEventListener('click', () => {
347
- const saveName = prompt("Entrez un nom pour la sauvegarde :");
348
- if (!saveName) return;
349
- const saveData = {
350
- answer: answerContent.innerHTML,
351
- thinking: thoughtsContent.innerHTML,
352
- date: new Date().toLocaleString()
353
- };
354
- let savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
355
- savedExercises[saveName] = saveData;
356
- localStorage.setItem('savedExercises', JSON.stringify(savedExercises));
357
- Swal.fire({
358
- icon: 'success',
359
- title: 'Sauvegarde réussie',
360
- text: 'Votre solution a bien été sauvegardée !',
361
- timer: 2000,
362
- showConfirmButton: false
363
- });
364
- });
365
 
366
- // Chargement des sauvegardes dans le modal
367
- const loadSavedList = () => {
368
- savedList.innerHTML = '';
369
- const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
370
- for (const [name, data] of Object.entries(savedExercises)) {
371
- const li = document.createElement('li');
372
- li.innerHTML = `<button class="w-full text-left text-blue-600 hover:underline" data-save="${name}">${name} <span class="text-gray-500 text-xs">(${data.date})</span></button>`;
373
- savedList.appendChild(li);
374
- }
375
- };
376
 
377
- savedList.addEventListener('click', (e) => {
378
- if (e.target && e.target.dataset.save) {
379
- const saveName = e.target.dataset.save;
380
- const savedExercises = JSON.parse(localStorage.getItem('savedExercises') || '{}');
381
- const data = savedExercises[saveName];
382
- if (data) {
383
- form.classList.add('hidden');
384
- loader.classList.add('hidden');
385
- solutionSection.classList.remove('hidden');
386
- thoughtsContent.innerHTML = data.thinking;
387
- answerContent.innerHTML = data.answer;
388
- savedModal.classList.remove('active');
389
- }
390
- }
391
- });
392
 
393
- // Ouverture / fermeture du modal de sauvegardes
394
- openSaved.addEventListener('click', () => { loadSavedList(); savedModal.classList.add('active'); });
395
- closeSaved.addEventListener('click', () => { savedModal.classList.remove('active'); });
 
 
 
 
 
 
 
 
 
 
 
 
396
 
397
- // Bouton présent uniquement dans le modal pour lancer un nouvel exercice
398
- newExercise.addEventListener('click', () => {
399
- form.reset();
400
- form.classList.remove('hidden');
401
- solutionSection.classList.add('hidden');
402
- imagePreview.classList.add('hidden');
403
- thoughtsContent.innerHTML = '';
404
- answerContent.innerHTML = '';
405
- thoughtsBuffer = '';
406
- answerBuffer = '';
407
- savedModal.classList.remove('active');
408
- });
409
- });
410
- </script>
411
- </body>
412
- </html>
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>Résolution d'exercices avec IA</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.0/es5/tex-mml-chtml.js"></script>
9
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
10
+ <style>
11
+ body {
12
+ font-family: 'Arial', sans-serif;
13
+ background-color: #f8f9fa;
14
+ padding-bottom: 50px;
15
+ }
16
+ .container {
17
+ max-width: 800px;
18
+ margin: 0 auto;
19
+ }
20
+ .header {
21
+ text-align: center;
22
+ padding: 30px 0;
23
+ }
24
+ .header h1 {
25
+ color: #0d6efd;
26
+ font-weight: bold;
27
+ }
28
+ .upload-container {
29
+ background-color: #ffffff;
30
+ border-radius: 10px;
31
+ padding: 30px;
32
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
33
+ margin-bottom: 30px;
34
+ }
35
+ .preview-container {
36
+ margin-top: 20px;
37
+ text-align: center;
38
+ }
39
+ #imagePreview {
40
+ max-width: 100%;
41
+ max-height: 400px;
42
+ display: none;
43
+ margin: 0 auto;
44
+ border-radius: 5px;
45
+ border: 1px solid #dee2e6;
46
+ }
47
+ .drop-zone {
48
+ border: 2px dashed #0d6efd;
49
+ border-radius: 5px;
50
+ padding: 50px 20px;
51
+ text-align: center;
52
+ cursor: pointer;
53
+ margin-bottom: 20px;
54
+ transition: all 0.3s;
55
+ }
56
+ .drop-zone:hover {
57
+ background-color: #f1f8ff;
58
+ }
59
+ .drop-zone i {
60
+ font-size: 48px;
61
+ color: #0d6efd;
62
+ margin-bottom: 15px;
63
+ }
64
+ .drop-zone-active {
65
+ background-color: #e8f4ff;
66
+ border-color: #0d6efd;
67
+ }
68
+ #response {
69
+ background-color: #ffffff;
70
+ border-radius: 10px;
71
+ padding: 20px;
72
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
73
+ min-height: 100px;
74
+ white-space: pre-wrap;
75
+ font-size: 16px;
76
+ line-height: 1.6;
77
+ display: none;
78
+ }
79
+ .btn-primary {
80
+ background-color: #0d6efd;
81
+ border-color: #0d6efd;
82
+ padding: 10px 20px;
83
+ font-weight: 600;
84
+ }
85
+ .btn-primary:hover {
86
+ background-color: #0b5ed7;
87
+ border-color: #0a58ca;
88
+ }
89
+ .btn-secondary {
90
+ background-color: #6c757d;
91
+ border-color: #6c757d;
92
+ padding: 10px 20px;
93
+ font-weight: 600;
94
+ }
95
+ .btn-group {
96
+ margin-bottom: 20px;
97
+ }
98
+ #loading {
99
+ display: none;
100
+ text-align: center;
101
+ margin: 20px 0;
102
+ }
103
+ .spinner-border {
104
+ width: 3rem;
105
+ height: 3rem;
106
+ }
107
+ .thinking-indicator {
108
+ display: inline-block;
109
+ padding: 5px 15px;
110
+ background-color: #e6f7ff;
111
+ border-radius: 20px;
112
+ color: #1890ff;
113
+ font-weight: 500;
114
+ margin-bottom: 15px;
115
+ }
116
+ .response-header {
117
+ margin-bottom: 15px;
118
+ font-weight: bold;
119
+ font-size: 18px;
120
+ }
121
+ .model-selector {
122
+ margin-bottom: 20px;
123
+ }
124
+ </style>
125
  </head>
126
+ <body>
127
+ <div class="container">
128
+ <div class="header">
129
+ <h1>Résolveur d'exercices par IA</h1>
130
+ <p class="lead">Téléchargez une image de votre exercice pour obtenir une solution détaillée</p>
131
+ </div>
 
 
 
132
 
133
+ <div class="upload-container">
134
+ <div class="model-selector">
135
+ <label class="form-label">Choisissez un modèle :</label>
136
+ <div class="btn-group" role="group">
137
+ <input type="radio" class="btn-check" name="modelOption" id="modelPro" value="pro" checked>
138
+ <label class="btn btn-outline-primary" for="modelPro">Gemini Pro (précis mais plus lent)</label>
139
+
140
+ <input type="radio" class="btn-check" name="modelOption" id="modelFlash" value="flash">
141
+ <label class="btn btn-outline-primary" for="modelFlash">Gemini Flash (plus rapide)</label>
142
+ </div>
 
143
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
+ <div class="drop-zone" id="dropZone">
146
+ <i class="fas fa-cloud-upload-alt"></i>
147
+ <p>Déposez votre image ici ou cliquez pour choisir un fichier</p>
148
+ <input type="file" id="fileInput" accept="image/*" style="display: none;">
149
+ </div>
150
+
151
+ <div class="preview-container">
152
+ <img id="imagePreview" alt="Aperçu de l'image">
153
+ </div>
154
 
155
+ <div class="d-grid gap-2 d-md-flex justify-content-md-end mt-3">
156
+ <button class="btn btn-secondary me-md-2" id="resetBtn" type="button" disabled>Réinitialiser</button>
157
+ <button class="btn btn-primary" id="submitBtn" type="button" disabled>Résoudre l'exercice</button>
158
+ </div>
 
 
 
 
 
 
159
  </div>
160
+
161
+ <div id="loading">
162
+ <div class="spinner-border text-primary" role="status">
163
+ <span class="visually-hidden">Chargement...</span>
164
+ </div>
165
+ <p class="mt-2" id="processingText">Traitement en cours...</p>
 
166
  </div>
 
 
 
167
 
168
+ <div id="response"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  </div>
 
170
 
171
+ <script>
172
+ document.addEventListener('DOMContentLoaded', () => {
173
+ const dropZone = document.getElementById('dropZone');
174
+ const fileInput = document.getElementById('fileInput');
175
+ const imagePreview = document.getElementById('imagePreview');
176
+ const submitBtn = document.getElementById('submitBtn');
177
+ const resetBtn = document.getElementById('resetBtn');
178
+ const loading = document.getElementById('loading');
179
+ const processingText = document.getElementById('processingText');
180
+ const response = document.getElementById('response');
181
+ const modelPro = document.getElementById('modelPro');
182
+ const modelFlash = document.getElementById('modelFlash');
 
 
 
 
 
 
 
 
 
183
 
184
+ let selectedFile = null;
 
 
 
 
 
185
 
186
+ // Fonctions pour la zone de dépôt
187
+ dropZone.addEventListener('click', () => {
188
+ fileInput.click();
189
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
+ dropZone.addEventListener('dragover', (e) => {
192
+ e.preventDefault();
193
+ dropZone.classList.add('drop-zone-active');
194
+ });
195
 
196
+ dropZone.addEventListener('dragleave', () => {
197
+ dropZone.classList.remove('drop-zone-active');
198
+ });
 
 
199
 
200
+ dropZone.addEventListener('drop', (e) => {
201
+ e.preventDefault();
202
+ dropZone.classList.remove('drop-zone-active');
203
+
204
+ if (e.dataTransfer.files.length) {
205
+ handleFile(e.dataTransfer.files[0]);
206
+ }
207
+ });
 
 
 
 
 
 
 
208
 
209
+ fileInput.addEventListener('change', () => {
210
+ if (fileInput.files.length) {
211
+ handleFile(fileInput.files[0]);
212
+ }
213
+ });
214
 
215
+ function handleFile(file) {
216
+ if (!file.type.match('image.*')) {
217
+ alert('Veuillez sélectionner une image');
218
+ return;
219
+ }
 
 
 
 
 
 
 
 
 
220
 
221
+ selectedFile = file;
222
+ const reader = new FileReader();
223
+
224
+ reader.onload = (e) => {
225
+ imagePreview.src = e.target.result;
226
+ imagePreview.style.display = 'block';
227
+ submitBtn.disabled = false;
228
+ resetBtn.disabled = false;
229
+ };
230
+
231
+ reader.readAsDataURL(file);
 
 
 
 
 
 
 
 
 
 
 
 
232
  }
233
+
234
+ // Réinitialiser
235
+ resetBtn.addEventListener('click', () => {
236
+ selectedFile = null;
237
+ fileInput.value = '';
238
+ imagePreview.src = '';
239
+ imagePreview.style.display = 'none';
240
+ submitBtn.disabled = true;
241
+ resetBtn.disabled = true;
242
+ response.style.display = 'none';
243
+ response.innerHTML = '';
244
+ });
245
+
246
+ // Soumettre l'image
247
+ submitBtn.addEventListener('click', () => {
248
+ if (!selectedFile) {
249
+ alert('Veuillez sélectionner une image');
250
+ return;
251
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
+ // Préparer les données
254
+ const formData = new FormData();
255
+ formData.append('image', selectedFile);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
 
257
+ // Afficher le chargement
258
+ loading.style.display = 'block';
259
+ processingText.textContent = 'Traitement en cours...';
260
+ response.style.display = 'none';
261
+ submitBtn.disabled = true;
 
 
 
 
 
262
 
263
+ // Déterminer l'endpoint en fonction du modèle choisi
264
+ const endpoint = modelPro.checked ? '/solve' : '/solved';
 
 
 
 
 
 
 
 
 
 
 
 
 
265
 
266
+ // Initialiser SSE
267
+ const eventSource = new EventSource(endpoint);
268
+
269
+ // Créer FormData et effectuer la requête
270
+ const xhr = new XMLHttpRequest();
271
+ xhr.open('POST', endpoint, true);
272
+ xhr.onload = function() {
273
+ // Gestion manuelle non nécessaire car nous utilisons SSE
274
+ };
275
+ xhr.onerror = function() {
276
+ alert('Erreur lors de l\'envoi de l\'image');
277
+ loading.style.display = 'none';
278
+ submitBtn.disabled = false;
279
+ };
280
+ xhr.send(formData);
281
 
282
+ // Configurer EventSource pour la réponse en streaming
283
+ const sse = new EventSource(endpoint);
284
+
285
+ response.innerHTML = '';
286
+ response.style.display = 'block';
287
+
288
+ let isThinking = false;
289
+ let thinkingIndicator = null;
 
 
 
 
 
 
 
 
290
 
291
+ sse.onmessage = function(event) {
292
+ const data = JSON.parse(event.data);
293
+
294
+ if (data.mode === "thinking" && !isThinking) {
295
+ isThinking = true;
296
+ processingText.textContent = "L'IA réfléchit...";
297
+
298
+ // Ajouter l'indicateur de réflexion
299
+ if (!thinkingIndicator) {
300
+ thinkingIndicator = document.createElement('div');
301
+ thinkingIndicator.className = 'thinking-indicator';
302
+ thinkingIndicator.innerHTML = '<i class="fas fa-brain"></i> L\'IA réfléchit...';
303
+ response.appendChild(thinkingIndicator);
304
+ }
305
+ }
306
+
307
+ if (data.mode === "answering") {
308
+ isThinking = false;
309
+ processingText.textContent = "L'IA répond...";
310
+
311
+ // Supprimer l'indicateur de réflexion s'il existe
312
+ if (thinkingIndicator) {
313
+ thinkingIndicator.remove();
314
+ thinkingIndicator = null;
315
+
316
+ // Ajouter un en-tête de réponse
317
+ const responseHeader = document.createElement('div');
318
+ responseHeader.className = 'response-header';
319
+ responseHeader.textContent = 'Solution :';
320
+ response.appendChild(responseHeader);
321
+ }
322
+ }
323
+
324
+ if (data.content) {
325
+ const contentDiv = document.createElement('div');
326
+ contentDiv.innerHTML = data.content;
327
+ response.appendChild(contentDiv);
328
+
329
+ // Rendre les équations LaTeX
330
+ if (window.MathJax) {
331
+ MathJax.typesetPromise([contentDiv]);
332
+ }
333
+ }
334
+
335
+ if (data.error) {
336
+ const errorDiv = document.createElement('div');
337
+ errorDiv.className = 'alert alert-danger';
338
+ errorDiv.textContent = `Erreur: ${data.error}`;
339
+ response.appendChild(errorDiv);
340
+
341
+ sse.close();
342
+ loading.style.display = 'none';
343
+ submitBtn.disabled = false;
344
+ }
345
+ };
346
+
347
+ sse.onerror = function() {
348
+ console.error('Erreur SSE');
349
+ sse.close();
350
+ loading.style.display = 'none';
351
+ submitBtn.disabled = false;
352
+
353
+ // Afficher un message d'erreur seulement si aucune réponse n'a été reçue
354
+ if (response.children.length === 0 || (thinkingIndicator && response.children.length === 1)) {
355
+ const errorDiv = document.createElement('div');
356
+ errorDiv.className = 'alert alert-danger';
357
+ errorDiv.textContent = 'La connexion a été perdue ou une erreur est survenue.';
358
+ response.appendChild(errorDiv);
359
+ }
360
+ };
361
+
362
+ sse.onopen = function() {
363
+ console.log('Connexion SSE établie');
364
+ };
365
+ });
366
+ });
367
+ </script>
368
+ </body>
369
+ </html>