diff --git a/README.md b/README.md
index c0dd61c..7ec43f9 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,7 @@ IAProf
5. Appliquez les migrations :
```
+ python manage.py makemigrations main
python manage.py migrate
```
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..e69de29
diff --git a/main/openaiApprecioations copy.py b/main/openaiApprecioations copy.py
deleted file mode 100644
index 2d4ab06..0000000
--- a/main/openaiApprecioations copy.py
+++ /dev/null
@@ -1,129 +0,0 @@
-from openai import OpenAI
-from PyPDF2 import PdfReader
-from django.conf import settings
-
-liste_eleves = {}
-client = OpenAI(api_key=settings.OPENAI_API_KEY)
-
-def split_by_eleve(text):
- """
- Découpe le texte en une liste de chaînes, chaque élément commençant par 'ELEVE'.
- """
- import re
- # On split sur chaque occurrence de 'ELEVE' suivie d'un ou plusieurs chiffres
- parts = re.split(r'(ELEVE\d+)', text)
- result = []
- for i in range(1, len(parts), 2):
- # parts[i] est 'ELEVEid', parts[i+1] est le texte associé
- bloc = parts[i] + parts[i+1] if i+1 < len(parts) else parts[i]
- result.append(bloc.strip())
- return result
-
-def anonymiserPdf(file, eleves):
- eleve_id = 1
- pages_content = ""
-
- for page in file.pages:
- lines = page.extract_text().split('\n')
- curent_page = []
- write = False
- eleve_found = False
-
- for i, line in enumerate(lines):
- # Attendre la ligne qui se termine par " Trimestre"
- if not write:
- if line.strip().endswith("Trimestre"):
- write = True
- continue
-
- # Enregistrer la ligne suivante (nom de l'élève)
- if write and not eleve_found:
- eleve_nom = line.strip()
- if eleve_nom not in eleves:
- eleves[eleve_nom] = f"ELEVE{eleve_id}"
- eleve_id += 1
- curent_page.append(eleves[eleve_nom] + "\n")
- eleve_found = True
- continue
-
- # Attendre la ligne qui commence par "Appréciations"
- if eleve_found:
- if line.strip().startswith("Appréciations"):
- # Commencer à écrire les lignes suivantes
- for l in lines[i+1:]:
- if l.startswith("M.") or l.startswith("Mme"):
- continue
- curent_page.append(l + "\n")
- break # Fin du traitement de cette page
-
- pages_content += "".join(curent_page)
- return pages_content
-
-def get_eleve_name(text, eleves):
- """
- Extrait le nom de l'élève à partir du texte.
- """
- for line in text.split('\n'):
- line = line.strip()
- if line in eleves:
- return eleves[line]
- return None
-
-def replace_eleve_ids_with_names(text, eleves):
- # Inverse the eleves dict to map ELEVEid -> nom prénom
- id_to_name = {v: k for k, v in eleves.items()}
- # Trier les IDs par longueur décroissante pour éviter les remplacements partiels
- for eleve_id in sorted(id_to_name.keys(), key=len, reverse=True):
- nom = id_to_name[eleve_id]
- if eleve_id in text:
- text = text.replace(eleve_id, nom)
- return text
-
-def generatationAppreciations(appreciations_path, modele_path=None):
-
- if (modele_path):
- modele = PdfReader(modele_path)
- modele_anonyme = anonymiserPdf(modele, liste_eleves)
-
- appreciations = PdfReader(appreciations_path)
- appreciation_anonyme = anonymiserPdf(appreciations, liste_eleves)
- tableau_appreciations = split_by_eleve(appreciation_anonyme)
-
- responses = []
- #debug
- tableau_appreciations = tableau_appreciations[:3]
- for eleve in tableau_appreciations:
- if modele_path:
- # Si un modèle est fourni, on utilise le modèle pour chaque élève
- data = [
- {
- "role": "developer",
- "content": "Tu dois générer des appréciations générales de bulletins avec le style de l'exemple suivant (500 caractères max)"
- },{
- "role": "assistant",
- "content": modele_anonyme
- },
- {
- "role": "user",
- "content": eleve
- }]
- else:
- data = [
- {
- "role": "developer",
- "content": "Tu dois générer des appréciations générales de bulletins (500 caractères max)"
- },
- {
- "role": "user",
- "content": eleve
- }]
- completion = client.chat.completions.create(
- model="gpt-4.1-mini",
- messages=data,
- temperature=1,
- top_p=1)
- resultat = "" + get_eleve_name(completion.choices[0].message.content, liste_eleves) + "\n"
- resultat += replace_eleve_ids_with_names(completion.choices[0].message.content, liste_eleves)
- responses.append(resultat)
-
- return responses
diff --git a/main/templates/export_pdf.js b/main/templates/export_pdf.js
deleted file mode 100644
index 1a31bc9..0000000
--- a/main/templates/export_pdf.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// CDN jsPDF + autoTable pour export PDF
-document.addEventListener('DOMContentLoaded', function() {
- if (!window.jspdfLoaded) {
- const script1 = document.createElement('script');
- script1.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
- script1.onload = function() {
- const script2 = document.createElement('script');
- script2.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js';
- script2.onload = function() { window.jspdfLoaded = true; };
- document.body.appendChild(script2);
- };
- document.body.appendChild(script1);
- }
-});
-
-function exportTableToPDF() {
- if (!window.jspdfLoaded) {
- alert('Les librairies PDF ne sont pas encore chargées. Veuillez réessayer dans quelques secondes.');
- return;
- }
- const { jsPDF } = window.jspdf;
- const doc = new jsPDF();
- doc.text('Tableau des appréciations', 14, 14);
- const table = document.getElementById('appreciations-table');
- const rows = Array.from(table.querySelectorAll('tbody tr'))
- .map(tr => [
- tr.querySelector('.eleve')?.textContent.trim() || '',
- tr.querySelector('.appreciation')?.textContent.trim() || ''
- ]);
- doc.autoTable({
- head: [['Élève', 'Appréciation']],
- body: rows,
- startY: 20,
- styles: { fontSize: 10, cellWidth: 'wrap' },
- headStyles: { fillColor: [55, 90, 127] }
- });
- doc.save('appreciations.pdf');
-}
diff --git a/main/templates/generation.html b/main/templates/generation.html
index 43a20ce..3c977b4 100644
--- a/main/templates/generation.html
+++ b/main/templates/generation.html
@@ -60,11 +60,77 @@
[class*="bg-"] {
background-color: inherit !important;
}
- /* Forcer le texte noir sur fond blanc pour le modal */
- #processingModal .modal-content, #processingModal .modal-header, #processingModal .modal-body, #processingModal p {
+ #processingModal .modal-content, #processingModal .modal-header, #processingModal .modal-body, #processingModal p, #processingModal .modal-body * {
background-color: #fff !important;
color: #212529 !important;
}
+ @media (prefers-color-scheme: dark) {
+ #processingModal .modal-content, #processingModal .modal-header, #processingModal .modal-body, #processingModal p, #processingModal .modal-body * {
+ background-color: #fff !important;
+ color: #212529 !important;
+ }
+ }
+ #btn-aide {
+ background-color: #ffe082 !important;
+ color: #23272b !important;
+ border: 1px solid #444c56 !important;
+ font-weight: 600;
+ }
+ @media (prefers-color-scheme: dark) {
+ #btn-aide {
+ background-color: #ffe082 !important;
+ color: #23272b !important;
+ border: 1px solid #444c56 !important;
+ font-weight: 600;
+ }
+ }
+ }
+ #aideModal .modal-content, #aideModal .modal-header, #aideModal .modal-body, #aideModal p, #aideModal .modal-body * {
+ background-color: #fff !important;
+ color: #212529 !important;
+ }
+ @media (prefers-color-scheme: dark) {
+ #aideModal .modal-content, #aideModal .modal-header, #aideModal .modal-body, #aideModal p, #aideModal .modal-body * {
+ background-color: #fff !important;
+ color: #212529 !important;
+ }
+ }
+ html, body {
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ overflow: hidden;
+ }
+ body {
+ min-height: 100vh;
+ overflow: hidden;
+ }
+ #layoutSidenav {
+ min-height: 0;
+ height: 100%;
+ overflow: hidden;
+ }
+ #layoutSidenav_content {
+ min-height: 0;
+ height: 100%;
+ overflow: hidden;
+ }
+ main {
+ min-height: 0;
+ height: 100%;
+ overflow: hidden;
+ }
+ .container-fluid {
+ min-height: 0;
+ overflow: hidden;
+ }
+ .card.shadow.mb-4 {
+ min-height: 0;
+ overflow: hidden;
+ }
+ .card-body {
+ min-height: 0;
+ overflow: hidden;
}
@@ -83,8 +149,25 @@