You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

288 lines
12 KiB

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>IAProf</title>
<link href="https://cdn.jsdelivr.net/npm/startbootstrap-sb-admin@7.0.6/dist/css/styles.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
:root {
color-scheme: light dark;
}
body, .bg-light {
background-color: #f8f9fa;
color: #212529;
}
@media (prefers-color-scheme: dark) {
html, body, .bg-light {
background-color: #181a1b !important;
color: #f8f9fa !important;
}
.card, .card-header, .card-body {
background-color: #23272b !important;
color: #f8f9fa !important;
}
.table, .table-bordered {
color: #f8f9fa;
background-color: #23272b !important;
border-color: #444c56 !important;
}
.table-bordered th, .table-bordered td {
border-color: #444c56 !important;
background-color: #23272b !important;
color: #f8f9fa !important;
}
.table-striped > tbody > tr:nth-of-type(odd) {
background-color: #20232a !important;
}
.form-select, .form-label, .btn, .alert {
background-color: #23272b !important;
color: #f8f9fa !important;
border-color: #444c56 !important;
}
.btn-primary {
background-color: #375a7f !important;
border-color: #375a7f !important;
}
.btn-danger {
background-color: #c9302c !important;
border-color: #c9302c !important;
}
.btn-success {
background-color: #449d44 !important;
border-color: #449d44 !important;
}
.navbar, .navbar-dark, .bg-dark {
background-color: #23272b !important;
}
.alert-warning {
background-color: #3a3a3a !important;
color: #ffe082 !important;
border-color: #444c56 !important;
}
[class*="bg-"] {
background-color: inherit !important;
}
#credit-restant {
background-color: #ffe082 !important;
color: #23272b !important;
border: 1px solid #444c56 !important;
}
}
</style>
</head>
<body class="bg-light">
<nav class="navbar navbar-expand navbar-dark bg-dark">
<a class="navbar-brand ps-3" href="/generation/">IAProf</a>
<ul class="navbar-nav ms-auto me-3">
<li class="nav-item">
<a class="nav-link" href="/logout/">Se déconnecter</a>
</li>
</ul>
</nav>
<div id="layoutSidenav">
<div id="layoutSidenav_content">
<main>
<div class="container-fluid px-4 mt-4">
<div class="card shadow mb-4">
<div class="card-header py-3">
<h2 class="m-0 font-weight-bold text-primary">Génération des appréciations</h2>
</div>
<div class="card-body">
<div class="mb-3 d-flex justify-content-between align-items-center">
<div>
<label for="modele-select" class="form-label">Choisir un modèle</label>
<select id="modele-select" class="form-select">
{% for modele in modeles %}
<option value="{{ modele.code }}" {% if modele.code == 'gpt-4.1-mini' %}selected{% endif %}>{{ modele.nom }}</option>
{% endfor %}
{% if not modeles or not modeles|dictsort:'code'|length %}
<option value="gpt-4.1-mini" selected>gpt-4.1-mini</option>
{% endif %}
</select>
</div>
<div>
<span class="badge bg-info text-dark" id="credit-restant">Crédits restants : {{ credit }}</span>
</div>
</div>
<div class="mb-3 d-flex gap-2">
<button id="generation-toggle" class="btn btn-primary mb-3">Lancer la génération</button>
<button id="export-pdf" class="btn btn-secondary mb-3" type="button" onclick="exportTableToPDF()">Exporter en PDF</button>
</div>
<div id="credit-alert" class="alert alert-danger d-none" role="alert">
Vous n'avez plus de crédits pour générer des appréciations.
</div>
<table class="table table-bordered" id="appreciations-table">
<thead>
<tr>
<th>Élève</th>
<th>Appréciation</th>
</tr>
</thead>
<tbody>
{% if appreciations_json and appreciations_json|length > 0 %}
{% for appreciation in appreciations_json %}
<tr>
<td class="eleve">{{ appreciation.eleve }}</td>
<td class="appreciation"></td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="2"><div class="alert alert-warning mb-0">Aucune appréciation générée.</div></td>
</tr>
{% endif %}
</tbody>
</table>
<script>
let stopGeneration = false;
let enCours = false;
function getRowsSansAppreciation() {
return Array.from(document.querySelectorAll('#appreciations-table tbody tr')).filter(row => !row.querySelector('.appreciation').textContent.trim());
}
document.addEventListener('DOMContentLoaded', function() {
const btnToggle = document.getElementById('generation-toggle');
const selectModele = document.getElementById('modele-select');
const creditRestant = document.getElementById('credit-restant');
const creditAlert = document.getElementById('credit-alert');
let rows = getRowsSansAppreciation();
function setButtonState(state) {
if (state === 'lancer') {
btnToggle.textContent = 'Lancer la génération';
btnToggle.className = 'btn btn-primary mb-3';
} else if (state === 'arreter') {
btnToggle.textContent = 'Arrêter la génération';
btnToggle.className = 'btn btn-danger mb-3';
} else if (state === 'continuer') {
btnToggle.textContent = 'Continuer la génération';
btnToggle.className = 'btn btn-success mb-3';
}
}
function traiterLignes() {
rows = getRowsSansAppreciation();
if (rows.length === 0 || stopGeneration) {
enCours = false;
if (stopGeneration) setButtonState('continuer');
else setButtonState('lancer');
return;
}
enCours = true;
setButtonState('arreter');
const row = rows[0];
const eleve = row.querySelector('.eleve').textContent;
const appreciationCell = row.querySelector('.appreciation');
appreciationCell.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Génération...';
fetch("{% url 'generer_appreciation_ajax' %}", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}'
},
body: JSON.stringify({eleve: eleve, modele: selectModele.value})
})
.then(response => response.json())
.then(data => {
appreciationCell.textContent = data.appreciation;
if (typeof data.credit !== 'undefined') {
creditRestant.textContent = 'Crédits restants : ' + data.credit;
}
if (data.stop) {
stopGeneration = true;
setButtonState('lancer');
if (data.credit === 0) {
creditAlert.classList.remove('d-none');
}
return;
}
traiterLignes();
})
.catch(() => {
appreciationCell.textContent = 'Erreur lors de la génération';
traiterLignes();
});
}
btnToggle.addEventListener('click', function() {
const credit = parseInt(creditRestant.textContent.replace(/\D/g, ''));
if (credit === 0) {
creditAlert.classList.remove('d-none');
return;
} else {
creditAlert.classList.add('d-none');
}
if (!enCours && (btnToggle.textContent === 'Lancer la génération' || btnToggle.textContent === 'Continuer la génération')) {
stopGeneration = false;
setButtonState('arreter');
traiterLignes();
} else if (enCours && btnToggle.textContent === 'Arrêter la génération') {
stopGeneration = true;
setButtonState('continuer');
}
});
setButtonState('lancer');
});
</script>
<script>
// 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() {
const doc = new window.jspdf.jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' });
doc.text('Tableau des appréciations', 40, 40);
// Couleurs fixes mode clair
const headColor = [55, 90, 127];
const textColor = [33, 37, 41];
const bgColor = [255, 255, 255];
const borderColor = [200, 200, 200];
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: 60,
margin: { left: 30, right: 30 },
styles: { fontSize: 11, overflow: 'linebreak', cellWidth: 'auto', textColor: textColor, fillColor: bgColor, lineColor: borderColor },
headStyles: { fillColor: headColor, textColor: textColor, lineColor: borderColor },
bodyStyles: { fillColor: bgColor, textColor: textColor, lineColor: borderColor },
alternateRowStyles: { fillColor: [245, 245, 245] },
columnStyles: {
0: { cellWidth: 'auto'},
1: { cellWidth: 'auto' }
},
tableWidth: 'auto',
pageBreak: 'auto',
});
doc.save('appreciations.pdf');
}
</script>
</div>
</div>
</div>
</main>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>