Docfile commited on
Commit
4896d95
·
verified ·
1 Parent(s): 8071ff2

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +227 -627
templates/index.html CHANGED
@@ -3,693 +3,293 @@
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>
695
  </body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Résolveur avec Gemini AI</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
9
+ <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
10
  <style>
11
+ .drop-zone {
12
+ max-width: 100%;
13
+ height: 200px;
14
+ padding: 25px;
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  text-align: center;
19
+ cursor: pointer;
20
+ color: #444;
21
+ border: 2px dashed #009578;
22
+ border-radius: 10px;
23
  }
24
+
25
+ .drop-zone--over {
26
+ border-style: solid;
 
 
27
  }
28
+
29
+ .drop-zone__input {
30
+ display: none;
 
 
31
  }
32
+
33
+ .drop-zone__thumb {
34
+ width: 100%;
35
+ height: 100%;
36
+ border-radius: 10px;
 
 
37
  overflow: hidden;
38
+ background-color: #cccccc;
39
+ background-size: cover;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  position: relative;
 
41
  }
42
+
43
+ .drop-zone__thumb::after {
44
+ content: attr(data-label);
 
 
 
 
 
45
  position: absolute;
46
  bottom: 0;
47
  left: 0;
48
  width: 100%;
49
+ padding: 5px 0;
50
+ color: #ffffff;
51
+ background: rgba(0, 0, 0, 0.75);
52
+ font-size: 14px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
55
+
56
+ #response, #thinking {
57
+ white-space: pre-wrap;
58
+ padding: 15px;
59
+ border-radius: 10px;
60
+ background-color: #f8f9fa;
61
+ margin-top: 20px;
 
62
  }
63
+
64
+ #thinking {
65
+ color: #666;
66
+ font-style: italic;
 
 
 
67
  }
