Testpdf / templates /index.html
Docfile's picture
Update templates/index.html
1e647e7 verified
<!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 - 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>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
background-color: #f4f7f6; /* Fond légèrement gris */
color: #333;
}
h1 {
text-align: center;
color: #2c3e50; /* Bleu foncé */
margin-bottom: 10px;
font-size: 2.5em;
}
.subtitle {
text-align: center;
color: #555;
margin-bottom: 20px;
font-size: 1.1em;
}
.telegram-join-button-container {
text-align: center;
margin-bottom: 30px;
}
.telegram-button {
background-color: #0088cc; /* Bleu Telegram */
color: white;
border: none;
padding: 12px 25px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
cursor: pointer;
border-radius: 8px;
transition: background-color 0.3s, transform 0.2s;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.telegram-button:hover {
background-color: #0077b3;
transform: translateY(-2px);
}
.telegram-button:active {
transform: translateY(0px);
}
.container {
display: flex;
flex-direction: column;
gap: 25px;
background-color: #ffffff;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.upload-section {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 30px;
border: 3px dashed #bdc3c7; /* Gris clair */
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
background-color: #ecf0f1; /* Gris très clair */
min-height: 150px;
}
.upload-section:hover {
border-color: #3498db; /* Bleu lors du survol */
background-color: #e8f4fb;
}
.upload-section p {
margin: 0;
font-size: 1.1em;
color: #555;
}
.upload-icon {
font-size: 2.5em;
color: #3498db;
margin-bottom: 10px;
}
#file-input {
display: none;
}
.preview-container {
width: 100%;
text-align: center;
margin-top: 15px;
}
#image-preview {
max-width: 100%;
max-height: 300px;
display: none;
border-radius: 8px;
border: 1px solid #ddd;
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
}
#solving-container {
display: none;
background-color: #f9f9f9;
padding: 25px;
border-radius: 10px;
border: 1px solid #e0e0e0;
}
.response-container {
margin-top: 20px;
padding: 25px;
border: 1px solid #d1d8dd;
border-radius: 10px;
background-color: #fff;
display: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
#response {
background-color: #fdfdfd;
padding: 15px;
border-radius: 6px;
border: 1px solid #eee;
min-height: 50px;
white-space: pre-wrap; /* Pour conserver les sauts de ligne */
word-wrap: break-word; /* Pour couper les mots longs */
}
.thinking { /* Style pour le texte "Mariam réfléchit..." */
color: #3498db; /* Bleu */
font-style: italic;
font-weight: bold;
}
.button {
background-color: #3498db; /* Bleu primaire */
color: white;
border: none;
padding: 12px 25px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 10px 0; /* Centré et prend toute la largeur */
width: 100%;
box-sizing: border-box;
cursor: pointer;
border-radius: 8px;
transition: background-color 0.3s, transform 0.2s;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.button:hover {
background-color: #2980b9; /* Bleu plus foncé au survol */
transform: translateY(-2px);
}
.button:active {
transform: translateY(0px);
}
.button:disabled {
background-color: #bdc3c7; /* Gris pour désactivé */
cursor: not-allowed;
transform: translateY(0px);
box-shadow: none;
}
.copy-button {
background-color: #2ecc71; /* Vert pour copier */
margin-top: 15px;
}
.copy-button:hover {
background-color: #27ae60; /* Vert plus foncé */
}
.telegram-notice {
background-color: #eaf5ff; /* Bleu très clair */
border-left: 5px solid #3498db; /* Bordure bleue */
padding: 12px 15px;
margin: 15px 0;
font-size: 0.95em;
border-radius: 0 5px 5px 0;
}
.loading {
text-align: center;
font-style: italic;
margin: 15px 0;
color: #555;
}
.loading::before {
content: "⏳ ";
}
.status {
text-align: center;
margin-bottom: 15px;
font-weight: bold;
font-size: 1.1em;
color: #2c3e50;
}
.status small {
font-weight: normal;
color: #7f8c8d; /* Gris */
font-size: 0.9em;
display: block;
margin-top: 5px;
}
/* Styles pour les messages d'erreur/succès dans le status */
.status.error { color: #e74c3c; } /* Rouge */
.status.completed { color: #2ecc71; } /* Vert */
</style>
</head>
<body>
<h1>🖼️ Science ( Math, physique, chimie)🧠</h1>
<p class="subtitle">Avec Mariam, votre assistante IA</p>
<div class="telegram-join-button-container">
<a href="https://t.me/+ic4zemy1E1k0MzQ0" target="_blank" class="telegram-button">
🚀 Rejoindre le Groupe Telegram
</a>
</div>
<div class="container">
<div id="upload-section" class="upload-section">
<div class="upload-icon">📤</div>
<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 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 imagePreview = document.getElementById('image-preview');
const solveButton = document.getElementById('solve-button');
const solvingContainer = document.getElementById('solving-container');
const responseContainer = document.getElementById('response-container');
const responseDiv = document.getElementById('response'); // Renommé pour clarté
const copyButton = document.getElementById('copy-button');
const statusElement = document.getElementById('status');
const loadingText = document.getElementById('loading-text');
let selectedFile = null;
uploadSection.addEventListener('click', () => fileInput.click());
uploadSection.addEventListener('dragover', (e) => {
e.preventDefault();
uploadSection.style.borderColor = '#3498db';
uploadSection.style.backgroundColor = '#e8f4fb';
});
uploadSection.addEventListener('dragleave', () => {
uploadSection.style.borderColor = '#bdc3c7';
uploadSection.style.backgroundColor = '#ecf0f1';
});
uploadSection.addEventListener('drop', (e) => {
e.preventDefault();
uploadSection.style.borderColor = '#bdc3c7';
uploadSection.style.backgroundColor = '#ecf0f1';
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 (format PNG, JPG, GIF, etc.)');
return;
}
selectedFile = file;
solveButton.disabled = false;
solveButton.textContent = '🔍 Résoudre'; // Réinitialiser le texte du bouton
const reader = new FileReader();
reader.onload = (e) => {
imagePreview.src = e.target.result;
imagePreview.style.display = 'block';
};
reader.readAsDataURL(file);
// Cacher la zone de résolution si une nouvelle image est sélectionnée
solvingContainer.style.display = 'none';
responseContainer.style.display = 'none';
}
solveButton.addEventListener('click', () => {
if (!selectedFile) return;
solveButton.disabled = true;
solveButton.textContent = '⏳ Traitement...';
solvingContainer.style.display = 'block';
responseContainer.style.display = 'none';
statusElement.className = 'status'; // Reset class
statusElement.textContent = 'Préparation de la requête...';
loadingText.style.display = 'block';
responseDiv.innerHTML = '';
const formData = new FormData();
formData.append('image', selectedFile);
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) {
statusElement.className = 'status error';
statusElement.textContent = 'Erreur de traitement:';
responseDiv.innerHTML = `<p style="color:red;">${data.error}</p>`;
responseContainer.style.display = 'block';
loadingText.style.display = 'none';
eventSource.close();
solveButton.disabled = false;
solveButton.textContent = '🔍 Résoudre';
return;
}
if (data.status === 'pending') {
statusElement.textContent = 'En file d\'attente...';
} else if (data.status === 'processing') {
statusElement.innerHTML = '<span class="thinking">Mariam</span> traite votre image... <br><small>La réponse sera également envoyée sur Telegram.</small>';
} else if (data.status === 'completed') {
statusElement.className = 'status completed';
statusElement.textContent = 'Traitement terminé avec succès ! 🎉';
responseContainer.style.display = 'block';
loadingText.style.display = 'none';
responseDiv.innerHTML = data.response; // Utiliser innerHTML si la réponse contient du HTML
renderMathInElement(responseDiv, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false},
{left: '\\(', right: '\\)', display: false},
{left: '\\[', right: '\\]', display: true}
]
});
eventSource.close();
solveButton.disabled = false;
solveButton.textContent = '🔍 Résoudre';
} else if (data.status === 'error') {
statusElement.className = 'status error';
statusElement.textContent = 'Erreur de traitement:';
responseDiv.innerHTML = `<p style="color:red;">${data.error || 'Une erreur inattendue est survenue durant le traitement.'}</p>`;
responseContainer.style.display = 'block';
loadingText.style.display = 'none';
eventSource.close();
solveButton.disabled = false;
solveButton.textContent = '🔍 Résoudre';
}
};
eventSource.onerror = function() {
eventSource.close();
fetch('/task/' + taskId)
.then(response => response.json())
.then(taskData => {
if (taskData.status === 'completed') {
statusElement.className = 'status completed';
statusElement.textContent = 'Traitement terminé (récupéré après déconnexion) !';
responseContainer.style.display = 'block';
loadingText.style.display = 'none';
responseDiv.innerHTML = taskData.response;
renderMathInElement(responseDiv, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false},
{left: '\\(', right: '\\)', display: false},
{left: '\\[', right: '\\]', display: true}
]
});
} else if (taskData.status === 'error' || (taskData.error && taskData.error !== "Task not found or not completed yet")) {
statusElement.className = 'status error';
statusElement.textContent = 'Erreur (récupéré après déconnexion):';
responseDiv.innerHTML = `<p style="color:red;">${taskData.error || 'Une erreur inattendue est survenue.'}</p>`;
} else {
statusElement.className = 'status error';
statusElement.textContent = 'Erreur de connexion:';
responseDiv.innerHTML = 'La connexion au flux a été perdue, mais le traitement continue en arrière-plan. La réponse sera envoyée sur Telegram si configuré. Vous pouvez essayer de rafraîchir pour voir si la tâche est terminée.';
}
})
.catch(error => {
statusElement.className = 'status error';
statusElement.textContent = 'Erreur de connexion:';
responseDiv.innerHTML = 'La connexion au flux a été perdue et la récupération a échoué. Le traitement peut continuer en arrière-plan. La réponse sera envoyée sur Telegram si configuré.';
})
.finally(() => {
responseContainer.style.display = 'block';
loadingText.style.display = 'none';
solveButton.disabled = false;
solveButton.textContent = '🔍 Résoudre';
});
};
})
.catch(error => {
statusElement.className = 'status error';
statusElement.textContent = 'Erreur Initiale:';
responseDiv.innerHTML = `<p style="color:red;">${error.message || 'Une erreur est survenue lors de la communication avec le serveur.'}</p>`;
responseContainer.style.display = 'block';
loadingText.style.display = 'none';
solveButton.disabled = false;
solveButton.textContent = '🔍 Résoudre';
});
});
copyButton.addEventListener('click', () => {
const textToCopy = responseDiv.innerText || responseDiv.textContent; // Pour mieux copier le texte brut
navigator.clipboard.writeText(textToCopy).then(() => {
copyButton.textContent = '✅ Copié!';
setTimeout(() => {
copyButton.textContent = '📋 Copier la réponse';
}, 2000);
}).catch(err => {
console.error('Erreur de copie: ', err);
// Fallback pour les anciens navigateurs (moins fiable)
const range = document.createRange();
range.selectNode(responseDiv);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
try {
document.execCommand('copy');
copyButton.textContent = '✅ Copié! (fallback)';
} catch (e) {
copyButton.textContent = 'Erreur copie';
}
window.getSelection().removeAllRanges();
setTimeout(() => {
copyButton.textContent = '📋 Copier la réponse';
}, 2000);
});
});
// Rendu KaTeX initial pour toute la page (si des formules sont en dehors des réponses)
renderMathInElement(document.body, {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false},
{left: '\\(', right: '\\)', display: false},
{left: '\\[', right: '\\]', display: true}
],
throwOnError: false // Important pour ne pas bloquer le script si KaTeX rencontre une erreur
});
});
</script>
</body>
</html>