Docfile commited on
Commit
09130d3
·
verified ·
1 Parent(s): de3fc06

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +465 -240
templates/index.html CHANGED
@@ -1,287 +1,512 @@
 
1
  <html lang="fr">
2
  <head>
3
  <meta charset="UTF-8">
4
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
- <title>Résolveur Mathématique IMO</title>
6
  <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.4/socket.io.js"></script>
7
  <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
- body {
10
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  min-height: 100vh;
13
- padding: 20px;
14
- }
15
- .container {
16
- max-width: 1200px;
17
- margin: 0 auto;
18
- background: white;
19
- border-radius: 15px;
20
- box-shadow: 0 20px 40px rgba(0,0,0,0.1);
21
- overflow: hidden;
22
- }
23
- .header {
24
- background: linear-gradient(135deg, #2196F3, #21CBF3);
25
- padding: 30px;
26
- text-align: center;
27
- color: white;
 
 
 
28
  }
29
- .header h1 { font-size: 2.5em; margin-bottom: 10px; }
30
- .header p { font-size: 1.1em; opacity: 0.9; }
31
-
32
- .upload-section {
33
- padding: 40px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  text-align: center;
35
- border-bottom: 1px solid #eee;
 
 
 
 
 
 
36
  }
37
- .upload-box {
38
- border: 3px dashed #ddd;
39
- border-radius: 10px;
40
- padding: 40px;
41
- transition: all 0.3s ease;
 
42
  cursor: pointer;
43
  }
44
- .upload-box:hover { border-color: #2196F3; background: #f8f9ff; }
45
- .upload-box.dragover { border-color: #2196F3; background: #e3f2fd; }
46
-
47
- .btn {
48
- background: linear-gradient(135deg, #2196F3, #21CBF3);
49
  color: white;
50
- padding: 12px 30px;
51
  border: none;
52
- border-radius: 25px;
53
- font-size: 16px;
 
54
  cursor: pointer;
55
- transition: all 0.3s ease;
 
 
 
56
  }
57
- .btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(33,150,243,0.3); }
58
-
59
- .logs-section {
60
- display: flex;
61
- height: 600px;
62
  }
63
- .extracted-text {
64
- flex: 1;
65
- padding: 20px;
66
- border-right: 1px solid #eee;
 
 
 
 
67
  }
68
- .logs-panel {
69
- flex: 1;
70
- padding: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  }
72
- .log-container {
73
- height: 500px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  overflow-y: auto;
75
- background: #1e1e1e;
76
- color: #fff;
77
- padding: 15px;
 
 
 
 
 
78
  border-radius: 8px;
 
 
 
79
  font-family: 'Courier New', monospace;
80
- font-size: 13px;
81
  }
 
82
  .log-entry {
83
- margin-bottom: 5px;
84
- padding: 5px;
85
- border-radius: 3px;
86
- }
87
- .log-info { color: #4CAF50; }
88
- .log-success { color: #8BC34A; background: rgba(139,195,74,0.1); }
89
- .log-warning { color: #FF9800; }
90
- .log-error { color: #F44336; background: rgba(244,67,54,0.1); }
91
-
92
- .status-bar {
93
- padding: 20px;
94
- background: #f5f5f5;
95
- text-align: center;
96
  }
97
- .status-badge {
98
- display: inline-block;
99
- padding: 8px 16px;
100
- border-radius: 20px;
101
- font-weight: bold;
102
- text-transform: uppercase;
103
- }
104
- .status-processing { background: #FFC107; color: #333; }
105
- .status-completed { background: #4CAF50; color: white; }
106
- .status-failed { background: #F44336; color: white; }
107
-
108
- .download-section {
109
- padding: 20px;
110
  text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  display: none;
112
  }
113
- .hidden { display: none; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  </style>
115
  </head>
116
  <body>
117
- <div class="container">
118
- <div class="header">
119
- <h1>🧮 Résolveur Mathématique IMO</h1>
120
- <p>Uploadez une image de votre problème mathématique et obtenez une solution rigoureuse</p>
121
- </div>
122
-
123
- <div class="upload-section">
124
- <div class="upload-box" id="uploadBox">
125
- <h3>📁 Glisser-déposer votre image ici</h3>
126
- <p>ou cliquez pour sélectionner un fichier</p>
127
- <input type="file" id="fileInput" accept="image/*" style="display: none;">
128
- <!-- MODIFICATION 1: Suppression du onclick et ajout d'un id -->
129
- <button class="btn" id="uploadButton">
130
- Choisir un fichier
131
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  </div>
133
- </div>
134
-
135
- <div class="status-bar">
136
- <div id="statusBadge" class="status-badge" style="display: none;">En attente</div>
137
- <div id="taskInfo" style="margin-top: 10px; display: none;"></div>
138
- </div>
139
-
140
- <div class="logs-section hidden" id="logsSection">
141
- <div class="extracted-text">
142
- <h3>📝 Texte extrait de l'image</h3>
143
- <div id="extractedText" style="background: #f9f9f9; padding: 15px; border-radius: 8px; margin-top: 10px; white-space: pre-wrap; max-height: 450px; overflow-y: auto;"></div>
 
 
 
 
 
 
 
144
  </div>
145
- <div class="logs-panel">
146
- <h3>📊 Logs de traitement en temps réel</h3>
147
- <div id="logContainer" class="log-container"></div>
 
 
 
 
 
 
 
 
 
 
148
  </div>
149
- </div>
150
-
151
- <div class="download-section" id="downloadSection">
152
- <h3>✅ Solution prête !</h3>
153
- <button class="btn" id="downloadBtn">📥 Télécharger la solution</button>
154
- </div>
155
  </div>
 
156
  <script>
157
- const socket = io();
158
- let currentTaskId = null;
159
-
160
- // Gestion de l'upload
161
- const uploadBox = document.getElementById('uploadBox');
162
- const fileInput = document.getElementById('fileInput');
163
- // MODIFICATION 2: Sélection du bouton par son nouvel id
164
- const uploadButton = document.getElementById('uploadButton');
165
-
166
- // L'utilisateur peut cliquer sur toute la zone
167
- uploadBox.addEventListener('click', () => fileInput.click());
168
-
169
- // MODIFICATION 3: Ajout d'un écouteur dédié au bouton
170
- uploadButton.addEventListener('click', (e) => {
171
- // Empêche le clic de "remonter" à la div parente (uploadBox),
172
- // ce qui évite de déclencher l'ouverture de la fenêtre deux fois.
173
- e.stopPropagation();
174
- fileInput.click();
175
- });
176
-
177
- // Gestion du glisser-déposer (Drag & Drop)
178
- uploadBox.addEventListener('dragover', (e) => {
179
- e.preventDefault();
180
- uploadBox.classList.add('dragover');
181
- });
182
- uploadBox.addEventListener('dragleave', () => {
183
- uploadBox.classList.remove('dragover');
184
- });
185
- uploadBox.addEventListener('drop', (e) => {
186
- e.preventDefault();
187
- uploadBox.classList.remove('dragover');
188
- const files = e.dataTransfer.files;
189
- if (files.length > 0) {
190
- uploadFile(files[0]);
191
  }
192
- });
193
-
194
- // Gestion de la sélection de fichier
195
- fileInput.addEventListener('change', (e) => {
196
- if (e.target.files.length > 0) {
197
- uploadFile(e.target.files[0]);
 
 
 
 
 
 
198
  }
199
- });
200
-
201
- function uploadFile(file) {
202
- const formData = new FormData();
203
- formData.append('file', file);
204
-
205
- updateStatus('processing', 'Upload en cours...');
206
-
207
- fetch('/upload', {
208
- method: 'POST',
209
- body: formData
210
- })
211
- .then(response => response.json())
212
- .then(data => {
213
- if (data.error) {
214
- updateStatus('failed', data.error);
215
- } else {
216
- currentTaskId = data.task_id;
217
- document.getElementById('extractedText').textContent = data.extracted_text;
218
- document.getElementById('logsSection').classList.remove('hidden');
219
- updateStatus('processing', 'Résolution en cours...');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  }
221
- })
222
- .catch(error => {
223
- updateStatus('failed', 'Erreur lors de l\'upload');
224
- console.error('Error:', error);
225
- });
226
- }
227
-
228
- function updateStatus(status, message) {
229
- const badge = document.getElementById('statusBadge');
230
- const info = document.getElementById('taskInfo');
231
-
232
- badge.style.display = 'inline-block';
233
- badge.className = `status-badge status-${status}`;
234
- badge.textContent = status === 'processing' ? 'En cours' :
235
- status === 'completed' ? 'Terminé' : 'Échec';
236
-
237
- info.style.display = 'block';
238
- info.textContent = message;
239
- }
240
-
241
- function addLog(timestamp, level, message) {
242
- const container = document.getElementById('logContainer');
243
- const logEntry = document.createElement('div');
244
- logEntry.className = `log-entry log-${level}`;
245
- logEntry.innerHTML = `<span style="color: #666;">[${timestamp}]</span> ${message}`;
246
- container.appendChild(logEntry);
247
- container.scrollTop = container.scrollHeight;
248
- }
249
-
250
- // WebSocket events
251
- socket.on('log_update', (data) => {
252
- if (data.task_id === currentTaskId) {
253
- addLog(data.log.timestamp, data.log.level, data.log.message);
254
  }
255
- });
256
-
257
- socket.on('task_completed', (data) => {
258
- if (data.task_id === currentTaskId) {
259
- updateStatus('completed', 'Solution générée avec succès !');
260
- const downloadSection = document.getElementById('downloadSection');
261
- downloadSection.style.display = 'block';
262
-
263
- document.getElementById('downloadBtn').onclick = () => {
264
- window.location.href = `/download/${currentTaskId}`;
265
- };
266
  }
267
- });
268
-
269
- socket.on('task_failed', (data) => {
270
- if (data.task_id === currentTaskId) {
271
- updateStatus('failed', 'Échec de la résolution (solution partielle disponible)');
272
- const downloadSection = document.getElementById('downloadSection');
273
- downloadSection.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
- document.getElementById('downloadBtn').onclick = () => {
276
- window.location.href = `/download/${currentTaskId}`;
277
- };
278
  }
279
- });
280
-
281
- socket.on('task_error', (data) => {
282
- if (data.task_id === currentTaskId) {
283
- updateStatus('failed', `Erreur: ${data.error}`);
284
  }
 
 
 
 
 
285
  });
286
  </script>
287
  </body>
 
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é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
+ * {
21
+ margin: 0;
22
+ padding: 0;
23
+ box-sizing: border-box;
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>