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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +467 -241
app.py CHANGED
@@ -430,295 +430,521 @@ def list_tasks():
430
  @app.route('/template')
431
  def get_template():
432
  template_content = '''
433
- <html lang="fr">
 
434
  <head>
435
  <meta charset="UTF-8">
436
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
437
- <title>Résolveur Mathématique IMO</title>
438
  <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.4/socket.io.js"></script>
439
  <style>
440
- * { margin: 0; padding: 0; box-sizing: border-box; }
441
- body {
442
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
443
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
  min-height: 100vh;
445
- padding: 20px;
446
- }
447
- .container {
448
- max-width: 1200px;
449
- margin: 0 auto;
450
- background: white;
451
- border-radius: 15px;
452
- box-shadow: 0 20px 40px rgba(0,0,0,0.1);
453
- overflow: hidden;
454
- }
455
- .header {
456
- background: linear-gradient(135deg, #2196F3, #21CBF3);
457
- padding: 30px;
458
- text-align: center;
459
- color: white;
460
  }
461
- .header h1 { font-size: 2.5em; margin-bottom: 10px; }
462
- .header p { font-size: 1.1em; opacity: 0.9; }
463
-
464
- .upload-section {
465
- padding: 40px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  text-align: center;
467
- border-bottom: 1px solid #eee;
468
  }
469
- .upload-box {
470
- border: 3px dashed #ddd;
471
- border-radius: 10px;
472
- padding: 40px;
473
- transition: all 0.3s ease;
 
 
 
 
 
 
 
474
  cursor: pointer;
475
  }
476
- .upload-box:hover { border-color: #2196F3; background: #f8f9ff; }
477
- .upload-box.dragover { border-color: #2196F3; background: #e3f2fd; }
478
-
479
- .btn {
480
- background: linear-gradient(135deg, #2196F3, #21CBF3);
481
  color: white;
482
- padding: 12px 30px;
483
  border: none;
484
- border-radius: 25px;
485
- font-size: 16px;
 
486
  cursor: pointer;
487
- transition: all 0.3s ease;
 
 
 
488
  }
489
- .btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(33,150,243,0.3); }
490
-
491
- .logs-section {
492
- display: flex;
493
- height: 600px;
494
  }
495
- .extracted-text {
496
- flex: 1;
497
- padding: 20px;
498
- border-right: 1px solid #eee;
 
 
 
 
499
  }
500
- .logs-panel {
501
- flex: 1;
502
- padding: 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
  }
504
- .log-container {
505
- height: 500px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  overflow-y: auto;
507
- background: #1e1e1e;
508
- color: #fff;
509
- padding: 15px;
 
 
 
 
 
510
  border-radius: 8px;
 
 
 
511
  font-family: 'Courier New', monospace;
512
- font-size: 13px;
513
  }
 
514
  .log-entry {
515
- margin-bottom: 5px;
516
- padding: 5px;
517
- border-radius: 3px;
518
- }
519
- .log-info { color: #4CAF50; }
520
- .log-success { color: #8BC34A; background: rgba(139,195,74,0.1); }
521
- .log-warning { color: #FF9800; }
522
- .log-error { color: #F44336; background: rgba(244,67,54,0.1); }
523
-
524
- .status-bar {
525
- padding: 20px;
526
- background: #f5f5f5;
527
- text-align: center;
528
  }
529
- .status-badge {
530
- display: inline-block;
531
- padding: 8px 16px;
532
- border-radius: 20px;
533
- font-weight: bold;
534
- text-transform: uppercase;
535
  }
536
- .status-processing { background: #FFC107; color: #333; }
537
- .status-completed { background: #4CAF50; color: white; }
538
- .status-failed { background: #F44336; color: white; }
539
-
540
- .download-section {
541
- padding: 20px;
 
542
  text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
  display: none;
544
  }
545
- .hidden { display: none; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
546
  </style>
547
  </head>
548
  <body>
549
- <div class="container">
550
- <div class="header">
551
- <h1>🧮 Résolveur Mathématique IMO</h1>
552
- <p>Uploadez une image de votre problème mathématique et obtenez une solution rigoureuse</p>
553
- </div>
554
-
555
- <div class="upload-section">
556
- <div class="upload-box" id="uploadBox">
557
- <h3>📁 Glisser-déposer votre image ici</h3>
558
- <p>ou cliquez pour sélectionner un fichier</p>
559
- <input type="file" id="fileInput" accept="image/*" style="display: none;">
560
- <!-- MODIFICATION 1: Suppression du onclick et ajout d'un id -->
561
- <button class="btn" id="uploadButton">
562
- Choisir un fichier
563
- </button>
564
  </div>
565
- </div>
566
-
567
- <div class="status-bar">
568
- <div id="statusBadge" class="status-badge" style="display: none;">En attente</div>
569
- <div id="taskInfo" style="margin-top: 10px; display: none;"></div>
570
- </div>
571
-
572
- <div class="logs-section hidden" id="logsSection">
573
- <div class="extracted-text">
574
- <h3>📝 Texte extrait de l'image</h3>
575
- <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>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
  </div>
577
- <div class="logs-panel">
578
- <h3>📊 Logs de traitement en temps réel</h3>
579
- <div id="logContainer" class="log-container"></div>
 
 
580
  </div>
581
- </div>
582
-
583
- <div class="download-section" id="downloadSection">
584
- <h3>✅ Solution prête !</h3>
585
- <button class="btn" id="downloadBtn">📥 Télécharger la solution</button>
586
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  </div>
 
588
  <script>
589
- const socket = io();
590
- let currentTaskId = null;
591
-
592
- // Gestion de l'upload
593
- const uploadBox = document.getElementById('uploadBox');
594
- const fileInput = document.getElementById('fileInput');
595
- // MODIFICATION 2: Sélection du bouton par son nouvel id
596
- const uploadButton = document.getElementById('uploadButton');
597
-
598
- // L'utilisateur peut cliquer sur toute la zone
599
- uploadBox.addEventListener('click', () => fileInput.click());
600
-
601
- // MODIFICATION 3: Ajout d'un écouteur dédié au bouton
602
- uploadButton.addEventListener('click', (e) => {
603
- // Empêche le clic de "remonter" à la div parente (uploadBox),
604
- // ce qui évite de déclencher l'ouverture de la fenêtre deux fois.
605
- e.stopPropagation();
606
- fileInput.click();
607
- });
608
-
609
- // Gestion du glisser-déposer (Drag & Drop)
610
- uploadBox.addEventListener('dragover', (e) => {
611
- e.preventDefault();
612
- uploadBox.classList.add('dragover');
613
- });
614
- uploadBox.addEventListener('dragleave', () => {
615
- uploadBox.classList.remove('dragover');
616
- });
617
- uploadBox.addEventListener('drop', (e) => {
618
- e.preventDefault();
619
- uploadBox.classList.remove('dragover');
620
- const files = e.dataTransfer.files;
621
- if (files.length > 0) {
622
- uploadFile(files[0]);
623
  }
624
- });
625
-
626
- // Gestion de la sélection de fichier
627
- fileInput.addEventListener('change', (e) => {
628
- if (e.target.files.length > 0) {
629
- uploadFile(e.target.files[0]);
 
 
 
 
 
 
630
  }
631
- });
632
-
633
- function uploadFile(file) {
634
- const formData = new FormData();
635
- formData.append('file', file);
636
-
637
- updateStatus('processing', 'Upload en cours...');
638
-
639
- fetch('/upload', {
640
- method: 'POST',
641
- body: formData
642
- })
643
- .then(response => response.json())
644
- .then(data => {
645
- if (data.error) {
646
- updateStatus('failed', data.error);
647
- } else {
648
- currentTaskId = data.task_id;
649
- document.getElementById('extractedText').textContent = data.extracted_text;
650
- document.getElementById('logsSection').classList.remove('hidden');
651
- updateStatus('processing', 'Résolution en cours...');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
652
  }
653
- })
654
- .catch(error => {
655
- updateStatus('failed', 'Erreur lors de l\'upload');
656
- console.error('Error:', error);
657
- });
658
- }
659
-
660
- function updateStatus(status, message) {
661
- const badge = document.getElementById('statusBadge');
662
- const info = document.getElementById('taskInfo');
663
-
664
- badge.style.display = 'inline-block';
665
- badge.className = `status-badge status-${status}`;
666
- badge.textContent = status === 'processing' ? 'En cours' :
667
- status === 'completed' ? 'Terminé' : 'Échec';
668
-
669
- info.style.display = 'block';
670
- info.textContent = message;
671
- }
672
-
673
- function addLog(timestamp, level, message) {
674
- const container = document.getElementById('logContainer');
675
- const logEntry = document.createElement('div');
676
- logEntry.className = `log-entry log-${level}`;
677
- logEntry.innerHTML = `<span style="color: #666;">[${timestamp}]</span> ${message}`;
678
- container.appendChild(logEntry);
679
- container.scrollTop = container.scrollHeight;
680
- }
681
-
682
- // WebSocket events
683
- socket.on('log_update', (data) => {
684
- if (data.task_id === currentTaskId) {
685
- addLog(data.log.timestamp, data.log.level, data.log.message);
686
  }
687
- });
688
-
689
- socket.on('task_completed', (data) => {
690
- if (data.task_id === currentTaskId) {
691
- updateStatus('completed', 'Solution générée avec succès !');
692
- const downloadSection = document.getElementById('downloadSection');
693
- downloadSection.style.display = 'block';
694
-
695
- document.getElementById('downloadBtn').onclick = () => {
696
- window.location.href = `/download/${currentTaskId}`;
697
- };
698
  }
699
- });
700
-
701
- socket.on('task_failed', (data) => {
702
- if (data.task_id === currentTaskId) {
703
- updateStatus('failed', 'Échec de la résolution (solution partielle disponible)');
704
- const downloadSection = document.getElementById('downloadSection');
705
- downloadSection.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706
 
707
- document.getElementById('downloadBtn').onclick = () => {
708
- window.location.href = `/download/${currentTaskId}`;
709
- };
710
  }
711
- });
712
-
713
- socket.on('task_error', (data) => {
714
- if (data.task_id === currentTaskId) {
715
- updateStatus('failed', `Erreur: ${data.error}`);
716
  }
 
 
 
 
 
717
  });
718
  </script>
719
  </body>
720
  </html>
721
-
 
722
  '''
