SRL / index-old1.html
OUAREDAEK's picture
Upload folder using huggingface_hub
cdf0628 verified
Raw
History Blame Contribute Delete
55.1 kB
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cognitive-Driven AI for Language Learning | Professionnel</title>
<!-- Chart.js pour les visualisations -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Font Awesome pour les icônes -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-color: #3b82f6;
--secondary-color: #10b981;
--accent-color: #8b5cf6;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--dark-color: #1e293b;
--light-color: #f8fafc;
--gray-color: #64748b;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
color: var(--dark-color);
line-height: 1.6;
min-height: 100vh;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
/* Header */
.header {
background: linear-gradient(135deg, var(--dark-color) 0%, #0f172a 100%);
color: white;
padding: 2rem;
border-radius: 15px;
margin-bottom: 2rem;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color), var(--accent-color));
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 15px;
}
.header p {
font-size: 1.1rem;
opacity: 0.9;
max-width: 800px;
}
.badge {
background: var(--primary-color);
color: white;
padding: 5px 15px;
border-radius: 20px;
font-size: 0.9rem;
display: inline-block;
margin-top: 10px;
}
/* Navigation */
.nav-tabs {
display: flex;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
margin-bottom: 2rem;
flex-wrap: wrap;
}
.nav-tab {
flex: 1;
min-width: 200px;
padding: 1.2rem;
border: none;
background: transparent;
color: var(--gray-color);
font-size: 1rem;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
transition: all 0.3s ease;
}
.nav-tab:hover {
background: #f8fafc;
color: var(--primary-color);
}
.nav-tab.active {
background: var(--primary-color);
color: white;
box-shadow: 0 5px 15px rgba(59, 130, 246, 0.3);
}
/* Sections */
.tab-content {
display: none;
animation: fadeIn 0.5s ease;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
background: white;
border-radius: 15px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05);
border-left: 5px solid var(--primary-color);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 2px solid #f1f5f9;
}
.card-header h3 {
font-size: 1.5rem;
color: var(--dark-color);
display: flex;
align-items: center;
gap: 10px;
}
/* Form Elements */
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: var(--dark-color);
display: flex;
align-items: center;
gap: 8px;
}
.form-control {
width: 100%;
padding: 12px 15px;
border: 2px solid #e2e8f0;
border-radius: 10px;
font-size: 1rem;
transition: all 0.3s;
background: #f8fafc;
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
background: white;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 1.5rem;
}
.form-col {
flex: 1;
}
/* Buttons */
.btn {
padding: 12px 25px;
border: none;
border-radius: 10px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color) 0%, #2563eb 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(59, 130, 246, 0.3);
}
.btn-secondary {
background: var(--secondary-color);
color: white;
}
.btn-large {
padding: 15px 30px;
font-size: 1.1rem;
}
/* Visualizations */
.visualization-container {
background: white;
border-radius: 15px;
padding: 2rem;
margin-top: 2rem;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05);
}
.chart-container {
position: relative;
height: 400px;
width: 100%;
}
/* Planner Cards */
.planning-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 20px;
margin-top: 2rem;
}
.session-card {
background: white;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
border-top: 4px solid var(--primary-color);
transition: transform 0.3s;
}
.session-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
.session-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.session-date {
font-size: 1.2rem;
font-weight: bold;
color: var(--dark-color);
}
.session-repetition {
background: var(--primary-color);
color: white;
padding: 5px 15px;
border-radius: 20px;
font-size: 0.9rem;
}
.session-details {
margin-top: 1rem;
display: flex;
flex-direction: column;
gap: 8px;
}
.detail-item {
display: flex;
justify-content: space-between;
padding: 5px 0;
border-bottom: 1px dashed #e2e8f0;
}
/* Notifications */
.notification-card {
background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
border-left: 5px solid var(--warning-color);
padding: 1.5rem;
border-radius: 12px;
margin-bottom: 1rem;
animation: slideIn 0.5s ease;
}
@keyframes slideIn {
from { transform: translateX(-20px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* Stats Cards */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
margin-top: 2rem;
}
.stat-card {
background: white;
border-radius: 12px;
padding: 1.5rem;
text-align: center;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
}
.stat-value {
font-size: 2.5rem;
font-weight: bold;
color: var(--primary-color);
margin: 10px 0;
}
.stat-label {
color: var(--gray-color);
font-size: 0.9rem;
}
/* Quality Indicators */
.quality-indicator {
display: flex;
gap: 10px;
margin: 15px 0;
}
.quality-dot {
width: 25px;
height: 25px;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 0.8rem;
}
.quality-dot:hover {
transform: scale(1.2);
}
.quality-0 { background: linear-gradient(135deg, #ef4444, #dc2626); }
.quality-1 { background: linear-gradient(135deg, #f59e0b, #d97706); }
.quality-2 { background: linear-gradient(135deg, #10b981, #059669); }
.quality-3 { background: linear-gradient(135deg, #3b82f6, #1d4ed8); }
.quality-dot.active {
box-shadow: 0 0 0 3px white, 0 0 0 6px currentColor;
}
/* Responsive */
@media (max-width: 768px) {
.form-row {
flex-direction: column;
gap: 0;
}
.nav-tabs {
flex-direction: column;
}
.planning-grid {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 2rem;
}
}
</style>
</head>
<body>
<div class="container">
<!-- Header -->
<header class="header">
<h1><i class="fas fa-brain"></i> Cognitive-Driven AI for Language Learning</h1>
<p>Optimizing Long-Term Memory Retention through Spaced Repetition (SM-2), Self-Regulated Learning, Metacognition, and Gentle Reminders via Multi-Armed Bandits</p>
<div class="badge">Version Professionnelle 2.0 | SM-2 Niveau Avancé</div>
</header>
<!-- Navigation -->
<nav class="nav-tabs">
<button class="nav-tab active" onclick="showTab('objectif')">
<i class="fas fa-bullseye"></i> Objectif
</button>
<button class="nav-tab" onclick="showTab('planner')">
<i class="fas fa-calendar-alt"></i> Planner SM-2
</button>
<button class="nav-tab" onclick="showTab('journal')">
<i class="fas fa-book"></i> Journal SRL
</button>
<button class="nav-tab" onclick="showTab('notifications')">
<i class="fas fa-bell"></i> Notifications IA
</button>
<button class="nav-tab" onclick="showTab('stats')">
<i class="fas fa-chart-line"></i> Statistiques
</button>
<button class="nav-tab" onclick="showTab('learn')">
<i class="fas fa-graduation-cap"></i> Apprendre SM-2
</button>
</nav>
<!-- Objectif Section -->
<section id="objectif" class="tab-content active">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-bullseye"></i> Définir mon Objectif d'Apprentissage</h3>
</div>
<div class="form-row">
<div class="form-col">
<label class="form-label"><i class="fas fa-language"></i> Compétence Cible</label>
<select id="competence" class="form-control" onchange="previewSM2Curve()">
<option value="">Sélectionnez une compétence...</option>
</select>
</div>
<div class="form-col">
<label class="form-label"><i class="fas fa-brain"></i> Niveau Actuel</label>
<select id="level" class="form-control">
<option value="0">Débutant</option>
<option value="1" selected>Intermédiaire</option>
<option value="2">Avancé</option>
<option value="3">Expert</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-col">
<label class="form-label"><i class="fas fa-calendar-start"></i> Date de Début</label>
<input type="date" id="start" class="form-control" value="2026-01-29">
</div>
<div class="form-col">
<label class="form-label"><i class="fas fa-calendar-end"></i> Date de Fin</label>
<input type="date" id="end" class="form-control" value="2026-12-31">
</div>
</div>
<div class="form-group">
<label class="form-label"><i class="fas fa-clock"></i> Durée par Session (minutes)</label>
<select id="duration" class="form-control">
<option value="10">10 minutes (Micro-learning)</option>
<option value="20" selected>20 minutes (Optimal)</option>
<option value="30">30 minutes (Approfondissement)</option>
<option value="45">45 minutes (Session intensive)</option>
</select>
</div>
<div class="form-group">
<label class="form-label"><i class="fas fa-chart-line"></i> Qualité Estimée de Rétention</label>
<div class="quality-indicator">
<div class="quality-dot quality-0" data-quality="0" onclick="selectQuality(0)">0</div>
<div class="quality-dot quality-1" data-quality="1" onclick="selectQuality(1)">1</div>
<div class="quality-dot quality-2 active" data-quality="2" onclick="selectQuality(2)">2</div>
<div class="quality-dot quality-3" data-quality="3" onclick="selectQuality(3)">3</div>
</div>
<small id="quality-description">Bon - Consolidation progressive</small>
</div>
<button class="btn btn-primary btn-large" onclick="generatePlanner()">
<i class="fas fa-cogs"></i> Générer le Planner SM-2
</button>
<div id="preview" style="display: none; margin-top: 2rem;">
<h4><i class="fas fa-eye"></i> Aperçu de la Courbe SM-2</h4>
<div class="chart-container">
<canvas id="intervalChart"></canvas>
</div>
</div>
</div>
</section>
<!-- Planner Section -->
<section id="planner" class="tab-content">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-calendar-alt"></i> Planner SM-2 Personnalisé</h3>
<button class="btn btn-secondary" onclick="exportPlanner()">
<i class="fas fa-download"></i> Exporter
</button>
</div>
<div id="plannerStats" class="stats-grid"></div>
<div id="plannerContent" class="planning-grid"></div>
<div class="visualization-container">
<h4><i class="fas fa-chart-line"></i> Visualisation des Intervalles SM-2</h4>
<div class="chart-container">
<canvas id="sm2Visualization"></canvas>
</div>
</div>
</div>
</section>
<!-- Journal Section -->
<section id="journal" class="tab-content">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-book"></i> Journal d'Apprentissage Autorégulé</h3>
<button class="btn btn-secondary" onclick="loadJournalEntries()">
<i class="fas fa-history"></i> Historique
</button>
</div>
<div class="form-row">
<div class="form-col">
<label class="form-label"><i class="fas fa-tasks"></i> Planifié (To-Do)</label>
<textarea id="todo" class="form-control" rows="3" placeholder="Ce que vous prévoyez d'apprendre..."></textarea>
</div>
<div class="form-col">
<label class="form-label"><i class="fas fa-spinner"></i> En Cours (Doing)</label>
<textarea id="doing" class="form-control" rows="3" placeholder="Ce que vous faites actuellement..."></textarea>
</div>
</div>
<div class="form-group">
<label class="form-label"><i class="fas fa-check-circle"></i> Accompli (Done)</label>
<textarea id="done" class="form-control" rows="3" placeholder="Ce que vous avez accompli..."></textarea>
</div>
<div class="form-group">
<label class="form-label"><i class="fas fa-star"></i> Auto-évaluation (1-5)</label>
<div class="quality-indicator">
<div class="quality-dot quality-0" onclick="setEvaluation(1)">1</div>
<div class="quality-dot quality-1" onclick="setEvaluation(2)">2</div>
<div class="quality-dot quality-2 active" onclick="setEvaluation(3)">3</div>
<div class="quality-dot quality-3" onclick="setEvaluation(4)">4</div>
<div class="quality-dot" onclick="setEvaluation(5)" style="background:linear-gradient(135deg, #8b5cf6, #7c3aed)">5</div>
</div>
<input type="hidden" id="auto_eval" value="3">
</div>
<div class="form-group">
<label class="form-label"><i class="fas fa-lightbulb"></i> Stratégie Métacognitive</label>
<textarea id="metacognition" class="form-control" rows="3" placeholder="Quelle stratégie avez-vous utilisée ? Que changeriez-vous ?"></textarea>
</div>
<button class="btn btn-primary btn-large" onclick="saveJournal()">
<i class="fas fa-save"></i> Sauvegarder l'Entrée
</button>
<div id="journalHistory" style="display: none; margin-top: 2rem;">
<h4><i class="fas fa-history"></i> Dernières Entrées</h4>
<div id="journalEntries"></div>
</div>
</div>
</section>
<!-- Notifications Section -->
<section id="notifications" class="tab-content">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-bell"></i> Notifications Intelligentes par Bandits Multi-Bras</h3>
<button class="btn btn-secondary" onclick="getIntelligentReminder()">
<i class="fas fa-sync"></i> Nouveau Rappel
</button>
</div>
<div id="notificationContainer">
<div class="notification-card">
<p><i class="fas fa-info-circle"></i> Cliquez sur "Nouveau Rappel" pour recevoir une notification personnalisée basée sur vos habitudes d'apprentissage.</p>
</div>
</div>
<div class="visualization-container">
<h4><i class="fas fa-chart-bar"></i> Efficacité des Types de Notifications</h4>
<div class="chart-container">
<canvas id="notificationChart"></canvas>
</div>
</div>
</div>
</section>
<!-- Statistics Section -->
<section id="stats" class="tab-content">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-chart-line"></i> Tableau de Bord d'Apprentissage</h3>
<button class="btn btn-secondary" onclick="refreshStats()">
<i class="fas fa-redo"></i> Actualiser
</button>
</div>
<div id="statsContainer" class="stats-grid"></div>
<div class="visualization-container">
<h4><i class="fas fa-chart-area"></i> Progression de la Rétention</h4>
<div class="chart-container">
<canvas id="retentionChart"></canvas>
</div>
</div>
</div>
</section>
<!-- Learn SM-2 Section -->
<section id="learn" class="tab-content">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-graduation-cap"></i> Apprendre l'Algorithme SM-2</h3>
</div>
<div id="sm2Explanation"></div>
<div class="visualization-container">
<h4><i class="fas fa-chart-network"></i> Comparaison des Niveaux de Qualité SM-2</h4>
<div class="chart-container">
<canvas id="comparisonChart"></canvas>
</div>
</div>
</div>
</section>
</div>
<script>
// Variables globales
let currentChart = null;
let currentQuality = 2; // Niveau 2 par défaut
let previewChart = null;
// Initialisation
document.addEventListener('DOMContentLoaded', function() {
loadCompetences();
refreshStats();
loadSM2Explanation();
updateComparisonChart();
});
// Navigation
function showTab(tabId) {
// Masquer toutes les sections
document.querySelectorAll('.tab-content').forEach(section => {
section.classList.remove('active');
});
// Désactiver tous les onglets
document.querySelectorAll('.nav-tab').forEach(tab => {
tab.classList.remove('active');
});
// Activer la section sélectionnée
document.getElementById(tabId).classList.add('active');
// Activer l'onglet correspondant
const tabNames = {
'objectif': 'Objectif',
'planner': 'Planner',
'journal': 'Journal',
'notifications': 'Notifications',
'stats': 'Statistiques',
'learn': 'Apprendre'
};
document.querySelectorAll('.nav-tab').forEach(tab => {
if (tab.textContent.includes(tabNames[tabId])) {
tab.classList.add('active');
}
});
// Actions spécifiques par onglet
if (tabId === 'stats') {
refreshStats();
} else if (tabId === 'learn') {
updateComparisonChart();
}
}
// Charger les compétences
async function loadCompetences() {
try {
const response = await fetch('/api/competences');
const competences = await response.json();
const select = document.getElementById('competence');
select.innerHTML = '<option value="">Sélectionnez une compétence...</option>';
competences.forEach(comp => {
const option = document.createElement('option');
option.value = comp.label;
option.textContent = `${comp.id} - ${comp.label} (${comp.category})`;
select.appendChild(option);
});
} catch (error) {
console.error('Erreur chargement compétences:', error);
}
}
// Sélection de la qualité
function selectQuality(level) {
currentQuality = level;
// Mettre à jour les indicateurs visuels
document.querySelectorAll('.quality-dot').forEach(dot => {
dot.classList.remove('active');
});
document.querySelector(`.quality-dot[data-quality="${level}"]`).classList.add('active');
// Mettre à jour la description
const descriptions = [
"Difficulté - Révision fréquente nécessaire",
"Moyen - Intervalles standard recommandés",
"Bon - Consolidation progressive",
"Excellent - Rétention à long terme optimale"
];
document.getElementById('quality-description').textContent = descriptions[level];
// Mettre à jour l'aperçu
previewSM2Curve();
}
// Prévisualiser la courbe SM-2
async function previewSM2Curve() {
const competence = document.getElementById('competence').value;
if (!competence) {
document.getElementById('preview').style.display = 'none';
return;
}
try {
const response = await fetch(`/api/planner/visualize?competence=${encodeURIComponent(competence)}&quality=${currentQuality}`);
const data = await response.json();
if (data.success) {
document.getElementById('preview').style.display = 'block';
const ctx = document.getElementById('intervalChart').getContext('2d');
if (previewChart) {
previewChart.destroy();
}
const labels = data.intervals.map((_, i) => `Répétition ${i + 1}`);
previewChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Intervalle (jours)',
data: data.intervals,
backgroundColor: '#3b82f6',
borderColor: '#2563eb',
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: `Courbe SM-2 - Niveau ${currentQuality}`
}
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Jours'
}
}
}
}
});
}
} catch (error) {
console.error('Erreur génération preview:', error);
}
}
// Générer le planner
async function generatePlanner() {
const competence = document.getElementById('competence').value;
const startDate = document.getElementById('start').value;
const duration = document.getElementById('duration').value;
if (!competence || !startDate) {
alert('Veuillez sélectionner une compétence et une date de début');
return;
}
try {
const response = await fetch('/api/planner/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
competence: competence,
start_date: startDate,
session_duration: duration,
repetitions: 6,
quality_level: currentQuality
})
});
const data = await response.json();
if (data.success) {
displayPlanner(data.plan, data.intervals);
showTab('planner');
} else {
alert('Erreur: ' + data.error);
}
} catch (error) {
console.error('Erreur génération planner:', error);
alert('Erreur lors de la génération du planner');
}
}
// Afficher le planner
function displayPlanner(plan, intervals) {
const container = document.getElementById('plannerContent');
const statsContainer = document.getElementById('plannerStats');
// Calculer le total des jours
const totalDays = intervals.reduce((a, b) => a + b, 0);
// Statistiques
statsContainer.innerHTML = `
<div class="stat-card">
<div class="stat-value">${plan.length}</div>
<div class="stat-label">Sessions Planifiées</div>
</div>
<div class="stat-card">
<div class="stat-value">${totalDays}</div>
<div class="stat-label">Jours Totaux</div>
</div>
<div class="stat-card">
<div class="stat-value">${currentQuality + 1}/4</div>
<div class="stat-label">Niveau de Qualité</div>
</div>
<div class="stat-card">
<div class="stat-value">${document.getElementById('duration').value}min</div>
<div class="stat-label">Durée/Session</div>
</div>
`;
// Sessions
container.innerHTML = '';
plan.forEach(session => {
const card = document.createElement('div');
card.className = 'session-card';
card.innerHTML = `
<div class="session-header">
<div class="session-date">
<i class="fas fa-calendar-day"></i> ${session.date}
<div style="font-size: 0.9rem; color: #64748b;">${session.jour}</div>
</div>
<div class="session-repetition">
R${session.repetition}
</div>
</div>
<h4 style="margin: 10px 0; color: #1e293b;">${session.competence}</h4>
<div class="session-details">
<div class="detail-item">
<span><i class="fas fa-clock"></i> Durée</span>
<span>${session.duree}</span>
</div>
<div class="detail-item">
<span><i class="fas fa-sync-alt"></i> Intervalle</span>
<span>${session.intervalle}</span>
</div>
<div class="detail-item">
<span><i class="fas fa-bullseye"></i> Type</span>
<span>${session.type}</span>
</div>
<div class="detail-item">
<span><i class="fas fa-lightbulb"></i> Stratégie</span>
<span>${session.strategie}</span>
</div>
</div>
<div style="margin-top: 15px; padding: 10px; background: #f1f5f9; border-radius: 8px; font-size: 0.9rem;">
<i class="fas fa-bell"></i> ${session.reminder || ''}
</div>
`;
container.appendChild(card);
});
// Visualisation
createSM2Visualization(intervals, plan[0]?.competence || 'Compétence');
}
// Créer la visualisation SM-2
function createSM2Visualization(intervals, competence) {
const ctx = document.getElementById('sm2Visualization').getContext('2d');
if (currentChart) {
currentChart.destroy();
}
const labels = intervals.map((_, i) => `Répétition ${i + 1}`);
currentChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Intervalle (jours)',
data: intervals,
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: `Courbe de Répétition Espacée - ${competence}`,
font: { size: 16 }
},
tooltip: {
callbacks: {
label: function(context) {
return `Intervalle: ${context.raw} jours`;
}
}
}
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Jours'
}
},
x: {
title: {
display: true,
text: 'Numéro de Répétition'
}
}
}
}
});
}
// Journal
function setEvaluation(score) {
document.getElementById('auto_eval').value = score;
// Mettre à jour les indicateurs
document.querySelectorAll('.quality-indicator .quality-dot').forEach((dot, index) => {
dot.classList.toggle('active', index === score - 1);
});
}
async function saveJournal() {
const entry = {
todo: document.getElementById('todo').value,
doing: document.getElementById('doing').value,
done: document.getElementById('done').value,
metacognition: document.getElementById('metacognition').value,
auto_eval: document.getElementById('auto_eval').value,
competence: document.getElementById('competence').value,
timestamp: new Date().toISOString()
};
try {
const response = await fetch('/api/journal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(entry)
});
const result = await response.json();
if (result.success) {
alert('✅ Journal sauvegardé avec succès !');
clearJournalForm();
} else {
alert('Erreur: ' + result.error);
}
} catch (error) {
console.error('Erreur sauvegarde journal:', error);
alert('Erreur lors de la sauvegarde');
}
}
function clearJournalForm() {
document.getElementById('todo').value = '';
document.getElementById('doing').value = '';
document.getElementById('done').value = '';
document.getElementById('metacognition').value = '';
setEvaluation(3);
}
async function loadJournalEntries() {
try {
const response = await fetch('/api/journal/entries?limit=5');
const entries = await response.json();
const container = document.getElementById('journalEntries');
container.innerHTML = '';
entries.forEach(entry => {
const div = document.createElement('div');
div.className = 'session-card';
div.innerHTML = `
<div class="session-header">
<div class="session-date">
${new Date(entry.timestamp).toLocaleDateString('fr-FR')}
</div>
<div style="background: #10b981; color: white; padding: 5px 15px; border-radius: 20px;">
${entry.auto_eval || '?'}/5
</div>
</div>
${entry.competence ? `<div style="color: #3b82f6; margin: 5px 0;"><i class="fas fa-language"></i> ${entry.competence}</div>` : ''}
${entry.done ? `<div style="margin-top: 10px;"><strong>Accompli:</strong> ${entry.done.substring(0, 100)}...</div>` : ''}
`;
container.appendChild(div);
});
document.getElementById('journalHistory').style.display = 'block';
} catch (error) {
console.error('Erreur chargement journal:', error);
}
}
// Notifications
async function getIntelligentReminder() {
try {
const response = await fetch('/api/notifications/intelligent');
const data = await response.json();
const container = document.getElementById('notificationContainer');
const card = document.createElement('div');
card.className = 'notification-card';
const icons = {
'motivation': '🌱',
'rappel_court': '⏰',
'encouragement': '💪',
'metacognition': '🤔'
};
card.innerHTML = `
<div style="display: flex; align-items: center; gap: 15px; margin-bottom: 10px;">
<div style="font-size: 2rem;">${icons[data.type] || '🔔'}</div>
<div>
<div style="font-weight: bold; color: #1e293b;">${data.type ? data.type.charAt(0).toUpperCase() + data.type.slice(1).replace('_', ' ') : 'Notification'}</div>
<div style="font-size: 0.9rem; color: #64748b;">
Efficacité: ${(data.effectiveness_score * 100).toFixed(1)}%
</div>
</div>
</div>
<p style="font-size: 1.1rem; line-height: 1.5;">${data.message}</p>
<div style="margin-top: 10px; font-size: 0.8rem; color: #64748b; text-align: right;">
${new Date(data.timestamp).toLocaleTimeString('fr-FR')}
</div>
`;
container.insertBefore(card, container.firstChild);
} catch (error) {
console.error('Erreur notification:', error);
}
}
// Statistiques
async function refreshStats() {
try {
const response = await fetch('/api/learner/stats');
const stats = await response.json();
const container = document.getElementById('statsContainer');
container.innerHTML = `
<div class="stat-card">
<div class="stat-value">${stats.total_sessions}</div>
<div class="stat-label">Sessions Complétées</div>
</div>
<div class="stat-card">
<div class="stat-value">${stats.success_rate.toFixed(2)}/5</div>
<div class="stat-label">Score Moyen</div>
</div>
<div class="stat-card">
<div class="stat-value">${stats.journal_entries}</div>
<div class="stat-label">Entrées Journal</div>
</div>
<div class="stat-card">
<div class="stat-value">${stats.planned_sessions}</div>
<div class="stat-label">Sessions Planifiées</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: ${stats.engagement_level === 'high' ? '#10b981' : stats.engagement_level === 'medium' ? '#f59e0b' : '#ef4444'}">
${stats.engagement_level}
</div>
<div class="stat-label">Niveau d'Engagement</div>
</div>
<div class="stat-card">
<div class="stat-value" style="color: ${stats.risk_level === 'low' ? '#10b981' : stats.risk_level === 'medium' ? '#f59e0b' : '#ef4444'}">
${stats.risk_level}
</div>
<div class="stat-label">Niveau de Risque</div>
</div>
`;
// Mettre à jour le graphique de rétention
updateRetentionChart(stats);
} catch (error) {
console.error('Erreur statistiques:', error);
}
}
function updateRetentionChart(stats) {
const ctx = document.getElementById('retentionChart').getContext('2d');
if (window.retentionChart) {
window.retentionChart.destroy();
}
// Données simulées pour la démonstration
const labels = ['Sem 1', 'Sem 2', 'Sem 3', 'Sem 4', 'Sem 5', 'Sem 6'];
const retentionData = [0.3, 0.5, 0.65, 0.75, 0.8, 0.85];
const engagementData = [0.7, 0.8, 0.75, 0.85, 0.9, 0.88];
window.retentionChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Taux de Rétention',
data: retentionData,
borderColor: '#3b82f6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4
},
{
label: 'Niveau d\'Engagement',
data: engagementData,
borderColor: '#10b981',
backgroundColor: 'rgba(16, 185, 129, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 1,
ticks: {
callback: function(value) {
return (value * 100) + '%';
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
return context.dataset.label + ': ' + (context.raw * 100).toFixed(1) + '%';
}
}
}
}
}
});
}
// Apprendre SM-2
async function loadSM2Explanation() {
try {
const response = await fetch('/api/sm2/explain');
const explanation = await response.json();
const container = document.getElementById('sm2Explanation');
container.innerHTML = `
<div class="card" style="margin-bottom: 20px;">
<h3 style="color: #3b82f6; margin-bottom: 15px;">${explanation.title}</h3>
<p style="margin-bottom: 20px; line-height: 1.6;">${explanation.description}</p>
<h4 style="margin-bottom: 15px; color: #1e293b;"><i class="fas fa-layer-group"></i> Niveaux de Qualité</h4>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; margin-bottom: 20px;">
${Object.entries(explanation.quality_levels).map(([level, desc]) => `
<div style="padding: 15px; background: #f8fafc; border-radius: 10px; border-left: 4px solid ${getQualityColor(level)};">
<div style="font-weight: bold; color: ${getQualityColor(level)};">Niveau ${level}</div>
<div style="font-size: 0.9rem; color: #64748b;">${desc}</div>
</div>
`).join('')}
</div>
<h4 style="margin-bottom: 15px; color: #1e293b;"><i class="fas fa-lightbulb"></i> Principes Fondamentaux</h4>
<ul style="margin-bottom: 20px; padding-left: 20px;">
${explanation.principles.map(principle => `
<li style="margin-bottom: 10px; line-height: 1.5;">${principle}</li>
`).join('')}
</ul>
<h4 style="margin-bottom: 15px; color: #1e293b;"><i class="fas fa-check-circle"></i> Recommandations</h4>
<ul style="padding-left: 20px;">
${explanation.recommendations.map(recommendation => `
<li style="margin-bottom: 10px; line-height: 1.5;">${recommendation}</li>
`).join('')}
</ul>
</div>
`;
} catch (error) {
console.error('Erreur chargement explication SM-2:', error);
}
}
function getQualityColor(level) {
const colors = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6'];
return colors[parseInt(level)] || colors[0];
}
function updateComparisonChart() {
const ctx = document.getElementById('comparisonChart').getContext('2d');
if (window.comparisonChart) {
window.comparisonChart.destroy();
}
// Données des différents niveaux de qualité
const intervals = {
'Difficulté (N0)': [1, 2, 4, 8, 16, 32],
'Moyen (N1)': [1, 3, 7, 14, 30, 60],
'Bon (N2)': [2, 7, 16, 35, 70, 140],
'Excellent (N3)': [3, 10, 25, 60, 120, 240]
};
const labels = ['R1', 'R2', 'R3', 'R4', 'R5', 'R6'];
const colors = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6'];
const datasets = Object.entries(intervals).map(([label, data], index) => ({
label: label,
data: data,
borderColor: colors[index],
backgroundColor: colors[index] + '20',
borderWidth: 2,
tension: 0.4,
fill: false
}));
window.comparisonChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Intervalle (jours)'
}
}
},
plugins: {
title: {
display: true,
text: 'Comparaison des Courbes SM-2 par Niveau de Qualité',
font: { size: 16 }
},
tooltip: {
callbacks: {
label: function(context) {
return `${context.dataset.label}: ${context.raw} jours`;
}
}
}
}
}
});
}
// Export
function exportPlanner() {
const plannerData = document.getElementById('plannerContent').innerText;
const blob = new Blob([plannerData], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'planner-sm2.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
</script>
</body>
</html>