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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +293 -414
app.py CHANGED
@@ -435,18 +435,27 @@ def get_template():
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
  * {
@@ -456,489 +465,359 @@ def get_template():
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>
 
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 IA</title>
439
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.5/socket.io.min.js"></script>
440
+ <link rel="preconnect" href="https://fonts.googleapis.com">
441
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
442
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
443
  <style>
444
+ /* --- Reset & Variables --- */
445
  :root {
446
+ --bg-color: #f4f7fa;
447
+ --main-color: #ffffff;
448
+ --primary-color: #3b82f6;
449
+ --primary-hover: #2563eb;
450
+ --text-color: #1f2937;
451
+ --text-light: #6b7280;
452
+ --border-color: #e5e7eb;
453
+ --dark-bg: #111827;
454
+ --success-color: #10b981;
455
+ --warning-color: #f59e0b;
456
+ --error-color: #ef4444;
457
+ --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
458
+ --border-radius: 0.75rem;
459
  }
460
 
461
  * {
 
465
  }
466
 
467
  body {
468
+ font-family: 'Inter', sans-serif;
469
+ background-color: var(--bg-color);
470
+ color: var(--text-color);
471
  line-height: 1.6;
472
+ padding: 2rem;
473
  }
474
 
475
+ /* --- Layout & Main Container --- */
476
+ .container {
 
 
 
 
 
 
 
 
 
 
 
 
477
  max-width: 1200px;
478
  margin: 0 auto;
479
+ background-color: var(--main-color);
480
+ border-radius: var(--border-radius);
481
+ box-shadow: var(--shadow);
482
+ overflow: hidden;
483
  }
484
 
485
+ /* --- Header --- */
486
+ .header {
487
+ padding: 2.5rem;
488
+ text-align: center;
489
+ border-bottom: 1px solid var(--border-color);
490
+ background: linear-gradient(to top, #ffffff, #f9fafb);
491
  }
492
+ .header h1 {
493
+ font-size: 2.25rem;
494
+ font-weight: 700;
495
+ letter-spacing: -0.025em;
496
+ color: var(--text-color);
497
+ }
498
+ .header h1 .icon {
499
+ color: var(--primary-color);
500
+ }
501
+ .header p {
502
+ margin-top: 0.5rem;
503
+ color: var(--text-light);
504
+ font-size: 1.1rem;
505
+ max-width: 600px;
506
+ margin-left: auto;
507
+ margin-right: auto;
508
  }
509
 
510
+ /* --- Upload Section --- */
511
+ .main-content {
512
+ padding: 2.5rem;
 
 
 
 
513
  }
514
 
515
  .upload-area {
516
+ border: 2px dashed var(--border-color);
517
+ border-radius: var(--border-radius);
518
+ padding: 3rem;
519
  text-align: center;
520
+ background-color: #fcfdff;
521
+ transition: all 0.3s ease;
522
  }
523
+ .upload-area.dragover {
524
+ border-color: var(--primary-color);
525
+ background-color: #eff6ff;
 
 
526
  }
527
+ .upload-icon {
528
+ font-size: 3rem;
529
+ color: var(--primary-color);
530
+ margin-bottom: 1rem;
531
+ }
532
+ .upload-area p {
533
+ color: var(--text-light);
534
+ margin-bottom: 1.5rem;
535
  }
536
 
537
+ /* --- Main Button --- */
538
+ .btn {
539
+ display: inline-block;
540
+ background-color: var(--primary-color);
541
  color: white;
542
+ padding: 0.8rem 2rem;
543
  border: none;
544
+ border-radius: 9999px; /* pill shape */
545
  font-size: 1rem;
546
  font-weight: 500;
547
  cursor: pointer;
548
+ text-decoration: none;
549
+ transition: background-color 0.2s ease, transform 0.2s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
  }
551
+ .btn:hover {
552
+ background-color: var(--primary-hover);
553
+ transform: translateY(-2px);
 
554
  }
555
 
556
+ /* --- Results & Logs Section --- */
557
+ .results-grid {
558
+ margin-top: 2.5rem;
559
  display: grid;
560
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
561
  gap: 2rem;
 
562
  }
563
 
564
+ .panel {
565
+ background-color: #f9fafb;
566
+ border: 1px solid var(--border-color);
567
+ border-radius: var(--border-radius);
568
  padding: 1.5rem;
569
+ }
570
+ .panel h3 {
571
+ font-size: 1.25rem;
572
+ font-weight: 600;
573
+ margin-bottom: 1rem;
574
+ border-bottom: 1px solid var(--border-color);
575
+ padding-bottom: 0.75rem;
576
+ }
577
+
578
+ #extractedText {
579
  white-space: pre-wrap;
580
+ word-wrap: break-word;
581
  font-family: 'Courier New', monospace;
582
+ background-color: var(--main-color);
583
+ padding: 1rem;
584
+ border-radius: 0.5rem;
585
+ max-height: 450px;
586
+ overflow-y: auto;
587
+ color: var(--text-light);
588
  }
589
 
590
+ /* --- Logs --- */
591
+ #logContainer {
592
+ height: 450px;
 
 
 
593
  overflow-y: auto;
594
+ background: var(--dark-bg);
595
+ color: #d1d5db;
596
+ padding: 1rem;
597
+ border-radius: 0.5rem;
598
  font-family: 'Courier New', monospace;
599
+ font-size: 0.875rem;
600
  }
 