723
  return template_content
724
 
 
430
  @app.route('/template')
431
  def get_template():
432
  template_content = '''
433
+ <!DOCTYPE html>
434
+ <html lang="fr">
435
  <head>
436
  <meta charset="UTF-8">
437
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
438
+ <title>Résolveur Mathématique IMO - Version 2</title>
439
  <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.4/socket.io.js"></script>
440
  <style>
441
+ :root {
442
+ --primary: #6366f1;
443
+ --primary-dark: #4f46e5;
444
+ --success: #10b981;
445
+ --warning: #f59e0b;
446
+ --error: #ef4444;
447
+ --bg-light: #f8fafc;
448
+ --text-dark: #1e293b;
449
+ --border: #e2e8f0;
450
+ }
451
+
452
+ * {
453
+ margin: 0;
454
+ padding: 0;
455
+ box-sizing: border-box;
456
+ }
457
+
458
+ body {
459
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
460
+ background: var(--bg-light);
461
+ color: var(--text-dark);
462
+ line-height: 1.6;
463
+ }
464
+
465
+ .app-container {
466
  min-height: 100vh;
467
+ display: flex;
468
+ flex-direction: column;
 
 
 
 
 
 
 
 
 
 
 
 
 
469
  }
470
+
471
+ .navbar {
472
+ background: white;
473
+ border-bottom: 1px solid var(--border);
474
+ padding: 1rem 0;
475
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
476
+ }
477
+
478
+ .nav-content {
479
+ max-width: 1200px;
480
+ margin: 0 auto;
481
+ padding: 0 2rem;
482
+ display: flex;
483
+ align-items: center;
484
+ gap: 1rem;
485
+ }
486
+
487
+ .logo {
488
+ font-size: 1.5rem;
489
+ font-weight: bold;
490
+ color: var(--primary);
491
+ }
492
+
493
+ .main-content {
494
+ flex: 1;
495
+ max-width: 1200px;
496
+ margin: 0 auto;
497
+ padding: 2rem;
498
+ width: 100%;
499
+ }
500
+
501
+ .card {
502
+ background: white;
503
+ border-radius: 12px;
504
+ padding: 2rem;
505
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
506
+ border: 1px solid var(--border);
507
+ margin-bottom: 2rem;
508
+ }
509
+
510
+ .upload-area {
511
  text-align: center;
512
+ padding: 3rem 2rem;
513
  }
514
+
515
+ .file-input-wrapper {
516
+ position: relative;
517
+ display: inline-block;
518
+ margin: 2rem 0;
519
+ }
520
+
521
+ .file-input {
522
+ position: absolute;
523
+ opacity: 0;
524
+ width: 100%;
525
+ height: 100%;
526
  cursor: pointer;
527
  }
528
+
529
+ .upload-btn {
530
+ background: var(--primary);
 
 
531
  color: white;
532
+ padding: 1rem 2rem;
533
  border: none;
534
+ border-radius: 8px;
535
+ font-size: 1rem;
536
+ font-weight: 500;
537
  cursor: pointer;
538
+ transition: all 0.2s;
539
+ display: inline-flex;
540
+ align-items: center;
541
+ gap: 0.5rem;
542
  }
543
+
544
+ .upload-btn:hover {
545
+ background: var(--primary-dark);
546
+ transform: translateY(-1px);
 
547
  }
548
+
549
+ .drop-zone {
550
+ border: 2px dashed var(--border);
551
+ border-radius: 12px;
552
+ padding: 3rem;
553
+ margin: 2rem 0;
554
+ transition: all 0.3s;
555
+ cursor: pointer;
556
  }
557
+
558
+ .drop-zone:hover {
559
+ border-color: var(--primary);
560
+ background: rgba(99, 102, 241, 0.05);
561
+ }
562
+
563
+ .drop-zone.active {
564
+ border-color: var(--primary);
565
+ background: rgba(99, 102, 241, 0.1);
566
+ }
567
+
568
+ .status-indicator {
569
+ display: inline-flex;
570
+ align-items: center;
571
+ gap: 0.5rem;
572
+ padding: 0.5rem 1rem;
573
+ border-radius: 6px;
574
+ font-weight: 500;
575
+ margin-bottom: 1rem;
576
+ }
577
+
578
+ .status-processing {
579
+ background: rgba(245, 158, 11, 0.1);
580
+ color: var(--warning);
581
+ }
582
+
583
+ .status-success {
584
+ background: rgba(16, 185, 129, 0.1);
585
+ color: var(--success);
586
  }
587
+
588
+ .status-error {
589
+ background: rgba(239, 68, 68, 0.1);
590
+ color: var(--error);
591
+ }
592
+
593
+ .content-grid {
594
+ display: grid;
595
+ grid-template-columns: 1fr 1fr;
596
+ gap: 2rem;
597
+ margin-top: 2rem;
598
+ }
599
+
600
+ .text-preview {
601
+ background: #f1f5f9;
602
+ border-radius: 8px;
603
+ padding: 1.5rem;
604
+ max-height: 400px;
605
  overflow-y: auto;
606
+ white-space: pre-wrap;
607
+ font-family: 'Courier New', monospace;
608
+ font-size: 0.9rem;
609
+ }
610
+
611
+ .logs {
612
+ background: #0f172a;
613
+ color: #e2e8f0;
614
  border-radius: 8px;
615
+ padding: 1rem;
616
+ max-height: 400px;
617
+ overflow-y: auto;
618
  font-family: 'Courier New', monospace;
619
+ font-size: 0.85rem;
620
  }
621
+
622
  .log-entry {
623
+ padding: 0.25rem 0;
624
+ border-bottom: 1px solid rgba(255,255,255,0.1);
 
 
 
 
 
 
 
 
 
 
 
625
  }
626
+
627
+ .log-timestamp {
628
+ color: #64748b;
 
 
 
629
  }
630
+
631
+ .log-info { color: #3b82f6; }
632
+ .log-success { color: var(--success); }
633
+ .log-warning { color: var(--warning); }
634
+ .log-error { color: var(--error); }
635
+
636
+ .download-area {
637
  text-align: center;
638
+ padding: 2rem;
639
+ background: rgba(16, 185, 129, 0.05);
640
+ border-radius: 12px;
641
+ border: 1px solid rgba(16, 185, 129, 0.2);
642
+ }
643
+
644
+ .download-btn {
645
+ background: var(--success);
646
+ color: white;
647
+ padding: 1rem 2rem;
648
+ border: none;
649
+ border-radius: 8px;
650
+ font-size: 1rem;
651
+ cursor: pointer;
652
+ text-decoration: none;
653
+ display: inline-flex;
654
+ align-items: center;
655
+ gap: 0.5rem;
656
+ transition: all 0.2s;
657
+ }
658
+
659
+ .download-btn:hover {
660
+ background: #059669;
661
+ }
662
+
663
+ .hidden {
664
  display: none;
665
  }
666
+
667
+ .spinner {
668
+ width: 20px;
669
+ height: 20px;
670
+ border: 2px solid transparent;
671
+ border-top: 2px solid currentColor;
672
+ border-radius: 50%;
673
+ animation: spin 1s linear infinite;
674
+ }
675
+
676
+ @keyframes spin {
677
+ to { transform: rotate(360deg); }
678
+ }
679
+
680
+ @media (max-width: 768px) {
681
+ .content-grid {
682
+ grid-template-columns: 1fr;
683
+ }
684
+ .main-content {
685
+ padding: 1rem;
686
+ }
687
+ }
688
  </style>
689
  </head>
690
  <body>
691
+ <div class="app-container">
692
+ <nav class="navbar">
693
+ <div class="nav-content">
694
+ <div class="logo">🧮 Math Solver IMO</div>
695
+ <div style="margin-left: auto;">
696
+ <span style="font-size: 0.9rem; color: #64748b;">Résolveur de problèmes mathématiques</span>
697
+ </div>
 
 
 
 
 
 
 
 
698
  </div>
699
+ </nav>
700
+
701
+ <main class="main-content">
702
+ <!-- Section Upload -->
703
+ <div class="card">
704
+ <div class="upload-area">
705
+ <h2 style="margin-bottom: 1rem;">📸 Uploader votre problème mathématique</h2>
706
+ <p style="color: #64748b; margin-bottom: 2rem;">
707
+ Prenez une photo ou uploadez une image de votre problème mathématique
708
+ </p>
709
+
710
+ <!-- Zone de drop -->
711
+ <div class="drop-zone" id="dropZone">
712
+ <div>
713
+ <div style="font-size: 3rem; margin-bottom: 1rem;">📁</div>
714
+ <h3>Glissez votre image ici</h3>
715
+ <p style="color: #64748b; margin: 1rem 0;">ou</p>
716
+
717
+ <div class="file-input-wrapper">
718
+ <input type="file" class="file-input" id="fileInput" accept="image/*">
719
+ <button class="upload-btn">
720
+ <span>📷</span>
721
+ Choisir une image
722
+ </button>
723
+ </div>
724
+ </div>
725
+ </div>
726
+
727
+ <p style="font-size: 0.85rem; color: #64748b;">
728
+ Formats supportés: PNG, JPG, JPEG, GIF, BMP, TIFF
729
+ </p>
730
+ </div>
731
  </div>
732
+
733
+ <!-- Section Status -->
734
+ <div class="card" id="statusCard" style="display: none;">
735
+ <div id="statusIndicator"></div>
736
+ <div id="statusMessage"></div>
737
  </div>
738
+
739
+ <!-- Section Contenu -->
740
+ <div class="content-grid" id="contentGrid" style="display: none;">
741
+ <div class="card">
742
+ <h3 style="margin-bottom: 1rem;">📝 Texte extrait</h3>
743
+ <div class="text-preview" id="extractedText"></div>
744
+ </div>
745
+
746
+ <div class="card">
747
+ <h3 style="margin-bottom: 1rem;">📊 Logs en temps réel</h3>
748
+ <div class="logs" id="logsContainer"></div>
749
+ </div>
750
+ </div>
751
+
752
+ <!-- Section Download -->
753
+ <div class="card hidden" id="downloadCard">
754
+ <div class="download-area">
755
+ <h3 style="margin-bottom: 1rem;">✅ Solution prête !</h3>
756
+ <p style="margin-bottom: 2rem; color: #64748b;">
757
+ Votre problème mathématique a été résolu avec succès
758
+ </p>
759
+ <a class="download-btn" id="downloadLink">
760
+ <span>📥</span>
761
+ Télécharger la solution
762
+ </a>
763
+ </div>
764
+ </div>
765
+ </main>
766
  </div>
767
+
768
  <script>
769
+ class MathSolverApp {
770
+ constructor() {
771
+ this.socket = io();
772
+ this.currentTaskId = null;
773
+ this.initializeElements();
774
+ this.setupEventListeners();
775
+ this.setupSocketEvents();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
776
  }
777
+
778
+ initializeElements() {
779
+ this.dropZone = document.getElementById('dropZone');
780
+ this.fileInput = document.getElementById('fileInput');
781
+ this.statusCard = document.getElementById('statusCard');
782
+ this.statusIndicator = document.getElementById('statusIndicator');
783
+ this.statusMessage = document.getElementById('statusMessage');
784
+ this.contentGrid = document.getElementById('contentGrid');
785
+ this.extractedText = document.getElementById('extractedText');
786
+ this.logsContainer = document.getElementById('logsContainer');
787
+ this.downloadCard = document.getElementById('downloadCard');
788
+ this.downloadLink = document.getElementById('downloadLink');
789
  }
790
+
791
+ setupEventListeners() {
792
+ // File input change
793
+ this.fileInput.addEventListener('change', (e) => {
794
+ if (e.target.files.length > 0) {
795
+ this.handleFile(e.target.files[0]);
796
+ }
797
+ });
798
+
799
+ // Drop zone events
800
+ this.dropZone.addEventListener('dragover', (e) => {
801
+ e.preventDefault();
802
+ this.dropZone.classList.add('active');
803
+ });
804
+
805
+ this.dropZone.addEventListener('dragleave', (e) => {
806
+ e.preventDefault();
807
+ this.dropZone.classList.remove('active');
808
+ });
809
+
810
+ this.dropZone.addEventListener('drop', (e) => {
811
+ e.preventDefault();
812
+ this.dropZone.classList.remove('active');
813
+
814
+ const files = e.dataTransfer.files;
815
+ if (files.length > 0) {
816
+ this.handleFile(files[0]);
817
+ }
818
+ });
819
+
820
+ // Click on drop zone
821
+ this.dropZone.addEventListener('click', () => {
822
+ this.fileInput.click();
823
+ });
824
+ }
825
+
826
+ setupSocketEvents() {
827
+ this.socket.on('log_update', (data) => {
828
+ if (data.task_id === this.currentTaskId) {
829
+ this.addLog(data.log);
830
+ }
831
+ });
832
+
833
+ this.socket.on('task_completed', (data) => {
834
+ if (data.task_id === this.currentTaskId) {
835
+ this.showSuccess('Solution générée avec succès !');
836
+ this.showDownload();
837
+ }
838
+ });
839
+
840
+ this.socket.on('task_failed', (data) => {
841
+ if (data.task_id === this.currentTaskId) {
842
+ this.showError('Échec de la résolution (solution partielle disponible)');
843
+ this.showDownload();
844
+ }
845
+ });
846
+
847
+ this.socket.on('task_error', (data) => {
848
+ if (data.task_id === this.currentTaskId) {
849
+ this.showError(`Erreur: ${data.error}`);
850
+ }
851
+ });
852
+ }
853
+
854
+ async handleFile(file) {
855
+ if (!this.isValidFile(file)) {
856
+ this.showError('Type de fichier non supporté');
857
+ return;
858
+ }
859
+
860
+ const formData = new FormData();
861
+ formData.append('file', file);
862
+
863
+ this.showProcessing('Upload en cours...');
864
+
865
+ try {
866
+ const response = await fetch('/upload', {
867
+ method: 'POST',
868
+ body: formData
869
+ });
870
+
871
+ const data = await response.json();
872
+
873
+ if (data.error) {
874
+ this.showError(data.error);
875
+ } else {
876
+ this.currentTaskId = data.task_id;
877
+ this.extractedText.textContent = data.extracted_text;
878
+ this.contentGrid.style.display = 'grid';
879
+ this.showProcessing('Résolution en cours...');
880
+ }
881
+ } catch (error) {
882
+ this.showError('Erreur lors de l\'upload');
883
+ console.error('Upload error:', error);
884
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
885
  }
886
+
887
+ isValidFile(file) {
888
+ const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif', 'image/bmp', 'image/tiff'];
889
+ return allowedTypes.includes(file.type);
 
 
 
 
 
 
 
890
  }
891
+
892
+ showProcessing(message) {
893
+ this.statusCard.style.display = 'block';
894
+ this.statusIndicator.innerHTML = `
895
+ <div class="status-indicator status-processing">
896
+ <div class="spinner"></div>
897
+ En cours
898
+ </div>
899
+ `;
900
+ this.statusMessage.textContent = message;
901
+ }
902
+
903
+ showSuccess(message) {
904
+ this.statusIndicator.innerHTML = `
905
+ <div class="status-indicator status-success">
906
+ ✅ Terminé
907
+ </div>
908
+ `;
909
+ this.statusMessage.textContent = message;
910
+ }
911
+
912
+ showError(message) {
913
+ this.statusIndicator.innerHTML = `
914
+ <div class="status-indicator status-error">
915
+ ❌ Erreur
916
+ </div>
917
+ `;
918
+ this.statusMessage.textContent = message;
919
+ }
920
+
921
+ addLog(log) {
922
+ const logEntry = document.createElement('div');
923
+ logEntry.className = 'log-entry';
924
+ logEntry.innerHTML = `
925
+ <span class="log-timestamp">[${log.timestamp}]</span>
926
+ <span class="log-${log.level}">${log.message}</span>
927
+ `;
928
 
929
+ this.logsContainer.appendChild(logEntry);
930
+ this.logsContainer.scrollTop = this.logsContainer.scrollHeight;
 
931
  }
932
+
933
+ showDownload() {
934
+ this.downloadCard.classList.remove('hidden');
935
+ this.downloadLink.href = `/download/${this.currentTaskId}`;
 
936
  }
937
+ }
938
+
939
+ // Initialize app when DOM is loaded
940
+ document.addEventListener('DOMContentLoaded', () => {
941
+ new MathSolverApp();
942
  });
943
  </script>
944
  </body>
945
  </html>
946
+
947
+
948
  '''
949
  return template_content
950