Docfile commited on
Commit
078c480
·
verified ·
1 Parent(s): 2abfdf4

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +614 -287
templates/index.html CHANGED
@@ -3,365 +3,692 @@
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>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Résolveur d'exercices</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet">
9
  <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.0/es5/tex-mml-chtml.js"></script>
 
10
  <style>
11
+ :root {
12
+ --primary-color: #4361ee;
13
+ --secondary-color: #3f37c9;
14
+ --accent-color: #f72585;
15
+ --text-color: #333;
16
+ --light-color: #f8f9fa;
17
+ --dark-color: #212529;
18
+ --success-color: #4cc9a0;
19
+ --border-radius: 8px;
20
+ --box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
21
+ --transition: all 0.3s ease;
22
+ }
23
+
24
+ * {
25
+ margin: 0;
26
+ padding: 0;
27
+ box-sizing: border-box;
28
+ }
29
+
30
  body {
31
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
32
+ background: linear-gradient(135deg, #f5f7fa 0%, #e4e8f0 100%);
33
+ color: var(--text-color);
34
+ line-height: 1.6;
35
+ min-height: 100vh;
36
+ padding: 20px;
37
+ }
38
+
39
+ header {
40
+ text-align: center;
41
+ margin-bottom: 2rem;
42
+ animation: fadeIn 1s;
43
+ }
44
+
45
+ h1 {
46
+ color: var(--primary-color);
47
+ font-size: 2.2rem;
48
+ margin-bottom: 0.5rem;
49
+ }
50
+
51
+ .subheading {
52
+ color: var(--secondary-color);
53
+ font-size: 1.1rem;
54
+ font-weight: 500;
55
  }
56
+
57
  .container {
58
  max-width: 800px;
59
  margin: 0 auto;
60
+ background-color: white;
61
+ border-radius: var(--border-radius);
62
+ box-shadow: var(--box-shadow);
63
+ overflow: hidden;
64
  }
65
+
66
+ .tabs {
67
+ display: flex;
68
+ background-color: var(--light-color);
69
+ border-bottom: 1px solid #e1e5ea;
70
+ }
71
+
72
+ .tab {
73
+ flex: 1;
74
+ padding: 15px;
75
  text-align: center;
76
+ cursor: pointer;
77
+ transition: var(--transition);
78
+ font-weight: 600;
79
+ color: var(--text-color);
80
+ position: relative;
81
+ overflow: hidden;
82
  }
83
+
84
+ .tab.active {
85
+ background-color: white;
86
+ color: var(--primary-color);
87
  }
88
+
89
+ .tab:after {
90
+ content: '';
91
+ position: absolute;
92
+ bottom: 0;
93
+ left: 0;
94
+ width: 100%;
95
+ height: 3px;
96
+ background-color: var(--primary-color);
97
+ transform: scaleX(0);
98
+ transition: transform 0.3s ease;
99
  }
100
+
101
+ .tab.active:after {
102
+ transform: scaleX(1);
103
  }
104
+
105
+ .tab-content {
106
+ padding: 30px;
107
  display: none;
 
 
 
108
  }
109
+
110
+ .tab-content.active {
111
+ display: block;
112
+ animation: fadeIn 0.5s;
113
+ }
114
+
115
+ .upload-container {
116
  text-align: center;
117
+ padding: 20px;
118
+ border: 2px dashed #d1d5db;
119
+ border-radius: var(--border-radius);
120
  margin-bottom: 20px;
121
+ transition: var(--transition);
122
+ cursor: pointer;
123
  }
124
+
125
+ .upload-container:hover {
126
+ border-color: var(--primary-color);
127
+ background-color: rgba(67, 97, 238, 0.05);
128
  }
129
+
130
+ .upload-icon {
131
+ font-size: 3rem;
132
+ color: var(--primary-color);
133
  margin-bottom: 15px;
134
  }
135
+
136
+ .upload-text {
137
+ margin-bottom: 15px;
138
+ font-size: 1.1rem;
139
  }
140
+
141
+ .upload-input {
 
 
 
 
 
 
 
142
  display: none;
143
  }
144
+
145
+ .btn {
146
+ display: inline-block;
147
+ background-color: var(--primary-color);
148
+ color: white;
149
+ padding: 12px 25px;
150
+ border: none;
151
+ border-radius: var(--border-radius);
152
+ cursor: pointer;
153
+ font-size: 1rem;
154
  font-weight: 600;
155
+ transition: var(--transition);
156
+ text-align: center;
157
+ text-decoration: none;
158
+ margin: 10px 0;
159
  }
160
+
161
+ .btn:hover {
162
+ background-color: var(--secondary-color);
163
+ transform: translateY(-2px);
164
+ box-shadow: 0 5px 15px rgba(67, 97, 238, 0.3);
165
  }
166
+
167
+ .btn-full {
168
+ width: 100%;
169
+ }
170
+
171
  .btn-secondary {
172
+ background-color: var(--light-color);
173
+ color: var(--text-color);
 
 
174
  }
175
+
176
+ .btn-secondary:hover {
177
+ background-color: #e2e6ea;
178
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
179
  }
180
+
181
+ .btn-accent {
182
+ background-color: var(--accent-color);
183
+ }
184
+
185
+ .btn-accent:hover {
186
+ background-color: #e5147a;
187
+ box-shadow: 0 5px 15px rgba(247, 37, 133, 0.3);
188
+ }
189
+
190
+ .image-preview {
191
+ max-width: 100%;
192
+ height: auto;
193
  margin: 20px 0;
194
+ border-radius: var(--border-radius);
195
+ box-shadow: var(--box-shadow);
196
+ display: none;
197
+ }
198
+
199
+ .result-container {
200
+ margin-top: 30px;
201
+ padding: 20px;
202
+ background-color: var(--light-color);
203
+ border-radius: var(--border-radius);
204
+ display: none;
205
+ }
206
+
207
+ .loading {
208
+ display: flex;
209
+ flex-direction: column;
210
+ align-items: center;
211
+ justify-content: center;
212
+ padding: 30px;
213
+ display: none;
214
  }
215
+
216
+ .loading-text {
217
+ margin-top: 15px;
218
+ font-weight: 600;
219
+ color: var(--primary-color);
220
  }
221
+
222
  .thinking-indicator {
223
  display: inline-block;
224
+ margin-left: 10px;
225
+ font-size: 1.5rem;
226
+ color: var(--primary-color);
227
+ animation: pulse 1.5s infinite;
228
+ }
229
+
230
+ @keyframes pulse {
231
+ 0% { transform: scale(0.95); opacity: 0.7; }
232
+ 50% { transform: scale(1.05); opacity: 1; }
233
+ 100% { transform: scale(0.95); opacity: 0.7; }
234
+ }
235
+
236
+ @keyframes fadeIn {
237
+ from { opacity: 0; transform: translateY(10px); }
238
+ to { opacity: 1; transform: translateY(0); }
239
+ }
240
+
241
+ .features {
242
+ display: grid;
243
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
244
+ gap: 20px;
245
+ margin: 30px 0;
246
+ }
247
+
248
+ .feature {
249
+ text-align: center;
250
+ padding: 20px;
251
+ background-color: white;
252
+ border-radius: var(--border-radius);
253
+ box-shadow: var(--box-shadow);
254
+ transition: var(--transition);
255
  }
256
+
257
+ .feature:hover {
258
+ transform: translateY(-5px);
259
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
260
+ }
261
+
262
+ .feature-icon {
263
+ font-size: 2.5rem;
264
+ color: var(--primary-color);
265
  margin-bottom: 15px;
 
 
266
  }
267
+
268
+ .feature-title {
269
+ font-size: 1.2rem;
270
+ margin-bottom: 10px;
271
+ font-weight: 600;
272
+ }
273
+
274
+ .feature-desc {
275
+ color: #666;
276
+ font-size: 0.95rem;
277
+ }
278
+
279
+ footer {
280
+ text-align: center;
281
+ margin-top: 3rem;
282
+ color: #6c757d;
283
+ font-size: 0.9rem;
284
+ }
285
+
286
+ /* Mobile responsive styles */
287
+ @media (max-width: 768px) {
288
+ body {
289
+ padding: 10px;
290
+ }
291
+
292
+ h1 {
293
+ font-size: 1.8rem;
294
+ }
295
+
296
+ .subheading {
297
+ font-size: 1rem;
298
+ }
299
+
300
+ .container {
301
+ border-radius: var(--border-radius);
302
+ }
303
+
304
+ .tab {
305
+ padding: 12px 5px;
306
+ font-size: 0.9rem;
307
+ }
308
+
309
+ .tab-content {
310
+ padding: 20px 15px;
311
+ }
312
+
313
+ .upload-icon {
314
+ font-size: 2.5rem;
315
+ }
316
+
317
+ .upload-text {
318
+ font-size: 1rem;
319
+ }
320
+
321
+ .btn {
322
+ padding: 10px 20px;
323
+ font-size: 0.95rem;
324
+ }
325
+
326
+ .features {
327
+ grid-template-columns: 1fr;
328
+ }
329
  }
330
  </style>
331
  </head>
332
  <body>
333
+ <header>
334
+ <h1>SolveMath.AI</h1>
335
+ <p class="subheading">Résolution avancée d'exercices par Intelligence Artificielle</p>
336
+ </header>
337
+
338
  <div class="container">
339
+ <div class="tabs">
340
+ <div class="tab active" data-tab="standard">Standard</div>
341
+ <div class="tab" data-tab="pro">Pro (Réflexion)</div>
342
  </div>
343
+
344
+ <div class="tab-content active" id="standard-content">
345
+ <p>Utilisez notre IA standard pour résoudre rapidement vos exercices.</p>
346
+
347
+ <div class="upload-container" id="standard-upload">
348
+ <i class="fas fa-cloud-upload-alt upload-icon"></i>
349
+ <p class="upload-text">Cliquez ou déposez une image de votre exercice</p>
350
+ <input type="file" id="standard-file" class="upload-input" accept="image/*">
 
 
 
 
 
 
 
 
 
351
  </div>
352
+
353
+ <img id="standard-preview" class="image-preview">
354
+
355
+ <button id="standard-solve" class="btn btn-full" disabled>Résoudre l'exercice</button>
356
+
357
+ <div id="standard-loading" class="loading">
358
+ <i class="fas fa-cog fa-spin fa-2x"></i>
359
+ <p class="loading-text">Analyse en cours... <span id="standard-thinking-status"></span></p>
360
  </div>
361
+
362
+ <div id="standard-result" class="result-container">
363
+ <h3>Solution :</h3>
364
+ <div id="standard-solution"></div>
365
  </div>
366
  </div>
367
+
368
+ <div class="tab-content" id="pro-content">
369
+ <p>Mode Pro : utilise une réflexion approfondie pour résoudre des exercices complexes.</p>
370
+
371
+ <div class="upload-container" id="pro-upload">
372
+ <i class="fas fa-cloud-upload-alt upload-icon"></i>
373
+ <p class="upload-text">Cliquez ou déposez une image de votre exercice complexe</p>
374
+ <input type="file" id="pro-file" class="upload-input" accept="image/*">
375
+ </div>
376
+
377
+ <img id="pro-preview" class="image-preview">
378
+
379
+ <button id="pro-solve" class="btn btn-full btn-accent" disabled>Résoudre avec réflexion approfondie</button>
380
+
381
+ <div id="pro-loading" class="loading">
382
+ <i class="fas fa-brain fa-2x"></i>
383
+ <p class="loading-text">Analyse approfondie... <span id="pro-thinking-status"></span></p>
384
+ </div>
385
+
386
+ <div id="pro-result" class="result-container">
387
+ <h3>Solution détaillée :</h3>
388
+ <div id="pro-solution"></div>
389
  </div>
 
390
  </div>
 
 
391
  </div>
392
+
393
+ <div class="features">
394
+ <div class="feature">
395
+ <i class="fas fa-bolt feature-icon"></i>
396
+ <h3 class="feature-title">Rapide</h3>
397
+ <p class="feature-desc">Solutions instantanées pour les exercices simples</p>
398
+ </div>
399
+
400
+ <div class="feature">
401
+ <i class="fas fa-brain feature-icon"></i>
402
+ <p class="feature-title">Intelligent</p>
403
+ <p class="feature-desc">Résolution détaillée de problèmes complexes</p>
404
+ </div>
405
+
406
+ <div class="feature">
407
+ <i class="fas fa-mobile-alt feature-icon"></i>
408
+ <p class="feature-title">Mobile</p>
409
+ <p class="feature-desc">Fonctionne parfaitement sur tous les appareils</p>
410
+ </div>
411
+ </div>
412
+
413
+ <footer>
414
+ <p>&copy; 2025 SolveMath.AI - Propulsé par l'intelligence artificielle</p>
415
+ </footer>
416
+
417
  <script>
418
+ // Tab switching functionality
419
+ const tabs = document.querySelectorAll('.tab');
420
+ const tabContents = document.querySelectorAll('.tab-content');
421
+
422
+ tabs.forEach(tab => {
423
+ tab.addEventListener('click', () => {
424
+ const tabId = tab.getAttribute('data-tab');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
 
426
+ // Update active tab
427
+ tabs.forEach(t => t.classList.remove('active'));
428
+ tab.classList.add('active');
429
+
430
+ // Update active content
431
+ tabContents.forEach(content => content.classList.remove('active'));
432
+ document.getElementById(`${tabId}-content`).classList.add('active');
 
 
433
  });
434
+ });
435
+
436
+ // File upload functionality - Standard mode
437
+ const standardUpload = document.getElementById('standard-upload');
438
+ const standardFile = document.getElementById('standard-file');
439
+ const standardPreview = document.getElementById('standard-preview');
440
+ const standardSolve = document.getElementById('standard-solve');
441
+ const standardLoading = document.getElementById('standard-loading');
442
+ const standardResult = document.getElementById('standard-result');
443
+ const standardSolution = document.getElementById('standard-solution');
444
+ const standardThinkingStatus = document.getElementById('standard-thinking-status');
445
+
446
+ standardUpload.addEventListener('click', () => standardFile.click());
447
+
448
+ standardFile.addEventListener('change', (e) => {
449
+ if (e.target.files.length > 0) {
450
+ const file = e.target.files[0];
451
+ const reader = new FileReader();
452
+
453
+ reader.onload = (e) => {
454
+ standardPreview.src = e.target.result;
455
+ standardPreview.style.display = 'block';
456
+ standardSolve.disabled = false;
457
+ };
458
+
459
+ reader.readAsDataURL(file);
460
+ }
461
+ });
462
+
463
+ // File upload functionality - Pro mode
464
+ const proUpload = document.getElementById('pro-upload');
465
+ const proFile = document.getElementById('pro-file');
466
+ const proPreview = document.getElementById('pro-preview');
467
+ const proSolve = document.getElementById('pro-solve');
468
+ const proLoading = document.getElementById('pro-loading');
469
+ const proResult = document.getElementById('pro-result');
470
+ const proSolution = document.getElementById('pro-solution');
471
+ const proThinkingStatus = document.getElementById('pro-thinking-status');
472
+
473
+ proUpload.addEventListener('click', () => proFile.click());
474
+
475
+ proFile.addEventListener('change', (e) => {
476
+ if (e.target.files.length > 0) {
477
+ const file = e.target.files[0];
478
  const reader = new FileReader();
479
 
480
  reader.onload = (e) => {
481
+ proPreview.src = e.target.result;
482
+ proPreview.style.display = 'block';
483
+ proSolve.disabled = false;
 
484
  };
485
 
486
  reader.readAsDataURL(file);
487
  }
488
+ });
489
+
490
+ // Drag and drop functionality
491
+ ['standard', 'pro'].forEach(mode => {
492
+ const uploadContainer = document.getElementById(`${mode}-upload`);
493
+ const fileInput = document.getElementById(`${mode}-file`);
494
+ const preview = document.getElementById(`${mode}-preview`);
495
+ const solveBtn = document.getElementById(`${mode}-solve`);
496
+
497
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
498
+ uploadContainer.addEventListener(eventName, preventDefaults, false);
499
+ });
500
+
501
+ function preventDefaults(e) {
502
+ e.preventDefault();
503
+ e.stopPropagation();
504
+ }
505
+
506
+ ['dragenter', 'dragover'].forEach(eventName => {
507
+ uploadContainer.addEventListener(eventName, () => {
508
+ uploadContainer.classList.add('highlight');
509
+ }, false);
510
  });
511
+
512
+ ['dragleave', 'drop'].forEach(eventName => {
513
+ uploadContainer.addEventListener(eventName, () => {
514
+ uploadContainer.classList.remove('highlight');
515
+ }, false);
516
+ });
517
+
518
+ uploadContainer.addEventListener('drop', (e) => {
519
+ const file = e.dataTransfer.files[0];
520
+ if (file && file.type.startsWith('image/')) {
521
+ fileInput.files = e.dataTransfer.files;
522
+
523
+ const reader = new FileReader();
524
+ reader.onload = (e) => {
525
+ preview.src = e.target.result;
526
+ preview.style.display = 'block';
527
+ solveBtn.disabled = false;
528
+ };
529
+ reader.readAsDataURL(file);
530
  }
531
+ }, false);
532
+ });
533
+
534
+ // Solve functionality - Standard
535
+ standardSolve.addEventListener('click', () => {
536
+ if (!standardFile.files.length) return;
537
+
538
+ const formData = new FormData();
539
+ formData.append('image', standardFile.files[0]);
540
+
541
+ standardSolve.disabled = true;
542
+ standardLoading.style.display = 'flex';
543
+ standardResult.style.display = 'none';
544
+ standardSolution.innerHTML = '';
545
+
546
+ const eventSource = new EventSource('/solved?' + new URLSearchParams({
547
+ timestamp: new Date().getTime()
548
+ }));
549
+
550
+ let accumulatedText = '';
551
+
552
+ eventSource.addEventListener('message', function(event) {
553
+ const data = JSON.parse(event.data);
554
 
555
+ if (data.mode === 'thinking') {
556
+ standardThinkingStatus.innerHTML = '<span class="thinking-indicator">🤔</span>';
557
+ }
 
 
 
 
 
 
 
 
 
 
 
 
558
 
559
+ if (data.mode === 'answering') {
560
+ standardThinkingStatus.innerHTML = '';
561
+ }
562
 
563
+ if (data.content) {
564
+ accumulatedText += data.content;
565
+ standardSolution.innerHTML = accumulatedText;
566
+ standardResult.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
567
 
568
+ // Render LaTeX if present
569
+ if (typeof MathJax !== 'undefined') {
570
+ MathJax.typesetPromise([standardSolution]);
 
 
 
 
 
 
571
  }
572
+ }
573
+
574
+ if (data.error) {
575
+ standardSolution.innerHTML = `<p class="error">Erreur: ${data.error}</p>`;
576
+ standardResult.style.display = 'block';
577
+ eventSource.close();
578
+ standardLoading.style.display = 'none';
579
+ standardSolve.disabled = false;
580
+ }
581
+ });
582
+
583
+ eventSource.addEventListener('error', function() {
584
+ eventSource.close();
585
+ standardLoading.style.display = 'none';
586
+ standardSolve.disabled = false;
587
+
588
+ if (!accumulatedText) {
589
+ standardSolution.innerHTML = '<p class="error">Une erreur est survenue lors de la communication avec le serveur.</p>';
590
+ standardResult.style.display = 'block';
591
+ }
592
+ });
593
+
594
+ // Actual fetch to backend
595
+ fetch('/solved', {
596
+ method: 'POST',
597
+ body: formData
598
+ }).catch(error => {
599
+ console.error('Error:', error);
600
+ eventSource.close();
601
+ standardLoading.style.display = 'none';
602
+ standardSolve.disabled = false;
603
+ standardSolution.innerHTML = '<p class="error">Une erreur est survenue lors de l\'envoi de l\'image.</p>';
604
+ standardResult.style.display = 'block';
605
+ });
606
+ });
607
+
608
+ // Solve functionality - Pro
609
+ proSolve.addEventListener('click', () => {
610
+ if (!proFile.files.length) return;
611
+
612
+ const formData = new FormData();
613
+ formData.append('image', proFile.files[0]);
614
+
615
+ proSolve.disabled = true;
616
+ proLoading.style.display = 'flex';
617
+ proResult.style.display = 'none';
618
+ proSolution.innerHTML = '';
619
+
620
+ const eventSource = new EventSource('/solve?' + new URLSearchParams({
621
+ timestamp: new Date().getTime()
622
+ }));
623
+
624
+ let accumulatedText = '';
625
+
626
+ eventSource.addEventListener('message', function(event) {
627
+ const data = JSON.parse(event.data);
628
 
629
+ if (data.mode === 'thinking') {
630
+ proThinkingStatus.innerHTML = '<span class="thinking-indicator">💭</span>';
631
+ }
632
+
633
+ if (data.mode === 'answering') {
634
+ proThinkingStatus.innerHTML = '';
635
+ }
636
+
637
+ if (data.content) {
638
+ accumulatedText += data.content;
639
+ proSolution.innerHTML = accumulatedText;
640
+ proResult.style.display = 'block';
641
 
642
+ // Render LaTeX if present
643
+ if (typeof MathJax !== 'undefined') {
644
+ MathJax.typesetPromise([proSolution]);
 
 
 
645
  }
646
+ }
647
 
648
+ if (data.error) {
649
+ proSolution.innerHTML = `<p class="error">Erreur: ${data.error}</p>`;
650
+ proResult.style.display = 'block';
651
+ eventSource.close();
652
+ proLoading.style.display = 'none';
653
+ proSolve.disabled = false;
654
+ }
655
+ });
656
+
657
+ eventSource.addEventListener('error', function() {
658
+ eventSource.close();
659
+ proLoading.style.display = 'none';
660
+ proSolve.disabled = false;
661
+
662
+ if (!accumulatedText) {
663
+ proSolution.innerHTML = '<p class="error">Une erreur est survenue lors de la communication avec le serveur.</p>';
664
+ proResult.style.display = 'block';
665
+ }
666
+ });
667
+
668
+ // Actual fetch to backend
669
+ fetch('/solve', {
670
+ method: 'POST',
671
+ body: formData
672
+ }).catch(error => {
673
+ console.error('Error:', error);
674
+ eventSource.close();
675
+ proLoading.style.display = 'none';
676
+ proSolve.disabled = false;
677
+ proSolution.innerHTML = '<p class="error">Une erreur est survenue lors de l\'envoi de l\'image.</p>';
678
+ proResult.style.display = 'block';
679
+ });
680
+ });
681
+
682
+ // Support for drag and drop highlight styling
683
+ document.querySelectorAll('.upload-container').forEach(container => {
684
+ container.addEventListener('dragenter', () => {
685
+ container.style.borderColor = 'var(--primary-color)';
686
+ container.style.backgroundColor = 'rgba(67, 97, 238, 0.05)';
687
+ });
688
+
689
+ container.addEventListener('dragleave', () => {
690
+ container.style.borderColor = '#d1d5db';
691
+ container.style.backgroundColor = '';
692
  });
693
  });
694
  </script>