601
  .log-entry {
602
+ margin-bottom: 0.25rem;
603
+ padding: 0.2rem 0.5rem;
604
+ border-radius: 3px;
 
 
 
605
  }
606
+ .log-timestamp { color: #9ca3af; margin-right: 0.5rem; }
607
  .log-info { color: #3b82f6; }
608
+ .log-success { color: #10b981; }
609
+ .log-warning { color: #f59e0b; }
610
+ .log-error { color: #ef4444; font-weight: bold; }
611
+
612
+ /* --- Status Bar & Download --- */
613
+ #status-section {
614
+ padding: 1.5rem 2.5rem;
615
+ background-color: #f9fafb;
616
+ border-top: 1px solid var(--border-color);
617
  text-align: center;
 
 
 
 
618
  }
619
+ .status-badge {
 
 
 
 
 
 
 
 
 
620
  display: inline-flex;
621
  align-items: center;
622
  gap: 0.5rem;
623
+ padding: 0.5rem 1.25rem;
624
+ border-radius: 9999px;
625
+ font-weight: 500;
626
  }
627
+ .status-processing { background-color: #fef3c7; color: #92400e; }
628
+ .status-completed { background-color: #d1fae5; color: #065f46; }
629
+ .status-failed { background-color: #fee2e2; color: #991b1b; }
630
 
631
+ #downloadSection {
632
+ margin-top: 1rem;
633
  }
634
+
635
  .hidden {
636
  display: none;
637
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
  </style>
639
  </head>
640
  <body>
641
+ <div class="container">
642
+ <header class="header">
643
+ <h1><span class="icon">🧮</span> Résolveur Mathématique IA</h1>
644
+ <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>
645
+ </header>
 
 
 
 
646
 
647
  <main class="main-content">
648
+ <!-- Section d'upload corrigée -->
649
+ <section id="upload-section">
650
+ <div class="upload-area" id="uploadArea">
651
+ <div class="upload-icon">📄</div>
652
+ <p>Glissez-déposez votre image ici, ou cliquez sur le bouton pour la sélectionner.</p>
653
+ <!-- Ce bouton est maintenant le point d'interaction principal pour le clic -->
654
+ <button type="button" class="btn" id="selectFileBtn">Choisir un fichier</button>
655
+ <!-- L'input de fichier est caché et sera activé par le JS -->
656
+ <input type="file" id="fileInput" accept="image/*" class="hidden">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
657
  </div>
658
+ </section>
659
+
660
+ <!-- Section pour afficher le statut global, cachée au début -->
661
+ <section id="status-section" class="hidden">
662
+ <div id="statusBadge" class="status-badge"></div>
663
+ <div id="downloadSection" class="hidden">
664
+ <a href="#" id="downloadBtn" class="btn">📥 Télécharger la solution</a>
 
 
 
 
 
 
665
  </div>
666
+ </section>
667
+
668
+ <!-- Grille pour les résultats, cachée au début -->
669
+ <section id="results-grid" class="results-grid hidden">
670
+ <div class="panel">
671
+ <h3>📝 Texte extrait de l'image</h3>
672
+ <div id="extractedText">Le texte de votre image apparaîtra ici...</div>
673
  </div>
674
+ <div class="panel">
675
+ <h3>📊 Journal de traitement</h3>
676
+ <div id="logContainer"></div>
 
 
 
 
 
 
 
 
 
 
677
  </div>
678
+ </section>
679
+
680
  </main>
681
  </div>
682
 
683
  <script>
684
+ const socket = io();
685
+ let currentTaskId = null;
686
+
687
+ // --- DOM Elements ---
688
+ const uploadArea = document.getElementById('uploadArea');
689
+ const fileInput = document.getElementById('fileInput');
690
+ const selectFileBtn = document.getElementById('selectFileBtn');
691
+ const statusSection = document.getElementById('status-section');
692
+ const statusBadge = document.getElementById('statusBadge');
693
+ const resultsGrid = document.getElementById('results-grid');
694
+ const extractedTextEl = document.getElementById('extractedText');
695
+ const logContainer = document.getElementById('logContainer');
696
+ const downloadSection = document.getElementById('downloadSection');
697
+ const downloadBtn = document.getElementById('downloadBtn');
698
+
699
+ // --- Event Listeners ---
 
 
 
 
 
700
 
701
+ // CORRECTION : Le bouton déclenche l'input de fichier
702
+ selectFileBtn.addEventListener('click', () => {
703
+ fileInput.click();
704
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
 
706
+ // L'input de fichier, une fois un fichier choisi, lance l'upload
707
+ fileInput.addEventListener('change', (e) => {
708
+ if (e.target.files.length > 0) {
709
+ handleFileUpload(e.target.files[0]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
710
  }
711
+ });
712
 
713
+ // Gestion du glisser-déposer (Drag & Drop)
714
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
715
+ uploadArea.addEventListener(eventName, preventDefaults, false);
716
+ });
717
+ function preventDefaults(e) {
718
+ e.preventDefault();
719
+ e.stopPropagation();
720
+ }
721
 
722
+ ['dragenter', 'dragover'].forEach(eventName => {
723
+ uploadArea.addEventListener(eventName, () => uploadArea.classList.add('dragover'), false);
724
+ });
725
+ ['dragleave', 'drop'].forEach(eventName => {
726
+ uploadArea.addEventListener(eventName, () => uploadArea.classList.remove('dragover'), false);
727
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
728
 
729
+ uploadArea.addEventListener('drop', (e) => {
730
+ const files = e.dataTransfer.files;
731
+ if (files.length > 0) {
732
+ handleFileUpload(files[0]);
733
  }
734
+ });
735
 
736
+ // --- Core Functions ---
 
 
 
 
 
 
 
 
 
737
 
738
+ function resetUI() {
739
+ statusSection.classList.add('hidden');
740
+ resultsGrid.classList.add('hidden');
741
+ downloadSection.classList.add('hidden');
742
+ logContainer.innerHTML = '';
743
+ extractedTextEl.textContent = 'Le texte de votre image apparaîtra ici...';
744
+ currentTaskId = null;
745
+ }
746
 
747
+ function handleFileUpload(file) {
748
+ resetUI();
749
+ const formData = new FormData();
750
+ formData.append('file', file);
751
+
752
+ updateStatus('processing', '🚀 Envoi et analyse de l\'image...');
753
+ statusSection.classList.remove('hidden');
 
754
 
755
+ fetch('/upload', {
756
+ method: 'POST',
757
+ body: formData
758
+ })
759
+ .then(response => {
760
+ if (!response.ok) {
761
+ throw new Error(`Erreur serveur: ${response.statusText}`);
762
+ }
763
+ return response.json();
764
+ })
765
+ .then(data => {
766
+ if (data.error) {
767
+ throw new Error(data.error);
768
+ }
769
+ currentTaskId = data.task_id;
770
+ extractedTextEl.textContent = data.extracted_text;
771
+ resultsGrid.classList.remove('hidden');
772
+ updateStatus('processing', '🧠 Résolution en cours...');
773
+ })
774
+ .catch(error => {
775
+ updateStatus('failed', `❌ Erreur critique : ${error.message}`);
776
+ console.error('Upload Error:', error);
777
+ });
778
+ }
779
 
780
+ function updateStatus(status, message) {
781
+ statusBadge.className = `status-badge status-${status}`;
782
+ statusBadge.textContent = message;
783
+ }
784
+
785
+ function addLog(timestamp, level, message) {
786
+ const logEntry = document.createElement('div');
787
+ logEntry.className = `log-entry log-${level}`;
788
+ logEntry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span> ${message}`;
789
+ logContainer.appendChild(logEntry);
790
+ logContainer.scrollTop = logContainer.scrollHeight;
791
  }
792
+
793
+ // --- WebSocket Event Handlers ---
794
 
795
+ socket.on('log_update', (data) => {
796
+ if (data.task_id === currentTaskId) {
797
+ addLog(data.log.timestamp, data.log.level, data.log.message);
798
+ }
799
+ });
800
+
801
+ socket.on('task_completed', (data) => {
802
+ if (data.task_id === currentTaskId) {
803
+ updateStatus('completed', '✅ Solution générée avec succès !');
804
+ downloadSection.classList.remove('hidden');
805
+ downloadBtn.href = `/download/${currentTaskId}`;
806
+ }
807
+ });
808
+
809
+ socket.on('task_failed', (data) => {
810
+ if (data.task_id === currentTaskId) {
811
+ updateStatus('failed', '⚠️ Échec de la résolution (solution partielle disponible)');
812
+ downloadSection.classList.remove('hidden');
813
+ downloadBtn.href = `/download/${currentTaskId}`;
814
+ }
815
+ });
816
+
817
+ socket.on('task_error', (data) => {
818
+ if (data.task_id === currentTaskId) {
819
+ updateStatus('failed', `❌ Erreur système: ${data.error}`);
820
+ }
821
  });
822
  </script>
823
  </body>