Spaces:
Running
Running
<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> |