Docfile commited on
Commit
f94e218
·
verified ·
1 Parent(s): ec56b6e

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +293 -414
templates/index.html CHANGED
@@ -3,18 +3,27 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Résolveur Mathématique IMO - Version 2</title>
7
- <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.4/socket.io.js"></script>
 
 
 
8
  <style>
 
9
  :root {
10
- --primary: #6366f1;
11
- --primary-dark: #4f46e5;
12
- --success: #10b981;
13
- --warning: #f59e0b;
14
- --error: #ef4444;
15
- --bg-light: #f8fafc;
16
- --text-dark: #1e293b;
17
- --border: #e2e8f0;
 
 
 
 
 
18
  }
19
 
20
  * {
@@ -24,489 +33,359 @@
24
  }
25
 
26
  body {
27
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
28
- background: var(--bg-light);
29
- color: var(--text-dark);
30
  line-height: 1.6;
 
31
  }
32
 
33
- .app-container {
34
- min-height: 100vh;
35
- display: flex;
36
- flex-direction: column;
37
- }
38
-
39
- .navbar {
40
- background: white;
41
- border-bottom: 1px solid var(--border);
42
- padding: 1rem 0;
43
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
44
- }
45
-
46
- .nav-content {
47
  max-width: 1200px;
48
  margin: 0 auto;
49
- padding: 0 2rem;
50
- display: flex;
51
- align-items: center;
52
- gap: 1rem;
53
  }
54
 
55
- .logo {
56
- font-size: 1.5rem;
57
- font-weight: bold;
58
- color: var(--primary);
 
 
59
  }
60
-
61
- .main-content {
62
- flex: 1;
63
- max-width: 1200px;
64
- margin: 0 auto;
65
- padding: 2rem;
66
- width: 100%;
 
 
 
 
 
 
 
 
 
67
  }
68
 
69
- .card {
70
- background: white;
71
- border-radius: 12px;
72
- padding: 2rem;
73
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
74
- border: 1px solid var(--border);
75
- margin-bottom: 2rem;
76
  }
77
 
78
  .upload-area {
 
 
 
79
  text-align: center;
80
- padding: 3rem 2rem;
 
81
  }
82
-
83
- .file-input-wrapper {
84
- position: relative;
85
- display: inline-block;
86
- margin: 2rem 0;
87
  }
88
-
89
- .file-input {
90
- position: absolute;
91
- opacity: 0;
92
- width: 100%;
93
- height: 100%;
94
- cursor: pointer;
 
95
  }
96
 
97
- .upload-btn {
98
- background: var(--primary);
 
 
99
  color: white;
100
- padding: 1rem 2rem;
101
  border: none;
102
- border-radius: 8px;
103
  font-size: 1rem;
104
  font-weight: 500;
105
  cursor: pointer;
106
- transition: all 0.2s;
107
- display: inline-flex;
108
- align-items: center;
109
- gap: 0.5rem;
110
- }
111
-
112
- .upload-btn:hover {
113
- background: var(--primary-dark);
114
- transform: translateY(-1px);
115
- }
116
-
117
- .drop-zone {
118
- border: 2px dashed var(--border);
119
- border-radius: 12px;
120
- padding: 3rem;
121
- margin: 2rem 0;
122
- transition: all 0.3s;
123
- cursor: pointer;
124
- }
125
-
126
- .drop-zone:hover {
127
- border-color: var(--primary);
128
- background: rgba(99, 102, 241, 0.05);
129
- }
130
-
131
- .drop-zone.active {
132
- border-color: var(--primary);
133
- background: rgba(99, 102, 241, 0.1);
134
- }
135
-
136
- .status-indicator {
137
- display: inline-flex;
138
- align-items: center;
139
- gap: 0.5rem;
140
- padding: 0.5rem 1rem;
141
- border-radius: 6px;
142
- font-weight: 500;
143
- margin-bottom: 1rem;
144
- }
145
-
146
- .status-processing {
147
- background: rgba(245, 158, 11, 0.1);
148
- color: var(--warning);
149
- }
150
-
151
- .status-success {
152
- background: rgba(16, 185, 129, 0.1);
153
- color: var(--success);
154
  }
155
-
156
- .status-error {
157
- background: rgba(239, 68, 68, 0.1);
158
- color: var(--error);
159
  }
