Chaitu2112's picture
Update static/design.html
ec6cb6d verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>AI PDF & Video → MCQ Generator</title>
<!-- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> -->
<!-- Updated Bootstrap 5.3.2 -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background: url("https://guptadeepak.com/content/images/size/w2000/2024/07/The-Future-of-AI-and-Its-Impact-on-Humanity.webp") no-repeat center center fixed;
background-size: cover;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
}
body::before {
content: "";
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(255,255,255,0.09);
backdrop-filter: blur(8px);
z-index: -1;
}
header {
background: linear-gradient(to right, #0d47a1, #c62828);
padding: 10px 20px;
color: white;
/* display: none; */
}
/* Login Page */
/* #loginPage {
height: 100vh; display: flex; justify-content: center; align-items: center;
}
.login-card {
background: rgba(255, 255, 255, 0.15);
border-radius: 16px;
padding: 40px;
width: 380px;
box-shadow: 0 8px 25px rgba(0,0,0,0.3);
backdrop-filter: blur(10px);
animation: fadeInUp 1s ease forwards;
}
.login-card h3 {
text-align: center;
margin-bottom: 25px;
color: #0d47a1;
font-weight: 700;
}
.form-control { border-radius: 8px; border: 1px solid #1976d2; }
.btn-login {
background: linear-gradient(135deg, #1976d2, #42a5f5);
color: #fff; border: none;
width: 100%; padding: 12px; border-radius: 10px;
font-size: 1.1rem; margin-top: 15px;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.btn-login:hover {
transform: translateY(-5px) scale(1.03);
box-shadow: 0 0 15px rgba(255,255,255,0.6),
0 0 30px rgba(255,255,255,0.4),
0 8px 25px rgba(0,0,0,0.4);
filter: brightness(1.1);
}
#loginError { display:none; text-align:center; margin-top:10px; } */
/* Stats card */
.stats-card {
border-radius: 12px; padding: 20px; text-align: center;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
opacity: 0; transform: translateY(40px);
animation: fadeInUp 1s ease forwards; transition: all 0.3s ease;
background: linear-gradient(135deg, #1976d2, #42a5f5); color: #fff;
}
.stats-card:hover {
transform: translateY(-10px) scale(1.05);
box-shadow: 0 0 15px rgba(255,255,255,0.6), 0 0 30px rgba(255,255,255,0.4), 0 8px 25px rgba(0,0,0,0.4);
filter: brightness(1.2);
}
.section-card {
background: linear-gradient(135deg, #e3f2fd, #bbdefb);
border-radius: 12px; padding: 25px; margin-bottom: 40px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
opacity: 0; transform: scale(0.9) translateY(40px);
animation: fadePop 1s ease forwards; transition: all 0.3s ease;
}
.upload-box {
border: 2px dashed #0d47a1; padding: 25px; border-radius: 12px;
background: rgba(255,255,255,0.9); text-align: center;
}
footer {
background: linear-gradient(to right, #c62828, #0d47a1);
color: #fff; padding: 12px; text-align: center; font-size:0.9rem;
/* display: none; */
}
@keyframes fadeInUp { from {opacity:0; transform:translateY(40px);} to {opacity:1; transform:translateY(0);} }
@keyframes fadePop { from {opacity:0; transform:scale(0.9) translateY(40px);} to {opacity:1; transform:scale(1) translateY(0);} }
/* Main nav */
.main-nav { height:80vh; display:flex; justify-content:center; align-items:center; display:none; }
.nav-vertical { display:flex; flex-direction:column; gap:20px; align-items:center; }
.nav-vertical .btn {
padding: 15px 30px; font-size: 1.1rem; border-radius: 12px;
color: #fff; background: linear-gradient(135deg, #1976d2, #42a5f5);
border: none; box-shadow: 0 4px 12px rgba(0,0,0,0.2);
opacity: 0; transform: translateY(40px);
animation: fadeInUp 1s ease forwards; transition: all 0.3s ease;
width: 400px; text-align: left; padding-left: 20px;
}
.nav-vertical .btn:hover {
transform: translateY(-10px) scale(1.04);
box-shadow: 0 0 15px rgba(255,255,255,0.6), 0 0 30px rgba(255,255,255,0.4), 0 8px 25px rgba(0,0,0,0.4);
filter: brightness(1.12);
}
.page { display: none; padding-top: 18px; }
/* Questions table styles */
.question-table {
max-height: 600px;
overflow-y: auto;
}
.question-row {
border-left: 4px solid #1976d2;
}
.question-row.mcq {
border-left-color: #28a745;
}
.question-row.descriptive {
border-left-color: #ffc107;
}
.ai-card {
background: rgba(255, 255, 255, 0.05);
border-radius: 20px;
padding: 40px;
backdrop-filter: blur(10px);
box-shadow: 0 0 30px rgba(0, 255, 255, 0.6),
0 0 60px rgba(0, 119, 255, 0.4);
text-align: center;
transition: all 0.3s ease;
}
.ai-card:hover {
box-shadow: 0 0 50px rgba(0, 255, 255, 0.9),
0 0 100px rgba(0, 119, 255, 0.6);
transform: scale(1.05);
}
.brand-title {
font-weight: bold;
font-size: 50px;
color: #fff;
text-shadow: 0 0 20px cyan, 0 0 40px #007bff, 0 0 60px #00e5ff;
letter-spacing: 2px;
}
/* Sticky table header */
.table thead th {
position: sticky;
top: 0;
background: #f8f9fa; /* Bootstrap table-light background */
z-index: 2;
}
</style>
</head>
<body>
<!-- Login -->
<!-- <div id="loginPage">
<div class="login-card">
<h3>🔐 Login</h3>
<div class="mb-3"><label class="form-label">Username</label>
<input type="text" id="username" class="form-control" placeholder="Enter username"></div>
<div class="mb-3"><label class="form-label">Password</label>
<input type="password" id="password" class="form-control" placeholder="Enter password"></div>
<button class="btn-login" onclick="login()">Login</button>
<div id="loginError" class="text-danger small">⚠ Invalid username or password</div>
</div>
</div> -->
<!-- Header -->
<header class="d-flex justify-content-between align-items-center" id="mainHeader">
<img src="http://icfaionline.in/assets/img/icfai-logo.png" class="p-1" alt="ICFAI Logo" height="60" />
<!-- <button class="btn btn-light btn-sm" onclick="logout()">Logout</button> -->
</header>
<!-- NAV -->
<div id="mainPage" class="main-nav">
<div class="container">
<div class="row">
<div class="col-xl-10 d-flex align-items-center justify-content-between">
<!-- ICFAI AI Solutions Text -->
<div class="brand-title" style="padding:15px; font-weight:bold; font-size:50px; color:#fff;">
<div class="ai-card">
<div class="brand-title">
ICFAI AI Solutions
</div>
</div>
</div>
<!-- NAV -->
<div class="nav-vertical">
<button class="btn" data-page="pdfPage">Generate Questions from PDFs</button>
<button class="btn" data-page="videoPage">Generate Questions from Video</button>
<button class="btn" data-page="questionsPage">View Generated Questions</button>
<button class="btn" data-page="generatePaperPage">Generate Question Paper</button>
<a class="btn" href="http://192.168.1.77:8501" target="_blank">Multilingual Video Generator</a>
<a class="btn" href="http://192.168.1.56/Summarizer/" target="_blank">Text Summarizer</a>
</div>
</div>
</div>
</div>
</div>
<!-- PDF PAGE -->
<div id="pdfPage" class="page container my-5">
<h3 class="step-title"> Generate Questions from PDFs </h3>
<div class="row g-3 mb-4">
<button class="btn btn-light btn-sm float-end" style="margin-left:10px" onclick="goHome()">🏠 Home</button>
<div class="col-md-3"><div class="stats-card"><h6>PDFs uploaded</h6><h3>0</h3></div></div>
<div class="col-md-3"><div class="stats-card"><h6>Pages (current PDF)</h6><h3>0</h3></div></div>
<div class="col-md-3"><div class="stats-card"><h6>MCQs generated</h6><h3>0</h3></div></div>
<div class="col-md-3"><div class="stats-card"><h6>Descriptive Qs</h6><h3>0</h3></div></div>
</div>
<div class="section-card">
<div class="upload-box">
<img src="https://img.icons8.com/ios-filled/70/0d47a1/pdf.png" alt="PDF Upload" class="mb-2"/>
<p>Drag and drop file here</p>
<p><small>Limit 200MB per file • PDF</small></p>
<input id="pdfUploader" type="file" class="form-control form-control-sm btn-upload" accept=".pdf">
</div>
</div>
</div>
<!-- VIDEO PAGE -->
<div id="videoPage" class="page container my-5">
<h3 class="step-title"> Generate Questions from Video </h3>
<button class="btn btn-light btn-sm float-end" style="margin-left:10px" onclick="goHome()">🏠 Home</button>
<div class="row g-3 mb-4">
<div class="col-md-3"><div class="stats-card"><h6>Videos uploaded</h6><h3 id="videoUploadsStat">0</h3></div></div>
<div class="col-md-3"><div class="stats-card"><h6>MCQs generated</h6><h3 id="videoMcqStat">0</h3></div></div>
</div>
<div class="section-card">
<div class="upload-box">
<img src="https://img.icons8.com/ios-filled/70/c62828/video.png" alt="Video Upload" class="mb-2"/>
<p>Drag and drop file here</p>
<p><small>Limit 200MB per file • MP4, MOV, MKV, AVI, MPEG4</small></p>
<input id="videoUploader" type="file" class="form-control form-control-sm btn-upload" accept="video/*">
</div>
</div>
</div>
<!-- QUESTIONS PAGE -->
<div id="questionsPage" class="page container my-5">
<button class="btn btn-light btn-sm float-end" style="margin-left:10px" onclick="goHome()">🏠 Home</button>
<h3 class="step-title"> Generated Questions in Database </h3>
<div class="section-card">
<!-- Remove the form element and keep just the search controls -->
<div class="row mb-3">
<div class="col-md-6">
<input type="text" id="searchInput" class="form-control" placeholder="Search by topic or keyword...">
</div>
<div class="col-md-4">
<select id="questionTypeSelect" class="form-select">
<option value="all">All Types</option>
<option value="MCQ">MCQ Only</option>
<option value="Descriptive">Descriptive Only</option>
</select>
</div>
<div class="col-md-2">
<!-- Change to regular button, not submit -->
<button type="button" id="searchBtn" class="btn btn-primary w-100">Search</button>
</div>
</div>
<div id="questionsCount" class="mb-3"></div>
<div id="questionsTable" class="question-table"></div>
</div>
</div>
<!-- Generate Question Paper Page -->
<div id="generatePaperPage" class="page container my-5">
<button class="btn btn-light btn-sm float-end" style="margin-left:10px" onclick="goHome()">🏠 Home</button>
<h3 class="step-title">Generate Question Paper</h3>
<div class="section-card">
<div class="row mb-4">
<div class="col-md-6">
<h5>Question Distribution by Difficulty Level</h5>
<div class="filter-section">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Level 1 </label>
<input type="number" id="level1Count" class="form-control level-input" min="0" value="0">
</div>
<div class="col-md-4">
<label class="form-label">Level 2</label>
<input type="number" id="level2Count" class="form-control level-input" min="0" value="0">
</div>
<div class="col-md-4">
<label class="form-label">Level 3</label>
<input type="number" id="level3Count" class="form-control level-input" min="0" value="0">
</div>
<div class="col-md-4">
<label class="form-label">Level 4</label>
<input type="number" id="level4Count" class="form-control level-input" min="0" value="0">
</div>
<div class="col-md-4">
<label class="form-label">Level 5</label>
<input type="number" id="level5Count" class="form-control level-input" min="0" value="0">
</div>
</div>
</div>
</div>
<div class="col-md-6">
<h5>Question Types & Topics</h5>
<div class="filter-section">
<div class="mb-3">
<label class="form-label">Question Types</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="typeMCQ" value="mcq" checked>
<label class="form-check-label" for="typeMCQ">MCQ</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="typeDescriptive" value="descriptive" checked>
<label class="form-check-label" for="typeDescriptive">Descriptive</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Topic Selection</label>
<div>
<div class="form-check">
<input class="form-check-input" type="radio" name="chapterScope" id="allChapters" value="all" checked>
<label class="form-check-label" for="allChapters">
All Topics
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="chapterScope" id="selectedChapters" value="selected">
<label class="form-check-label" for="selectedChapters">
Selected Topics Only
</label>
</div>
</div>
</div>
</div>
<div class="mb-3" id="chaptersSelectionContainer" style="display: none;">
<label class="form-label">Select Topics</label>
<select id="chaptersSelect" class="form-select topic-select" multiple>
<!-- Chapters will be loaded dynamically -->
</select>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<button class="btn btn-primary btn-lg" onclick="generateQuestionPaper()">
🎯 Generate Question Paper
</button>
<div id="paperGenerationStatus" class="mt-3"></div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div id="generatedPaperResults" class="mt-2 p-2" style="background:#ffffff; border-radius:8px; border:1px solid #e9ecef; max-height:60vh; overflow:auto;">
<div class="text-center text-muted">
Configure your question paper above and click "Generate Question Paper"
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ADD THE QUESTION PAPER SECTION HERE -->
<!-- Generate Question Paper (REPLACEMENT) -->
<style>
/* Filter section styles */
.filter-section {
background: rgba(255, 255, 255, 0.9);
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.level-input {
width: 70px;
text-align: center;
}
.topic-select {
min-height: 120px;
}
/* local styles for editable questions table inside resultsDiv */
#paperControls { margin-top: 10px; }
#paperResultsTable { width: 100%; table-layout: fixed; word-break: break-word; }
#paperResultsTable th, #paperResultsTable td { vertical-align: top; white-space: normal; }
.editable { min-height: 28px; padding: 6px; border-radius: 4px; }
.small-action { font-size: 0.9rem; }
.select-col { width: 40px; text-align: center; }
.id-col { width: 60px; }
.type-col { width: 80px; }
.actions-col { width: 110px; }
</style>
<script>
// Helper state for paper UI (client-only)
const PAPER_STATE = {
loadedQuestions: [], // {id, topic, type, question, option_a..d, difficulty, flagged}
selectedIds: new Set(),
availableTopics: []
};
// Add this helper function for notifications if not already present
function showNotification(message, type = 'info') {
// Create a simple notification
const notification = document.createElement('div');
notification.className = `alert alert-${type === 'error' ? 'danger' : type} alert-dismissible fade show`;
notification.style.position = 'fixed';
notification.style.top = '20px';
notification.style.right = '20px';
notification.style.zIndex = '9999';
notification.style.minWidth = '300px';
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(notification);
// Auto remove after 3 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 3000);
}
// Load saved questions from /questions endpoint and render editable table
async function loadSavedQuestionsForPaper() {
const resultsDiv = document.getElementById('resultsDiv');
resultsDiv.innerHTML = '<div class="text-center"><div class="spinner-border spinner-border-sm" role="status"></div> Loading saved questions...</div>';
try {
// Remove the ?flagged=true parameter to load ALL questions
const resp = await fetch(`${backendBase}/questions`);
const json = await resp.json();
// Keep only relevant fields and normalize type
PAPER_STATE.loadedQuestions = (json || []).map(q => ({
id: q.id,
topic: q.topic || '',
type: (q.type || '').toUpperCase(),
question: q.question || '',
option_a: q.option_a || '',
option_b: q.option_b || '',
option_c: q.option_c || '',
option_d: q.option_d || '',
difficulty: (q.difficulty && q.difficulty.toString()) || '',
flagged: q.flagged !== undefined ? q.flagged : true // Handle null/undefined as true
}));
// default select none
PAPER_STATE.selectedIds = new Set();
renderEditableQuestionsTable();
} catch (err) {
console.error('Error loading saved questions for paper:', err);
resultsDiv.innerHTML = '<div class="alert alert-danger">Error loading saved questions.</div>';
}
updatePaperSummary();
}
// Render editable table into resultsDiv
function renderEditableQuestionsTable() {
const resultsDiv = document.getElementById('resultsDiv');
if (!PAPER_STATE.loadedQuestions.length) {
resultsDiv.innerHTML = '<div class="alert alert-info small mb-0">No flagged questions found. Questions removed from previous sessions will not appear here.</div>';
return;
}
const table = document.createElement('table');
table.id = 'paperResultsTable';
table.className = 'table table-sm table-bordered';
const thead = document.createElement('thead');
thead.innerHTML = `
<tr>
<th class="select-col"><input id="paperSelectAllToggle" type="checkbox" title="Select all"></th>
<th class="id-col">ID</th>
<th class="type-col">Type</th>
<th>Flagged Status</th>
<th>Topic</th>
<th>Question (click to edit)</th>
<th>Options (click to edit; MCQ only)</th>
<th style="width:110px">Difficulty</th>
<th class="actions-col">Actions</th>
</tr>
`;
table.appendChild(thead);
const tbody = document.createElement('tbody');
PAPER_STATE.loadedQuestions.forEach(q => {
const tr = document.createElement('tr');
tr.dataset.qid = q.id;
// Checkbox
const chkTd = document.createElement('td');
chkTd.className = 'select-col text-center align-middle';
const chk = document.createElement('input');
chk.type = 'checkbox';
chk.className = 'paper-select-row';
chk.checked = PAPER_STATE.selectedIds.has(q.id);
chk.addEventListener('change', (ev) => {
if (ev.target.checked) PAPER_STATE.selectedIds.add(q.id);
else PAPER_STATE.selectedIds.delete(q.id);
updatePaperSummary();
});
chkTd.appendChild(chk);
tr.appendChild(chkTd);
// ID
const idTd = document.createElement('td');
idTd.textContent = q.id;
tr.appendChild(idTd);
// Type
const typeTd = document.createElement('td');
typeTd.innerHTML = `<span class="badge ${q.type === 'MCQ' ? 'bg-success' : 'bg-warning'}">${q.type}</span>`;
tr.appendChild(typeTd);
// Flagged Status
const flagTd = document.createElement('td');
const flagStatus = q.flagged === true ?
'<span class="badge bg-success">Approved</span>' :
q.flagged === false ?
'<span class="badge bg-danger">Not Approved</span>' :
'<span class="badge bg-secondary">Pending</span>';
flagTd.innerHTML = flagStatus;
tr.appendChild(flagTd);
// Topic
const topicTd = document.createElement('td');
const topicDiv = document.createElement('div');
topicDiv.className = 'editable';
topicDiv.contentEditable = 'true';
topicDiv.innerText = q.topic || '';
topicDiv.addEventListener('input', (ev) => {
q.topic = topicDiv.innerText.trim();
});
topicTd.appendChild(topicDiv);
tr.appendChild(topicTd);
// Question (editable)
const qTd = document.createElement('td');
const qDiv = document.createElement('div');
qDiv.className = 'editable';
qDiv.contentEditable = 'true';
qDiv.innerText = q.question;
qDiv.addEventListener('input', (ev) => {
q.question = qDiv.innerText.trim();
});
qTd.appendChild(qDiv);
tr.appendChild(qTd);
// Options
const optsTd = document.createElement('td');
if (q.type === 'MCQ') {
const optsWrapper = document.createElement('div');
optsWrapper.style.display = 'grid';
optsWrapper.style.gridTemplateColumns = '1fr 1fr';
optsWrapper.style.gap = '6px';
['option_a','option_b','option_c','option_d'].forEach((optKey, i) => {
const lbl = ['A','B','C','D'][i];
const optBox = document.createElement('div');
const optLabel = document.createElement('div');
optLabel.style.fontWeight = '600';
optLabel.style.fontSize = '0.85rem';
optLabel.textContent = lbl + ')';
const optDiv = document.createElement('div');
optDiv.className = 'editable';
optDiv.contentEditable = 'true';
optDiv.innerText = q[optKey] || '';
optDiv.addEventListener('input', () => {
q[optKey] = optDiv.innerText.trim();
});
optBox.appendChild(optLabel);
optBox.appendChild(optDiv);
optsWrapper.appendChild(optBox);
});
optsTd.appendChild(optsWrapper);
} else {
optsTd.innerHTML = `<div class="text-muted small">N/A for descriptive</div>`;
}
tr.appendChild(optsTd);
// Difficulty (editable select)
const diffTd = document.createElement('td');
const sel = document.createElement('select');
sel.className = 'form-select form-select-sm';
const blankOpt = document.createElement('option'); blankOpt.value = ''; blankOpt.textContent = '—';
sel.appendChild(blankOpt);
for (let i = 1; i <= 5; i++) {
const o = document.createElement('option'); o.value = String(i); o.textContent = String(i);
if (String(q.difficulty) === String(i)) o.selected = true;
sel.appendChild(o);
}
sel.addEventListener('change', () => { q.difficulty = sel.value; });
diffTd.appendChild(sel);
tr.appendChild(diffTd);
// Actions
const actTd = document.createElement('td');
actTd.className = 'text-center';
// Approve button
const approveBtn = document.createElement('button');
approveBtn.className = 'btn btn-sm btn-success me-1';
approveBtn.innerHTML = '✓ Approve';
approveBtn.addEventListener('click', async () => {
const success = await updateQuestionFlag(q.id, true);
if (success) {
q.flagged = true;
renderEditableQuestionsTable();
}
});
// Reject button
const rejectBtn = document.createElement('button');
rejectBtn.className = 'btn btn-sm btn-danger';
rejectBtn.innerHTML = '✗ Reject';
rejectBtn.addEventListener('click', async () => {
const success = await updateQuestionFlag(q.id, false);
if (success) {
q.flagged = false;
renderEditableQuestionsTable();
}
});
actTd.appendChild(approveBtn);
actTd.appendChild(rejectBtn);
tr.appendChild(actTd);
tbody.appendChild(tr);
});
table.appendChild(tbody);
// replace content
resultsDiv.innerHTML = '';
resultsDiv.appendChild(table);
// wire select all checkbox
const paperSelectAllToggle = document.getElementById('paperSelectAllToggle');
if (paperSelectAllToggle) {
paperSelectAllToggle.checked = PAPER_STATE.selectedIds.size === PAPER_STATE.loadedQuestions.length && PAPER_STATE.loadedQuestions.length > 0;
paperSelectAllToggle.addEventListener('change', (ev) => {
if (ev.target.checked) {
PAPER_STATE.loadedQuestions.forEach(q => PAPER_STATE.selectedIds.add(q.id));
} else {
PAPER_STATE.selectedIds.clear();
}
// update all checkboxes in table
table.querySelectorAll('.paper-select-row').forEach((c, i) => c.checked = ev.target.checked);
updatePaperSummary();
});
}
updatePaperSummary();
}
// Summary text
function updatePaperSummary() {
const summaryEl = document.getElementById('paperSelectionSummary');
const totalLoaded = PAPER_STATE.loadedQuestions.length;
const selected = PAPER_STATE.selectedIds.size;
summaryEl.textContent = `${selected} selected • ${totalLoaded} flagged questions loaded`;
}
// Initialize event listeners once (not inside render function)
function initializePaperEventListeners() {
// Select all button
document.getElementById('selectAllPaperBtn').addEventListener('click', () => {
PAPER_STATE.loadedQuestions.forEach(q => PAPER_STATE.selectedIds.add(q.id));
// refresh table checkboxes
document.querySelectorAll('#paperResultsTable .paper-select-row').forEach(c => c.checked = true);
updatePaperSummary();
});
// Clear selection button
document.getElementById('clearSelectionPaperBtn').addEventListener('click', () => {
PAPER_STATE.selectedIds.clear();
document.querySelectorAll('#paperResultsTable .paper-select-row').forEach(c => c.checked = false);
updatePaperSummary();
});
// Load button
document.getElementById('loadSavedForPaperBtn').addEventListener('click', async () => {
await loadSavedQuestionsForPaper();
});
// Approve Selected button
document.getElementById('approveSelectedBtn').addEventListener('click', async () => {
await bulkUpdateFlags(true);
});
// Reject Selected button
document.getElementById('rejectSelectedBtn').addEventListener('click', async () => {
await bulkUpdateFlags(false);
});
}
// Bulk update function for approve/reject
function initializeQuestionTableEventListeners() {
// Editable field event listeners
document.querySelectorAll('#questionsTable .editable').forEach(element => {
element.addEventListener('blur', function() {
const questionId = this.getAttribute('data-id');
const field = this.getAttribute('data-field');
const value = this.innerText.trim();
if (questionId && field) {
updateQuestionField(questionId, field, value);
}
});
// Add Enter key support to save on Enter
element.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
this.blur();
}
});
});
// Difficulty select event listeners
document.querySelectorAll('#questionsTable .difficulty-select').forEach(select => {
select.addEventListener('change', function() {
const questionId = this.getAttribute('data-id');
const value = this.value;
if (questionId) {
updateQuestionField(questionId, 'difficulty', value);
}
});
});
// Approve button event listeners
document.querySelectorAll('#questionsTable .approve-btn').forEach(button => {
button.addEventListener('click', function() {
const questionId = this.getAttribute('data-id');
if (questionId) {
updateQuestionFlag(questionId, true);
}
});
});
// Reject button event listeners
document.querySelectorAll('#questionsTable .reject-btn').forEach(button => {
button.addEventListener('click', function() {
const questionId = this.getAttribute('data-id');
if (questionId) {
updateQuestionFlag(questionId, false);
}
});
});
}
// Bulk update function for approve/reject
async function bulkUpdateFlags(flagged) {
const selectedIds = Array.from(PAPER_STATE.selectedIds);
if (selectedIds.length === 0) {
alert('No questions selected.');
return;
}
const questionUpdates = selectedIds.map(id => ({
id: parseInt(id),
flagged: flagged
}));
try {
const response = await fetch(`${backendBase}/bulk_update_flags`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ question_updates: questionUpdates })
});
const data = await response.json();
if (data.status === 'success') {
// Update local state
PAPER_STATE.loadedQuestions.forEach(q => {
if (PAPER_STATE.selectedIds.has(q.id)) {
q.flagged = flagged;
}
});
renderEditableQuestionsTable();
showNotification(`Successfully ${flagged ? 'approved' : 'Not Approved'} ${selectedIds.length} questions.`, 'success');
} else {
showNotification(`Error updating questions: ${data.error}`, 'error');
}
} catch (error) {
console.error('Error updating bulk flags:', error);
showNotification('Error updating questions.', 'error');
}
}
// Function to clean option text by removing "Answer: X Difficulty: Y" patterns
function cleanOptionText(optionText) {
// Remove patterns like "Answer: B Difficulty: 4" from the end of the option text
return optionText
.replace(/\s*Answer:\s*[A-D]\s*Difficulty:\s*\d+$/, '') // Remove "Answer: X Difficulty: Y"
.replace(/\s*Difficulty:\s*\d+\s*Answer:\s*[A-D]$/, '') // Remove "Difficulty: Y Answer: X"
.replace(/\s*Answer:\s*[A-D]$/, '') // Remove just "Answer: X"
.replace(/\s*Difficulty:\s*\d+$/, '') // Remove just "Difficulty: Y"
.trim();
}
// Function to download question paper as text file
function downloadQuestionPaper(questions) {
if (!Array.isArray(questions)) questions = [];
const mcqs = questions.filter(q => (q.type || '').toUpperCase() === 'MCQ');
const descs = questions.filter(q => (q.type || '').toUpperCase() !== 'MCQ');
const ordered = [...mcqs, ...descs];
// Create Word document content with proper XML structure
let wordContent = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument
xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml"
xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w10="urn:schemas-microsoft-com:office:word"
xmlns:sl="http://schemas.microsoft.com/schemaLibrary/2003/core"
xmlns:aml="http://schemas.microsoft.com/aml/2001/core"
xmlns:wx="http://schemas.microsoft.com/office/word/2003/auxHint"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882">
<w:body>
<wx:sect>
<w:p>
<w:r>
<w:rPr>
<w:b/>
<w:sz w:val="32"/>
<w:sz-cs w:val="32"/>
</w:rPr>
<w:t>QUESTION PAPER</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t>${'='.repeat(50)}</w:t>
</w:r>
</w:p>
<w:p><w:r><w:t></w:t></w:r></w:p>`;
ordered.forEach((q, index) => {
const cleanQuestion = cleanOptionText(q.question || '');
// Question number and text
wordContent += `
<w:p>
<w:r>
<w:rPr>
<w:b/>
</w:rPr>
<w:t>${index + 1}. ${cleanQuestion}</w:t>
</w:r>
</w:p>`;
if ((q.type || '').toUpperCase() === 'MCQ') {
// MCQ Options
wordContent += `
<w:p>
<w:r>
<w:t> A) ${cleanOptionText(q.option_a || '')}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t> B) ${cleanOptionText(q.option_b || '')}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t> C) ${cleanOptionText(q.option_c || '')}</w:t>
</w:r>
</w:p>
<w:p>
<w:r>
<w:t> D) ${cleanOptionText(q.option_d || '')}</w:t>
</w:r>
</w:p>`;
} else {
// Descriptive question indicator
wordContent += `
<w:p>
<w:r>
<w:t> [Descriptive Question]</w:t>
</w:r>
</w:p>`;
}
// Add space between questions
wordContent += `<w:p><w:r><w:t></w:t></w:r></w:p>`;
});
// Close the document
wordContent += `
</wx:sect>
</w:body>
</w:wordDocument>`;
const blob = new Blob([wordContent], {
type: 'application/msword'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'question_paper.doc';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// On load: keep resultsDiv empty and summary set
(function initPaperUI() {
updatePaperSummary();
initializePaperEventListeners();
loadChaptersForPaper();
})();
// Add search functionality
function initializeSearchFunctionality() {
const searchForm = document.getElementById('searchForm');
const searchInput = document.getElementById('searchInput');
const questionTypeSelect = document.getElementById('questionTypeSelect');
const searchBtn = document.getElementById('searchBtn');
// Handle search button click
if (searchBtn) {
searchBtn.addEventListener('click', function(e) {
e.preventDefault(); // Prevent form submission
e.stopPropagation(); // Stop event bubbling
const searchTerm = searchInput ? searchInput.value : '';
const questionType = questionTypeSelect ? questionTypeSelect.value : 'all';
console.log('Searching for:', searchTerm, 'Type:', questionType);
loadQuestionsFromDB(searchTerm, questionType);
});
}
// Handle form submission (as backup)
if (searchForm) {
searchForm.addEventListener('submit', function(e) {
e.preventDefault(); // This is crucial - prevents page refresh
const searchTerm = searchInput ? searchInput.value : '';
const questionType = questionTypeSelect ? questionTypeSelect.value : 'all';
console.log('Form submit - Searching for:', searchTerm, 'Type:', questionType);
loadQuestionsFromDB(searchTerm, questionType);
});
}
// Also trigger search when type changes
if (questionTypeSelect) {
questionTypeSelect.addEventListener('change', function() {
const searchTerm = searchInput ? searchInput.value : '';
const questionType = this.value;
console.log('Type changed - Searching for:', searchTerm, 'Type:', questionType);
loadQuestionsFromDB(searchTerm, questionType);
});
}
}
// Load topics for the question paper generation
let LAST_CHAPTERS = []; // cached chapters from server
// Load chapters (same UI as PDF page)
async function loadChaptersForPaper() {
try {
const response = await fetch(`${backendBase}/questions`);
const questions = await response.json();
// Extract unique chapters/topics
const chapters = [...new Set(questions.map(q => q.topic).filter(topic => topic && topic.trim() !== ''))].sort();
const chaptersSelect = document.getElementById('chaptersSelect');
chaptersSelect.innerHTML = '';
chapters.forEach(chapter => {
const option = document.createElement('option');
option.value = chapter;
option.textContent = chapter;
chaptersSelect.appendChild(option);
});
console.log(`Loaded ${chapters.length} chapters for paper generation`);
} catch (error) {
console.error('Error loading chapters:', error);
}
}
function getTopicsForSelectedChapters() {
const checked = Array.from(document.querySelectorAll('.paper-chap-chk:checked')).map(i => i.value);
if (!checked.length) return []; // empty means "all"
// map checked chapter numbers to topics using cached LAST_CHAPTERS
const topics = [];
checked.forEach(chid => {
const found = LAST_CHAPTERS.find(c => String(c.chapter) === String(chid));
if (found && Array.isArray(found.topics)) {
found.topics.forEach(t => {
if (t && !topics.includes(t)) topics.push(t);
});
}
});
return topics;
}
// Generate question paper
async function generateQuestionPaper() {
const statusEl = document.getElementById('paperGenerationStatus');
const resultsDiv = document.getElementById('generatedPaperResults');
statusEl.innerHTML = '<div class="spinner-border spinner-border-sm" role="status"></div> Generating question paper...';
resultsDiv.innerHTML = '';
try {
// Get level counts
const levels = {
1: parseInt(document.getElementById('level1Count').value) || 0,
2: parseInt(document.getElementById('level2Count').value) || 0,
3: parseInt(document.getElementById('level3Count').value) || 0,
4: parseInt(document.getElementById('level4Count').value) || 0,
5: parseInt(document.getElementById('level5Count').value) || 0
};
// Get question types
const types = {
mcq: document.getElementById('typeMCQ').checked,
descriptive: document.getElementById('typeDescriptive').checked
};
// Get chapter selection
const chapterScope = document.querySelector('input[name="chapterScope"]:checked').value;
let selectedChapters = [];
if (chapterScope === 'selected') {
const chaptersSelect = document.getElementById('chaptersSelect');
selectedChapters = Array.from(chaptersSelect.selectedOptions).map(option => option.value);
if (selectedChapters.length === 0) {
statusEl.innerHTML = '<div class="alert alert-warning">Please select at least one chapter.</div>';
return;
}
}
const requestData = {
levels: levels,
types: types,
topics: chapterScope === 'all' ? 'all' : selectedChapters
};
console.log('Generating paper with data:', requestData);
const response = await fetch(`${backendBase}/generate_question_paper`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
});
const data = await response.json();
if (data.status === 'success') {
// Reorder: MCQs first, then descriptive
const allQuestions = Array.isArray(data.questions) ? data.questions : [];
const mcqList = allQuestions.filter(q => (q.type || '').toUpperCase() === 'MCQ');
const descList = allQuestions.filter(q => (q.type || '').toUpperCase() !== 'MCQ');
const ordered = [...mcqList, ...descList];
statusEl.innerHTML = `<div class="alert alert-success">✅ Generated paper with ${data.total_selected} questions</div>`;
// Add chapter scope info
const scopeInfo = chapterScope === 'all'
? 'All Topics'
: `Selected Topics: ${selectedChapters.join(', ')}`;
statusEl.innerHTML += `<div class="mt-2 text-dark fw-semibold">Scope: ${scopeInfo}</div>`;
// Display ordered paper (MCQs then descriptives)
displayGeneratedPaper(ordered);
// Show level summary
const levelSummary = Object.entries(data.level_summary || {})
.map(([level, count]) => `Level ${level}: ${count}`)
.join(', ');
statusEl.innerHTML += `<div class="mt-2 text-dark fw-semibold">Distribution: ${levelSummary}</div>`;
} else {
statusEl.innerHTML = `<div class="alert alert-danger">❌ Error: ${data.error}</div>`;
}
} catch (error) {
console.error('Error generating question paper:', error);
statusEl.innerHTML = `<div class="alert alert-danger">❌ Error generating question paper: ${error.message}</div>`;
}
}
// Initialize chapter selection functionality
function initializeChapterSelection() {
const allChaptersRadio = document.getElementById('allChapters');
const selectedChaptersRadio = document.getElementById('selectedChapters');
const chaptersContainer = document.getElementById('chaptersSelectionContainer');
if (allChaptersRadio && selectedChaptersRadio && chaptersContainer) {
// Handle radio button changes
allChaptersRadio.addEventListener('change', function() {
chaptersContainer.style.display = 'none';
});
selectedChaptersRadio.addEventListener('change', function() {
chaptersContainer.style.display = 'block';
});
}
}
</script>
<footer class="mt-4" id="mainFooter">© 2025 ICFAI Group</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Backend + Navigation Scripts -->
<script>
const backendBase = "";
// const backendBase = "https://transpulmonary-divergently-alfreda.ngrok-free.dev";
// function login() {
// const user = document.getElementById("username").value.trim();
// const pass = document.getElementById("password").value.trim();
// const VALID_USER = "admin";
// const VALID_PASS = "icfai@123";
// if (user === VALID_USER && pass === VALID_PASS) {
// document.getElementById("loginPage").style.display = "none";
// document.getElementById("mainPage").style.display = "flex";
// document.getElementById("mainHeader").style.display = "flex";
// document.getElementById("mainFooter").style.display = "block";
// } else {
// document.getElementById("loginError").style.display = "block";
// }
// }
// function logout() {
// document.getElementById("mainPage").style.display = "none";
// document.getElementById("mainHeader").style.display = "none";
// document.getElementById("mainFooter").style.display = "none";
// document.getElementById("loginPage").style.display = "flex";
// document.getElementById("username").value = "";
// document.getElementById("password").value = "";
// document.getElementById("loginError").style.display = "none";
// }
// Navigation
document.querySelectorAll('.nav-vertical .btn').forEach(btn => {
btn.addEventListener('click', function() {
const pageId = this.getAttribute('data-page');
if (pageId) {
document.querySelectorAll('.page').forEach(page => page.style.display = 'none');
document.getElementById(pageId).style.display = 'block';
// Load questions when questions page is opened
if (pageId === 'questionsPage') {
// Get current search values
const searchInput = document.getElementById('searchInput');
const questionTypeSelect = document.getElementById('questionTypeSelect');
const currentSearch = searchInput ? searchInput.value : '';
const currentType = questionTypeSelect ? questionTypeSelect.value : 'all';
// Load questions with current filters
loadQuestionsFromDB(currentSearch, currentType);
loadTopics();
initializeSearchFunctionality(); // Initialize search functionality
}
if (pageId === 'generatePaperPage') {
loadChaptersForPaper();
initializeChapterSelection();
}
}
});
});
</script>
<!-- Questions Page Functionality -->
<script>
async function loadQuestionsFromDB(searchTerm = '', questionType = '') {
console.log('loadQuestionsFromDB called with:', { searchTerm, questionType });
const questionsTable = document.getElementById('questionsTable');
const questionsCount = document.getElementById('questionsCount');
questionsTable.innerHTML = '<div class="text-center"><div class="spinner-border" role="status"></div><p>Loading questions...</p></div>';
try {
let url = `${backendBase}/questions`;
const params = new URLSearchParams();
console.log('Before params - searchTerm:', searchTerm, 'questionType:', questionType);
if (searchTerm && searchTerm.trim() !== '') {
params.append('search', searchTerm.trim());
}
if (questionType && questionType !== 'all') {
params.append('qtype', questionType);
}
console.log('Params:', params.toString());
if (params.toString()) {
url += '?' + params.toString();
}
console.log('Final URL:', url);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const questions = await response.json();
// Update count
questionsCount.innerHTML = `<div class="alert alert-info">Found ${questions.length} questions</div>`;
if (questions.length === 0) {
questionsTable.innerHTML = '<div class="alert alert-warning text-center">No questions found in the database.</div>';
return;
}
// Create table with editable fields
let tableHTML = `
<div style="max-height: 500px; overflow-y: auto;">
<table class="table table-striped table-hover">
<thead class="table-light sticky-top">
<tr>
<th>ID</th>
<th>Topic</th>
<th>Type</th>
<th>Question</th>
<th>Options/Answer</th>
<th>Difficulty</th>
<th>Flagged</th>
<th>Actions</th>
<th>Created</th>
</tr>
</thead>
<tbody>
`;
questions.forEach(q => {
const createdDate = q.created_at ? new Date(q.created_at).toLocaleDateString() : 'N/A';
let optionsHtml = '';
if (q.type === 'MCQ') {
optionsHtml = `
<div><strong>A:</strong> <span class="editable" contenteditable="true" data-field="option_a" data-id="${q.id}">${q.option_a || ''}</span></div>
<div><strong>B:</strong> <span class="editable" contenteditable="true" data-field="option_b" data-id="${q.id}">${q.option_b || ''}</span></div>
<div><strong>C:</strong> <span class="editable" contenteditable="true" data-field="option_c" data-id="${q.id}">${q.option_c || ''}</span></div>
<div><strong>D:</strong> <span class="editable" contenteditable="true" data-field="option_d" data-id="${q.id}">${q.option_d || ''}</span></div>
<div class="text-success"><strong>Answer:</strong> <span class="editable" contenteditable="true" data-field="answer" data-id="${q.id}">${q.answer || ''}</span></div>
`;
} else {
optionsHtml = `
<div class="text-success">
<strong>Answer:</strong>
<span class="editable" contenteditable="true" data-field="descriptive_answer" data-id="${q.id}">
${q.descriptive_answer || 'Not provided'}
</span>
</div>
`;
}
// Determine flagged status display
const flaggedStatus = q.flagged === true ?
'<span class="badge bg-success">Approved</span>' :
q.flagged === false ?
'<span class="badge bg-danger">Not Approved</span>' :
'<span class="badge bg-secondary">Pending</span>';
tableHTML += `
<tr class="question-row ${(q.type || '').toLowerCase()}">
<td>${q.id}</td>
<td>
<span class="editable" contenteditable="true" data-field="topic" data-id="${q.id}">
${q.topic || 'N/A'}
</span>
</td>
<td><span class="badge ${q.type === 'MCQ' ? 'bg-success' : 'bg-warning'}">${q.type || 'Unknown'}</span></td>
<td>
<span class="editable" contenteditable="true" data-field="question" data-id="${q.id}">
${q.question || ''}
</span>
</td>
<td>${optionsHtml}</td>
<td>
<select class="form-select form-select-sm difficulty-select" data-id="${q.id}">
<option value="">—</option>
${[1,2,3,4,5].map(i => `<option value="${i}" ${q.difficulty == i ? 'selected' : ''}>${i}</option>`).join('')}
</select>
</td>
<td>${flaggedStatus}</td>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-success approve-btn" data-id="${q.id}" title="Approve Question" ${q.flagged === true ? 'disabled' : ''}>
</button>
<button class="btn btn-danger reject-btn" data-id="${q.id}" title="Reject Question" ${q.flagged === false ? 'disabled' : ''}>
</button>
</div>
</td>
<td>${createdDate}</td>
</tr>
`;
});
tableHTML += '</tbody></table></div>';
questionsTable.innerHTML = tableHTML;
// Initialize event listeners for the new table
initializeQuestionTableEventListeners();
} catch (error) {
console.error('Error loading questions:', error);
questionsTable.innerHTML = '<div class="alert alert-danger">Error loading questions from database: ' + error.message + '</div>';
}
}
// Function to update individual question field
async function updateQuestionField(questionId, field, value) {
try {
const response = await fetch(`${backendBase}/update_question`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: parseInt(questionId),
updates: {
[field]: value
}
})
});
const data = await response.json();
if (data.status === 'success') {
console.log(`✅ Question ${questionId} ${field} updated successfully`);
showNotification(`Question ${field} updated successfully`, 'success');
return true;
} else {
console.error('❌ Failed to update question:', data.error);
showNotification(`Failed to update question: ${data.error}`, 'error');
return false;
}
} catch (error) {
console.error('❌ Error updating question:', error);
showNotification('Error updating question. Please try again.', 'error');
return false;
}
}
// Function to update question flag (approve/reject)
async function updateQuestionFlag(questionId, flagged) {
try {
const response = await fetch(`${backendBase}/update_question`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: parseInt(questionId),
updates: {
flagged: flagged
}
})
});
const data = await response.json();
if (data.status === 'success') {
// Reload the questions to reflect the change
loadQuestionsFromDB();
} else {
console.error('Failed to update question flag:', data.error);
alert('Failed to update question status. Please try again.');
}
} catch (error) {
console.error('Error updating question flag:', error);
alert('Error updating question status. Please try again.');
}
}
// debounce helper
function debounce(fn, wait = 300) {
let t;
return function(...args) {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), wait);
};
}
// ensure the search input triggers filtered loads on typing
(function wireLiveSearch() {
const searchInput = document.getElementById('searchInput');
const questionTypeSelect = document.getElementById('questionTypeSelect');
const searchBtn = document.getElementById('searchBtn');
if (!searchInput) return;
const doSearch = () => {
const q = searchInput.value ? searchInput.value.trim() : '';
const type = (questionTypeSelect && questionTypeSelect.value) ? questionTypeSelect.value : 'all';
loadQuestionsFromDB(q, type);
};
// live search on input with debounce
searchInput.addEventListener('input', debounce(doSearch, 300));
// also update when question type changes
if (questionTypeSelect) {
questionTypeSelect.addEventListener('change', () => {
// run with current search text
const q = searchInput.value ? searchInput.value.trim() : '';
loadQuestionsFromDB(q, questionTypeSelect.value);
});
}
// keep the manual search button (if present) wired
if (searchBtn) {
searchBtn.addEventListener('click', (e) => {
e.preventDefault();
doSearch();
});
}
})();
</script>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> -->
<script>
function displayGeneratedPaper(questions, targetElementId = 'generatedPaperResults') {
const resultsDiv = document.getElementById(targetElementId);
if (!resultsDiv) {
console.error('Target element not found:', targetElementId);
return;
}
resultsDiv.innerHTML = '';
if (!questions || questions.length === 0) {
resultsDiv.innerHTML = '<div class="alert alert-warning">No questions found.</div>';
return;
}
// Shuffle MCQ options
const processedQuestions = questions.map(q => {
if (q.type === 'MCQ') {
return shuffleMCQOptions(q);
}
return q;
});
const mcqs = processedQuestions.filter(q => (q.type || '').toUpperCase() === 'MCQ');
const descs = processedQuestions.filter(q => (q.type || '').toUpperCase() !== 'MCQ');
const paperContainer = document.createElement('div');
paperContainer.className = 'question-paper';
const header = document.createElement('h5');
header.className = 'mb-3 text-primary';
header.textContent = `Generated Question Paper (${questions.length} questions) - OPTIONS SHUFFLED`;
paperContainer.appendChild(header);
// If there are MCQs, render them first
if (mcqs.length) {
const mcqSection = document.createElement('div');
mcqSection.className = 'mb-3';
mcqSection.innerHTML = `<h6>Multiple Choice Questions (${mcqs.length}) - Options Shuffled</h6>`;
const table = document.createElement('table');
table.className = 'table table-bordered table-striped question-paper w-100 mb-3';
table.style.tableLayout = 'fixed';
let tableHTML = `<thead class="table-dark"><tr><th width="5%">#</th><th width="60%">Question</th><th width="35%">Options</th></tr></thead><tbody>`;
mcqs.forEach((q, i) => {
// Use the already formatted options with new labels
const cleanA = cleanOptionText(q.option_a || '');
const cleanB = cleanOptionText(q.option_b || '');
const cleanC = cleanOptionText(q.option_c || '');
const cleanD = cleanOptionText(q.option_d || '');
tableHTML += `<tr><td><strong>${i+1}</strong></td><td>${q.question}</td><td>
<div><strong>A:</strong> ${cleanA}</div>
<div><strong>B:</strong> ${cleanB}</div>
<div><strong>C:</strong> ${cleanC}</div>
<div><strong>D:</strong> ${cleanD}</div>
</td></tr>`;
});
tableHTML += '</tbody>';
table.innerHTML = tableHTML;
mcqSection.appendChild(table);
paperContainer.appendChild(mcqSection);
}
// Rest of your display code remains the same...
// Then descriptive questions
if (descs.length) {
const descSection = document.createElement('div');
descSection.className = 'mb-3';
descSection.innerHTML = `<h6>Descriptive / Short-answer Questions (${descs.length})</h6>`;
const table = document.createElement('table');
table.className = 'table table-bordered table-striped question-paper w-100';
table.style.tableLayout = 'fixed';
let tableHTML = `<thead class="table-dark"><tr><th width="5%">#</th><th width="95%">Question</th></tr></thead><tbody>`;
descs.forEach((q, i) => {
tableHTML += `<tr><td><strong>${i+1}</strong></td><td>${q.question}</td></tr>`;
});
tableHTML += '</tbody>';
table.innerHTML = tableHTML;
descSection.appendChild(table);
paperContainer.appendChild(descSection);
}
// Download button
const downloadBtn = document.createElement('button');
downloadBtn.className = 'btn btn-success mt-3';
downloadBtn.innerHTML = '📥 Download Question Paper';
downloadBtn.onclick = () => downloadQuestionPaper(processedQuestions);
paperContainer.appendChild(downloadBtn);
resultsDiv.appendChild(paperContainer);
}
// Function to shuffle an array (Fisher-Yates algorithm)
function shuffleArray(array) {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
// Function to remove existing option labels (A., B., C., D.)
function removeOptionLabel(optionText) {
if (!optionText) return '';
// Remove patterns like "A. ", "B) ", "C:", "D - " etc.
return optionText.replace(/^[A-D][\.\:\-\)]\s*/, '').trim();
}
// Function to shuffle MCQ options while preserving the correct answer
function shuffleMCQOptions(question) {
if (question.type !== 'MCQ') return question;
// Create options with cleaned text (without labels)
const options = [
{ label: 'A', text: removeOptionLabel(question.option_a || '') },
{ label: 'B', text: removeOptionLabel(question.option_b || '') },
{ label: 'C', text: removeOptionLabel(question.option_c || '') },
{ label: 'D', text: removeOptionLabel(question.option_d || '') }
].filter(opt => opt.text.trim() !== ''); // Remove empty options
// Shuffle the options
const shuffledOptions = shuffleArray(options);
// Find the new position of the correct answer
const originalAnswer = question.answer;
let newAnswer = '';
shuffledOptions.forEach((option, index) => {
if (option.label === originalAnswer) {
newAnswer = ['A', 'B', 'C', 'D'][index];
}
});
// Create the shuffled question with new labels
return {
...question,
option_a: `A. ${shuffledOptions[0]?.text || ''}`,
option_b: `B. ${shuffledOptions[1]?.text || ''}`,
option_c: `C. ${shuffledOptions[2]?.text || ''}`,
option_d: `D. ${shuffledOptions[3]?.text || ''}`,
answer: newAnswer,
original_answer: originalAnswer, // Keep original for reference
options_shuffled: true // Flag to indicate options were shuffled
};
}
</script>
<!-- PDF Logic -->
<script>
async function uploadAndExtractTOC(file) {
const fd = new FormData();
fd.append("file", file, file.name);
const res = await fetch(`${backendBase}/extract_toc`, { method: "POST", body: fd });
return res.json();
}
// PDF UI wiring
(function initPdf() {
const pdfInput = document.querySelector('#pdfPage #pdfUploader');
const pdfBox = pdfInput && pdfInput.closest('.upload-box');
const statNodes = Array.from(document.querySelectorAll('#pdfPage .stats-card h3') || []);
function updateDashboardFromResponse(resp) {
try {
if (!resp) return;
if (statNodes[0]) statNodes[0].textContent = (resp.global_state?.pdf_uploads ?? statNodes[0].textContent);
if (statNodes[1]) statNodes[1].textContent = (typeof resp.pages === 'number' ? resp.pages : (resp.global_state?.last_pdf_pages ?? statNodes[1].textContent));
if (statNodes[2]) statNodes[2].textContent = (typeof resp.mcqCount === 'number') ? resp.mcqCount : statNodes[2].textContent;
if (statNodes[3]) statNodes[3].textContent = (typeof resp.descCount === 'number') ? resp.descCount : statNodes[3].textContent;
} catch (e) {
console.warn("Failed to update dashboard:", e);
}
}
if (!pdfInput || !pdfBox) return;
// Build controls area under upload box
const ctrlWrap = document.createElement('div');
ctrlWrap.className = 'mt-3';
pdfBox.appendChild(ctrlWrap);
const statusEl = document.createElement('div');
statusEl.style.marginTop = '8px';
ctrlWrap.appendChild(statusEl);
const tocEl = document.createElement('div');
tocEl.style.marginTop = '8px';
ctrlWrap.appendChild(tocEl);
const optionsForm = document.createElement('div');
optionsForm.innerHTML = `
<div class="row g-2 align-items-center">
<div class="col-auto">
<label class="form-label mb-0">Scope</label>
<div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="scopeRadio" id="scopeAll" value="all" checked>
<label class="form-check-label small" for="scopeAll">All topics</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="scopeRadio" id="scopeSelected" value="selected">
<label class="form-check-label small" for="scopeSelected">Selected chapters only</label>
</div>
</div>
</div>
<div class="col-auto">
<label class="form-label mb-0">MCQ source</label>
<div>
<select id="mcqSource" class="form-select form-select-sm">
<option value="llama_open">Llama3 (open-ended)</option>
<option value="textbook">From textbook (use chapter content)</option>
</select>
</div>
</div>
<div class="col-auto">
<label class="form-label mb-0">Question type</label>
<div>
<select id="questionType" class="form-select form-select-sm">
<option value="mcq">MCQs</option>
<option value="descriptive">Descriptive</option>
<option value="both" selected>Both</option>
</select>
</div>
</div>
<div class="col-auto">
<label class="form-label mb-0 small">MCQs per topic</label>
<input id="numMcqs" type="number" min="1" max="50" value="5" class="form-control form-control-sm" style="width:80px;">
</div>
<div class="col-auto">
<label class="form-label mb-0 small">Descriptive per topic</label>
<input id="numDesc" type="number" min="0" max="20" value="3" class="form-control form-control-sm" style="width:80px;">
</div>
<div class="col-auto">
<button id="generateBtn" class="btn btn-primary btn-sm" style="margin-top:6px; display:none;">Generate MCQs</button>
</div>
</div>
`;
ctrlWrap.appendChild(optionsForm);
const chaptersContainer = document.createElement('div');
chaptersContainer.className = 'mt-3';
ctrlWrap.appendChild(chaptersContainer);
const resultsArea = document.createElement('div');
resultsArea.className = 'mt-3';
pdfBox.appendChild(resultsArea);
let lastMatches = [];
pdfInput.addEventListener('change', async (e) => {
resultsArea.innerHTML = '';
chaptersContainer.innerHTML = '';
tocEl.innerHTML = '';
const f = e.target.files[0];
if (!f) return;
statusEl.innerHTML = '<div class="spinner-border spinner-border-sm" role="status"></div> Parsing TOC...';
try {
const r = await uploadAndExtractTOC(f);
if (r.status === 'success') {
statusEl.innerHTML = `<div class="small text-success">Found ${r.matches?.length || 0} TOC entries across ${r.chapters_count || 0} chapters.</div>`;
lastMatches = r.matches || [];
// Build chapter groups
const groups = {};
(lastMatches || []).forEach(m => {
const chap = (m.subnum || '0').split('.')[0];
groups[chap] = groups[chap] || [];
groups[chap].push(m);
});
// Show chapter checkboxes
chaptersContainer.innerHTML = '<div class="small fw-bold mb-2">Chapters (pick for "Selected chapters only")</div>';
const grid = document.createElement('div');
grid.className = 'row g-2';
Object.keys(groups).sort((a,b)=>parseInt(a)-parseInt(b)).forEach(chap => {
const col = document.createElement('div');
col.className = 'col-6 col-md-3';
const card = document.createElement('div');
card.className = 'p-2 border rounded small';
const chkId = `chap_chk_${chap}`;
card.innerHTML = `<div class="form-check"><input class="form-check-input chap-chk" type="checkbox" value="${chap}" id="${chkId}"><label class="form-check-label" for="${chkId}">Chapter ${chap}${groups[chap].length} topics</label></div>`;
col.appendChild(card);
grid.appendChild(col);
});
chaptersContainer.appendChild(grid);
tocEl.innerHTML = `<div class="small">Detected ${lastMatches.length} TOC entries across ${Object.keys(groups).length} chapters — Pages detected: ${r.pages ?? '-'}</div>`;
document.getElementById('generateBtn').style.display = 'inline-block';
} else {
statusEl.textContent = 'TOC parse failed: ' + (r.error || 'unknown');
document.getElementById('generateBtn').style.display = 'none';
}
} catch (err) {
statusEl.textContent = 'Error: ' + err;
document.getElementById('generateBtn').style.display = 'none';
}
});
ctrlWrap.addEventListener('change', (ev) => {
const scope = ctrlWrap.querySelector('input[name="scopeRadio"]:checked').value;
chaptersContainer.style.display = (scope === 'selected') ? '' : 'none';
});
chaptersContainer.style.display = 'none';
document.getElementById('generateBtn').addEventListener('click', async () => {
resultsArea.innerHTML = '';
const f = pdfInput.files[0];
if (!f) return alert('Choose a PDF first');
const scope = ctrlWrap.querySelector('input[name="scopeRadio"]:checked').value;
const selectedChapters = [];
if (scope === 'selected') {
document.querySelectorAll('.chap-chk:checked').forEach(c => selectedChapters.push(parseInt(c.value)));
if (!selectedChapters.length) return alert('Select at least one chapter or choose "All topics"');
}
const qType = document.getElementById('questionType').value;
const mcqSource = document.getElementById('mcqSource').value;
const numMcqs = parseInt(document.getElementById('numMcqs').value || '5', 10);
const numDesc = parseInt(document.getElementById('numDesc').value || '3', 10);
const btn = document.getElementById('generateBtn');
btn.disabled = true;
btn.textContent = 'Generating…';
try {
const fd = new FormData();
fd.append("file", f, f.name);
fd.append("chapters", JSON.stringify(selectedChapters || []));
fd.append("question_type", qType);
fd.append("mcq_source", mcqSource);
fd.append("num_mcqs", String(numMcqs));
fd.append("num_desc", String(numDesc));
const response = await fetch(`${backendBase}/generate_pdf_mcqs`, { method: "POST", body: fd });
const r = await response.json();
if (r.status === 'success') {
// Update dashboard counts
updateDashboardFromResponse(r);
// Render results
const results = r.results || {};
const header = document.createElement('div');
header.className='mb-2';
header.innerHTML = `<div class="small"><b>Generated for ${Object.keys(results).length} topics</b> — MCQs: ${r.mcqCount || 0} — Descriptive: ${r.descCount || 0}</div>`;
resultsArea.appendChild(header);
// Downloads
const dlRow = document.createElement('div');
dlRow.className='mb-3';
const keys = r.download_keys || {};
if (keys.docx) {
const a = document.createElement('a');
a.href = `${backendBase}/download/${keys.docx}`;
a.className='btn btn-outline-primary btn-sm me-2';
a.textContent = 'Download DOCX';
dlRow.appendChild(a);
}
if (keys.excel) {
const a = document.createElement('a');
a.href = `${backendBase}/download/${keys.excel}`;
a.className='btn btn-outline-success btn-sm me-2';
a.textContent = 'Download Excel';
dlRow.appendChild(a);
}
if (keys.csv) {
const a = document.createElement('a');
a.href = `${backendBase}/download/${keys.csv}`;
a.className='btn btn-outline-secondary btn-sm me-2';
a.textContent = 'Download CSV';
dlRow.appendChild(a);
}
// Create Export to Database button
const exportPdfBtn = document.createElement('button');
exportPdfBtn.className = 'btn btn-warning btn-sm me-2';
exportPdfBtn.innerHTML = '📤 Export to Database';
exportPdfBtn.addEventListener('click', async () => {
exportPdfBtn.disabled = true;
exportPdfBtn.textContent = 'Exporting…';
try {
const payload = results;
const resp = await fetch(`${backendBase}/save_questions_to_db`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
const j = await resp.json().catch(() => ({status: 'error', error: 'invalid_json'}));
if (j.status === 'success') {
alert('✅ PDF questions exported to database.');
} else {
alert('❌ Export failed: ' + (j.error || JSON.stringify(j)));
}
} catch (err) {
alert('⚠ Error exporting PDF questions: ' + err);
} finally {
exportPdfBtn.disabled = false;
exportPdfBtn.innerHTML = '📤 Export to Database';
}
});
dlRow.appendChild(exportPdfBtn);
resultsArea.appendChild(dlRow);
const topicsWrap = document.createElement('div'); topicsWrap.className='accordion'; resultsArea.appendChild(topicsWrap);
const renderMCQ = (qType === 'mcq' || qType === 'both');
const renderDesc = (qType === 'descriptive' || qType === 'both');
Object.keys(results).forEach((topicTitle, idx) => {
const block = results[topicTitle];
const mcqs = block.mcqs || [];
const descrs = block.descriptive || [];
const item = document.createElement('div'); item.className='accordion-item';
const hId = `heading${idx}`;
const cId = `collapse${idx}`;
item.innerHTML = `
<h2 class="accordion-header" id="${hId}">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#${cId}" aria-expanded="false" aria-controls="${cId}">
${topicTitle} <small class="text-muted ms-2"> (MCQs: ${mcqs.length} • Descr: ${descrs.length})</small>
</button>
</h2>
<div id="${cId}" class="accordion-collapse collapse" aria-labelledby="${hId}">
<div class="accordion-body">
<div class="result-content"></div>
</div>
</div>
`;
topicsWrap.appendChild(item);
const contentDiv = item.querySelector('.result-content');
if (renderMCQ && mcqs && mcqs.length) {
const mcqTitle = document.createElement('div'); mcqTitle.className='mb-2';
mcqTitle.innerHTML = `<b>Multiple Choice Questions</b>`;
contentDiv.appendChild(mcqTitle);
mcqs.forEach((m, mi) => {
const qDiv = document.createElement('div'); qDiv.className = 'mb-2';
const qtext = m.question || `Q${mi+1}.`;
const difficulty = m.difficulty || 'N/A';
qDiv.innerHTML = `<div><strong>${mi+1}. ${qtext}</strong> <small class="text-muted"> — Level ${difficulty}</small></div>`;
const ul = document.createElement('ul'); ul.className='small';
(m.options || []).forEach(opt => {
const li = document.createElement('li'); li.textContent = opt;
ul.appendChild(li);
});
qDiv.appendChild(ul);
if (m.answer) {
const ans = document.createElement('div'); ans.className='text-success small'; ans.textContent = `✅ Answer: ${m.answer}`;
qDiv.appendChild(ans);
}
contentDiv.appendChild(qDiv);
});
}
if (renderDesc && descrs && descrs.length) {
const descTitle = document.createElement('div'); descTitle.className='mt-3 mb-2';
descTitle.innerHTML = `<b>Descriptive / Short-answer Questions</b>`;
contentDiv.appendChild(descTitle);
descrs.forEach((d, di) => {
const dDiv = document.createElement('div'); dDiv.className='mb-2';
const q = typeof d === 'string' ? d : (d.question || '');
const a = (typeof d === 'object' ? (d.answer || '') : '');
const diff = (typeof d === 'object' ? (d.difficulty || 'N/A') : 'N/A');
dDiv.innerHTML = `<div>${di+1}. ${q} <small class="text-muted"> — Level ${diff}</small></div>`;
if (a) {
const ans = document.createElement('div'); ans.className='text-success small'; ans.textContent = `✅ Answer: ${a}`;
dDiv.appendChild(ans);
}
contentDiv.appendChild(dDiv);
});
}
});
function setFilter(mode) {
Array.from(resultsArea.querySelectorAll('.result-content')).forEach(rc=>{
if (mode === 'mcq') {
rc.querySelectorAll('div').forEach(div=>{
const txt = div.innerText || '';
if (txt.includes('Descriptive / Short-answer Questions')) div.style.display = 'none';
if (txt.includes('Multiple Choice Questions')) div.style.display = '';
if (!txt.includes('Descriptive / Short-answer Questions') && !txt.includes('Multiple Choice Questions')) div.style.display = '';
});
} else if (mode === 'des') {
rc.querySelectorAll('div').forEach(div=>{
const txt = div.innerText || '';
if (txt.includes('Multiple Choice Questions')) div.style.display = 'none';
if (txt.includes('Descriptive / Short-answer Questions')) div.style.display = '';
if (!txt.includes('Descriptive / Short-answer Questions') && !txt.includes('Multiple Choice Questions')) div.style.display = '';
});
} else {
rc.querySelectorAll('div').forEach(div=>div.style.display = '');
}
});
}
if (qType === 'mcq') setFilter('mcq');
else if (qType === 'descriptive') setFilter('des');
else setFilter('both');
} else {
alert('Generation failed: ' + (r.error || 'unknown'));
}
} catch (err) {
alert('Error: ' + err);
} finally {
btn.disabled = false;
btn.textContent = 'Generate MCQs';
}
});
})();
</script>
<!-- === FULL VIDEO LOGIC (from original) === -->
<script>
document.addEventListener('DOMContentLoaded', () => {
// note: uses shared backendBase variable defined earlier
const statNodes = Array.from(document.querySelectorAll('#videoPage .stats-card h3') || []);
function updateDashboardFromResponse(resp) {
try {
if (!resp) return;
// update video uploads
//const vUploads = resp.global_state?.video_uploads;
// video uploads might be at resp.global_state.video_uploads OR resp.video_uploads (top-level)
const vUploads = (resp.global_state && typeof resp.global_state.video_uploads === 'number')
? resp.global_state.video_uploads
: (typeof resp.video_uploads === 'number' ? resp.video_uploads : undefined);
if (typeof vUploads === 'number') {
const el = document.getElementById('videoUploadsStat');
if (el) el.textContent = vUploads;
}
// update MCQs generated (per-request or global)
const mcqsNow = (typeof resp.mcqCount === 'number') ? resp.mcqCount : resp.global_state?.mcq_count;
if (typeof mcqsNow === 'number') {
const el2 = document.getElementById('videoMcqStat');
if (el2) el2.textContent = mcqsNow;
}
} catch (e) {
console.warn("updateDashboardFromResponse failed", e);
}
}
async function transcribeVideo(file, whisper_model="small") {
const fd = new FormData();
fd.append("file", file, file.name);
fd.append("whisper_model", whisper_model);
const r = await fetch(`${backendBase}/transcribe_video`, { method: "POST", body: fd });
const json = await r.json().catch(()=>({status:"error", error:"invalid_json_response"}));
return json;
}
async function generateVideoMCQs({file=null, summary="", questionType="both", numQs=10}) {
const fd = new FormData();
if (file) fd.append("file", file, file.name);
fd.append("summary", summary || "");
fd.append("question_type", questionType);
fd.append("num_qs", String(numQs));
const r = await fetch(`${backendBase}/generate_video_mcqs`, { method: "POST", body: fd });
const json = await r.json().catch(()=>({status:"error", error:"invalid_json_response"}));
return json;
}
// Find the video input (inside Video page)
const videoInput = document.querySelector('#videoPage #videoUploader');
if (!videoInput) {
console.warn("No video input found in #videoPage");
return;
}
const videoBox = videoInput.closest('.upload-box') || document.body;
// build controls
const ctrl = document.createElement('div'); ctrl.className = 'mt-3';
const transBtn = document.createElement('button'); transBtn.className = 'btn btn-outline-primary btn-sm me-2';
transBtn.textContent = 'Transcribe';
transBtn.disabled = true;
const genBtn = document.createElement('button'); genBtn.className = 'btn btn-primary btn-sm';
genBtn.textContent = 'Generate MCQs';
genBtn.disabled = true;
ctrl.appendChild(transBtn); ctrl.appendChild(genBtn);
videoBox.appendChild(ctrl);
const status = document.createElement('div'); status.className = 'mt-2 small'; status.style.whiteSpace = 'pre-wrap';
videoBox.appendChild(status);
const transcriptArea = document.createElement('textarea'); transcriptArea.className='form-control mt-2';
transcriptArea.rows = 8; transcriptArea.placeholder = 'Transcript will appear here (or paste one)...';
videoBox.appendChild(transcriptArea);
const summaryDiv = document.createElement('div'); summaryDiv.className='mt-2';
videoBox.appendChild(summaryDiv);
const chunksDiv = document.createElement('div'); chunksDiv.className='mt-2 small text-muted';
videoBox.appendChild(chunksDiv);
const opts = document.createElement('div'); opts.className='mt-2';
opts.innerHTML = `
<div class="row g-2 align-items-center">
<div class="col-auto">
<label class="form-label mb-0 small">Question type</label>
<select id="videoQuestionType" class="form-select form-select-sm">
<option value="mcq">MCQs</option>
</select>
</div>
<div class="col-auto">
<label class="form-label mb-0 small">Number of MCQs</label>
<input id="videoNumQs" class="form-control form-control-sm" value="10" />
</div>
</div>
`;
videoBox.appendChild(opts);
const resultsWrap = document.createElement('div'); resultsWrap.className='mt-3';
videoBox.appendChild(resultsWrap);
// enable buttons when user selects a file
videoInput.addEventListener('change', (e) => {
const f = e.target.files && e.target.files[0];
if (f) {
transBtn.disabled = false;
genBtn.disabled = false;
status.textContent = `Selected file: ${f.name} (${Math.round(f.size/1024)} KB)`;
console.log("Video selected", f);
} else {
transBtn.disabled = true;
genBtn.disabled = true;
}
});
// Transcribe button
transBtn.addEventListener('click', async () => {
const f = videoInput.files && videoInput.files[0];
if (!f) return alert('Choose a video file first.');
status.textContent = 'Transcribing... (may take minutes)';
transBtn.disabled = true; genBtn.disabled = true;
try {
const r = await transcribeVideo(f, "small");
if (r.status === 'success') {
status.textContent = 'Transcription complete.';
transcriptArea.value = r.transcript || '';
summaryDiv.innerHTML = `<b>Summary</b><div class="small mt-1">${(r.summary || '').replace(/\n/g,'<br>')}</div>`;
if (r.chunks && r.chunks.length) {
chunksDiv.innerHTML = '<b>Chunk summaries:</b><br>' + r.chunks.slice(0,10).map((c,i)=>`<div>● Chunk ${i+1}: ${c}</div>`).join('');
} else chunksDiv.innerHTML = '';
genBtn.disabled = false;
if (r.global_state) updateDashboardFromResponse(r);
} else {
status.textContent = 'Transcription error: ' + (r.error || 'unknown');
console.warn("transcribe error", r);
}
} catch (err) {
status.textContent = 'Network / JS error during transcription: ' + err;
console.error(err);
} finally {
transBtn.disabled = false;
}
});
// Generate button
genBtn.addEventListener('click', async () => {
const f = videoInput.files && videoInput.files[0];
const qType = document.getElementById('videoQuestionType').value;
const numQs = parseInt(document.getElementById('videoNumQs').value || '10', 10);
resultsWrap.innerHTML = '';
status.textContent = 'Generating questions...';
genBtn.disabled = true; transBtn.disabled = true;
try {
const curSummary = summaryDiv.innerText.trim() ? summaryDiv.innerText.trim() : (transcriptArea.value.trim() ? "" : "");
let response;
if (curSummary) {
response = await generateVideoMCQs({ file: null, summary: curSummary, questionType: qType, numQs: numQs });
} else {
if (!f) return alert('No file selected and no summary available.');
response = await generateVideoMCQs({ file: f, summary: "", questionType: qType, numQs: numQs });
}
if (response.status === 'success') {
status.textContent = `Generated — MCQs: ${response.mcqCount || 0} `;
updateDashboardFromResponse(response);
// downloads
const keys = response.download_keys || {};
const dlRow = document.createElement('div'); dlRow.className = 'mb-2';
if (keys.docx) {
const a = document.createElement('a'); a.href = `${backendBase}/download/${keys.docx}`; a.className='btn btn-outline-primary btn-sm me-2'; a.textContent='Download DOCX';
dlRow.appendChild(a);
}
if (keys.excel) {
const a = document.createElement('a'); a.href = `${backendBase}/download/${keys.excel}`; a.className='btn btn-outline-success btn-sm me-2'; a.textContent='Download Excel';
dlRow.appendChild(a);
}
if (keys.csv) {
const a = document.createElement('a'); a.href = `${backendBase}/download/${keys.csv}`; a.className='btn btn-outline-secondary btn-sm me-2'; a.textContent='Download CSV';
dlRow.appendChild(a);
}
resultsWrap.appendChild(dlRow);
// render results per topic
const res = response.results || {};
for (const topic of Object.keys(res)) {
const block = res[topic];
if (block.mcqs && block.mcqs.length) {
const h = document.createElement('div'); h.className='mt-2'; h.innerHTML = `<b>MCQs</b>`;
resultsWrap.appendChild(h);
block.mcqs.forEach((m,i) => {
const div = document.createElement('div'); div.className='mb-2';
div.innerHTML = `<div><strong>${i+1}. ${m.question}</strong></div>`;
const ul = document.createElement('ul'); ul.className='small';
(m.options || []).forEach(opt => { const li = document.createElement('li'); li.textContent = opt; ul.appendChild(li); });
div.appendChild(ul);
if (m.answer) { const a = document.createElement('div'); a.className='text-success small'; a.textContent = `✅ Answer: ${m.answer}`; div.appendChild(a); }
resultsWrap.appendChild(div);
});
}
if (block.descriptive && block.descriptive.length) {
const h = document.createElement('div'); h.className='mt-3'; h.innerHTML = `<b>Descriptive</b>`;
resultsWrap.appendChild(h);
block.descriptive.forEach((d,i) => {
const div = document.createElement('div'); div.className='mb-2';
const q = (typeof d === 'object') ? d.question : d;
const a = (typeof d === 'object') ? d.answer : '';
const diff = (typeof d === 'object') ? d.difficulty : 'N/A';
div.innerHTML = `<div>${i+1}. ${q} <small class="text-muted"> — Level ${diff}</small></div>`;
if (a) { const an = document.createElement('div'); an.className='text-success small'; an.textContent = `✅ Answer: ${a}`; div.appendChild(an); }
resultsWrap.appendChild(div);
});
}
}
} else {
status.textContent = 'Generation error: ' + (response.error || 'unknown');
console.warn("generation error", response);
}
} catch (err) {
console.error("generate exception", err);
status.textContent = 'Network / JS error during generation: ' + err;
} finally {
genBtn.disabled = false; transBtn.disabled = false;
}
});
}); // DOMContentLoaded
</script>
<!-- NAV & HISTORY HANDLING (Back button -> main page) -->
<script>
(function setupNavHistory(){
const mainPage = document.getElementById('mainPage');
const pages = Array.from(document.querySelectorAll('.page'));
function showPageById(id) {
// hide all pages & main
pages.forEach(p => p.style.display = 'none');
mainPage.style.display = 'none';
// show requested
const el = document.getElementById(id);
if (el) el.style.display = 'block';
}
// add click handlers for nav buttons
document.querySelectorAll('.nav-vertical .btn').forEach(btn => {
btn.addEventListener('click', (ev) => {
const pageId = btn.getAttribute('data-page');
if (!pageId) return;
showPageById(pageId);
// push state so back button works
history.pushState({page: pageId}, "", "#" + pageId);
});
});
// handle popstate (back/forward)
window.addEventListener('popstate', (e) => {
if (e.state && e.state.page) {
showPageById(e.state.page);
} else {
// go home
pages.forEach(p => p.style.display = 'none');
mainPage.style.display = 'flex';
}
});
// initial load: if hash present, open that page (and push state)
const initialHash = window.location.hash && window.location.hash.replace('#','');
if (initialHash && document.getElementById(initialHash)) {
// show target and replace history state
showPageById(initialHash);
history.replaceState({page: initialHash}, "", "#" + initialHash);
} else {
// show main page
pages.forEach(p => p.style.display = 'none');
mainPage.style.display = 'flex';
history.replaceState({}, "", window.location.pathname);
}
})();
</script>
<script>
// global function to show main page / hide other pages
function goHome() {
try {
// hide all content pages
document.querySelectorAll('.page').forEach(p => p.style.display = 'none');
// show main navigation
const main = document.getElementById('mainPage');
if (main) main.style.display = 'flex';
// optionally show header/footer (if login state allows)
const header = document.getElementById('mainHeader');
const footer = document.getElementById('mainFooter');
if (header) header.style.display = header.style.display === 'none' ? 'flex' : header.style.display;
if (footer) footer.style.display = footer.style.display === 'none' ? 'block' : footer.style.display;
// push history state so back button works
history.pushState({ page: 'home' }, "", "#");
} catch (e) {
console.warn("goHome error", e);
}
}
// handle browser back/forward: if user navigates back to empty state, show main page
window.addEventListener('popstate', function (e) {
if (!e.state || e.state.page === 'home') {
document.querySelectorAll('.page').forEach(p => p.style.display = 'none');
const main = document.getElementById('mainPage');
if (main) main.style.display = 'flex';
}
});
</script>
</body>
</html>