Testpdf / templates /index.html
Docfile's picture
Update templates/index.html
8da5358 verified
raw
history blame
21.4 kB
<!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>
:root {
--primary-color: #3498db;
--primary-hover: #2980b9;
--secondary-color: #2ecc71;
--secondary-hover: #27ae60;
--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;
}
.preview-container {
margin-top: var(--spacing-unit);
}
#image-preview {
max-width: 100%;
max-height: 300px;
display: none;
border-radius: 0.5rem;
border: 1px solid var(--border-color);
}
.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;
}
.copy-button {
background-color: var(--secondary-color);
}
.copy-button:hover {
background-color: var(--secondary-hover);
}
#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;
}
}
</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="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');
const copyButton = document.getElementById('copy-button');
const statusElement = document.getElementById('status');
const loadingText = document.getElementById('loading-text');
let selectedFile = null;
window.selectStyle = function(style) {
document.getElementById(`style-${style}`).checked = true;
};
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[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';
const reader = new FileReader();
reader.onload = (e) => {
imagePreview.src = e.target.result;
imagePreview.style.display = 'block';
};
reader.readAsDataURL(file);
solvingContainer.style.display = 'none';
responseContainer.style.display = 'none';
}
solveButton.addEventListener('click', () => {
if (!selectedFile) return;
const selectedStyle = document.querySelector('input[name="resolution-style"]:checked').value;
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();
formData.append('image', selectedFile);
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);
return;
}
updateStatus(data);
};
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 votre image... <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 = data.response;
renderMathInElement(responseDiv);
showResponse();
break;
case 'error':
handleError(data.error || 'Une erreur inattendue est survenue.');
break;
}
}
function showResponse() {
responseContainer.style.display = 'block';
loadingText.style.display = 'none';
solveButton.disabled = false;
solveButton.textContent = '🔍 Résoudre';
}
function handleEventSourceError(taskId) {
fetch('/task/' + taskId)
.then(response => response.json())
.then(taskData => {
if (taskData.status === 'completed') {
updateStatus({
status: 'completed',
response: taskData.response
});
} else if (taskData.status === 'error' || taskData.error) {
handleError(taskData.error || 'Une erreur est survenue.');
} else {
handleError('La connexion au flux a été perdue. La réponse sera envoyée sur Telegram.');
}
})
.catch(() => {
handleError('La connexion au flux a été perdue et la récupération a échoué.');
});
}
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(() => {
// Fallback pour les anciens navigateurs
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>