160
 
161
- .content-grid {
 
 
162
  display: grid;
163
- grid-template-columns: 1fr 1fr;
164
  gap: 2rem;
165
- margin-top: 2rem;
166
  }
167
 
168
- .text-preview {
169
- background: #f1f5f9;
170
- border-radius: 8px;
 
171
  padding: 1.5rem;
172
- max-height: 400px;
173
- overflow-y: auto;
 
 
 
 
 
 
 
 
174
  white-space: pre-wrap;
 
175
  font-family: 'Courier New', monospace;
176
- font-size: 0.9rem;
 
 
 
 
 
177
  }
178
 
179
- .logs {
180
- background: #0f172a;
181
- color: #e2e8f0;
182
- border-radius: 8px;
183
- padding: 1rem;
184
- max-height: 400px;
185
  overflow-y: auto;
 
 
 
 
186
  font-family: 'Courier New', monospace;
187
- font-size: 0.85rem;
188
  }
189
-
190
  .log-entry {
191
- padding: 0.25rem 0;
192
- border-bottom: 1px solid rgba(255,255,255,0.1);
193
- }
194
-
195
- .log-timestamp {
196
- color: #64748b;
197
  }
198
-
199
  .log-info { color: #3b82f6; }
200
- .log-success { color: var(--success); }
201
- .log-warning { color: var(--warning); }
202
- .log-error { color: var(--error); }
203
-
204
- .download-area {
 
 
 
 
205
  text-align: center;
206
- padding: 2rem;
207
- background: rgba(16, 185, 129, 0.05);
208
- border-radius: 12px;
209
- border: 1px solid rgba(16, 185, 129, 0.2);
210
  }
211
-
212
- .download-btn {
213
- background: var(--success);
214
- color: white;
215
- padding: 1rem 2rem;
216
- border: none;
217
- border-radius: 8px;
218
- font-size: 1rem;
219
- cursor: pointer;
220
- text-decoration: none;
221
  display: inline-flex;
222
  align-items: center;
223
  gap: 0.5rem;
224
- transition: all 0.2s;
 
 
225
  }
 
 
 
226
 
227
- .download-btn:hover {
228
- background: #059669;
229
  }
230
-
231
  .hidden {
232
  display: none;
233
  }
234
-
235
- .spinner {
236
- width: 20px;
237
- height: 20px;
238
- border: 2px solid transparent;
239
- border-top: 2px solid currentColor;
240
- border-radius: 50%;
241
- animation: spin 1s linear infinite;
242
- }
243
-
244
- @keyframes spin {
245
- to { transform: rotate(360deg); }
246
- }
247
-
248
- @media (max-width: 768px) {
249
- .content-grid {
250
- grid-template-columns: 1fr;
251
- }
252
- .main-content {
253
- padding: 1rem;
254
- }
255
- }
256
  </style>
257
  </head>
258
  <body>
259
- <div class="app-container">
260
- <nav class="navbar">
261
- <div class="nav-content">
262
- <div class="logo">🧮 Math Solver IMO</div>
263
- <div style="margin-left: auto;">
264
- <span style="font-size: 0.9rem; color: #64748b;">Résolveur de problèmes mathématiques</span>
265
- </div>
266
- </div>
267
- </nav>
268
 
269
  <main class="main-content">
270
- <!-- Section Upload -->
271
- <div class="card">
272
- <div class="upload-area">
273
- <h2 style="margin-bottom: 1rem;">📸 Uploader votre problème mathématique</h2>
274
- <p style="color: #64748b; margin-bottom: 2rem;">
275
- Prenez une photo ou uploadez une image de votre problème mathématique
276
- </p>
277
-
278
- <!-- Zone de drop -->
279
- <div class="drop-zone" id="dropZone">
280
- <div>
281
- <div style="font-size: 3rem; margin-bottom: 1rem;">📁</div>
282
- <h3>Glissez votre image ici</h3>
283
- <p style="color: #64748b; margin: 1rem 0;">ou</p>
284
-
285
- <div class="file-input-wrapper">
286
- <input type="file" class="file-input" id="fileInput" accept="image/*">
287
- <button class="upload-btn">
288
- <span>📷</span>
289
- Choisir une image
290
- </button>
291
- </div>
292
- </div>
293
- </div>
294
-
295
- <p style="font-size: 0.85rem; color: #64748b;">
296
- Formats supportés: PNG, JPG, JPEG, GIF, BMP, TIFF
297
- </p>
298
  </div>
299
- </div>
300
-
301
- <!-- Section Status -->
302
- <div class="card" id="statusCard" style="display: none;">
303
- <div id="statusIndicator"></div>
304
- <div id="statusMessage"></div>
305
- </div>
 
 
306
 
307
- <!-- Section Contenu -->
308
- <div class="content-grid" id="contentGrid" style="display: none;">
309
- <div class="card">
310
- <h3 style="margin-bottom: 1rem;">📝 Texte extrait</h3>
311
- <div class="text-preview" id="extractedText"></div>
312
  </div>
313
-
314
- <div class="card">
315
- <h3 style="margin-bottom: 1rem;">📊 Logs en temps réel</h3>
316
- <div class="logs" id="logsContainer"></div>
317
  </div>
318
- </div>
319
 
320
- <!-- Section Download -->
321
- <div class="card hidden" id="downloadCard">
322
- <div class="download-area">
323
- <h3 style="margin-bottom: 1rem;">✅ Solution prête !</h3>
324
- <p style="margin-bottom: 2rem; color: #64748b;">
325
- Votre problème mathématique a été résolu avec succès
326
- </p>
327
- <a class="download-btn" id="downloadLink">
328
- <span>📥</span>
329
- Télécharger la solution
330
- </a>
331
- </div>
332
- </div>
333
  </main>
334
  </div>
335
 
336
  <script>
337
- class MathSolverApp {
338
- constructor() {
339
- this.socket = io();
340
- this.currentTaskId = null;
341
- this.initializeElements();
342
- this.setupEventListeners();
343
- this.setupSocketEvents();
344
- }
345
-
346
- initializeElements() {
347
- this.dropZone = document.getElementById('dropZone');
348
- this.fileInput = document.getElementById('fileInput');
349
- this.statusCard = document.getElementById('statusCard');
350
- this.statusIndicator = document.getElementById('statusIndicator');
351
- this.statusMessage = document.getElementById('statusMessage');
352
- this.contentGrid = document.getElementById('contentGrid');
353
- this.extractedText = document.getElementById('extractedText');
354
- this.logsContainer = document.getElementById('logsContainer');
355
- this.downloadCard = document.getElementById('downloadCard');
356
- this.downloadLink = document.getElementById('downloadLink');
357
- }
358
-
359
- setupEventListeners() {
360
- // File input change
361
- this.fileInput.addEventListener('change', (e) => {
362
- if (e.target.files.length > 0) {
363
- this.handleFile(e.target.files[0]);
364
- }
365
- });
366
-
367
- // Drop zone events
368
- this.dropZone.addEventListener('dragover', (e) => {
369
- e.preventDefault();
370
- this.dropZone.classList.add('active');
371
- });
372
-
373
- this.dropZone.addEventListener('dragleave', (e) => {
374
- e.preventDefault();
375
- this.dropZone.classList.remove('active');
376
- });
377
-
378
- this.dropZone.addEventListener('drop', (e) => {
379
- e.preventDefault();
380
- this.dropZone.classList.remove('active');
381
-
382
- const files = e.dataTransfer.files;
383
- if (files.length > 0) {
384
- this.handleFile(files[0]);
385
- }
386
- });
387
 
388
- // Click on drop zone
389
- this.dropZone.addEventListener('click', () => {
390
- this.fileInput.click();
391
- });
392
  }
 
393
 
394
- setupSocketEvents() {
395
- this.socket.on('log_update', (data) => {
396
- if (data.task_id === this.currentTaskId) {
397
- this.addLog(data.log);
398
- }
399
- });
400
-
401
- this.socket.on('task_completed', (data) => {
402
- if (data.task_id === this.currentTaskId) {
403
- this.showSuccess('Solution générée avec succès !');
404
- this.showDownload();
405
- }
406
- });
407
 
408
- this.socket.on('task_failed', (data) => {
409
- if (data.task_id === this.currentTaskId) {
410
- this.showError('Échec de la résolution (solution partielle disponible)');
411
- this.showDownload();
412
- }
413
- });
414
 
415
- this.socket.on('task_error', (data) => {
416
- if (data.task_id === this.currentTaskId) {
417
- this.showError(`Erreur: ${data.error}`);
418
- }
419
- });
420
  }
 
421
 
422
- async handleFile(file) {
423
- if (!this.isValidFile(file)) {
424
- this.showError('Type de fichier non supporté');
425
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  }
427
-
428
- const formData = new FormData();
429
- formData.append('file', file);
430
-
431
- this.showProcessing('Upload en cours...');
432
-
433
- try {
434
- const response = await fetch('/upload', {
435
- method: 'POST',
436
- body: formData
437
- });
438
-
439
- const data = await response.json();
440
-
441
- if (data.error) {
442
- this.showError(data.error);
443
- } else {
444
- this.currentTaskId = data.task_id;
445
- this.extractedText.textContent = data.extracted_text;
446
- this.contentGrid.style.display = 'grid';
447
- this.showProcessing('Résolution en cours...');
448
- }
449
- } catch (error) {
450
- this.showError('Erreur lors de l\'upload');
451
- console.error('Upload error:', error);
452
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
453
  }
454
-
455
- isValidFile(file) {
456
- const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/bmp', 'image/tiff'];
457
- return allowedTypes.includes(file.type);
458
- }
459
-
460
- showProcessing(message) {
461
- this.statusCard.style.display = 'block';
462
- this.statusIndicator.innerHTML = `
463
- <div class="status-indicator status-processing">
464
- <div class="spinner"></div>
465
- En cours
466
- </div>
467
- `;
468
- this.statusMessage.textContent = message;
469
- }
470
-
471
- showSuccess(message) {
472
- this.statusIndicator.innerHTML = `
473
- <div class="status-indicator status-success">
474
- ✅ Terminé
475
- </div>
476
- `;
477
- this.statusMessage.textContent = message;
478
- }
479
-
480
- showError(message) {
481
- this.statusIndicator.innerHTML = `
482
- <div class="status-indicator status-error">
483
- ❌ Erreur
484
- </div>
485
- `;
486
- this.statusMessage.textContent = message;
487
  }
488
-
489
- addLog(log) {
490
- const logEntry = document.createElement('div');
491
- logEntry.className = 'log-entry';
492
- logEntry.innerHTML = `
493
- <span class="log-timestamp">[${log.timestamp}]</span>
494
- <span class="log-${log.level}">${log.message}</span>
495
- `;
496
-
497
- this.logsContainer.appendChild(logEntry);
498
- this.logsContainer.scrollTop = this.logsContainer.scrollHeight;
499
  }
500
-
501
- showDownload() {
502
- this.downloadCard.classList.remove('hidden');
503
- this.downloadLink.href = `/download/${this.currentTaskId}`;
 
504
  }
505
- }
506
-
507
- // Initialize app when DOM is loaded
508
- document.addEventListener('DOMContentLoaded', () => {
509
- new MathSolverApp();
510
  });
511
  </script>
512
  </body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Résolveur Mathématique IA</title>
7
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.5/socket.io.min.js"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11
  <style>
12
+ /* --- Reset & Variables --- */
13
  :root {
14
+ --bg-color: #f4f7fa;
15
+ --main-color: #ffffff;
16
+ --primary-color: #3b82f6;
17
+ --primary-hover: #2563eb;
18
+ --text-color: #1f2937;
19
+ --text-light: #6b7280;
20
+ --border-color: #e5e7eb;
21
+ --dark-bg: #111827;
22
+ --success-color: #10b981;
23
+ --warning-color: #f59e0b;
24
+ --error-color: #ef4444;
25
+ --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
26
+ --border-radius: 0.75rem;
27
  }
28
 
29
  * {
 
33
  }
34
 
35
  body {
36
+ font-family: 'Inter', sans-serif;
37
+ background-color: var(--bg-color);
38
+ color: var(--text-color);
39
  line-height: 1.6;
40
+ padding: 2rem;
41
  }
42
 
43
+ /* --- Layout & Main Container --- */
44
+ .container {
 
 
 
 
 
 
 
 
 
 
 
 
45
  max-width: 1200px;
46
  margin: 0 auto;
47
+ background-color: var(--main-color);
48
+ border-radius: var(--border-radius);
49
+ box-shadow: var(--shadow);
50
+ overflow: hidden;
51
  }
52
 
53
+ /* --- Header --- */
54
+ .header {
55
+ padding: 2.5rem;
56
+ text-align: center;
57
+ border-bottom: 1px solid var(--border-color);
58
+ background: linear-gradient(to top, #ffffff, #f9fafb);
59
  }
60
+ .header h1 {
61
+ font-size: 2.25rem;
62
+ font-weight: 700;
63
+ letter-spacing: -0.025em;
64
+ color: var(--text-color);
65
+ }
66
+ .header h1 .icon {
67
+ color: var(--primary-color);
68
+ }
69
+ .header p {
70
+ margin-top: 0.5rem;
71
+ color: var(--text-light);
72
+ font-size: 1.1rem;
73
+ max-width: 600px;
74
+ margin-left: auto;
75
+ margin-right: auto;
76
  }
77
 
78
+ /* --- Upload Section --- */
79
+ .main-content {
80
+ padding: 2.5rem;
 
 
 
 
81
  }
82
 
83
  .upload-area {
84
+ border: 2px dashed var(--border-color);
85
+ border-radius: var(--border-radius);
86
+ padding: 3rem;
87
  text-align: center;
88
+ background-color: #fcfdff;
89
+ transition: all 0.3s ease;
90
  }
91
+ .upload-area.dragover {
92
+ border-color: var(--primary-color);
93
+ background-color: #eff6ff;
 
 
94
  }
95
+ .upload-icon {
96
+ font-size: 3rem;
97
+ color: var(--primary-color);
98
+ margin-bottom: 1rem;
99
+ }
100
+ .upload-area p {
101
+ color: var(--text-light);
102
+ margin-bottom: 1.5rem;
103
  }
104
 
105
+ /* --- Main Button --- */
106
+ .btn {
107
+ display: inline-block;
108
+ background-color: var(--primary-color);
109
  color: white;
110
+ padding: 0.8rem 2rem;
111
  border: none;
112
+ border-radius: 9999px; /* pill shape */
113
  font-size: 1rem;
114
  font-weight: 500;
115
  cursor: pointer;
116
+ text-decoration: none;
117
+ transition: background-color 0.2s ease, transform 0.2s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  }
119
+ .btn:hover {
120
+ background-color: var(--primary-hover);
121
+ transform: translateY(-2px);
 
122
  }
123
 
124
+ /* --- Results & Logs Section --- */
125
+ .results-grid {
126
+ margin-top: 2.5rem;
127
  display: grid;
128
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
129
  gap: 2rem;
 
130
  }
131
 
132
+ .panel {
133
+ background-color: #f9fafb;
134
+ border: 1px solid var(--border-color);
135
+ border-radius: var(--border-radius);
136
  padding: 1.5rem;
137
+ }
138
+ .panel h3 {
139
+ font-size: 1.25rem;
140
+ font-weight: 600;
141
+ margin-bottom: 1rem;
142
+ border-bottom: 1px solid var(--border-color);
143
+ padding-bottom: 0.75rem;
144
+ }
145
+
146
+ #extractedText {
147
  white-space: pre-wrap;
148
+ word-wrap: break-word;
149
  font-family: 'Courier New', monospace;
150
+ background-color: var(--main-color);
151
+ padding: 1rem;
152
+ border-radius: 0.5rem;
153
+ max-height: 450px;
154
+ overflow-y: auto;
155
+ color: var(--text-light);
156
  }
157
 
158
+ /* --- Logs --- */
159
+ #logContainer {
160
+ height: 450px;
 
 
 
161
  overflow-y: auto;
162
+ background: var(--dark-bg);
163
+ color: #d1d5db;
164
+ padding: 1rem;
165
+ border-radius: 0.5rem;
166
  font-family: 'Courier New', monospace;
167
+ font-size: 0.875rem;
168
  }
 
169
  .log-entry {
170
+ margin-bottom: 0.25rem;
171
+ padding: 0.2rem 0.5rem;
172
+ border-radius: 3px;
 
 
 
173
  }
174
+ .log-timestamp { color: #9ca3af; margin-right: 0.5rem; }
175
  .log-info { color: #3b82f6; }
176
+ .log-success { color: #10b981; }
177
+ .log-warning { color: #f59e0b; }
178
+ .log-error { color: #ef4444; font-weight: bold; }
179
+
180
+ /* --- Status Bar & Download --- */
181
+ #status-section {
182
+ padding: 1.5rem 2.5rem;
183
+ background-color: #f9fafb;
184
+ border-top: 1px solid var(--border-color);
185
  text-align: center;
 
 
 
 
186
  }
187
+ .status-badge {
 
 
 
 
 
 
 
 
 
188
  display: inline-flex;
189
  align-items: center;
190
  gap: 0.5rem;
191
+ padding: 0.5rem 1.25rem;
192
+ border-radius: 9999px;
193
+ font-weight: 500;
194
  }
195
+ .status-processing { background-color: #fef3c7; color: #92400e; }
196
+ .status-completed { background-color: #d1fae5; color: #065f46; }
197
+ .status-failed { background-color: #fee2e2; color: #991b1b; }
198
 
199
+ #downloadSection {
200
+ margin-top: 1rem;
201
  }
202
+
203
  .hidden {
204
  display: none;
205
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  </style>
207
  </head>
208
  <body>
209
+ <div class="container">
210
+ <header class="header">
211
+ <h1><span class="icon">🧮</span> Résolveur Mathématique IA</h1>
212
+ <p>Soumettez une image d'un problème mathématique et laissez nos agents IA générer une solution rigoureuse, étape par étape.</p>
213
+ </header>
 
 
 
 
214
 
215
  <main class="main-content">
216
+ <!-- Section d'upload corrigée -->
217
+ <section id="upload-section">
218
+ <div class="upload-area" id="uploadArea">
219
+ <div class="upload-icon">📄</div>
220
+ <p>Glissez-déposez votre image ici, ou cliquez sur le bouton pour la sélectionner.</p>
221
+ <!-- Ce bouton est maintenant le point d'interaction principal pour le clic -->
222
+ <button type="button" class="btn" id="selectFileBtn">Choisir un fichier</button>
223
+ <!-- L'input de fichier est caché et sera activé par le JS -->
224
+ <input type="file" id="fileInput" accept="image/*" class="hidden">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  </div>
226
+ </section>
227
+
228
+ <!-- Section pour afficher le statut global, cachée au début -->
229
+ <section id="status-section" class="hidden">
230
+ <div id="statusBadge" class="status-badge"></div>
231
+ <div id="downloadSection" class="hidden">
232
+ <a href="#" id="downloadBtn" class="btn">📥 Télécharger la solution</a>
233
+ </div>
234
+ </section>
235
 
236
+ <!-- Grille pour les résultats, cachée au début -->
237
+ <section id="results-grid" class="results-grid hidden">
238
+ <div class="panel">
239
+ <h3>📝 Texte extrait de l'image</h3>
240
+ <div id="extractedText">Le texte de votre image apparaîtra ici...</div>
241
  </div>
242
+ <div class="panel">
243
+ <h3>📊 Journal de traitement</h3>
244
+ <div id="logContainer"></div>
 
245
  </div>
246
+ </section>
247
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  </main>
249
  </div>
250
 
251
  <script>
252
+ const socket = io();
253
+ let currentTaskId = null;
254
+
255
+ // --- DOM Elements ---
256
+ const uploadArea = document.getElementById('uploadArea');
257
+ const fileInput = document.getElementById('fileInput');
258
+ const selectFileBtn = document.getElementById('selectFileBtn');
259
+ const statusSection = document.getElementById('status-section');
260
+ const statusBadge = document.getElementById('statusBadge');
261
+ const resultsGrid = document.getElementById('results-grid');
262
+ const extractedTextEl = document.getElementById('extractedText');
263
+ const logContainer = document.getElementById('logContainer');
264
+ const downloadSection = document.getElementById('downloadSection');
265
+ const downloadBtn = document.getElementById('downloadBtn');
266
+
267
+ // --- Event Listeners ---
268
+
269
+ // CORRECTION : Le bouton déclenche l'input de fichier
270
+ selectFileBtn.addEventListener('click', () => {
271
+ fileInput.click();
272
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
+ // L'input de fichier, une fois un fichier choisi, lance l'upload
275
+ fileInput.addEventListener('change', (e) => {
276
+ if (e.target.files.length > 0) {
277
+ handleFileUpload(e.target.files[0]);
278
  }
279
+ });
280
 
281
+ // Gestion du glisser-déposer (Drag & Drop)
282
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
283
+ uploadArea.addEventListener(eventName, preventDefaults, false);
284
+ });
285
+ function preventDefaults(e) {
286
+ e.preventDefault();
287
+ e.stopPropagation();
288
+ }
 
 
 
 
 
289
 
290
+ ['dragenter', 'dragover'].forEach(eventName => {
291
+ uploadArea.addEventListener(eventName, () => uploadArea.classList.add('dragover'), false);
292
+ });
293
+ ['dragleave', 'drop'].forEach(eventName => {
294
+ uploadArea.addEventListener(eventName, () => uploadArea.classList.remove('dragover'), false);
295
+ });
296
 
297
+ uploadArea.addEventListener('drop', (e) => {
298
+ const files = e.dataTransfer.files;
299
+ if (files.length > 0) {
300
+ handleFileUpload(files[0]);
 
301
  }
302
+ });
303
 
304
+ // --- Core Functions ---
305
+
306
+ function resetUI() {
307
+ statusSection.classList.add('hidden');
308
+ resultsGrid.classList.add('hidden');
309
+ downloadSection.classList.add('hidden');
310
+ logContainer.innerHTML = '';
311
+ extractedTextEl.textContent = 'Le texte de votre image apparaîtra ici...';
312
+ currentTaskId = null;
313
+ }
314
+
315
+ function handleFileUpload(file) {
316
+ resetUI();
317
+ const formData = new FormData();
318
+ formData.append('file', file);
319
+
320
+ updateStatus('processing', '🚀 Envoi et analyse de l\'image...');
321
+ statusSection.classList.remove('hidden');
322
+
323
+ fetch('/upload', {
324
+ method: 'POST',
325
+ body: formData
326
+ })
327
+ .then(response => {
328
+ if (!response.ok) {
329
+ throw new Error(`Erreur serveur: ${response.statusText}`);
330
  }
331
+ return response.json();
332
+ })
333
+ .then(data => {
334
+ if (data.error) {
335
+ throw new Error(data.error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  }
337
+ currentTaskId = data.task_id;
338
+ extractedTextEl.textContent = data.extracted_text;
339
+ resultsGrid.classList.remove('hidden');
340
+ updateStatus('processing', '🧠 Résolution en cours...');
341
+ })
342
+ .catch(error => {
343
+ updateStatus('failed', `❌ Erreur critique : ${error.message}`);
344
+ console.error('Upload Error:', error);
345
+ });
346
+ }
347
+
348
+ function updateStatus(status, message) {
349
+ statusBadge.className = `status-badge status-${status}`;
350
+ statusBadge.textContent = message;
351
+ }
352
+
353
+ function addLog(timestamp, level, message) {
354
+ const logEntry = document.createElement('div');
355
+ logEntry.className = `log-entry log-${level}`;
356
+ logEntry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span> ${message}`;
357
+ logContainer.appendChild(logEntry);
358
+ logContainer.scrollTop = logContainer.scrollHeight;
359
+ }
360
+
361
+ // --- WebSocket Event Handlers ---
362
+
363
+ socket.on('log_update', (data) => {
364
+ if (data.task_id === currentTaskId) {
365
+ addLog(data.log.timestamp, data.log.level, data.log.message);
366
  }
367
+ });
368
+
369
+ socket.on('task_completed', (data) => {
370
+ if (data.task_id === currentTaskId) {
371
+ updateStatus('completed', '✅ Solution générée avec succès !');
372
+ downloadSection.classList.remove('hidden');
373
+ downloadBtn.href = `/download/${currentTaskId}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
374
  }
375
+ });
376
+
377
+ socket.on('task_failed', (data) => {
378
+ if (data.task_id === currentTaskId) {
379
+ updateStatus('failed', '⚠️ Échec de la résolution (solution partielle disponible)');
380
+ downloadSection.classList.remove('hidden');
381
+ downloadBtn.href = `/download/${currentTaskId}`;
 
 
 
 
382
  }
383
+ });
384
+
385
+ socket.on('task_error', (data) => {
386
+ if (data.task_id === currentTaskId) {
387
+ updateStatus('failed', `❌ Erreur système: ${data.error}`);
388
  }
 
 
 
 
 
389
  });
390
  </script>
391
  </body>