68
+
69
  .loading {
 
 
 
 
 
70
  display: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  text-align: center;
72
+ margin-top: 20px;
73
+ }
74
+
75
+ .loading-spinner {
76
+ width: 50px;
77
+ height: 50px;
78
+ border: 5px solid rgba(0, 0, 0, 0.1);
79
+ border-radius: 50%;
80
+ border-top-color: #009578;
81
+ animation: spin 1s ease-in-out infinite;
82
+ margin: 0 auto;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  }
84
+
85
+ @keyframes spin {
86
+ to { transform: rotate(360deg); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  }
88
  </style>
89
  </head>
90
  <body>
91
+ <div class="container my-5">
92
+ <h1 class="text-center mb-4">Résolveur de problèmes avec Gemini AI</h1>
93
+
94
+ <div class="row mb-4">
95
+ <div class="col-md-6 offset-md-3">
96
+ <div class="card">
97
+ <div class="card-body">
98
+ <h5 class="card-title">Téléchargez une image de votre problème</h5>
99
+ <div class="drop-zone">
100
+ <span class="drop-zone__prompt">Déposez votre image ici ou cliquez pour parcourir</span>
101
+ <input type="file" name="image" class="drop-zone__input" accept="image/*">
102
+ </div>
103
+
104
+ <div class="d-grid gap-2 mt-3">
105
+ <button id="solveBtn" class="btn btn-primary" disabled>Résoudre</button>
106
+ <button id="fastSolveBtn" class="btn btn-outline-primary" disabled>Résolution rapide (moins précise)</button>
107
+ </div>
108
+ </div>
109
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  </div>
111
  </div>
112
 
113
+ <div class="loading">
114
+ <div class="loading-spinner"></div>
115
+ <p class="mt-2">Traitement en cours...</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  </div>
117
+
118
+ <div id="thinking" style="display: none;">
119
+ <h4>Gemini réfléchit...</h4>
120
+ <div id="thinking-content"></div>
 
 
 
121
  </div>
122
 
123
+ <div id="response" style="display: none;"></div>
 
 
 
 
 
 
 
 
 
 
124
  </div>
125
 
 
 
 
 
126
  <script>
127
+ document.addEventListener("DOMContentLoaded", function() {
128
+ // Gestion de la zone de dépôt d'image
129
+ document.querySelectorAll(".drop-zone__input").forEach(inputElement => {
130
+ const dropZoneElement = inputElement.closest(".drop-zone");
 
 
 
 
 
 
 
131
 
132
+ dropZoneElement.addEventListener("click", e => {
133
+ inputElement.click();
134
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
+ inputElement.addEventListener("change", e => {
137
+ if (inputElement.files.length) {
138
+ updateThumbnail(dropZoneElement, inputElement.files[0]);
139
+ document.getElementById("solveBtn").disabled = false;
140
+ document.getElementById("fastSolveBtn").disabled = false;
141
+ }
142
+ });
143
 
144
+ dropZoneElement.addEventListener("dragover", e => {
145
+ e.preventDefault();
146
+ dropZoneElement.classList.add("drop-zone--over");
147
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
+ ["dragleave", "dragend"].forEach(type => {
150
+ dropZoneElement.addEventListener(type, e => {
151
+ dropZoneElement.classList.remove("drop-zone--over");
152
+ });
153
+ });
154
 
155
+ dropZoneElement.addEventListener("drop", e => {
156
+ e.preventDefault();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
+ if (e.dataTransfer.files.length) {
159
+ inputElement.files = e.dataTransfer.files;
160
+ updateThumbnail(dropZoneElement, e.dataTransfer.files[0]);
161
+ document.getElementById("solveBtn").disabled = false;
162
+ document.getElementById("fastSolveBtn").disabled = false;
163
+ }
164
+
165
+ dropZoneElement.classList.remove("drop-zone--over");
166
+ });
167
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
+ function updateThumbnail(dropZoneElement, file) {
170
+ let thumbnailElement = dropZoneElement.querySelector(".drop-zone__thumb");
171
 
172
+ // Supprimer le texte de prompt
173
+ if (dropZoneElement.querySelector(".drop-zone__prompt")) {
174
+ dropZoneElement.querySelector(".drop-zone__prompt").remove();
175
  }
176
 
177
+ // Première fois - créer l'élément thumbnail
178
+ if (!thumbnailElement) {
179
+ thumbnailElement = document.createElement("div");
180
+ thumbnailElement.classList.add("drop-zone__thumb");
181
+ dropZoneElement.appendChild(thumbnailElement);
182
  }
183
 
184
+ thumbnailElement.dataset.label = file.name;
 
 
 
 
 
 
 
 
 
185
 
186
+ // Afficher l'image
187
+ if (file.type.startsWith("image/")) {
188
+ const reader = new FileReader();
189
+
190
+ reader.readAsDataURL(file);
191
+ reader.onload = () => {
192
+ thumbnailElement.style.backgroundImage = `url('${reader.result}')`;
193
+ };
194
+ } else {
195
+ thumbnailElement.style.backgroundImage = null;
196
  }
197
+ }
198
 
199
+ // Gestion des boutons
200
+ document.getElementById("solveBtn").addEventListener("click", function() {
201
+ processImage("/solve");
 
 
 
 
 
 
202
  });
203
 
204
+ document.getElementById("fastSolveBtn").addEventListener("click", function() {
205
+ processImage("/solved");
 
 
 
 
 
 
 
 
 
206
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
+ // Fonction pour envoyer l'image et traiter la réponse
209
+ function processImage(endpoint) {
210
+ const fileInput = document.querySelector(".drop-zone__input");
211
+ if (!fileInput.files.length) return;
212
 
213
+ const file = fileInput.files[0];
214
+ const formData = new FormData();
215
+ formData.append("image", file);
216
 
217
+ // Réinitialiser l'affichage
218
+ document.getElementById("response").style.display = "none";
219
+ document.getElementById("response").innerHTML = "";
220
+ document.getElementById("thinking").style.display = "none";
221
+ document.getElementById("thinking-content").innerHTML = "";
222
+ document.querySelector(".loading").style.display = "block";
223
+
224
+ // Désactiver les boutons pendant le traitement
225
+ document.getElementById("solveBtn").disabled = true;
226
+ document.getElementById("fastSolveBtn").disabled = true;
227
+
228
+ // Configurer SSE
229
+ const eventSource = new EventSource(endpoint, {
230
+ method: "POST",
231
+ body: formData
232
+ });
233
 
234
+ let mode = "starting";
235
+
236
+ eventSource.onmessage = function(event) {
237
+ const data = JSON.parse(event.data);
238
+
239
+ // Masquer le chargement initial
240
+ document.querySelector(".loading").style.display = "none";
241
 
242
+ // Gestion du mode
243
+ if (data.mode) {
244
+ mode = data.mode;
245
+ if (mode === "thinking") {
246
+ document.getElementById("thinking").style.display = "block";
247
+ } else if (mode === "answering") {
248
+ document.getElementById("response").style.display = "block";
249
+ }
250
  }
251
+
252
+ // Ajout du contenu
253
+ if (data.content) {
254
+ if (mode === "thinking") {
255
+ document.getElementById("thinking-content").innerHTML += data.content;
256
+ } else if (mode === "answering") {
257
+ document.getElementById("response").innerHTML += data.content;
258
+ // Rendre LaTeX si présent
259
+ if (window.MathJax) {
260
+ MathJax.typesetPromise([document.getElementById("response")]);
261
+ }
262
+ }
263
+ }
264
+
265
+ // Gestion des erreurs
266
+ if (data.error) {
267
+ document.getElementById("response").style.display = "block";
268
+ document.getElementById("response").innerHTML += `<div class="alert alert-danger">${data.error}</div>`;
269
+ eventSource.close();
270
+ }
271
+ };
272
 
273
+ eventSource.onerror = function(error) {
274
+ document.querySelector(".loading").style.display = "none";
275
+ document.getElementById("response").style.display = "block";
276
+ document.getElementById("response").innerHTML += `<div class="alert alert-danger">Erreur de connexion. Veuillez réessayer.</div>`;
277
  eventSource.close();
278
+
279
+ // Réactiver les boutons
280
+ document.getElementById("solveBtn").disabled = false;
281
+ document.getElementById("fastSolveBtn").disabled = false;
282
+ };
 
 
 
 
283
 
284
+ // Fermeture de la connexion quand c'est terminé
285
+ eventSource.addEventListener("done", function() {
286
+ eventSource.close();
287
+
288
+ // Réactiver les boutons
289
+ document.getElementById("solveBtn").disabled = false;
290
+ document.getElementById("fastSolveBtn").disabled = false;
291
+ });
292
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  });
294
  </script>
295
  </body>