| <!DOCTYPE html> |
| <html lang="fr"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Résolveur d'Images & PDF - Mariam</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> |
| :root { |
| --primary-color: #3498db; |
| --primary-hover: #2980b9; |
| --secondary-color: #2ecc71; |
| --secondary-hover: #27ae60; |
| --danger-color: #e74c3c; |
| --danger-hover: #c0392b; |
| --background-color: #f4f7f6; |
| --text-color: #333; |
| --border-color: #e0e0e0; |
| --shadow: 0 4px 15px rgba(0,0,0,0.1); |
| --spacing-unit: 1rem; |
| } |
| |
| * { |
| box-sizing: border-box; |
| margin: 0; |
| padding: 0; |
| } |
| |
| body { |
| font-family: 'Segoe UI', system-ui, sans-serif; |
| max-width: 800px; |
| margin: 0 auto; |
| padding: calc(var(--spacing-unit) * 2); |
| line-height: 1.6; |
| background-color: var(--background-color); |
| color: var(--text-color); |
| } |
| |
| .header { |
| text-align: center; |
| margin-bottom: calc(var(--spacing-unit) * 2); |
| } |
| |
| .header h1 { |
| font-size: 2.5rem; |
| color: #2c3e50; |
| margin-bottom: calc(var(--spacing-unit) * 0.5); |
| } |
| |
| .header .subtitle { |
| font-size: 1.1rem; |
| color: #555; |
| } |
| |
| .telegram-join-button-container { |
| text-align: center; |
| margin-bottom: calc(var(--spacing-unit) * 2); |
| } |
| |
| .telegram-button { |
| display: inline-block; |
| background-color: #0088cc; |
| color: white; |
| padding: var(--spacing-unit) calc(var(--spacing-unit) * 2); |
| border-radius: 0.5rem; |
| text-decoration: none; |
| transition: all 0.3s ease; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| } |
| |
| .telegram-button:hover { |
| transform: translateY(-2px); |
| background-color: #006699; |
| } |
| |
| .container { |
| background-color: white; |
| padding: calc(var(--spacing-unit) * 2); |
| border-radius: 1rem; |
| box-shadow: var(--shadow); |
| } |
| |
| .style-selection { |
| background-color: #f9f9f9; |
| padding: calc(var(--spacing-unit) * 1.5); |
| border-radius: 0.75rem; |
| border: 1px solid var(--border-color); |
| margin-bottom: calc(var(--spacing-unit) * 1.5); |
| } |
| |
| .style-selection h3 { |
| margin-bottom: var(--spacing-unit); |
| color: #2c3e50; |
| font-size: 1.2rem; |
| } |
| |
| .radio-group { |
| display: flex; |
| flex-direction: column; |
| gap: var(--spacing-unit); |
| } |
| |
| .radio-option { |
| display: flex; |
| align-items: flex-start; |
| padding: calc(var(--spacing-unit) * 0.75); |
| border-radius: 0.5rem; |
| transition: background-color 0.2s; |
| cursor: pointer; |
| border: 1px solid transparent; |
| } |
| |
| .radio-option:hover { |
| background-color: #f0f4f8; |
| border-color: var(--primary-color); |
| } |
| |
| .radio-option input[type="radio"] { |
| margin-top: 0.25rem; |
| margin-right: calc(var(--spacing-unit) * 0.75); |
| width: 1.25rem; |
| height: 1.25rem; |
| accent-color: var(--primary-color); |
| } |
| |
| .radio-content { |
| flex: 1; |
| } |
| |
| .radio-label { |
| font-weight: 500; |
| margin-bottom: calc(var(--spacing-unit) * 0.25); |
| display: block; |
| } |
| |
| .radio-description { |
| font-size: 0.9rem; |
| color: #666; |
| } |
| |
| .upload-section { |
| border: 3px dashed var(--border-color); |
| padding: calc(var(--spacing-unit) * 2); |
| text-align: center; |
| border-radius: 0.75rem; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| background-color: #f8f9fa; |
| margin: calc(var(--spacing-unit) * 1.5) 0; |
| } |
| |
| .upload-section:hover { |
| border-color: var(--primary-color); |
| background-color: #e8f4fb; |
| } |
| |
| .upload-icon { |
| font-size: 2.5rem; |
| margin-bottom: var(--spacing-unit); |
| color: var(--primary-color); |
| } |
| |
| #file-input { |
| display: none; |
| } |
| |
| #file-preview-area { |
| margin-top: var(--spacing-unit); |
| display: flex; |
| flex-wrap: wrap; |
| gap: var(--spacing-unit); |
| justify-content: center; |
| } |
| |
| .preview-item { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: calc(var(--spacing-unit) * 0.5); |
| padding: calc(var(--spacing-unit) * 0.5); |
| border: 1px solid var(--border-color); |
| border-radius: 0.5rem; |
| background-color: #fdfdfd; |
| } |
| |
| .preview-item img { |
| max-width: 100px; |
| max-height: 100px; |
| border-radius: 0.25rem; |
| object-fit: cover; |
| } |
| .preview-item .pdf-icon { |
| font-size: 3rem; |
| color: var(--danger-color); |
| } |
| .preview-item span { |
| font-size: 0.8rem; |
| color: #555; |
| word-break: break-all; |
| max-width: 100px; |
| text-align: center; |
| } |
| |
| .button { |
| width: 100%; |
| padding: var(--spacing-unit); |
| border: none; |
| border-radius: 0.5rem; |
| font-size: 1rem; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| margin: var(--spacing-unit) 0; |
| background-color: var(--primary-color); |
| color: white; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| } |
| |
| .button:hover:not(:disabled) { |
| transform: translateY(-2px); |
| background-color: var(--primary-hover); |
| } |
| |
| .button:disabled { |
| background-color: #bdc3c7; |
| cursor: not-allowed; |
| } |
| |
| .clear-button { |
| background-color: var(--danger-color); |
| margin-top: 0; |
| } |
| .clear-button:hover:not(:disabled) { |
| background-color: var(--danger-hover); |
| } |
| |
| .copy-button { |
| background-color: var(--secondary-color); |
| } |
| |
| .copy-button:hover:not(:disabled) { |
| background-color: var(--secondary-hover); |
| } |
| |
| .cooldown-notice { |
| background-color: #fff3cd; |
| border: 1px solid #ffeaa7; |
| border-radius: 0.5rem; |
| padding: var(--spacing-unit); |
| margin: var(--spacing-unit) 0; |
| text-align: center; |
| color: #856404; |
| font-weight: 500; |
| } |
| |
| .cooldown-timer { |
| font-size: 1.2rem; |
| color: #d63031; |
| font-weight: bold; |
| } |
| |
| #solving-container { |
| display: none; |
| background-color: #f9f9f9; |
| padding: calc(var(--spacing-unit) * 1.5); |
| border-radius: 0.75rem; |
| border: 1px solid var(--border-color); |
| margin-top: calc(var(--spacing-unit) * 1.5); |
| } |
| |
| .status { |
| text-align: center; |
| margin-bottom: var(--spacing-unit); |
| font-weight: bold; |
| color: #2c3e50; |
| } |
| |
| .status.error { color: #e74c3c; } |
| .status.completed { color: #2ecc71; } |
| |
| .telegram-notice { |
| background-color: #eaf5ff; |
| border-left: 5px solid var(--primary-color); |
| padding: var(--spacing-unit); |
| margin: var(--spacing-unit) 0; |
| border-radius: 0 0.5rem 0.5rem 0; |
| } |
| |
| .response-container { |
| display: none; |
| margin-top: calc(var(--spacing-unit) * 1.5); |
| padding: calc(var(--spacing-unit) * 1.5); |
| background-color: white; |
| border-radius: 0.75rem; |
| border: 1px solid var(--border-color); |
| } |
| |
| #response { |
| background-color: #fdfdfd; |
| padding: var(--spacing-unit); |
| border-radius: 0.5rem; |
| border: 1px solid #eee; |
| min-height: 50px; |
| white-space: pre-wrap; |
| word-wrap: break-word; |
| } |
| |
| .loading { |
| text-align: center; |
| font-style: italic; |
| color: #555; |
| margin: var(--spacing-unit) 0; |
| } |
| |
| .loading::before { |
| content: "⏳ "; |
| } |
| |
| @media (max-width: 768px) { |
| :root { |
| --spacing-unit: 0.875rem; |
| } |
| |
| body { |
| padding: var(--spacing-unit); |
| } |
| |
| .header h1 { |
| font-size: 1.75rem; |
| } |
| |
| .container { |
| padding: var(--spacing-unit); |
| } |
| |
| .radio-option { |
| padding: calc(var(--spacing-unit) * 0.5); |
| } |
| |
| .radio-content { |
| font-size: 0.95rem; |
| } |
| |
| .radio-description { |
| font-size: 0.85rem; |
| } |
| |
| .upload-section { |
| padding: var(--spacing-unit); |
| } |
| |
| .telegram-button { |
| padding: calc(var(--spacing-unit) * 0.75) var(--spacing-unit); |
| font-size: 0.95rem; |
| } |
| .preview-item img { |
| max-width: 80px; |
| max-height: 80px; |
| } |
| .preview-item .pdf-icon { |
| font-size: 2.5rem; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="header"> |
| <h1>🖼️ Science (Math, Physique, Chimie) 🧠</h1> |
| <p class="subtitle">Avec Mariam, votre assistante IA</p> |
| </div> |
|
|
| <div class="telegram-join-button-container"> |
| <a href="https://t.me/+ic4zemy1E1k0MzQ0" target="_blank" class="telegram-button"> |
| 🚀 Rejoindre le Groupe Telegram pour obtenir le PDF |
| </a> |
| </div> |
|
|
| <div class="container"> |
| <div class="style-selection"> |
| <h3>🎨 Choisissez le style de résolution</h3> |
| <div class="radio-group"> |
| <div class="radio-option" onclick="selectStyle('light')"> |
| <input type="radio" id="style-light" name="resolution-style" value="light"> |
| <div class="radio-content"> |
| <label class="radio-label" for="style-light">📝 Résolution Light</label> |
| <div class="radio-description">Format simple et épuré, idéal pour une lecture rapide</div> |
| </div> |
| </div> |
| |
| <div class="radio-option" onclick="selectStyle('colorful')"> |
| <input type="radio" id="style-colorful" name="resolution-style" value="colorful" checked> |
| <div class="radio-content"> |
| <label class="radio-label" for="style-colorful">🌈 Résolution Colorée</label> |
| <div class="radio-description">Format richement formaté avec couleurs, boîtes et mise en page élégante</div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div id="cooldown-notice" class="cooldown-notice" style="display: none;"> |
| ⏰ Veuillez attendre <span id="cooldown-timer" class="cooldown-timer">2:00</span> avant de pouvoir soumettre à nouveau. |
| </div> |
|
|
| <div id="upload-section" class="upload-section"> |
| <div class="upload-icon">📤</div> |
| <p>Cliquez ou glissez-déposez vos images et/ou 1 fichier PDF ici</p> |
| <input type="file" id="file-input" accept="image/*,application/pdf" multiple> |
| <div id="file-preview-area"> |
| |
| </div> |
| </div> |
| |
| <button id="clear-files-button" class="button clear-button" style="display: none;">🗑️ Effacer les fichiers</button> |
| <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 notre groupe Telegram. |
| </div> |
| <div class="loading" id="loading-text">Traitement en cours...</div> |
| <div class="response-container" id="response-container"> |
| <h3>Réponse de Mariam :</h3> |
| <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 filePreviewArea = document.getElementById('file-preview-area'); |
| const solveButton = document.getElementById('solve-button'); |
| const clearFilesButton = document.getElementById('clear-files-button'); |
| const solvingContainer = document.getElementById('solving-container'); |
| const responseContainer = document.getElementById('response-container'); |
| const responseDiv = document.getElementById('response'); |
| const copyButton = document.getElementById('copy-button'); |
| const statusElement = document.getElementById('status'); |
| const loadingText = document.getElementById('loading-text'); |
| const cooldownNotice = document.getElementById('cooldown-notice'); |
| const cooldownTimer = document.getElementById('cooldown-timer'); |
| |
| let selectedFiles = []; |
| let cooldownEndTime = 0; |
| let cooldownInterval = null; |
| |
| |
| checkCooldownOnLoad(); |
| |
| window.selectStyle = function(style) { |
| document.getElementById(`style-${style}`).checked = true; |
| }; |
| |
| function checkCooldownOnLoad() { |
| const savedCooldownEndTime = localStorage.getItem('mariamCooldownEndTime'); |
| if (savedCooldownEndTime) { |
| const endTime = parseInt(savedCooldownEndTime); |
| const now = Date.now(); |
| if (now < endTime) { |
| cooldownEndTime = endTime; |
| startCooldownTimer(); |
| } else { |
| localStorage.removeItem('mariamCooldownEndTime'); |
| } |
| } |
| } |
| |
| function startCooldown() { |
| cooldownEndTime = Date.now() + (2 * 60 * 1000); |
| localStorage.setItem('mariamCooldownEndTime', cooldownEndTime.toString()); |
| startCooldownTimer(); |
| } |
| |
| function startCooldownTimer() { |
| cooldownNotice.style.display = 'block'; |
| solveButton.disabled = true; |
| |
| cooldownInterval = setInterval(() => { |
| const now = Date.now(); |
| const remainingTime = Math.max(0, cooldownEndTime - now); |
| |
| if (remainingTime <= 0) { |
| clearInterval(cooldownInterval); |
| cooldownNotice.style.display = 'none'; |
| localStorage.removeItem('mariamCooldownEndTime'); |
| updateButtonsState(); |
| return; |
| } |
| |
| const minutes = Math.floor(remainingTime / 60000); |
| const seconds = Math.floor((remainingTime % 60000) / 1000); |
| cooldownTimer.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; |
| }, 1000); |
| } |
| |
| function isCooldownActive() { |
| return Date.now() < cooldownEndTime; |
| } |
| |
| uploadSection.addEventListener('click', () => fileInput.click()); |
| |
| uploadSection.addEventListener('dragover', (e) => { |
| e.preventDefault(); |
| uploadSection.style.borderColor = 'var(--primary-color)'; |
| uploadSection.style.backgroundColor = '#e8f4fb'; |
| }); |
| |
| uploadSection.addEventListener('dragleave', () => { |
| uploadSection.style.borderColor = 'var(--border-color)'; |
| uploadSection.style.backgroundColor = '#f8f9fa'; |
| }); |
| |
| uploadSection.addEventListener('drop', (e) => { |
| e.preventDefault(); |
| uploadSection.style.borderColor = 'var(--border-color)'; |
| uploadSection.style.backgroundColor = '#f8f9fa'; |
| |
| if (e.dataTransfer.files.length) { |
| handleFileSelection(e.dataTransfer.files); |
| } |
| }); |
| |
| fileInput.addEventListener('change', (e) => { |
| if (e.target.files.length) { |
| handleFileSelection(e.target.files); |
| } |
| }); |
| |
| function handleFileSelection(files) { |
| const newFiles = Array.from(files); |
| let pdfAlreadySelected = selectedFiles.some(f => f.type === 'application/pdf'); |
| |
| newFiles.forEach(file => { |
| if (file.type.startsWith('image/')) { |
| if (!selectedFiles.some(sf => sf.name === file.name && sf.size === file.size)) { |
| selectedFiles.push(file); |
| } |
| } else if (file.type === 'application/pdf') { |
| if (!pdfAlreadySelected) { |
| selectedFiles = selectedFiles.filter(f => f.type !== 'application/pdf'); |
| selectedFiles.push(file); |
| pdfAlreadySelected = true; |
| } else { |
| alert('Vous ne pouvez sélectionner qu\'un seul fichier PDF.'); |
| } |
| } else { |
| alert(`Le fichier "${file.name}" n'est pas une image ou un PDF valide et sera ignoré.`); |
| } |
| }); |
| |
| updateFilePreviews(); |
| updateButtonsState(); |
| |
| solvingContainer.style.display = 'none'; |
| responseContainer.style.display = 'none'; |
| } |
| |
| function updateFilePreviews() { |
| filePreviewArea.innerHTML = ''; |
| |
| if (selectedFiles.length === 0) { |
| filePreviewArea.style.display = 'none'; |
| return; |
| } |
| filePreviewArea.style.display = 'flex'; |
| |
| selectedFiles.forEach(file => { |
| const previewItem = document.createElement('div'); |
| previewItem.classList.add('preview-item'); |
| |
| const fileNameSpan = document.createElement('span'); |
| fileNameSpan.textContent = file.name.length > 15 ? file.name.substring(0,12) + "..." : file.name; |
| |
| if (file.type.startsWith('image/')) { |
| const img = document.createElement('img'); |
| const reader = new FileReader(); |
| reader.onload = (e) => { |
| img.src = e.target.result; |
| }; |
| reader.readAsDataURL(file); |
| previewItem.appendChild(img); |
| } else if (file.type === 'application/pdf') { |
| const pdfIcon = document.createElement('div'); |
| pdfIcon.classList.add('pdf-icon'); |
| pdfIcon.textContent = '📄'; |
| previewItem.appendChild(pdfIcon); |
| } |
| previewItem.appendChild(fileNameSpan); |
| filePreviewArea.appendChild(previewItem); |
| }); |
| } |
| |
| function updateButtonsState() { |
| if (selectedFiles.length > 0 && !isCooldownActive()) { |
| solveButton.disabled = false; |
| solveButton.textContent = `🔍 Résoudre (${selectedFiles.length} fichier(s))`; |
| clearFilesButton.style.display = 'block'; |
| } else if (selectedFiles.length > 0 && isCooldownActive()) { |
| solveButton.disabled = true; |
| solveButton.textContent = `🔍 Résoudre (${selectedFiles.length} fichier(s))`; |
| clearFilesButton.style.display = 'block'; |
| } else { |
| solveButton.disabled = true; |
| solveButton.textContent = '🔍 Résoudre'; |
| clearFilesButton.style.display = 'none'; |
| } |
| } |
| |
| clearFilesButton.addEventListener('click', () => { |
| selectedFiles = []; |
| fileInput.value = ''; |
| updateFilePreviews(); |
| updateButtonsState(); |
| solvingContainer.style.display = 'none'; |
| responseContainer.style.display = 'none'; |
| }); |
| |
| solveButton.addEventListener('click', () => { |
| if (selectedFiles.length === 0 || isCooldownActive()) return; |
| |
| const selectedStyle = document.querySelector('input[name="resolution-style"]:checked').value; |
| |
| |
| startCooldown(); |
| |
| solveButton.disabled = true; |
| solveButton.textContent = '⏳ Traitement...'; |
| solvingContainer.style.display = 'block'; |
| responseContainer.style.display = 'none'; |
| statusElement.className = 'status'; |
| statusElement.textContent = 'Préparation de la requête...'; |
| loadingText.style.display = 'block'; |
| responseDiv.innerHTML = ''; |
| |
| const formData = new FormData(); |
| selectedFiles.forEach(file => { |
| formData.append('user_files', file); |
| }); |
| formData.append('style', selectedStyle); |
| |
| fetch('/solve', { |
| method: 'POST', |
| body: formData |
| }) |
| .then(response => { |
| if (!response.ok) { |
| return response.json().then(err => { throw new Error(err.error || `Erreur Serveur: ${response.status}`) }); |
| } |
| return 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 + ')'; |
| |
| const eventSource = new EventSource('/stream/' + taskId); |
| |
| eventSource.onmessage = function(event) { |
| const data = JSON.parse(event.data); |
| |
| if (data.error) { |
| handleError(data.error); |
| eventSource.close(); |
| return; |
| } |
| |
| updateStatus(data); |
| if (data.status === 'completed' || data.status === 'error') { |
| eventSource.close(); |
| } |
| }; |
| |
| eventSource.onerror = function() { |
| eventSource.close(); |
| handleEventSourceError(taskId); |
| }; |
| }) |
| .catch(error => { |
| handleError(error.message); |
| }); |
| }); |
| |
| function handleError(errorMessage) { |
| statusElement.className = 'status error'; |
| statusElement.textContent = 'Erreur:'; |
| responseDiv.innerHTML = `<p style="color:red;">${errorMessage}</p>`; |
| showResponse(); |
| } |
| |
| function updateStatus(data) { |
| switch(data.status) { |
| case 'pending': |
| statusElement.textContent = 'En file d\'attente...'; |
| break; |
| case 'processing': |
| statusElement.innerHTML = '<span class="thinking">Mariam</span> traite vos fichiers... <br><small>La réponse sera également envoyée sur Telegram.</small>'; |
| break; |
| case 'completed': |
| statusElement.className = 'status completed'; |
| statusElement.textContent = 'Traitement terminé avec succès ! 🎉'; |
| responseDiv.innerHTML = '<p style="color: #2ecc71; font-size: 1.2rem; text-align: center; font-weight: bold;">📄 Votre PDF est disponible sur Telegram</p>'; |
| showResponse(); |
| break; |
| case 'error': |
| handleError(data.error || 'Une erreur inattendue est survenue.'); |
| break; |
| } |
| } |
| |
| function showResponse() { |
| responseContainer.style.display = 'block'; |
| loadingText.style.display = 'none'; |
| } |
| |
| function handleEventSourceError(taskId) { |
| fetch('/task/' + taskId) |
| .then(response => { |
| if (!response.ok) { |
| throw new Error(`Erreur serveur lors de la récupération de la tâche: ${response.status}`); |
| } |
| return response.json(); |
| }) |
| .then(taskData => { |
| if (taskData.status === 'completed' && taskData.response) { |
| updateStatus({ |
| status: 'completed', |
| response: taskData.response |
| }); |
| } else if (taskData.status === 'error' || taskData.error) { |
| handleError(taskData.error || 'Une erreur est survenue lors du traitement de la tâche.'); |
| } else { |
| handleError('La connexion au flux a été perdue. Vérifiez Telegram ou réessayez plus tard.'); |
| } |
| }) |
| .catch((err) => { |
| console.error("Erreur lors de la récupération de l'état de la tâche:", err); |
| handleError('La connexion au flux a été perdue et la récupération de l\'état final a échoué. La réponse pourrait être sur Telegram.'); |
| }); |
| } |
| |
| copyButton.addEventListener('click', () => { |
| const textToCopy = responseDiv.innerText || responseDiv.textContent; |
| navigator.clipboard.writeText(textToCopy) |
| .then(() => { |
| copyButton.textContent = '✅ Copié!'; |
| setTimeout(() => { |
| copyButton.textContent = '📋 Copier la réponse'; |
| }, 2000); |
| }) |
| .catch(() => { |
| const range = document.createRange(); |
| range.selectNode(responseDiv); |
| window.getSelection().removeAllRanges(); |
| window.getSelection().addRange(range); |
| try { |
| document.execCommand('copy'); |
| copyButton.textContent = '✅ Copié!'; |
| } catch (e) { |
| copyButton.textContent = '❌ Erreur de copie'; |
| } |
| window.getSelection().removeAllRanges(); |
| setTimeout(() => { |
| copyButton.textContent = '📋 Copier la réponse'; |
| }, 2000); |
| }); |
| }); |
| |
| renderMathInElement(document.body, { |
| delimiters: [ |
| {left: '$$', right: '$$', display: true}, |
| {left: '$', right: '$', display: false}, |
| {left: '\\(', right: '\\)', display: false}, |
| {left: '\\[', right: '\\]', display: true} |
| ], |
| throwOnError: false |
| }); |
| }); |
| </script> |
| </body> |
| </html> |