Docfile's picture
Update templates/index.html
ff94a5a verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mariam AI - Assistant Français Intelligent</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap');
:root {
--primary: #3182ce;
--primary-dark: #1a365d;
--primary-light: #ebf8ff;
--secondary: #4a5568;
--accent: #6366f1;
--success: #38a169;
--danger: #e53e3e;
--warning: #f97316;
--background: #f7fafc;
--card-bg: rgba(255, 255, 255, 0.95);
}
body {
font-family: 'Plus Jakarta Sans', sans-serif;
background-color: #fafafa;
scroll-behavior: smooth;
-webkit-tap-highlight-color: transparent;
/* MODIFICATION: Empêcher la sélection de texte par défaut */
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE/Edge */
user-select: none; /* Standard */
}
/* MODIFICATION: Réactiver la sélection pour les champs modifiables */
input, textarea {
-webkit-user-select: text; /* Safari */
-moz-user-select: text; /* Firefox */
-ms-user-select: text; /* IE/Edge */
user-select: text; /* Standard */
}
/* Optionnel: Si vous voulez autoriser la sélection dans les zones de sortie */
/* #francais-output, #etude-texte-output, .backup-content {
-webkit-user-select: text; -moz-user-select: text; -ms-user-select: text; user-select: text;
} */
.glassmorphism {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07);
}
.card-hover { transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); }
.card-hover:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.08); }
.gradient-text { background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; text-fill-color: transparent; }
.loading-dot { animation: bounce 1.4s infinite; }
.loading-dot:nth-child(2) { animation-delay: 0.2s; }
.loading-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes bounce { 0%, 80%, 100% { transform: translateY(0); } 40% { transform: translateY(-6px); } }
.custom-radio:checked+span { background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%); color: white; border-color: var(--primary); }
/* Styles alertes */
.alert-message { display: flex; align-items: flex-start; padding: 1rem; border-radius: 0.5rem; border-width: 1px; border-style: solid; }
.alert-message i { margin-right: 0.75rem; font-size: 1.25rem; margin-top: 0.125rem; flex-shrink: 0; }
.alert-message div p:first-child { font-weight: 500; }
.alert-message div p:last-child { font-size: 0.875rem; margin-top: 0.25rem; line-height: 1.4; }
.alert-warning { color: #92400e; background-color: #fffbeb; border-color: #fde68a; }
.alert-warning i { color: var(--warning); }
.alert-warning div p:last-child { color: #b45309; }
.alert-danger { color: #991b1b; background-color: #fef2f2; border-color: #fecaca; }
.alert-danger i { color: var(--danger); }
.alert-danger div p:last-child { color: #b91c1c; }
/* Animations */
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.fade-in { animation: fadeIn 0.3s ease-out forwards; }
@keyframes scaleIn { from { transform: scale(0.95); opacity: 0; } to { transform: scale(1); opacity: 1; } }
.scale-in { animation: scaleIn 0.3s ease-out forwards; }
/* Sauvegardes */
.backup-item { cursor: pointer; transition: all 0.25s ease; }
.backup-item:hover { transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.05); }
.backup-content { display: none; margin-top: 10px; max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out, opacity 0.3s ease-out, margin-top 0.3s ease-out, padding-top 0.3s ease-out; opacity: 0; padding-top: 0; }
.backup-content-expanded { display: block; max-height: 3000px; opacity: 1; margin-top: 1rem; padding-top: 0.75rem; }
/* Upload Images */
.image-preview { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 15px; }
.image-preview-item { position: relative; width: 100px; height: 100px; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); transition: transform 0.2s ease; }
.image-preview-item:hover { transform: scale(1.05); }
.image-preview-item img { width: 100%; height: 100%; object-fit: cover; }
.remove-image { position: absolute; top: 4px; right: 4px; background-color: rgba(0, 0, 0, 0.6); color: white; border: none; border-radius: 50%; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 11px; opacity: 0; transition: opacity 0.2s, transform 0.2s; }
.image-preview-item:hover .remove-image { opacity: 1; }
.remove-image:hover { background-color: rgba(0, 0, 0, 0.8); transform: scale(1.1); }
/* Style Prose */
.prose { white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; color: #2d3748; line-height: 1.7; font-size: 0.95rem; }
@media (min-width: 640px) { .prose { font-size: 1rem; } }
.prose h1, .prose h2, .prose h3, .prose h4 { color: var(--primary-dark); margin-top: 1.5em; margin-bottom: 0.5em; font-weight: 600; }
.prose p { margin-bottom: 1em; }
.prose ul, .prose ol { padding-left: 1.5em; margin-bottom: 1em; }
.prose blockquote { border-left: 4px solid var(--primary); padding-left: 1em; font-style: italic; color: var(--secondary); margin: 1em 0; }
.prose code { background-color: #edf2f7; padding: 0.2em 0.4em; border-radius: 0.25rem; font-size: 0.9em; }
.prose pre { background-color: #1a202c; color: #e2e8f0; padding: 1em; border-radius: 0.375rem; overflow-x: auto; }
.prose pre code { background-color: transparent; padding: 0; border-radius: 0; font-size: 0.875em; }
/* Loader */
.loader { display: inline-block; position: relative; width: 64px; height: 16px; }
.loader div { position: absolute; top: 6px; width: 10px; height: 10px; border-radius: 50%; background: var(--primary); animation-timing-function: cubic-bezier(0, 1, 1, 0); }
.loader div:nth-child(1) { left: 6px; animation: loader1 0.6s infinite; }
.loader div:nth-child(2) { left: 6px; animation: loader2 0.6s infinite; }
.loader div:nth-child(3) { left: 26px; animation: loader2 0.6s infinite; }
.loader div:nth-child(4) { left: 45px; animation: loader3 0.6s infinite; }
@keyframes loader1 { 0% { transform: scale(0); } 100% { transform: scale(1); } }
@keyframes loader2 { 0% { transform: translate(0, 0); } 100% { transform: translate(20px, 0); } }
@keyframes loader3 { 0% { transform: scale(1); } 100% { transform: scale(0); } }
/* Focus Ring */
.focus-ring { position: relative; }
.focus-ring:focus-within::after { content: ''; position: absolute; top: -3px; left: -3px; right: -3px; bottom: -3px; border-radius: 14px; border: 2px solid rgba(59, 130, 246, 0.3); pointer-events: none; animation: focusIn 0.2s ease-out forwards; }
@keyframes focusIn { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } }
/* DeepThink Checkbox */
.deepthink-label { display: flex; align-items: center; cursor: pointer; font-size: 0.875rem; color: var(--secondary); }
.deepthink-label input[type="checkbox"] { appearance: none; width: 1.25em; height: 1.25em; border: 2px solid #cbd5e0; border-radius: 0.375rem; margin-right: 0.5em; position: relative; top: 1px; transition: all 0.2s ease-in-out; cursor: pointer; flex-shrink: 0; }
.deepthink-label input[type="checkbox"]:checked { background-color: var(--primary); border-color: var(--primary); }
.deepthink-label input[type="checkbox"]:checked::after { content: '\f00c'; font-family: 'Font Awesome 6 Free'; font-weight: 900; color: white; font-size: 0.8em; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
.deepthink-label input[type="checkbox"]:disabled { background-color: #e2e8f0; border-color: #cbd5e0; cursor: not-allowed; }
.deepthink-label input[type="checkbox"]:disabled + span { color: #a0aec0; }
.deepthink-label input[type="checkbox"]:disabled:checked::after { color: #a0aec0; }
.deepthink-label:hover input[type="checkbox"]:not(:disabled) { border-color: var(--primary-dark); }
.deepthink-tooltip { position: relative; display: inline-flex; align-items: center; margin-left: 6px; }
.deepthink-tooltip .tooltip-text { visibility: hidden; width: 200px; background-color: #2d3748; color: #fff; text-align: center; border-radius: 6px; padding: 6px 10px; position: absolute; z-index: 10; bottom: 135%; left: 50%; margin-left: -100px; opacity: 0; transition: opacity 0.3s; font-size: 0.75rem; line-height: 1.4; pointer-events: none; }
.deepthink-tooltip .tooltip-text::after { content: ""; position: absolute; top: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; border-color: #2d3748 transparent transparent transparent; }
.deepthink-tooltip:hover .tooltip-text { visibility: visible; opacity: 1; }
#deepthink-status { font-style: italic; margin-left: 0.5rem; }
</style>
</head>
<body class="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-50 text-gray-800">
<nav class="glassmorphism sticky top-0 z-50 border-b border-blue-100">
<div class="container mx-auto px-4 sm:px-6 py-3 sm:py-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3 sm:space-x-4">
<div class="bg-gradient-to-r from-blue-600 to-blue-800 rounded-lg p-2 shadow-lg transform hover:scale-110 transition-transform duration-300">
<i class="fas fa-robot text-white text-lg sm:text-xl"></i>
</div>
<h1 class="text-xl sm:text-2xl font-bold gradient-text">Mariam AI</h1>
</div>
<div class="text-xs sm:text-sm text-blue-900 font-medium bg-blue-50 py-1 px-3 sm:px-4 rounded-full shadow-sm hidden sm:block">Assistant Français</div>
<div class="text-xs text-blue-900 font-medium bg-blue-50 py-1 px-3 rounded-full shadow-sm sm:hidden">Assistant</div>
</div>
</div>
</nav>
<main class="container mx-auto px-4 sm:px-6 py-8 sm:py-12">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 sm:gap-8">
<!-- Section Travail Argumentatif -->
<div class="card-hover glassmorphism rounded-2xl overflow-hidden scale-in">
<div class="p-6 sm:p-8">
<div class="flex items-center space-x-3 sm:space-x-4 mb-6 sm:mb-8">
<div class="bg-blue-100 rounded-lg p-2 sm:p-3 transform rotate-3">
<i class="fas fa-pen-fancy text-blue-600 text-lg sm:text-xl"></i>
</div>
<h2 class="text-xl sm:text-2xl font-bold text-gray-800">Travail Argumentatif</h2>
</div>
<form id="francais-form" class="space-y-6 sm:space-y-8">
<div class="space-y-2">
<label for="sujet-francais" class="block text-sm font-medium text-gray-700 flex items-center">
<i class="fas fa-book-open mr-2 text-blue-500"></i>Sujet <span class="text-red-500 ml-1">*</span>
</label>
<div class="focus-ring">
<textarea id="sujet-francais" name="sujet" rows="4"
class="w-full px-3 py-2 sm:px-4 sm:py-3 rounded-xl border-2 border-gray-200 focus:border-blue-500 focus:outline-none transition-all duration-200 resize-none text-base"
placeholder="Entrez votre sujet ici..." required></textarea>
</div>
<div class="text-xs text-gray-400 text-right" id="character-count">0c</div>
</div>
<div class="space-y-3">
<label class="block text-sm font-medium text-gray-700 flex items-center">
<i class="fas fa-tasks mr-2 text-blue-500"></i>Type d'argumentation
</label>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4">
<label class="relative">
<input type="radio" name="choix" value="Etaye" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer" checked>
<span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Étayer</span>
</label>
<label class="relative">
<input type="radio" name="choix" value="refute" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
<span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Réfuter</span>
</label>
<label class="relative">
<input type="radio" name="choix" value="discuter" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
<span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Discuter</span>
</label>
<label class="relative">
<input type="radio" name="choix" value="dissertation" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
<span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Dissertation</span>
</label>
</div>
</div>
<div class="space-y-3">
<label class="block text-sm font-medium text-gray-700 flex items-center">
<i class="fas fa-feather-alt mr-2 text-blue-500"></i>Style d'écriture
</label>
<div class="grid grid-cols-2 gap-3 sm:gap-4">
<label class="relative">
<input type="radio" name="style" value="raffiné" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer" checked>
<span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Raffiné</span>
</label>
<label class="relative">
<input type="radio" name="style" value="Normal" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
<span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">Normal</span>
</label>
</div>
</div>
<div class="flex items-center justify-start pt-1 sm:pt-2">
<label for="deepthink-checkbox" class="deepthink-label">
<input type="checkbox" id="deepthink-checkbox" name="use_deepthink_visual">
<span class="text-sm">Utiliser DeepThink</span>
<div class="deepthink-tooltip">
<i class="fas fa-info-circle text-gray-400 ml-1"></i>
<span class="tooltip-text">Modèle avancé (Pro). Plus lent, meilleure qualité. Limité: 1/jour.</span>
</div>
</label>
<span id="deepthink-status" class="text-xs text-gray-500 ml-2"></span>
</div>
<button type="submit"
class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white px-5 py-3 sm:px-6 sm:py-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-900 transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg text-base">
<div class="flex items-center justify-center space-x-2 sm:space-x-3">
<i class="fas fa-magic"></i>
<span>Générer</span>
</div>
</button>
</form>
<div id="francais-output" class="mt-6 sm:mt-8 p-4 sm:p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[150px]">
<!-- Le contenu généré sera inséré ici -->
</div>
</div>
</div>
<!-- Section Étude de texte -->
<div class="card-hover glassmorphism rounded-2xl overflow-hidden scale-in" style="animation-delay: 0.1s;">
<div class="p-6 sm:p-8">
<div class="flex items-center space-x-3 sm:space-x-4 mb-6 sm:mb-8">
<div class="bg-blue-100 rounded-lg p-2 sm:p-3 transform -rotate-3">
<i class="fas fa-file-alt text-blue-600 text-lg sm:text-xl"></i>
</div>
<h2 class="text-xl sm:text-2xl font-bold text-gray-800">Étude de texte</h2>
</div>
<form id="etude-texte-form" class="space-y-6 sm:space-y-8" enctype="multipart/form-data">
<div class="space-y-3">
<label class="block text-sm font-medium text-gray-700 flex items-center">
<i class="fas fa-image mr-2 text-blue-500"></i>Image(s) du texte <span class="text-red-500 ml-1">*</span>
</label>
<div class="border-3 border-dashed border-gray-300 rounded-xl p-6 sm:p-8 text-center cursor-pointer hover:border-blue-400 transition-all duration-200 group"
id="drop-zone">
<input type="file" id="image-upload" name="images" accept="image/*" class="hidden" multiple>
<div class="space-y-3 sm:space-y-4">
<div class="bg-blue-50 rounded-full w-14 h-14 sm:w-16 sm:h-16 flex items-center justify-center mx-auto transform transition-transform group-hover:scale-110 group-hover:rotate-6">
<i class="fas fa-cloud-upload-alt text-2xl sm:text-3xl text-blue-500"></i>
</div>
<p class="text-sm text-gray-600 font-medium">Glissez/cliquez pour ajouter</p>
<p class="text-xs text-gray-400">PNG, JPG, WEBP (max 10MB)</p>
</div>
</div>
<div id="image-preview" class="image-preview"></div>
</div>
<button type="submit"
class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white px-5 py-3 sm:px-6 sm:py-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-900 transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg text-base">
<div class="flex items-center justify-center space-x-2 sm:space-x-3">
<i class="fas fa-search"></i>
<span>Analyser</span>
</div>
</button>
</form>
<div id="etude-texte-output" class="mt-6 sm:mt-8 p-4 sm:p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[150px]">
<!-- Le contenu analysé sera inséré ici -->
</div>
</div>
</div>
</div>
<!-- Section Sauvegardes -->
<div class="mt-10 sm:mt-12">
<h2 class="text-xl sm:text-2xl font-bold text-gray-800 mb-4 sm:mb-6 flex items-center">
<i class="fas fa-save mr-2 sm:mr-3 text-blue-500"></i>
Sauvegardes
</h2>
<div id="backups-list" class="space-y-4">
<!-- Les sauvegardes seront listées ici -->
</div>
</div>
</main>
<footer class="bg-gray-50 border-t border-gray-200 py-5 sm:py-6 mt-12 sm:mt-16">
<div class="container mx-auto px-4 sm:px-6">
<div class="flex flex-col md:flex-row justify-between items-center space-y-3 md:space-y-0">
<div class="flex items-center space-x-2">
<div class="bg-blue-600 rounded-lg p-1 shadow">
<i class="fas fa-robot text-white text-sm"></i>
</div>
<span class="text-gray-600 text-sm">Mariam AI</span>
</div>
<div class="flex space-x-4 sm:space-x-6">
<a href="#" class="text-gray-500 hover:text-blue-600 transition-colors text-xs sm:text-sm flex items-center">
<i class="fas fa-question-circle mr-1"></i> Aide
</a>
<a href="#" class="text-gray-500 hover:text-blue-600 transition-colors text-xs sm:text-sm flex items-center">
<i class="fas fa-shield-alt mr-1"></i> Confidentialité
</a>
</div>
</div>
</div>
</footer>
<script>
// --- Constantes ---
const DEEPTHINK_STORAGE_KEY = 'deepThinkLastUsedDate';
// --- Gestionnaire de fichiers ---
const uploadedFiles = new Map();
function initializeFileUpload() {
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('image-upload');
const imagePreview = document.getElementById('image-preview');
if(!dropZone || !fileInput || !imagePreview) return;
dropZone.addEventListener('click', () => fileInput.click());
['dragenter', 'dragover'].forEach(ev => dropZone.addEventListener(ev, highlightDropZone));
['dragleave', 'drop'].forEach(ev => dropZone.addEventListener(ev, unhighlightDropZone));
dropZone.addEventListener('drop', (e) => { e.preventDefault(); handleFiles(e.dataTransfer.files); });
fileInput.addEventListener('change', () => { handleFiles(fileInput.files); fileInput.value = ''; });
function highlightDropZone(e) { e.preventDefault(); dropZone.classList.add('border-blue-500', 'bg-blue-50'); }
function unhighlightDropZone(e) { e.preventDefault(); dropZone.classList.remove('border-blue-500', 'bg-blue-50'); }
function handleFiles(files) {
const dataTransfer = new DataTransfer();
uploadedFiles.forEach(file => dataTransfer.items.add(file));
Array.from(files).forEach(file => {
if (!file.type.startsWith('image/')) return;
const fileId = `${file.name}-${file.size}`;
if (uploadedFiles.has(fileId)) return;
uploadedFiles.set(fileId, file);
dataTransfer.items.add(file);
renderPreview(file, fileId);
});
fileInput.files = dataTransfer.files;
}
function renderPreview(file, fileId) {
const reader = new FileReader();
reader.onload = (e) => {
const previewItem = document.createElement('div');
previewItem.classList.add('image-preview-item', 'scale-in');
previewItem.dataset.fileId = fileId;
previewItem.innerHTML = `<img src="${e.target.result}" alt="${file.name}"><button class="remove-image" title="Supprimer"><i class="fas fa-times"></i></button>`;
imagePreview.appendChild(previewItem);
previewItem.querySelector('.remove-image').addEventListener('click', (e) => { e.stopPropagation(); removeFile(fileId, previewItem); });
};
reader.readAsDataURL(file);
}
function removeFile(fileId, previewElement) {
uploadedFiles.delete(fileId);
const dataTransfer = new DataTransfer();
uploadedFiles.forEach(file => dataTransfer.items.add(file));
fileInput.files = dataTransfer.files;
previewElement.style.opacity = '0';
previewElement.style.transform = 'scale(0.9)';
setTimeout(() => { if (imagePreview.contains(previewElement)) imagePreview.removeChild(previewElement); }, 300);
}
}
// --- Gestion des sauvegardes ---
function sauvegarderReponse(titre, contenu) {
const sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
const dateSauvegarde = new Date().toISOString();
const MAX_SAUVEGARDES = 20;
if (sauvegardes.length >= MAX_SAUVEGARDES) sauvegardes.shift();
sauvegardes.push({ titre: titre || "Sauvegarde", contenu, date: dateSauvegarde });
try {
localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
displayNotification("Sauvegardé", "success");
afficherSauvegardes();
} catch (e) {
console.error("Erreur sauvegarde localStorage:", e);
displayNotification("Erreur sauvegarde: Stockage plein?", "error");
}
}
function displayNotification(message, type = 'success') {
const notification = document.createElement('div');
const colors = { success: 'bg-green-50 border-green-200 text-green-700', error: 'bg-red-50 border-red-200 text-red-700', warning: 'bg-yellow-50 border-yellow-200 text-yellow-700' };
const icons = { success: 'fa-check-circle text-green-500', error: 'fa-exclamation-circle text-red-500', warning: 'fa-exclamation-triangle text-yellow-500' };
notification.className = `fixed bottom-4 right-4 sm:bottom-6 sm:right-6 border ${colors[type] || colors.success} px-4 py-3 rounded-lg shadow-lg z-[100] flex items-center scale-in max-w-[calc(100%-2rem)] sm:max-w-sm`;
notification.innerHTML = `<i class="fas ${icons[type] || icons.success} mr-3 text-lg flex-shrink-0"></i><span class="text-sm font-medium">${message}</span>`;
document.body.appendChild(notification);
const duration = type === 'error' ? 6000 : 3000;
setTimeout(() => {
notification.style.opacity = '0'; notification.style.transform = 'scale(0.9)'; notification.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
setTimeout(() => { if (document.body.contains(notification)) document.body.removeChild(notification); }, 300);
}, duration);
}
function supprimerSauvegarde(originalIndex) {
try {
let sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
if (originalIndex >= 0 && originalIndex < sauvegardes.length) {
sauvegardes.splice(originalIndex, 1);
localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
afficherSauvegardes();
} else { console.error("Index suppression invalide:", originalIndex); displayNotification("Erreur suppression", "error"); }
} catch (e) { console.error("Erreur suppression sauvegarde:", e); displayNotification("Erreur suppression", "error"); }
}
function afficherSauvegardes() {
const backupsList = document.getElementById('backups-list');
if(!backupsList) return;
let sauvegardes = [];
try { sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]'); } catch(e) { console.error("Erreur lecture sauvegardes:", e); }
sauvegardes.sort((a, b) => new Date(b.date) - new Date(a.date));
backupsList.innerHTML = '';
if (sauvegardes.length === 0) {
backupsList.innerHTML = `<div class="bg-blue-50 rounded-lg p-6 sm:p-8 text-center"><div class="bg-white rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4 shadow-sm"><i class="fas fa-folder-open text-blue-300 text-2xl"></i></div><p class="text-gray-600 text-sm sm:text-base">Aucune sauvegarde.</p><p class="text-xs sm:text-sm text-gray-500 mt-2">Vos réponses apparaîtront ici.</p></div>`;
return;
}
sauvegardes.forEach((sauvegarde) => {
const date = new Date(sauvegarde.date);
const formattedDate = date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' });
const displayTitle = (sauvegarde.titre || "Sans titre").length > 40 ? sauvegarde.titre.substring(0, 37) + '...' : (sauvegarde.titre || "Sans titre");
const sauvegardeDiv = document.createElement('div');
sauvegardeDiv.dataset.originalDate = sauvegarde.date;
sauvegardeDiv.className = 'bg-white rounded-lg shadow-md p-4 relative backup-item hover:bg-blue-50 transition-all duration-200';
sauvegardeDiv.innerHTML = `
<div class="flex items-start cursor-pointer item-header">
<div class="bg-blue-100 rounded-lg p-2 mr-3 flex-shrink-0"> <i class="fas fa-file-alt text-blue-600 text-sm"></i> </div>
<div class="flex-grow overflow-hidden mr-2">
<h3 class="text-base font-semibold text-gray-800 truncate" title="${sauvegarde.titre || 'Sans titre'}">${displayTitle}</h3>
<p class="text-xs text-gray-500 mb-1 flex items-center"> <i class="fas fa-clock text-xs mr-1 text-gray-400"></i> ${formattedDate} </p>
</div>
<div class="flex space-x-1 sm:space-x-2 ml-auto flex-shrink-0">
<button class="text-gray-400 hover:text-blue-600 focus:outline-none copy-btn p-1 rounded-full hover:bg-gray-100" title="Copier"> <i class="fas fa-copy text-xs sm:text-sm"></i> </button>
<button class="text-gray-400 hover:text-red-600 focus:outline-none delete-btn p-1 rounded-full hover:bg-gray-100" title="Supprimer"> <i class="fas fa-trash-alt text-xs sm:text-sm"></i> </button>
</div>
</div>
<div class="backup-content text-sm text-gray-700 prose max-w-none border-t border-gray-100"></div>`;
backupsList.appendChild(sauvegardeDiv);
const backupContentDiv = sauvegardeDiv.querySelector('.backup-content');
const headerDiv = sauvegardeDiv.querySelector('.item-header');
headerDiv.addEventListener('click', () => {
const isExpanded = backupContentDiv.classList.contains('backup-content-expanded');
document.querySelectorAll('.backup-content-expanded').forEach(content => { if (content !== backupContentDiv) { content.classList.remove('backup-content-expanded'); content.innerHTML = ''; } });
if (!isExpanded) {
try { backupContentDiv.innerHTML = marked.parse(sauvegarde.contenu || ''); } catch(e) { backupContentDiv.innerHTML = "Erreur d'affichage."; console.error(e); }
backupContentDiv.classList.add('backup-content-expanded');
} else { backupContentDiv.classList.remove('backup-content-expanded'); backupContentDiv.innerHTML = ''; }
});
sauvegardeDiv.querySelector('.copy-btn').addEventListener('click', (e) => {
e.stopPropagation();
navigator.clipboard.writeText(sauvegarde.contenu || '').then(() => {
const icon = e.currentTarget.querySelector('i'); icon.className = 'fas fa-check text-green-500 text-xs sm:text-sm'; setTimeout(() => { icon.className = 'fas fa-copy text-xs sm:text-sm'; }, 1500);
}).catch(err => { console.error('Copy error:', err); displayNotification("Erreur copie", "error"); });
});
sauvegardeDiv.querySelector('.delete-btn').addEventListener('click', (e) => {
e.stopPropagation();
const itemDate = sauvegardeDiv.dataset.originalDate;
let originalIndex = -1;
try { const originalSauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]'); originalIndex = originalSauvegardes.findIndex(s => s.date === itemDate); } catch (err) { console.error("Erreur recherche sauvegarde:", err); }
if (originalIndex === -1) { console.error("Item not found by date:", itemDate); displayNotification("Erreur suppression", "error"); return; }
const confirmationModal = document.createElement('div');
confirmationModal.className = 'fixed inset-0 flex items-center justify-center z-[100] bg-black bg-opacity-50 scale-in p-4';
confirmationModal.innerHTML = `
<div class="bg-white rounded-lg p-5 sm:p-6 max-w-sm w-full shadow-xl">
<div class="flex items-center mb-4"> <div class="bg-red-100 rounded-full p-2 mr-3"><i class="fas fa-exclamation-triangle text-red-500"></i></div> <h3 class="text-base sm:text-lg font-semibold text-gray-800">Confirmation</h3> </div>
<p class="text-gray-600 mb-6 text-sm">Supprimer cette sauvegarde ?</p>
<div class="flex justify-end space-x-3"> <button class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 cancel-btn text-sm">Annuler</button> <button class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 confirm-btn text-sm">Supprimer</button> </div>
</div>`;
document.body.appendChild(confirmationModal);
confirmationModal.querySelector('.cancel-btn').addEventListener('click', () => closeModal(confirmationModal));
confirmationModal.querySelector('.confirm-btn').addEventListener('click', () => { supprimerSauvegarde(originalIndex); closeModal(confirmationModal); });
confirmationModal.addEventListener('click', (event) => { if (event.target === confirmationModal) closeModal(confirmationModal); });
});
});
}
function closeModal(modalElement) {
modalElement.style.opacity = '0'; modalElement.style.transition = 'opacity 0.3s ease';
setTimeout(() => { if (document.body.contains(modalElement)) document.body.removeChild(modalElement); }, 300);
}
// --- Gestion DeepThink ---
function checkDeepThinkAvailability() {
const checkbox = document.getElementById('deepthink-checkbox');
const statusSpan = document.getElementById('deepthink-status');
if (!checkbox || !statusSpan) return;
try {
const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
const todayStr = new Date().toISOString().split('T')[0];
if (lastUsedDateStr === todayStr) { checkbox.checked = false; checkbox.disabled = true; statusSpan.textContent = "(utilisé)"; statusSpan.classList.add('text-red-500'); }
else { checkbox.disabled = false; statusSpan.textContent = ""; statusSpan.classList.remove('text-red-500'); }
} catch (e) { console.error("Erreur localStorage (DeepThink):", e); checkbox.disabled = true; statusSpan.textContent = "(erreur)"; }
}
// --- Soumission des formulaires ---
async function submitFrancaisForm() {
const form = document.getElementById('francais-form');
const output = document.getElementById('francais-output');
const sujetTextarea = document.getElementById('sujet-francais');
const deepThinkCheckbox = document.getElementById('deepthink-checkbox');
const submitButton = form.querySelector('button[type="submit"]');
if(!form || !output || !sujetTextarea || !deepThinkCheckbox || !submitButton) return;
form.addEventListener('submit', async (e) => {
e.preventDefault();
const originalButtonContent = submitButton.innerHTML;
submitButton.disabled = true;
submitButton.innerHTML = `<div class="flex items-center justify-center"><div class="loader"><div></div><div></div><div></div><div></div></div><span class="ml-2 text-sm sm:text-base">Génération...</span></div>`;
const sujetValue = sujetTextarea.value.trim();
const isDeepThinkChecked = deepThinkCheckbox.checked;
// Validation
if (!sujetValue) {
output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Champ obligatoire</p><p>Veuillez entrer un sujet.</p></div></div>`;
sujetTextarea.focus(); sujetTextarea.classList.add('border-red-500'); setTimeout(() => sujetTextarea.classList.remove('border-red-500'), 3000);
submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; return;
}
if (isDeepThinkChecked) {
try {
const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
const todayStr = new Date().toISOString().split('T')[0];
if (lastUsedDateStr === todayStr) {
output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Limite atteinte</p><p>DeepThink déjà utilisé.</p></div></div>`;
checkDeepThinkAvailability(); submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; return;
}
} catch(e) { /* Proceed without check */ }
}
output.innerHTML = `<div class="flex justify-center items-center p-6"><div class="loader"><div></div><div></div><div></div><div></div></div></div>`;
const formData = new FormData(form); formData.append('use_deepthink', isDeepThinkChecked);
try {
const response = await fetch('/api/francais', { method: 'POST', body: formData });
const result = await response.json();
if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
try { output.innerHTML = marked.parse(result.output || ''); } catch (e) { output.innerHTML = "Erreur d'affichage."; console.error(e); }
const titreSauvegarde = sujetValue.substring(0, 40) + (sujetValue.length > 40 ? '...' : '');
const deepThinkSuffix = isDeepThinkChecked ? ' [DT]' : '';
sauvegarderReponse(titreSauvegarde + deepThinkSuffix, result.output);
if (isDeepThinkChecked) {
try { const todayStr = new Date().toISOString().split('T')[0]; localStorage.setItem(DEEPTHINK_STORAGE_KEY, todayStr); checkDeepThinkAvailability(); }
catch(e) { console.error("Erreur sauvegarde date DT:", e); }
}
} catch (error) {
console.error("Erreur soumission (fr):", error);
output.innerHTML = `<div class="alert-message alert-danger"><i class="fas fa-exclamation-circle"></i><div><p>Erreur</p><p>${error.message || "Vérifiez la console."}</p></div></div>`;
} finally { submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; }
});
}
async function submitEtudeTexteForm() {
const form = document.getElementById('etude-texte-form');
const output = document.getElementById('etude-texte-output');
const fileInput = document.getElementById('image-upload');
const submitButton = form.querySelector('button[type="submit"]');
if(!form || !output || !fileInput || !submitButton) return;
form.addEventListener('submit', async (e) => {
e.preventDefault();
const originalButtonContent = submitButton.innerHTML;
submitButton.disabled = true;
submitButton.innerHTML = `<div class="flex items-center justify-center"><div class="loader"><div></div><div></div><div></div><div></div></div><span class="ml-2 text-sm sm:text-base">Analyse...</span></div>`;
if (uploadedFiles.size === 0) {
output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Aucune image</p><p>Ajoutez au moins une image.</p></div></div>`;
submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; return;
}
output.innerHTML = `<div class="flex justify-center items-center p-6"><div class="loader"><div></div><div></div><div></div><div></div></div></div>`;
const formData = new FormData(); uploadedFiles.forEach((file) => formData.append('images', file, file.name));
try {
const response = await fetch('/api/etude-texte', { method: 'POST', body: formData });
const result = await response.json();
if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
try { output.innerHTML = marked.parse(result.output || ''); } catch (e) { output.innerHTML = "Erreur d'affichage."; console.error(e); }
const titre = `Analyse img ${new Date().toLocaleDateString('fr-FR', { month: 'short', day: 'numeric' })}`;
sauvegarderReponse(titre, result.output);
} catch (error) {
console.error("Erreur soumission (img):", error);
output.innerHTML = `<div class="alert-message alert-danger"><i class="fas fa-exclamation-circle"></i><div><p>Erreur</p><p>${error.message || "Vérifiez la console."}</p></div></div>`;
} finally { submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; }
});
}
// --- Animations & Améliorations UI ---
function animateOnScroll() {
const cards = document.querySelectorAll('.card-hover');
if (!('IntersectionObserver' in window)) { cards.forEach(card => card.style.opacity = '1'); return; }
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => { if (entry.isIntersecting) { requestAnimationFrame(() => { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; }); observer.unobserve(entry.target); } });
}, { threshold: 0.05 });
cards.forEach(card => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; observer.observe(card); });
}
function enhanceTextareaFocus() {
const textarea = document.getElementById('sujet-francais'); const counter = document.getElementById('character-count'); if(!textarea || !counter) return;
textarea.addEventListener('input', () => { const length = textarea.value.length; counter.textContent = `${length}c`; textarea.classList.remove('border-red-500'); });
}
function enhanceRadioButtons() {
document.querySelectorAll('input[type="radio"] + span').forEach(label => {
const input = label.previousElementSibling; if (input.disabled) return;
label.addEventListener('mouseenter', () => { if (!input.checked) label.classList.add('border-blue-400', 'shadow-md', 'transform' ,'-translate-y-px'); });
label.addEventListener('mouseleave', () => { if (!input.checked) label.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-px'); });
input.addEventListener('change', () => {
const groupName = input.name;
document.querySelectorAll(`input[name="${groupName}"] + span`).forEach(otherLabel => {
if (otherLabel !== label) otherLabel.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-px', 'border-primary');
else label.classList.remove('shadow-md', 'transform', '-translate-y-px');
});
});
});
}
// --- Initialisation Générale ---
document.addEventListener('DOMContentLoaded', () => {
initializeFileUpload();
submitFrancaisForm();
submitEtudeTexteForm();
animateOnScroll();
enhanceTextareaFocus();
enhanceRadioButtons();
checkDeepThinkAvailability();
afficherSauvegardes();
// MODIFICATION: Empêcher le menu contextuel (clic droit)
document.addEventListener('contextmenu', function(event) {
// Optionnel: Autoriser le clic droit sur les inputs/textareas
// if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
// return;
// }
event.preventDefault();
});
// MODIFICATION: Empêcher le début de sélection (complément CSS)
document.addEventListener('selectstart', function(event) {
// Autoriser la sélection dans les inputs et textareas
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
return;
}
// Optionnel: Autoriser la sélection dans les zones de sortie générées
// const outputIds = ['francais-output', 'etude-texte-output'];
// let parent = event.target.closest('.prose'); // Check if selection starts within a prose element
// if (parent && outputIds.includes(parent.id)) {
// return;
// }
// Optionnel: Autoriser la sélection dans les sauvegardes affichées
// if (event.target.closest('.backup-content')) {
// return;
// }
event.preventDefault();
});
// Welcome Message
try {
const showWelcome = sessionStorage.getItem('welcomeShown') !== 'true';
if (showWelcome) {
const welcomeContainer = document.createElement('div');
welcomeContainer.innerHTML = `<div class="fixed bottom-4 right-4 sm:bottom-6 sm:right-6 bg-white rounded-xl shadow-lg p-4 transform transition-all duration-500 opacity-0 translate-y-4 max-w-[calc(100%-2rem)] sm:max-w-xs z-[100]"><div class="flex items-start space-x-3"><div class="bg-gradient-to-r from-blue-500 to-blue-700 rounded-full p-2 mt-1 flex-shrink-0"><i class="fas fa-robot text-white text-lg"></i></div><div><p class="text-sm font-semibold text-gray-800">Bienvenue !</p><p class="text-xs text-gray-500 mt-1">Assistant prêt.</p></div></div><button class="absolute top-1 right-1 text-gray-400 hover:text-gray-600 close-welcome p-1"><i class="fas fa-times text-xs"></i></button></div>`;
document.body.appendChild(welcomeContainer);
const welcomeMsg = welcomeContainer.firstElementChild;
setTimeout(() => { welcomeMsg.classList.remove('opacity-0', 'translate-y-4'); }, 500);
welcomeMsg.querySelector('.close-welcome').addEventListener('click', () => closeWelcomeMessage(welcomeMsg));
setTimeout(() => { if (document.body.contains(welcomeMsg)) closeWelcomeMessage(welcomeMsg); }, 6000);
sessionStorage.setItem('welcomeShown', 'true');
}
} catch(e) { console.warn("SessionStorage (Welcome):", e); }
function closeWelcomeMessage(element) {
element.classList.add('opacity-0', 'translate-y-4'); element.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
setTimeout(() => { if (element?.parentElement) element.parentElement.remove(); }, 300);
}
});
</script>
</body>
</html>