Spaces:
Paused
Paused
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Résolveur d'Images</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/contrib/auto-render.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css"> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| line-height: 1.6; | |
| } | |
| h1 { | |
| text-align: center; | |
| color: #333; | |
| } | |
| .container { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 20px; | |
| } | |
| .upload-section { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 20px; | |
| border: 2px dashed #ccc; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| } | |
| .upload-section:hover { | |
| border-color: #888; | |
| } | |
| #file-input { | |
| display: none; | |
| } | |
| .preview-container { | |
| width: 100%; | |
| text-align: center; | |
| margin-top: 10px; | |
| } | |
| #image-preview { | |
| max-width: 100%; | |
| max-height: 300px; | |
| display: none; | |
| } | |
| #solving-container { | |
| display: none; | |
| background-color: #f5f5f5; | |
| padding: 20px; | |
| border-radius: 8px; | |
| } | |
| .response-container { | |
| margin-top: 20px; | |
| padding: 20px; | |
| border: 1px solid #ddd; | |
| border-radius: 8px; | |
| background-color: #fff; | |
| display: none; | |
| } | |
| .thinking { | |
| color: #777; | |
| font-style: italic; | |
| } | |
| .button { | |
| background-color: #4CAF50; | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| text-align: center; | |
| text-decoration: none; | |
| display: inline-block; | |
| font-size: 16px; | |
| margin: 10px 2px; | |
| cursor: pointer; | |
| border-radius: 4px; | |
| transition: background-color 0.3s; | |
| } | |
| .button:hover { | |
| background-color: #45a049; | |
| } | |
| .button:disabled { | |
| background-color: #cccccc; | |
| cursor: not-allowed; | |
| } | |
| .copy-button { | |
| background-color: #2196F3; | |
| } | |
| .copy-button:hover { | |
| background-color: #0b7dda; | |
| } | |
| .telegram-notice { | |
| background-color: #e3f2fd; | |
| border-left: 4px solid #2196F3; | |
| padding: 10px; | |
| margin: 15px 0; | |
| font-size: 14px; | |
| } | |
| .loading { | |
| text-align: center; | |
| font-style: italic; | |
| margin: 10px 0; | |
| } | |
| .status { | |
| text-align: center; | |
| margin-bottom: 10px; | |
| font-weight: bold; | |
| } | |
| .status small { | |
| font-weight: normal; | |
| color: #666; | |
| font-size: 0.85em; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Résolveur d'Images</h1> | |
| <div class="container"> | |
| <div id="upload-section" class="upload-section"> | |
| <p>Cliquez ou glissez-déposez une image ici</p> | |
| <input type="file" id="file-input" accept="image/*"> | |
| <div class="preview-container"> | |
| <img id="image-preview" src="#" alt="Aperçu de l'image"> | |
| </div> | |
| </div> | |
| <button id="solve-button" class="button" disabled>Résoudre</button> | |
| <div id="solving-container"> | |
| <div class="status" id="status">En attente de résolution...</div> | |
| <div class="telegram-notice"> | |
| La réponse complète sera également envoyée sous forme de fichier texte sur Telegram. | |
| </div> | |
| <div class="loading" id="loading-text">Traitement en cours...</div> | |
| <div class="response-container" id="response-container"> | |
| <div id="response"></div> | |
| <button id="copy-button" class="button copy-button">Copier la réponse</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const uploadSection = document.getElementById('upload-section'); | |
| const fileInput = document.getElementById('file-input'); | |
| const imagePreview = document.getElementById('image-preview'); | |
| const solveButton = document.getElementById('solve-button'); | |
| const solvingContainer = document.getElementById('solving-container'); | |
| const responseContainer = document.getElementById('response-container'); | |
| const response = document.getElementById('response'); | |
| const copyButton = document.getElementById('copy-button'); | |
| const statusElement = document.getElementById('status'); | |
| const loadingText = document.getElementById('loading-text'); | |
| let selectedFile = null; | |
| // Événements pour l'upload d'image | |
| uploadSection.addEventListener('click', () => fileInput.click()); | |
| uploadSection.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| uploadSection.style.borderColor = '#2196F3'; | |
| }); | |
| uploadSection.addEventListener('dragleave', () => { | |
| uploadSection.style.borderColor = '#ccc'; | |
| }); | |
| uploadSection.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| uploadSection.style.borderColor = '#ccc'; | |
| if (e.dataTransfer.files.length) { | |
| handleFileSelection(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| fileInput.addEventListener('change', (e) => { | |
| if (e.target.files.length) { | |
| handleFileSelection(e.target.files[0]); | |
| } | |
| }); | |
| function handleFileSelection(file) { | |
| if (!file.type.startsWith('image/')) { | |
| alert('Veuillez sélectionner une image valide'); | |
| return; | |
| } | |
| selectedFile = file; | |
| solveButton.disabled = false; | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| imagePreview.src = e.target.result; | |
| imagePreview.style.display = 'block'; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| // Événement pour résoudre l'image | |
| solveButton.addEventListener('click', () => { | |
| if (!selectedFile) return; | |
| solveButton.disabled = true; | |
| solvingContainer.style.display = 'block'; | |
| responseContainer.style.display = 'none'; | |
| statusElement.textContent = 'En attente de résolution...'; | |
| loadingText.style.display = 'block'; | |
| response.innerHTML = ''; | |
| const formData = new FormData(); | |
| formData.append('image', selectedFile); | |
| // Soumettre l'image pour traitement en arrière-plan | |
| fetch('/solve', { | |
| method: 'POST', | |
| body: formData | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.error) { | |
| throw new Error(data.error); | |
| } | |
| const taskId = data.task_id; | |
| statusElement.textContent = 'Traitement en arrière-plan (ID: ' + taskId + ')'; | |
| // Création d'une connexion SSE pour suivre le progrès | |
| const eventSource = new EventSource('/stream/' + taskId); | |
| let fullResponse = ''; | |
| eventSource.onmessage = function(event) { | |
| const data = JSON.parse(event.data); | |
| if (data.error) { | |
| statusElement.textContent = 'Erreur:'; | |
| response.innerHTML = data.error; | |
| responseContainer.style.display = 'block'; | |
| loadingText.style.display = 'none'; | |
| eventSource.close(); | |
| solveButton.disabled = false; | |
| return; | |
| } | |
| if (data.status === 'pending') { | |
| statusElement.textContent = 'En attente de traitement...'; | |
| } else if (data.status === 'processing') { | |
| statusElement.textContent = 'Gemini traite votre image...'; | |
| statusElement.innerHTML += '<br><small>La réponse sera également envoyée sur Telegram</small>'; | |
| } else if (data.status === 'completed') { | |
| statusElement.textContent = 'Traitement terminé!'; | |
| responseContainer.style.display = 'block'; | |
| loadingText.style.display = 'none'; | |
| fullResponse = data.response; | |
| response.innerHTML = fullResponse; | |
| renderMathInElement(response); | |
| eventSource.close(); | |
| solveButton.disabled = false; | |
| } else if (data.status === 'error') { | |
| statusElement.textContent = 'Erreur:'; | |
| response.innerHTML = data.error || 'Une erreur inattendue est survenue'; | |
| responseContainer.style.display = 'block'; | |
| loadingText.style.display = 'none'; | |
| eventSource.close(); | |
| solveButton.disabled = false; | |
| } | |
| }; | |
| eventSource.onerror = function() { | |
| eventSource.close(); | |
| // Essayer de récupérer le statut via une requête GET normale | |
| fetch('/task/' + taskId) | |
| .then(response => response.json()) | |
| .then(taskData => { | |
| if (taskData.status === 'completed') { | |
| statusElement.textContent = 'Traitement terminé!'; | |
| responseContainer.style.display = 'block'; | |
| loadingText.style.display = 'none'; | |
| response.innerHTML = taskData.response; | |
| renderMathInElement(response); | |
| } else if (taskData.status === 'error') { | |
| throw new Error(taskData.error || 'Une erreur inattendue est survenue'); | |
| } | |
| }) | |
| .catch(error => { | |
| statusElement.textContent = 'Erreur de connexion:'; | |
| response.innerHTML = 'La connexion a été perdue, mais le traitement continue en arrière-plan. La réponse sera envoyée sur Telegram.'; | |
| responseContainer.style.display = 'block'; | |
| loadingText.style.display = 'none'; | |
| }) | |
| .finally(() => { | |
| solveButton.disabled = false; | |
| }); | |
| }; | |
| }) | |
| .catch(error => { | |
| statusElement.textContent = 'Erreur:'; | |
| response.innerHTML = error.message || 'Une erreur est survenue lors de la communication avec le serveur.'; | |
| responseContainer.style.display = 'block'; | |
| loadingText.style.display = 'none'; | |
| solveButton.disabled = false; | |
| }); | |
| }); | |
| // Événement pour copier la réponse | |
| copyButton.addEventListener('click', () => { | |
| const range = document.createRange(); | |
| range.selectNode(response); | |
| window.getSelection().removeAllRanges(); | |
| window.getSelection().addRange(range); | |
| document.execCommand('copy'); | |
| window.getSelection().removeAllRanges(); | |
| copyButton.textContent = 'Copié!'; | |
| setTimeout(() => { | |
| copyButton.textContent = 'Copier la réponse'; | |
| }, 2000); | |
| }); | |
| }); | |
| // Rendu des formules LaTeX | |
| document.addEventListener('DOMContentLoaded', function() { | |
| renderMathInElement(document.body, { | |
| delimiters: [ | |
| {left: '$$', right: '$$', display: true}, | |
| {left: '$', right: '$', display: false}, | |
| {left: '\\(', right: '\\)', display: false}, | |
| {left: '\\[', right: '\\]', display: true} | |
| ] | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |