feat: add web interface for math exercise generator
- Create FastAPI backend with endpoints for generating math exercises - Build Bootstrap frontend with responsive UI for exercise configuration - Implement PDF generation functionality identical to original script - Add README with installation and usage instructions - Update requirements.txt with web dependencies - Configure .gitignore to exclude compiled files and generated PDFs
This commit is contained in:
18
.gitignore
vendored
18
.gitignore
vendored
@@ -1 +1,19 @@
|
|||||||
*env
|
*env
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# Generated PDF files
|
||||||
|
app/static/*.pdf
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|||||||
52
README.md
Normal file
52
README.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Générateur d'Exercices de Mathématiques - Version Web
|
||||||
|
|
||||||
|
Application web permettant de générer des exercices de multiplication et division personnalisés au format PDF.
|
||||||
|
|
||||||
|
## Fonctionnalités
|
||||||
|
|
||||||
|
- Génération d'exercices de multiplication et division mélangés
|
||||||
|
- Personnalisation des tables (minimale et maximale)
|
||||||
|
- Choix du nombre d'exercices à générer
|
||||||
|
- Format PDF prêt à l'impression
|
||||||
|
- Mise en page en trois colonnes pour économiser du papier
|
||||||
|
- Interface utilisateur responsive avec Bootstrap
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Installer les dépendances :
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lancement de l'application
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uvicorn app.main:app --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
L'application sera accessible à l'adresse : http://localhost:8000
|
||||||
|
|
||||||
|
## Utilisation
|
||||||
|
|
||||||
|
1. Accédez à l'interface web
|
||||||
|
2. Configurez les paramètres :
|
||||||
|
- Table minimale : la plus petite table de multiplication à inclure
|
||||||
|
- Table maximale : la plus grande table de multiplication à inclure
|
||||||
|
- Nombre d'exercices : nombre total d'exercices à générer
|
||||||
|
3. Cliquez sur "Générer le PDF"
|
||||||
|
4. Téléchargez et imprimez le fichier PDF généré
|
||||||
|
|
||||||
|
## Structure du projet
|
||||||
|
|
||||||
|
- `app/main.py` : Application FastAPI principale
|
||||||
|
- `app/templates/` : Templates HTML
|
||||||
|
- `app/static/` : Fichiers statiques (PDF générés)
|
||||||
|
- `generate_math_exercises.py` : Version originale en ligne de commande
|
||||||
|
|
||||||
|
## Technologies utilisées
|
||||||
|
|
||||||
|
- Python 3
|
||||||
|
- FastAPI (backend)
|
||||||
|
- Bootstrap 5 (frontend)
|
||||||
|
- Jinja2 (templating)
|
||||||
|
- fpdf2 (génération PDF)
|
||||||
203
app/main.py
Normal file
203
app/main.py
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
from typing import List
|
||||||
|
from fastapi import FastAPI, Request, Response
|
||||||
|
from fastapi.responses import HTMLResponse, FileResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from fpdf import FPDF
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
# Mount static files and templates
|
||||||
|
app.mount("/static", StaticFiles(directory="app/static"), name="static")
|
||||||
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
|
|
||||||
|
class MathExercisesPDF(FPDF):
|
||||||
|
def header(self):
|
||||||
|
self.set_font('Helvetica', 'B', 16)
|
||||||
|
self.cell(0, 10, 'Exercices de Multiplication et Division', 0, 1, 'C', new_x="LMARGIN", new_y="NEXT")
|
||||||
|
self.ln(10)
|
||||||
|
|
||||||
|
def footer(self):
|
||||||
|
self.set_y(-15)
|
||||||
|
self.set_font('Helvetica', 'I', 8)
|
||||||
|
self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C', new_x="RIGHT", new_y="TOP")
|
||||||
|
|
||||||
|
class ExerciseRequest(BaseModel):
|
||||||
|
min_table: int
|
||||||
|
max_table: int
|
||||||
|
num_exercises: int = 15
|
||||||
|
|
||||||
|
def generate_exercises(min_table: int, max_table: int, num_exercises: int = 15) -> List[str]:
|
||||||
|
"""Génère des exercices de multiplication et division aléatoires mélangés sans doublons"""
|
||||||
|
exercises = []
|
||||||
|
used_operations = set() # Pour éviter les doublons
|
||||||
|
|
||||||
|
# Générer le nombre exact d'exercices demandé
|
||||||
|
attempts = 0
|
||||||
|
max_attempts = num_exercises * 10 # Limite pour éviter une boucle infinie
|
||||||
|
|
||||||
|
while len(exercises) < num_exercises and attempts < max_attempts:
|
||||||
|
attempts += 1
|
||||||
|
|
||||||
|
# Choisir deux nombres aléatoires entre min_table et max_table
|
||||||
|
a = random.randint(min_table, max_table)
|
||||||
|
b = random.randint(min_table, max_table)
|
||||||
|
result = a * b
|
||||||
|
|
||||||
|
# Choisir aléatoirement le type d'exercice (seulement multiplication ou division)
|
||||||
|
exercise_type = random.choice(['multiplication', 'division'])
|
||||||
|
|
||||||
|
if exercise_type == 'multiplication':
|
||||||
|
# Exercice de multiplication
|
||||||
|
operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération
|
||||||
|
if operation_key not in used_operations:
|
||||||
|
exercise = f"{a} · {b} = ____"
|
||||||
|
exercises.append(exercise)
|
||||||
|
used_operations.add(operation_key)
|
||||||
|
else: # division
|
||||||
|
# Exercice de division
|
||||||
|
divisor = random.choice([a, b])
|
||||||
|
operation_key = f"div_{result}_{divisor}" # Clé unique pour cette opération
|
||||||
|
if operation_key not in used_operations:
|
||||||
|
exercise = f"{result} : {divisor} = ____"
|
||||||
|
exercises.append(exercise)
|
||||||
|
used_operations.add(operation_key)
|
||||||
|
|
||||||
|
# Si nous n'avons pas pu générer suffisamment d'exercices uniques,
|
||||||
|
# compléter avec des variations
|
||||||
|
if len(exercises) < num_exercises:
|
||||||
|
remaining = num_exercises - len(exercises)
|
||||||
|
for _ in range(remaining):
|
||||||
|
# Générer des variations avec des nombres légèrement différents
|
||||||
|
a = random.randint(min_table, max_table)
|
||||||
|
b = random.randint(min_table, max_table)
|
||||||
|
result = a * b
|
||||||
|
|
||||||
|
# Choisir aléatoirement le type d'exercice
|
||||||
|
exercise_type = random.choice(['multiplication', 'division'])
|
||||||
|
|
||||||
|
if exercise_type == 'multiplication':
|
||||||
|
exercise = f"{a} · {b} = ____"
|
||||||
|
else: # division
|
||||||
|
divisor = random.choice([a, b])
|
||||||
|
exercise = f"{result} : {divisor} = ____"
|
||||||
|
|
||||||
|
exercises.append(exercise)
|
||||||
|
|
||||||
|
return exercises
|
||||||
|
|
||||||
|
def create_math_exercises_pdf(min_table: int, max_table: int, num_exercises: int = 15) -> str:
|
||||||
|
"""Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes"""
|
||||||
|
pdf = MathExercisesPDF()
|
||||||
|
pdf.add_page()
|
||||||
|
pdf.set_font('Helvetica', '', 12)
|
||||||
|
|
||||||
|
# Ajouter des informations sur la plage de tables
|
||||||
|
if min_table == max_table:
|
||||||
|
table_info = f'Tables de multiplication et division pour {min_table}'
|
||||||
|
else:
|
||||||
|
table_info = f'Tables de multiplication et division de {min_table} à {max_table}'
|
||||||
|
|
||||||
|
pdf.cell(0, 10, table_info, 0, 1, 'C', new_x="LMARGIN", new_y="NEXT")
|
||||||
|
pdf.ln(5)
|
||||||
|
|
||||||
|
# Générer les exercices
|
||||||
|
exercises = generate_exercises(min_table, max_table, num_exercises)
|
||||||
|
|
||||||
|
# Pas d'en-têtes de colonnes
|
||||||
|
|
||||||
|
# Répartir les exercices en 3 colonnes
|
||||||
|
num_per_column = (num_exercises + 2) // 3 # Arrondi vers le haut
|
||||||
|
col1_exercises = exercises[:num_per_column]
|
||||||
|
col2_exercises = exercises[num_per_column:num_per_column*2]
|
||||||
|
col3_exercises = exercises[num_per_column*2:]
|
||||||
|
|
||||||
|
# Ajouter les exercices numérotés de 1 à n dans les colonnes
|
||||||
|
max_rows = max(len(col1_exercises), len(col2_exercises), len(col3_exercises))
|
||||||
|
|
||||||
|
for i in range(max_rows):
|
||||||
|
# Colonne 1
|
||||||
|
if i < len(col1_exercises):
|
||||||
|
exercise_num = i + 1
|
||||||
|
col1_text = f"{exercise_num}. {col1_exercises[i]}"
|
||||||
|
else:
|
||||||
|
col1_text = ""
|
||||||
|
|
||||||
|
# Colonne 2
|
||||||
|
if i < len(col2_exercises):
|
||||||
|
exercise_num = i + 1 + len(col1_exercises)
|
||||||
|
col2_text = f"{exercise_num}. {col2_exercises[i]}"
|
||||||
|
else:
|
||||||
|
col2_text = ""
|
||||||
|
|
||||||
|
# Colonne 3
|
||||||
|
if i < len(col3_exercises):
|
||||||
|
exercise_num = i + 1 + len(col1_exercises) + len(col2_exercises)
|
||||||
|
col3_text = f"{exercise_num}. {col3_exercises[i]}"
|
||||||
|
else:
|
||||||
|
col3_text = ""
|
||||||
|
|
||||||
|
# Ajouter la ligne
|
||||||
|
pdf.cell(60, 10, col1_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP")
|
||||||
|
pdf.cell(60, 10, col2_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP")
|
||||||
|
pdf.cell(60, 10, col3_text, 0, 1, 'L', new_x="LMARGIN", new_y="NEXT")
|
||||||
|
|
||||||
|
# Sauvegarder le PDF
|
||||||
|
if min_table == max_table:
|
||||||
|
filename = f'app/static/exercices_mathematiques_table_{min_table}_{num_exercises}_exercices.pdf'
|
||||||
|
else:
|
||||||
|
filename = f'app/static/exercices_mathematiques_tables_{min_table}_a_{max_table}_{num_exercises}_exercices.pdf'
|
||||||
|
pdf.output(filename)
|
||||||
|
return filename
|
||||||
|
|
||||||
|
@app.get("/", response_class=HTMLResponse)
|
||||||
|
async def read_root(request: Request):
|
||||||
|
return templates.TemplateResponse("index.html", {"request": request})
|
||||||
|
|
||||||
|
@app.post("/generate")
|
||||||
|
async def generate_exercises_endpoint(request: ExerciseRequest):
|
||||||
|
try:
|
||||||
|
if request.min_table < 1 or request.max_table < 1:
|
||||||
|
return {"error": "Les tables doivent être supérieures à 0"}
|
||||||
|
if request.min_table > request.max_table:
|
||||||
|
return {"error": "La table minimale doit être inférieure ou égale à la table maximale"}
|
||||||
|
if request.num_exercises < 1:
|
||||||
|
return {"error": "Le nombre d'exercices doit être supérieur à 0"}
|
||||||
|
|
||||||
|
filename = create_math_exercises_pdf(
|
||||||
|
request.min_table,
|
||||||
|
request.max_table,
|
||||||
|
request.num_exercises
|
||||||
|
)
|
||||||
|
|
||||||
|
# Extract just the filename for the download link
|
||||||
|
pdf_filename = os.path.basename(filename)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": "PDF généré avec succès!",
|
||||||
|
"pdf_filename": pdf_filename,
|
||||||
|
"min_table": request.min_table,
|
||||||
|
"max_table": request.max_table,
|
||||||
|
"num_exercises": request.num_exercises
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
return {"error": f"Erreur lors de la création du PDF: {str(e)}"}
|
||||||
|
|
||||||
|
@app.get("/download/{filename}")
|
||||||
|
async def download_pdf(filename: str):
|
||||||
|
file_path = f"app/static/{filename}"
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
return FileResponse(
|
||||||
|
path=file_path,
|
||||||
|
media_type='application/pdf',
|
||||||
|
filename=filename
|
||||||
|
)
|
||||||
|
return {"error": "File not found"}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||||
219
app/templates/index.html
Normal file
219
app/templates/index.html
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Générateur d'Exercices de Mathématiques</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
.card {
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||||
|
transition: 0.3s;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.card:hover {
|
||||||
|
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
.exercise-result {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 15px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.feature-icon {
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container mt-5 mb-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="text-center mb-5">
|
||||||
|
<h1 class="display-4 fw-bold">Générateur d'Exercices de Mathématiques</h1>
|
||||||
|
<p class="lead">Créez des exercices de multiplication et division personnalisés en PDF</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">Paramètres des Exercices</h5>
|
||||||
|
<form id="exerciseForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="minTable" class="form-label">Table minimale</label>
|
||||||
|
<input type="number" class="form-control" id="minTable" min="1" max="12" value="1" required>
|
||||||
|
<div class="form-text">La plus petite table de multiplication à inclure</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="maxTable" class="form-label">Table maximale</label>
|
||||||
|
<input type="number" class="form-control" id="maxTable" min="1" max="12" value="10" required>
|
||||||
|
<div class="form-text">La plus grande table de multiplication à inclure</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="numExercises" class="form-label">Nombre d'exercices</label>
|
||||||
|
<input type="number" class="form-control" id="numExercises" min="1" max="100" value="15" required>
|
||||||
|
<div class="form-text">Nombre total d'exercices à générer (entre 1 et 100)</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<button type="submit" class="btn btn-primary btn-lg" id="generateBtn">
|
||||||
|
<span id="buttonText">Générer le PDF</span>
|
||||||
|
<span id="spinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="resultContainer" class="exercise-result d-none">
|
||||||
|
<h5>Résultat</h5>
|
||||||
|
<div id="resultMessage"></div>
|
||||||
|
<div id="downloadLinkContainer" class="mt-3 d-none">
|
||||||
|
<a id="downloadLink" class="btn btn-success" href="#" download>Télécharger le PDF</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="feature-icon bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-x-lg text-primary" viewBox="0 0 16 16">
|
||||||
|
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="fw-bold">Multiplications</h5>
|
||||||
|
<p>Exercices de multiplication avec des opérateurs aléatoires de la table spécifiée.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="feature-icon bg-success bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-slash-lg text-success" viewBox="0 0 16 16">
|
||||||
|
<path d="M3.5 1.5l9 13"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="fw-bold">Divisions</h5>
|
||||||
|
<p>Exercices de division correspondants aux tables de multiplication sélectionnées.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="feature-icon bg-info bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-file-earmark-pdf text-info" viewBox="0 0 16 16">
|
||||||
|
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2zM9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5v2z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="fw-bold">Format PDF</h5>
|
||||||
|
<p>Générez des fichiers PDF prêts à imprimer avec une mise en page organisée.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class="feature-icon bg-warning bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-columns-gap text-warning" viewBox="0 0 16 16">
|
||||||
|
<path d="M6 1H1v3h5V1zM1 0a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1V1a1 1 0 0 0-1-1H1zm14 12h-5v3h5v-3zm-5-1a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-5zM6 8H1v7h5V8zm-1 1a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H5zm9-9h-5v5h5V1zm-1 1a1 1 0 0 0-1 1v3a1 1 0 0 0 1 1h5a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1h-5z"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h5 class="fw-bold">Trois Colonnes</h5>
|
||||||
|
<p>Les exercices sont présentés en trois colonnes pour économiser du papier.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="bg-light text-center text-muted py-4 mt-5">
|
||||||
|
<div class="container">
|
||||||
|
<p>Générateur d'Exercices de Mathématiques © 2023</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.getElementById('exerciseForm').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const minTable = parseInt(document.getElementById('minTable').value);
|
||||||
|
const maxTable = parseInt(document.getElementById('maxTable').value);
|
||||||
|
const numExercises = parseInt(document.getElementById('numExercises').value);
|
||||||
|
|
||||||
|
const generateBtn = document.getElementById('generateBtn');
|
||||||
|
const buttonText = document.getElementById('buttonText');
|
||||||
|
const spinner = document.getElementById('spinner');
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
buttonText.textContent = 'Génération en cours...';
|
||||||
|
spinner.classList.remove('d-none');
|
||||||
|
generateBtn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/generate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
min_table: minTable,
|
||||||
|
max_table: maxTable,
|
||||||
|
num_exercises: numExercises
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
const resultContainer = document.getElementById('resultContainer');
|
||||||
|
const resultMessage = document.getElementById('resultMessage');
|
||||||
|
const downloadLinkContainer = document.getElementById('downloadLinkContainer');
|
||||||
|
const downloadLink = document.getElementById('downloadLink');
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
resultMessage.innerHTML = `<div class="alert alert-danger">${result.error}</div>`;
|
||||||
|
downloadLinkContainer.classList.add('d-none');
|
||||||
|
} else {
|
||||||
|
resultMessage.innerHTML = `
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<h6>${result.message}</h6>
|
||||||
|
<ul class="mb-0">
|
||||||
|
<li>Tables de ${result.min_table} à ${result.max_table}</li>
|
||||||
|
<li>${result.num_exercises} exercices générés</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
downloadLink.href = `/download/${result.pdf_filename}`;
|
||||||
|
downloadLinkContainer.classList.remove('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
resultContainer.classList.remove('d-none');
|
||||||
|
} catch (error) {
|
||||||
|
const resultContainer = document.getElementById('resultContainer');
|
||||||
|
const resultMessage = document.getElementById('resultMessage');
|
||||||
|
|
||||||
|
resultMessage.innerHTML = `<div class="alert alert-danger">Erreur: ${error.message}</div>`;
|
||||||
|
resultContainer.classList.remove('d-none');
|
||||||
|
} finally {
|
||||||
|
// Reset button state
|
||||||
|
buttonText.textContent = 'Générer le PDF';
|
||||||
|
spinner.classList.add('d-none');
|
||||||
|
generateBtn.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -2,3 +2,7 @@ defusedxml==0.7.1
|
|||||||
fonttools==4.59.2
|
fonttools==4.59.2
|
||||||
fpdf2==2.8.4
|
fpdf2==2.8.4
|
||||||
pillow==11.3.0
|
pillow==11.3.0
|
||||||
|
fastapi==0.115.0
|
||||||
|
uvicorn==0.30.6
|
||||||
|
jinja2==3.1.4
|
||||||
|
python-multipart==0.0.9
|
||||||
|
|||||||
Reference in New Issue
Block a user