Mariam-cards / templates /index.html
Docfile's picture
Update templates/index.html
8e9eed4 verified
raw
history blame
13.6 kB
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Flashcards Generator</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.2/axios.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
body {
font-family: 'Space Grotesk', sans-serif;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
}
.glass-morph {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.card-hover {
transition: all 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
}
.gradient-text {
background: linear-gradient(45deg, #60a5fa, #a855f7);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.custom-loader {
width: 50px;
height: 50px;
border: 3px solid #fff;
border-radius: 50%;
display: inline-block;
position: relative;
box-sizing: border-box;
animation: rotation 1s linear infinite;
}
.custom-loader::after {
content: '';
box-sizing: border-box;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 40px;
height: 40px;
border-radius: 50%;
border: 3px solid transparent;
border-bottom-color: #60a5fa;
}
.custom-file-input {
position: relative;
display: inline-block;
width: 100%;
}
.custom-file-input input[type="file"] {
opacity: 0;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
.custom-file-label {
padding: 1rem 1.5rem;
background: rgba(255, 255, 255, 0.05);
border: 2px dashed rgba(255, 255, 255, 0.2);
border-radius: 0.75rem;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.custom-file-input:hover .custom-file-label {
border-color: rgba(96, 165, 250, 0.5);
background: rgba(96, 165, 250, 0.1);
}
@keyframes rotation {
0% { transform: rotate(0deg) }
100% { transform: rotate(360deg) }
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
</style>
</head>
<body class="min-h-screen text-gray-100">
<div id="app" class="container mx-auto px-4 py-12">
<!-- Hero Section -->
<div class="text-center mb-16 animate__animated animate__fadeIn">
<h1 class="text-5xl font-bold mb-4 gradient-text">AI Flashcards Generator</h1>
<p class="text-xl text-gray-400 mb-8">Transformez vos sujets en cartes d'apprentissage intelligentes</p>
</div>
<!-- Main Content -->
<div class="max-w-4xl mx-auto">
<!-- Input Section -->
<div class="glass-morph rounded-2xl p-8 mb-12 card-hover">
<form @submit.prevent="generateFlashcards" enctype="multipart/form-data">
<div class="mb-6">
<label for="topic" class="block text-lg font-medium mb-3 text-gray-300">
Quel sujet souhaitez-vous explorer ?
</label>
<div class="relative">
<input
type="text"
id="topic"
v-model="topic"
class="w-full px-6 py-4 bg-gray-800/50 rounded-xl border border-gray-700 focus:ring-2 focus:ring-blue-500 focus:border-transparent text-lg transition-all duration-300"
placeholder="Ex: Intelligence Artificielle, Quantum Computing..."
:disabled="isLoading"
>
</div>
</div>
<div class="mb-8">
<label class="block text-lg font-medium mb-3 text-gray-300">
Document PDF (optionnel)
</label>
<div class="custom-file-input">
<input
type="file"
ref="fileInput"
accept=".pdf"
@change="handleFileChange"
:disabled="isLoading"
>
<div class="custom-file-label">
<div v-if="selectedFile">
<span class="text-blue-400">[[selectedFile.name]]</span>
</div>
<div v-else>
<span class="text-gray-400">Glissez votre fichier PDF ici ou cliquez pour sélectionner</span>
</div>
</div>
</div>
</div>
<button
type="submit"
:disabled="isLoading"
class="w-full bg-gradient-to-r from-blue-500 to-purple-600 text-white py-4 px-8 rounded-xl font-medium text-lg hover:opacity-90 transition-all duration-300 flex items-center justify-center space-x-3"
>
<span v-if="!isLoading">Générer les Flashcards</span>
<span v-else class="custom-loader"></span>
</button>
</form>
</div>
<!-- Error Message -->
<transition name="fade">
<div v-if="error" class="mb-8 animate__animated animate__shakeX">
<div class="bg-red-500/20 border border-red-500/50 text-red-300 px-6 py-4 rounded-xl">
[[error]]
</div>
</div>
</transition>
<!-- Results Section -->
<transition name="fade">
<div v-if="flashcards.length > 0" class="glass-morph rounded-2xl overflow-hidden">
<!-- Tabs -->
<div class="border-b border-gray-700/50">
<div class="flex">
<button
v-for="tab in ['study', 'json']"
:key="tab"
@click="activeTab = tab"
:class="[
'px-8 py-4 font-medium text-lg transition-all duration-300',
activeTab === tab
? 'gradient-text border-b-2 border-blue-500'
: 'text-gray-400 hover:text-gray-300'
]"
>
[[tab === 'study' ? 'Mode Étude' : 'Mode JSON']]
</button>
</div>
</div>
<!-- Tab Content -->
<div class="p-6">
<!-- Study Mode -->
<div v-if="activeTab === 'study'" class="space-y-6">
<div
v-for="(card, index) in flashcards"
:key="index"
class="glass-morph rounded-xl p-6 card-hover cursor-pointer transition-all duration-300"
@click="card.showAnswer = !card.showAnswer"
>
<div class="flex items-start space-x-4">
<div class="w-12 h-12 flex items-center justify-center rounded-full bg-blue-500/20 text-blue-400 font-bold">
[[index + 1]]
</div>
<div class="flex-1">
<h3 class="text-xl font-medium mb-4">[[card.question]]</h3>
<transition name="fade">
<div v-if="card.showAnswer" class="mt-4 pt-4 border-t border-gray-700/50">
<p class="text-gray-300">[[card.answer]]</p>
</div>
</transition>
</div>
</div>
</div>
</div>
<!-- JSON Mode -->
<div v-if="activeTab === 'json'" class="bg-gray-800/50 rounded-xl p-6 overflow-x-auto">
<pre class="text-gray-300">[[JSON.stringify(flashcards, null, 2)]]</pre>
</div>
</div>
</div>
</transition>
</div>
</div>
<script>
const { createApp } = Vue
createApp({
delimiters: ['[[', ']]'],
data() {
return {
topic: '',
flashcards: [],
activeTab: 'study',
isLoading: false,
error: null,
selectedFile: null
}
},
methods: {
handleFileChange(event) {
const file = event.target.files[0]
if (file) {
if (file.type === 'application/pdf') {
this.selectedFile = file
this.error = null
} else {
this.error = 'Veuillez sélectionner un fichier PDF valide.'
event.target.value = ''
this.selectedFile = null
}
} else {
this.selectedFile = null
}
},
async generateFlashcards() {
if (!this.topic) {
this.error = 'Veuillez entrer un sujet.'
return
}
this.isLoading = true
this.error = null
this.flashcards = []
try {
const formData = new FormData()
formData.append('topic', this.topic)
if (this.selectedFile) {
formData.append('file', this.selectedFile)
}
const response = await axios.post('/generate', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (response.data.success) {
const cards = response.data.flashcards.map(card => ({
...card,
showAnswer: false
}))
setTimeout(() => {
this.flashcards = cards
this.$nextTick(() => {
gsap.from('.card-hover', {
y: 30,
opacity: 0,
duration: 0.5,
stagger: 0.1
})
})
}, 300)
}
} catch (error) {
this.error = error.response?.data?.error || 'Une erreur est survenue lors de la génération.'
} finally {
this.isLoading = false
}
}
},
mounted() {
gsap.from('.gradient-text', {
y: -50,
opacity: 0,
duration: 1,
ease: 'power3.out'
})
}
}).mount('#app')
</script>
</body>
</html>