Spaces:
Build error
Build error
<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; | |
} | |
@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; | |
} | |
.flip-card { | |
perspective: 1000px; | |
height: 300px; | |
} | |
.flip-card-inner { | |
position: relative; | |
width: 100%; | |
height: 100%; | |
text-align: center; | |
transition: transform 0.8s; | |
transform-style: preserve-3d; | |
cursor: pointer; | |
} | |
.flip-card.flipped .flip-card-inner { | |
transform: rotateY(180deg); | |
} | |
.flip-card-front, .flip-card-back { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
-webkit-backface-visibility: hidden; | |
backface-visibility: hidden; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
padding: 2rem; | |
border-radius: 1rem; | |
} | |
.flip-card-front { | |
background: rgba(255, 255, 255, 0.05); | |
} | |
.flip-card-back { | |
background: rgba(255, 255, 255, 0.07); | |
transform: rotateY(180deg); | |
} | |
.keyboard-shortcut { | |
display: inline-flex; | |
align-items: center; | |
justify-content: center; | |
min-width: 24px; | |
height: 24px; | |
padding: 0 6px; | |
border-radius: 4px; | |
background: rgba(255, 255, 255, 0.1); | |
font-size: 0.875rem; | |
margin: 0 2px; | |
} | |
.progress-bar { | |
height: 4px; | |
background: rgba(96, 165, 250, 0.2); | |
border-radius: 2px; | |
overflow: hidden; | |
} | |
.progress-value { | |
height: 100%; | |
background: linear-gradient(90deg, #60a5fa, #a855f7); | |
transition: width 0.3s ease; | |
} | |
</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"> | |
<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" | |
@keyup.enter="generateFlashcards" | |
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> | |
<button | |
@click="generateFlashcards" | |
: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> | |
</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"> | |
<!-- Progress Bar --> | |
<div class="progress-bar"> | |
<div class="progress-value" :style="{ width: `${(currentCardIndex + 1) * 100 / flashcards.length}%` }"></div> | |
</div> | |
<!-- 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"> | |
<!-- Keyboard Shortcuts Info --> | |
<div class="text-center mb-6 text-gray-400"> | |
<span class="keyboard-shortcut">←</span> Précédent | |
<span class="keyboard-shortcut mx-2">→</span> Suivant | |
<span class="keyboard-shortcut">Espace</span> Retourner la carte | |
</div> | |
<div class="flip-card" :class="{ 'flipped': currentCard.showAnswer }" @click="toggleCard"> | |
<div class="flip-card-inner"> | |
<div class="flip-card-front"> | |
<div class="text-center"> | |
<div class="text-2xl font-medium mb-4">[[currentCard.question]]</div> | |
<div class="text-gray-400 text-sm">(Cliquez pour voir la réponse)</div> | |
</div> | |
</div> | |
<div class="flip-card-back"> | |
<div class="text-center"> | |
<div class="text-xl">[[currentCard.answer]]</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Navigation Controls --> | |
<div class="flex items-center justify-center space-x-4 mt-8"> | |
<button | |
@click="previousCard" | |
:disabled="currentCardIndex === 0" | |
class="px-6 py-3 bg-blue-500/20 rounded-lg hover:bg-blue-500/30 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300" | |
> | |
← Précédent | |
</button> | |
<div class="text-lg font-medium"> | |
[[currentCardIndex + 1]] / [[flashcards.length]] | |
</div> | |
<button | |
@click="nextCard" | |
:disabled="currentCardIndex === flashcards.length - 1" | |
class="px-6 py-3 bg-blue-500/20 rounded-lg hover:bg-blue-500/30 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-300" | |
> | |
Suivant → | |
</button> | |
</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, | |
currentCardIndex: 0 | |
} | |
}, | |
computed: { | |
currentCard() { | |
return this.flashcards[this.currentCardIndex] || { | |
question: '', | |
answer: '', | |
showAnswer: false | |
} | |
} | |
}, | |
methods: { | |
async generateFlashcards() { | |
if (!this.topic.trim()) { | |
this.error = 'Veuillez entrer un sujet.' | |
return | |
} | |
this.isLoading = true | |
this.error = null | |
this.flashcards = [] | |
this.currentCardIndex = 0 | |
try { | |
const response = await axios.post('/generate', { | |
topic: this.topic | |
}) | |
if (response.data.success) { | |
const cards = response.data.flashcards.map(card => ({ | |
...card, | |
showAnswer: false | |
})) | |
setTimeout(() => { | |
this.flashcards = cards | |
this.$nextTick(() => { | |
gsap.from('.flip-card', { | |
y: 30, | |
opacity: 0, | |
duration: 0.5 | |
}) | |
}) | |
}, 300) | |
} | |
} catch (error) { | |
this.error = error.response?.data?.error || 'Une erreur est survenue lors de la génération.' | |
} finally { | |
this.isLoading = false | |
} | |
}, | |
nextCard() { | |
if (this.currentCardIndex < this.flashcards.length - 1) { | |
this.flashcards[this.currentCardIndex].showAnswer = false | |
this.currentCardIndex++ | |
this.animateCardTransition('next') | |
} | |
}, | |
previousCard() { | |
if (this.currentCardIndex > 0) { | |
this.flashcards[this.currentCardIndex].showAnswer = false | |
this.currentCardIndex-- | |
this.animateCardTransition('prev') | |
} | |
}, | |
toggleCard() { | |
this.flashcards[this.currentCardIndex].showAnswer = !this.flashcards[this.currentCardIndex].showAnswer | |
}, | |
animateCardTransition(direction) { | |
const xOffset = direction === 'next' ? -50 : 50 | |
gsap.fromTo('.flip-card', | |
{ | |
x: xOffset, | |
opacity: 0 | |
}, | |
{ | |
x: 0, | |
opacity: 1, | |
duration: 0.3 | |
} | |
) | |
} | |
}, | |
mounted() { | |
gsap.from('.gradient-text', { | |
y: -50, | |
opacity: 0, | |
duration: 1, | |
ease: 'power3.out' | |
}) | |
window.addEventListener('keydown', (e) => { | |
if (this.activeTab === 'study') { | |
if (e.key === 'ArrowRight') this.nextCard() | |
if (e.key === 'ArrowLeft') this.previousCard() | |
if (e.key === ' ') { | |
e.preventDefault() | |
this.toggleCard() | |
} | |
} | |
}) | |
} | |
}).mount('#app') | |
</script> | |
</body> | |
</html> |