feat: Add collapsible curriculum accordion tree, direct video upload to platform, resilient AI asset generation with smart synthesis fallback, and ingest Physics Unit 2

This commit is contained in:
Hamza-Ayed
2026-08-29 05:13:47 +03:00
parent 4e047ee8f2
commit 771ec6acc5
13 changed files with 869 additions and 301 deletions
+305 -278
View File
@@ -63,26 +63,44 @@ class CurriculumStudio
max-height: calc(100vh - 120px); overflow-y: auto;
}
.tree-node { margin-bottom: 14px; }
.tree-node { margin-bottom: 12px; }
.tree-header {
font-size: 13px; font-weight: 800; color: var(--accent-gold); display: flex; align-items: center; gap: 6px;
padding: 8px 12px; background: rgba(255, 209, 102, 0.08); border-radius: 10px; cursor: pointer;
font-size: 13px; font-weight: 800; color: var(--accent-gold); display: flex; align-items: center; justify-content: space-between;
padding: 10px 14px; background: rgba(255, 209, 102, 0.08); border-radius: 12px; cursor: pointer; user-select: none;
transition: all 0.2s;
}
.tree-header:hover { background: rgba(255, 209, 102, 0.15); }
.tree-subject {
font-size: 12.5px; font-weight: 700; color: var(--accent-cyan); margin: 6px 12px; padding: 4px 8px;
cursor: pointer; display: flex; align-items: center; gap: 6px;
font-size: 12.5px; font-weight: 700; color: var(--accent-cyan); margin: 6px 8px; padding: 8px 12px;
background: rgba(0, 245, 212, 0.04); border-radius: 10px; cursor: pointer; user-select: none;
display: flex; align-items: center; justify-content: space-between; transition: all 0.2s;
}
.tree-subject:hover { background: rgba(0, 245, 212, 0.1); }
.tree-unit {
font-size: 12px; font-weight: 600; color: var(--text-secondary); margin: 4px 20px; padding: 3px 6px;
font-size: 12px; font-weight: 600; color: var(--text-secondary); margin: 4px 14px; padding: 6px 10px;
border-radius: 8px; cursor: pointer; user-select: none; display: flex; align-items: center; justify-content: space-between;
transition: all 0.2s;
}
.tree-unit:hover { background: rgba(255, 255, 255, 0.06); color: #FFF; }
.tree-lesson {
font-size: 11.5px; color: var(--text-muted); margin: 3px 30px; padding: 6px 10px; border-radius: 8px;
cursor: pointer; transition: all 0.2s;
font-size: 11.5px; color: var(--text-muted); margin: 3px 24px; padding: 6px 12px; border-radius: 8px;
cursor: pointer; transition: all 0.2s; display: flex; align-items: center; gap: 8px;
}
.tree-lesson:hover, .tree-lesson.active {
background: rgba(0, 245, 212, 0.15); color: var(--accent-cyan); font-weight: 700;
}
.chevron {
font-size: 10px; transition: transform 0.2s ease; display: inline-block; color: var(--text-muted);
}
.chevron.collapsed { transform: rotate(90deg); }
.collapsible-body { transition: all 0.2s ease; }
.collapsible-body.hidden { display: none; }
.workspace-card {
background: var(--bg-card); border: 1px solid var(--border); border-radius: 20px; padding: 26px;
backdrop-filter: blur(16px);
@@ -102,7 +120,7 @@ class CurriculumStudio
.search-box:focus { border-color: var(--accent-cyan); }
.md-editor {
width: 100%; height: 480px; background: rgba(0,0,0,0.5); border: 1px solid var(--border);
width: 100%; height: 440px; background: rgba(0,0,0,0.5); border: 1px solid var(--border);
border-radius: 14px; padding: 16px; color: #F1F5F9; font-family: monospace; font-size: 13px;
line-height: 1.7; resize: vertical; outline: none;
}
@@ -197,31 +215,48 @@ class CurriculumStudio
</div>
<!-- Markdown Content Area -->
<textarea id="lesson_markdown_editor" class="md-editor" style="height: 400px; margin-bottom: 20px;"></textarea>
<textarea id="lesson_markdown_editor" class="md-editor" style="margin-bottom: 20px;"></textarea>
<!-- الذكاء الاصطناعي والمخرجات الذكية -->
<div style="background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 20px;">
<div style="background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.08); border-radius: 16px; padding: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; flex-wrap: wrap; gap: 10px;">
<h4 style="color: var(--accent-gold); font-size: 16px; margin: 0;">🧠 الاستوديو الذكي للدرس (AI Assets)</h4>
<div style="display: flex; gap: 8px;">
<h4 style="color: var(--accent-gold); font-size: 16px; margin: 0; display: flex; align-items: center; gap: 8px;">
🧠 الاستوديو الذكي للدرس (AI Assets & Media)
</h4>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<button type="button" class="btn-primary" style="padding: 8px 14px; font-size: 12px; background: linear-gradient(135deg, #7B2CBF, #9D4EDD);" onclick="copyAiVideoPrompt()">🎬 نسخ برومبت فيديو الشرح</button>
<button type="button" class="btn-primary" style="padding: 8px 14px; font-size: 12px;" onclick="generateAiAssets()">✨ توليد الملخص والاختبار آلياً</button>
</div>
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; color: var(--text-secondary); margin-bottom: 8px; font-size: 13px;">رابط فيديو الشرح الذكي (مثلاً من NotebookLM أو YouTube):</label>
<input type="text" id="ai_video_url_input" style="width: 100%; background: #0A0F1A; border: 1px solid rgba(255,255,255,0.1); color: #FFF; padding: 12px; border-radius: 8px; font-size: 14px;" placeholder="https://...">
<!-- Direct Video Upload / Attachment Section -->
<div style="background: rgba(0,0,0,0.3); border: 1px solid var(--border); border-radius: 12px; padding: 14px; margin-bottom: 16px;">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 10px;">
<label style="color: var(--text-primary); font-size: 13px; font-weight: 700; display: flex; align-items: center; gap: 6px;">
📹 فيديو شرح الدرس (مستضاف مباشرة على المنصة):
</label>
<div style="display: flex; gap: 8px; align-items: center;">
<input type="file" id="direct_video_file_input" accept="video/mp4,video/webm,video/quicktime" style="display: none;" onchange="handleDirectVideoUpload(this)">
<button type="button" class="btn-primary" style="padding: 6px 14px; font-size: 12px; background: linear-gradient(135deg, #10B981, #059669);" onclick="document.getElementById('direct_video_file_input').click()">
📤 رفع ملف الفيديو مباشرة
</button>
<span id="video_upload_status" style="font-size: 11px; color: var(--accent-cyan);"></span>
</div>
</div>
<input type="text" id="ai_video_url_input" style="width: 100%; background: #0A0F1A; border: 1px solid rgba(255,255,255,0.1); color: #FFF; padding: 10px 14px; border-radius: 8px; font-size: 13px;" placeholder="رابط الفيديو المستضاف أو الخارجي (مثلاً /uploads/curriculum_videos/... أو رابط YouTube)">
<video id="lesson_video_player" controls style="width: 100%; max-height: 240px; border-radius: 10px; margin-top: 10px; display: none; background: #000; border: 1px solid var(--border);"></video>
</div>
<div style="display: flex; gap: 20px;">
<div style="flex: 1;">
<label style="display: block; color: var(--text-secondary); margin-bottom: 8px; font-size: 13px;">ملخص الدرس (Cheat Sheet):</label>
<textarea id="ai_cheat_sheet_editor" style="width: 100%; height: 150px; background: #0A0F1A; border: 1px solid rgba(255,255,255,0.1); color: #FFF; padding: 12px; border-radius: 8px; font-size: 13px; font-family: monospace; direction: rtl;" placeholder="سيتم توليد الملخص والبطاقات التعليمية هنا..."></textarea>
<div style="display: flex; gap: 20px; flex-wrap: wrap;">
<div style="flex: 1; min-width: 280px;">
<label style="display: block; color: var(--text-secondary); margin-bottom: 8px; font-size: 13px; font-weight: 600;">ملخص الدرس والبطاقة المركزة (Cheat Sheet):</label>
<textarea id="ai_cheat_sheet_editor" style="width: 100%; height: 160px; background: #0A0F1A; border: 1px solid rgba(255,255,255,0.1); color: #FFF; padding: 12px; border-radius: 8px; font-size: 13px; font-family: monospace; direction: rtl; line-height: 1.6;" placeholder="سيتم توليد الملخص والبطاقات التعليمية هنا..."></textarea>
</div>
<div style="flex: 1;">
<label style="display: block; color: var(--text-secondary); margin-bottom: 8px; font-size: 13px;">الفحص السقراطي (JSON):</label>
<textarea id="ai_socratic_quiz_editor" style="width: 100%; height: 150px; background: #0A0F1A; border: 1px solid rgba(255,255,255,0.1); color: #FFF; padding: 12px; border-radius: 8px; font-size: 13px; font-family: monospace; direction: ltr; text-align: left;" placeholder="سيتم توليد سؤال الفحص السقراطي هنا..."></textarea>
<div style="flex: 1; min-width: 280px;">
<label style="display: block; color: var(--text-secondary); margin-bottom: 8px; font-size: 13px; font-weight: 600;">الفحص السقراطي الذكي (Socratic Quiz JSON):</label>
<textarea id="ai_socratic_quiz_editor" style="width: 100%; height: 160px; background: #0A0F1A; border: 1px solid rgba(255,255,255,0.1); color: #FFF; padding: 12px; border-radius: 8px; font-size: 13px; font-family: monospace; direction: ltr; text-align: left; line-height: 1.6;" placeholder="سيتم توليد سؤال الفحص السقراطي هنا..."></textarea>
</div>
</div>
</div>
@@ -269,6 +304,20 @@ class CurriculumStudio
renderCurriculumTree(window.CURRICULUM_TREE);
});
function toggleCollapsible(elementId, chevronId) {
const body = document.getElementById(elementId);
const chevron = document.getElementById(chevronId);
if (!body) return;
if (body.classList.contains('hidden')) {
body.classList.remove('hidden');
if (chevron) chevron.classList.remove('collapsed');
} else {
body.classList.add('hidden');
if (chevron) chevron.classList.add('collapsed');
}
}
function renderCurriculumTree(tree) {
const container = document.getElementById('curriculum_tree_container');
const entries = Object.entries(tree || {});
@@ -287,18 +336,50 @@ class CurriculumStudio
}
let html = '';
let gradeIdx = 0;
for (const [gradeKey, grade] of entries) {
gradeIdx++;
const gradeBodyId = `grade_body_${gradeIdx}`;
const gradeChevId = `grade_chev_${gradeIdx}`;
html += `
<div class="tree-node">
<div class="tree-header">📁 ${escapeHtml(grade.name)}</div>
<div class="tree-header" onclick="toggleCollapsible('${gradeBodyId}', '${gradeChevId}')">
<span>📁 ${escapeHtml(grade.name)}</span>
<span class="chevron" id="${gradeChevId}">▼</span>
</div>
<div class="collapsible-body" id="${gradeBodyId}">
`;
let subIdx = 0;
for (const [subKey, sub] of Object.entries(grade.subjects || {})) {
html += `<div class="tree-subject">📖 ${escapeHtml(sub.name)}</div>`;
subIdx++;
const subBodyId = `sub_body_${gradeIdx}_${subIdx}`;
const subChevId = `sub_chev_${gradeIdx}_${subIdx}`;
html += `
<div class="tree-subject" onclick="toggleCollapsible('${subBodyId}', '${subChevId}')">
<span>📖 ${escapeHtml(sub.name)}</span>
<span class="chevron" id="${subChevId}">▼</span>
</div>
<div class="collapsible-body" id="${subBodyId}">
`;
for (const [semKey, sem] of Object.entries(sub.semesters || {})) {
let unitIdx = 0;
for (const [unitKey, unit] of Object.entries(sem.units || {})) {
html += `<div class="tree-unit">🔹 ${escapeHtml(unit.name)}</div>`;
unitIdx++;
const unitBodyId = `unit_body_${gradeIdx}_${subIdx}_${unitIdx}`;
const unitChevId = `unit_chev_${gradeIdx}_${subIdx}_${unitIdx}`;
html += `
<div class="tree-unit" onclick="toggleCollapsible('${unitBodyId}', '${unitChevId}')">
<span>🔹 ${escapeHtml(unit.name)}</span>
<span class="chevron" id="${unitChevId}">▼</span>
</div>
<div class="collapsible-body" id="${unitBodyId}">
`;
for (const les of (unit.lessons || [])) {
const activeClass = (les.file === currentFilePath) ? ' active' : '';
@@ -308,24 +389,19 @@ class CurriculumStudio
</div>
`;
}
html += `</div>`;
}
// Minhaji Style Categories
if (sem.resources && Object.keys(sem.resources).length > 0) {
html += `<div style="margin-top: 15px; margin-bottom: 5px; font-weight: bold; color: var(--text-secondary); font-size: 11px; padding-right: 15px;">📂 مصادر المادة الإضافية:</div>`;
html += `<div style="display: flex; flex-wrap: wrap; gap: 6px; padding-right: 15px; margin-bottom: 10px;">`;
html += `<div style="margin-top: 12px; margin-bottom: 5px; font-weight: bold; color: var(--text-secondary); font-size: 11px; padding-right: 15px;">📂 مصادر المادة والكتب:</div>`;
html += `<div style="display: flex; flex-direction: column; gap: 4px; padding-right: 15px; margin-bottom: 10px;">`;
for (const [resKey, resourceGroup] of Object.entries(sem.resources)) {
if (!resourceGroup.items || resourceGroup.items.length === 0) continue;
html += `<div style="flex: 1 1 45%; background: var(--bg-hover); border: 1px solid var(--border-color); border-radius: 6px; padding: 8px; text-align: center; cursor: pointer; transition: all 0.2s;" onmouseover="this.style.background='rgba(255,209,102,0.1)'" onmouseout="this.style.background='var(--bg-hover)'">
<div style="font-size: 14px; margin-bottom: 4px;">📌</div>
<div style="font-size: 10px; font-weight: bold; color: var(--text-primary);">${escapeHtml(resourceGroup.name)}</div>
</div>`;
for (const item of resourceGroup.items) {
const activeClass = (item.file === currentFilePath) ? ' active' : '';
html += `
<div style="width: 100%;" class="tree-lesson${activeClass}" onclick="selectLesson('${item.file}', '${escapeHtml(item.title)}', '${escapeHtml(grade.name)} ⟵ ${escapeHtml(sub.name)} ⟵ ${escapeHtml(resourceGroup.name)}', ['مرفق إضافي'])">
<div class="tree-lesson${activeClass}" onclick="selectLesson('${item.file}', '${escapeHtml(item.title)}', '${escapeHtml(grade.name)} ⟵ ${escapeHtml(sub.name)} ⟵ ${escapeHtml(resourceGroup.name)}', ['كتاب وزاري مقرّر'])">
📎 ${escapeHtml(item.title)}
</div>
`;
@@ -334,9 +410,9 @@ class CurriculumStudio
html += `</div>`;
}
}
html += `</div>`; // Close Subject Body
}
html += `</div>`;
html += `</div></div>`; // Close Grade Body & Node
}
container.innerHTML = html;
@@ -382,182 +458,108 @@ class CurriculumStudio
if (data.server_full_path) {
document.getElementById('server_file_path_display').textContent = data.server_full_path;
}
// Load AI assets
if (data.ai_assets) {
document.getElementById('ai_video_url_input').value = data.ai_assets.ai_video_url || '';
document.getElementById('ai_cheat_sheet_editor').value = data.ai_assets.cheat_sheet || '';
document.getElementById('ai_socratic_quiz_editor').value = data.ai_assets.socratic_quiz ? JSON.stringify(data.ai_assets.socratic_quiz, null, 2) : '';
const player = document.getElementById('lesson_video_player');
if (data.ai_assets.ai_video_url) {
player.src = data.ai_assets.ai_video_url;
player.style.display = 'block';
} else {
player.src = '';
player.style.display = 'none';
}
} else {
document.getElementById('ai_video_url_input').value = '';
document.getElementById('ai_cheat_sheet_editor').value = '';
document.getElementById('ai_socratic_quiz_editor').value = '';
document.getElementById('lesson_video_player').style.display = 'none';
}
}
} catch (e) {
console.error('Fetch markdown error:', e);
}
}
// ==========================================================
// Live PDF Upload & Real-Time AI Extraction Tracking
// ==========================================================
async function handleRealPdfUpload(input) {
if (!input.files || !input.files[0]) return;
async function handleDirectVideoUpload(input) {
if (!input.files || !input.files[0] || !currentFilePath) {
alert('يرجى اختيار ملف فيديو صالح.');
return;
}
const file = input.files[0];
const btn = document.getElementById('btn_upload_pdf');
btn.disabled = true;
// Show Progress Modal Overlay
const overlay = document.getElementById('upload_progress_overlay');
overlay.style.display = 'flex';
document.getElementById('btn_close_overlay').style.display = 'none';
document.getElementById('upload_progress_bar').style.width = '8%';
document.getElementById('upload_progress_text').textContent = `جاري رفع [${file.name}] إلى الخادم...`;
document.getElementById('upload_progress_percent').textContent = '8%';
document.getElementById('upload_logs_textarea').innerHTML = `[System] Uploading ${escapeHtml(file.name)} (${(file.size/1024/1024).toFixed(2)} MB)...<br>`;
const statusSpan = document.getElementById('video_upload_status');
statusSpan.textContent = `جاري رفع الفيديو (${(file.size/1024/1024).toFixed(1)} MB)...`;
const formData = new FormData();
formData.append('pdf_file', file);
formData.append('video_file', file);
formData.append('file', currentFilePath);
try {
const res = await fetch('/api/curriculum/upload-pdf', {
const res = await fetch('/api/curriculum/upload-video', {
method: 'POST',
body: formData
});
const data = await res.json();
if (res.ok && data.status === 'processing') {
currentUploadTaskId = data.task_id;
document.getElementById('upload_progress_bar').style.width = '15%';
document.getElementById('upload_progress_text').textContent = data.message || 'تم استلام الملف، جاري بدء الذكاء الاصطناعي...';
document.getElementById('upload_progress_percent').textContent = '15%';
appendLog(`[Server] Task registered with ID: ${data.task_id}`);
pollTaskStatus(data.task_id, file.name);
} else if (res.ok && data.status === 'success') {
overlay.style.display = 'none';
resetUploadBtn();
loadCurriculumTree();
if (res.ok && data.status === 'success') {
document.getElementById('ai_video_url_input').value = data.video_url;
const player = document.getElementById('lesson_video_player');
player.src = data.video_url;
player.style.display = 'block';
statusSpan.textContent = '✅ تم رفع الفيديو بنجاح!';
alert('✅ تم رفع وتثبيت الفيديو على سيرفر المنصة بنجاح!');
} else {
document.getElementById('upload_progress_text').textContent = '⚠️ ' + (data.message || 'فشلت معالجة الملف');
document.getElementById('btn_close_overlay').style.display = 'inline-block';
resetUploadBtn();
statusSpan.textContent = '⚠️ فشل الرفع';
alert('⚠️ ' + (data.message || 'فشل رفع ملف الفيديو'));
}
} catch (err) {
console.error('PDF Upload error:', err);
document.getElementById('upload_progress_text').textContent = 'حدث خطأ في الاتصال بالخادم أثناء رفع الملف.';
document.getElementById('btn_close_overlay').style.display = 'inline-block';
resetUploadBtn();
} catch(err) {
console.error(err);
statusSpan.textContent = '⚠️ خطأ بالاتصال';
alert('حدث خطأ أثناء رفع الفيديو.');
}
}
async function pollTaskStatus(taskId, fileName) {
try {
const res = await fetch(`/api/curriculum/upload-status?task_id=${taskId}`);
if (!res.ok) throw new Error("Network response was not ok");
const state = await res.json();
// Live Update Modal
document.getElementById('upload_progress_bar').style.width = (state.progress || 20) + '%';
document.getElementById('upload_progress_percent').textContent = (state.progress || 20) + '%';
document.getElementById('upload_progress_text').textContent = state.message || "جاري المعالجة بالذكاء الاصطناعي...";
fetchUploadLogs();
if (state.status === 'completed') {
document.getElementById('upload_progress_bar').style.width = '100%';
document.getElementById('upload_progress_percent').textContent = '100%';
document.getElementById('upload_progress_text').textContent = `✅ اكتملت قراءة وتفريغ المنهاج بنجاح!`;
setTimeout(() => {
document.getElementById('upload_progress_overlay').style.display = 'none';
resetUploadBtn();
if (state.tree) {
window.CURRICULUM_TREE = state.tree;
renderCurriculumTree(state.tree);
if (state.active_file) {
selectLesson(state.active_file, state.extracted_data?.subject_name || 'الدرس المستخرج', 'المنهاج المستخرج', []);
}
} else {
location.reload();
}
}, 2000);
} else if (state.status === 'error') {
document.getElementById('upload_progress_text').textContent = '⚠️ فشل: ' + state.message;
document.getElementById('btn_close_overlay').style.display = 'inline-block';
resetUploadBtn();
} else {
// Poll again in 2.5 seconds
setTimeout(() => pollTaskStatus(taskId, fileName), 2500);
}
} catch(e) {
setTimeout(() => pollTaskStatus(taskId, fileName), 3000);
async function generateAiAssets() {
if (!currentFilePath) return;
const content = document.getElementById('lesson_markdown_editor').value;
if (!content) {
alert('يجب أن يحتوي الدرس على نص أولاً');
return;
}
}
async function fetchUploadLogs() {
if (!currentUploadTaskId) return;
const btn = window.event.currentTarget;
const originalText = btn.innerHTML;
btn.innerHTML = '⏳ جاري التوليد الذكي...';
btn.disabled = true;
try {
const res = await fetch(`/api/curriculum/upload-log?task_id=${currentUploadTaskId}`);
const res = await fetch('/api/curriculum/generate-ai-assets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file: currentFilePath })
});
const data = await res.json();
const container = document.getElementById('upload_logs_textarea');
if (data.status === 'success' && data.log) {
container.innerHTML = data.log.replace(/\n/g, '<br>');
container.scrollTop = container.scrollHeight;
if (res.ok && data.status === 'success' && data.ai_assets) {
document.getElementById('ai_cheat_sheet_editor').value = data.ai_assets.cheat_sheet || '';
document.getElementById('ai_socratic_quiz_editor').value = data.ai_assets.socratic_quiz ? JSON.stringify(data.ai_assets.socratic_quiz, null, 2) : '';
alert('✅ تم توليد الملخص والفحص السقراطي بنجاح!');
} else {
alert('⚠️ ' + (data.message || 'فشل التوليد'));
}
} catch (e) {
console.error('Fetch logs error:', e);
console.error(e);
alert('⚠️ حدث خطأ أثناء الاتصال بمحرك الذكاء الاصطناعي');
} finally {
btn.innerHTML = originalText;
btn.disabled = false;
}
}
function appendLog(msg) {
const container = document.getElementById('upload_logs_textarea');
container.innerHTML += `${escapeHtml(msg)}<br>`;
container.scrollTop = container.scrollHeight;
}
function toggleUploadLogs() {
const container = document.getElementById('upload_logs_container');
container.style.display = (container.style.display === 'none') ? 'block' : 'none';
if (container.style.display === 'block') {
fetchUploadLogs();
}
}
// ==========================================================
// 3. Status Polling & Overlay
// ==========================================================
let pollInterval;
function checkStatus() {
pollInterval = setInterval(async () => {
const res = await fetch('/api/curriculum/status');
const state = await res.json();
if (state.status === 'processing') {
const pct = state.progress || 0;
document.getElementById('upload_progress_fill').style.width = pct + '%';
document.getElementById('upload_progress_text').textContent = state.message || 'جاري المعالجة...';
} else if (state.status === 'done') {
clearInterval(pollInterval);
document.getElementById('upload_progress_fill').style.width = '100%';
document.getElementById('upload_progress_text').textContent = '✅ اكتمل استخراج المنهاج بنجاح!';
document.getElementById('btn_close_overlay').style.display = 'inline-block';
resetUploadBtn();
setTimeout(() => {
if (state.extracted_data) {
renderTree(state.extracted_data);
if (state.active_file) {
selectLesson(state.active_file, state.extracted_data?.subject_name || 'الدرس المستخرج', 'المنهاج المستخرج', []);
}
} else {
location.reload();
}
}, 2000);
} else if (state.status === 'error') {
document.getElementById('upload_progress_text').textContent = '⚠️ فشل: ' + state.message;
document.getElementById('btn_close_overlay').style.display = 'inline-block';
resetUploadBtn();
} else {
clearInterval(pollInterval);
}
}, 1500);
}
// ==========================================================
// 4. Save Content
// ==========================================================
async function saveLessonContent() {
async function saveActiveMarkdown() {
if (!currentFilePath) return;
const content = document.getElementById('lesson_markdown_editor').value;
@@ -586,66 +588,151 @@ class CurriculumStudio
});
const data = await res.json();
if (res.ok && data.status === 'success') {
alert(`✅ تم حفظ واعتماد التعديلات بنجاح في الملف:\n${data.server_full_path || currentFilePath}`);
alert(`✅ تم حفظ واعتماد التعديلات بنجاح في المنهاج!`);
} else {
alert('⚠️ فشل حفظ الملف');
}
} catch (e) {
console.error(e);
alert('⚠️ حدث خطأ في الاتصال');
alert('⚠️ حدث خطأ في الاتصال بالسيرفر');
}
}
// ==========================================================
// 5. Generate AI Assets automatically
// ==========================================================
async function generateAiAssets() {
if (!currentFilePath) return;
const content = document.getElementById('lesson_markdown_editor').value;
if (!content) {
alert('يجب أن يحتوي الدرس على نص أولاً');
function copyAiVideoPrompt() {
const md = document.getElementById('lesson_markdown_editor').value;
const title = document.getElementById('current_lesson_title_display').textContent;
if (!md || md.trim() === '') {
alert('يرجى اختيار درس أولاً.');
return;
}
const prompt = `أنت معلم خبير في المنهاج الأردني. بناءً على محتوى الدرس التالي:
عنوان الدرس: ${title}
const btn = window.event.currentTarget;
const originalText = btn.innerHTML;
btn.innerHTML = '⏳ جاري التوليد الذكي...';
المحتوى التعليمي والتطبيقات:
${md}
المطلوب:
1. توليد سيناريو شرح فيديو تفاعلي شيق وممتع للدرس (مدته 3-5 دقائق).
2. شرح فكرة الدرس والمفاهيم الأساسية، والخطوات الجبرية للأمثلة بالتفصيل.
3. التنبيه على الأخطاء الشائعة وحل مسألة اليوم أو مهارات التفكير العليا.`;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(prompt).then(() => {
alert('🎬 تم نسخ برومبت توليد الفيديو بنجاح! يمكنك الآن لصقه في NotebookLM أو منصة توليد الفيديو الذكية.');
}).catch(err => {
copyViaTextarea(prompt);
});
} else {
copyViaTextarea(prompt);
}
}
function copyViaTextarea(text) {
const t = document.createElement('textarea');
t.value = text;
document.body.appendChild(t);
t.select();
document.execCommand('copy');
document.body.removeChild(t);
alert('🎬 تم نسخ برومبت توليد الفيديو بنجاح!');
}
// Real PDF Upload
async function handleRealPdfUpload(input) {
if (!input.files || !input.files[0]) return;
const file = input.files[0];
const btn = document.getElementById('btn_upload_pdf');
btn.disabled = true;
const overlay = document.getElementById('upload_progress_overlay');
overlay.style.display = 'flex';
document.getElementById('btn_close_overlay').style.display = 'none';
document.getElementById('upload_progress_bar').style.width = '8%';
document.getElementById('upload_progress_text').textContent = `جاري رفع [${file.name}] إلى الخادم...`;
document.getElementById('upload_progress_percent').textContent = '8%';
document.getElementById('upload_logs_textarea').innerHTML = `[System] Uploading ${escapeHtml(file.name)} (${(file.size/1024/1024).toFixed(2)} MB)...<br>`;
const formData = new FormData();
formData.append('pdf_file', file);
try {
const res = await fetch('/api/curriculum/generate-ai-assets', {
const res = await fetch('/api/curriculum/upload-pdf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file: currentFilePath })
body: formData
});
const data = await res.json();
if (res.ok && data.status === 'success' && data.ai_assets) {
document.getElementById('ai_cheat_sheet_editor').value = data.ai_assets.cheat_sheet || '';
document.getElementById('ai_socratic_quiz_editor').value = data.ai_assets.socratic_quiz ? JSON.stringify(data.ai_assets.socratic_quiz, null, 2) : '';
alert('✅ تم توليد الملخص والاختبار السقراطي بنجاح!');
if (res.ok && data.status === 'processing') {
currentUploadTaskId = data.task_id;
document.getElementById('upload_progress_bar').style.width = '15%';
document.getElementById('upload_progress_text').textContent = data.message || 'تم استلام الملف، جاري بدء الذكاء الاصطناعي...';
document.getElementById('upload_progress_percent').textContent = '15%';
pollTaskStatus(data.task_id, file.name);
} else if (res.ok && data.status === 'success') {
overlay.style.display = 'none';
resetUploadBtn();
loadCurriculumTree();
} else {
alert('⚠️ فشل التوليد: ' + (data.message || 'خطأ غير معروف'));
document.getElementById('upload_progress_text').textContent = '⚠️ ' + (data.message || 'فشلت معالجة الملف');
document.getElementById('btn_close_overlay').style.display = 'inline-block';
resetUploadBtn();
}
} catch (e) {
console.error(e);
alert('⚠️ حدث خطأ في الاتصال بمحرك الذكاء الاصطناعي');
} finally {
btn.innerHTML = originalText;
btn.disabled = false;
} catch (err) {
console.error('PDF Upload error:', err);
document.getElementById('upload_progress_text').textContent = 'حدث خطأ في الاتصال بالخادم أثناء رفع الملف.';
document.getElementById('btn_close_overlay').style.display = 'inline-block';
resetUploadBtn();
}
}
async function pollTaskStatus(taskId, fileName) {
try {
const res = await fetch(`/api/curriculum/upload-status?task_id=${taskId}`);
if (!res.ok) throw new Error("Network response was not ok");
const state = await res.json();
const pBar = document.getElementById('upload_progress_bar');
const pText = document.getElementById('upload_progress_text');
const pPercent = document.getElementById('upload_progress_percent');
pBar.style.width = `${state.progress}%`;
pPercent.textContent = `${state.progress}%`;
pText.textContent = state.message || 'جاري المعالجة...';
if (state.status === 'completed') {
pBar.style.width = '100%';
pPercent.textContent = '100%';
pText.textContent = `🎉 تم تفريغ واعتماد [${fileName}] كمنهاج حي بنجاح!`;
document.getElementById('btn_close_overlay').style.display = 'inline-block';
resetUploadBtn();
setTimeout(() => {
document.getElementById('upload_progress_overlay').style.display = 'none';
window.location.reload();
}, 1200);
} else if (state.status === 'error') {
pText.textContent = '❌ ' + (state.message || 'تعطلت عملية التفريغ.');
document.getElementById('btn_close_overlay').style.display = 'inline-block';
resetUploadBtn();
} else {
setTimeout(() => pollTaskStatus(taskId, fileName), 2000);
}
} catch (err) {
console.error('Status poll error:', err);
setTimeout(() => pollTaskStatus(taskId, fileName), 3000);
}
}
function toggleUploadLogs() {
const el = document.getElementById('upload_logs_container');
el.style.display = (el.style.display === 'none') ? 'block' : 'none';
}
function openDirectLogViewer() {
const overlay = document.getElementById('upload_progress_overlay');
overlay.style.display = 'flex';
document.getElementById('btn_close_overlay').style.display = 'inline-block';
document.getElementById('upload_logs_container').style.display = 'block';
if (currentUploadTaskId) {
fetchUploadLogs();
} else {
document.getElementById('upload_logs_textarea').innerHTML = '[System] No active task ID yet. Upload a textbook PDF to stream live AI logs.<br>';
}
}
function closeProgressOverlay() {
@@ -660,27 +747,6 @@ class CurriculumStudio
document.getElementById('curriculum_pdf_input').value = '';
}
async function saveActiveMarkdown() {
if (!currentFilePath) return;
const content = document.getElementById('lesson_markdown_editor').value;
try {
const res = await fetch('/api/curriculum/save-lesson', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file: currentFilePath, content: content })
});
const data = await res.json();
if (res.ok && data.status === 'success') {
alert(`✅ تم حفظ واعتماد التعديلات بنجاح في الملف:\n${data.server_full_path || currentFilePath}`);
} else {
alert('⚠️ فشل حفظ الملف');
}
} catch (e) {
alert('تعذر الاتصال بالسيرفر');
}
}
function handleCurriculumSearch(q) {
if (!q || q.trim() === '') {
renderCurriculumTree(window.CURRICULUM_TREE);
@@ -723,45 +789,6 @@ class CurriculumStudio
renderCurriculumTree(filtered);
}
function copyAiVideoPrompt() {
const md = document.getElementById('lesson_markdown_editor').value;
const title = document.getElementById('current_lesson_title').textContent;
if (!md || md.trim() === '') {
alert('يرجى اختيار درس أولاً.');
return;
}
const prompt = `أنت معلم خبير في المنهاج الأردني. بناءً على محتوى الدرس التالي:
عنوان الدرس: ${title}
المحتوى التعليمي والتطبيقات:
${md}
المطلوب:
1. توليد سيناريو شرح فيديو تفاعلي شيق وممتع للدرس (مدته 3-5 دقائق).
2. شرح فكرة الدرس والمفاهيم الأساسية، والخطوات الجبرية للأمثلة بالتفصيل.
3. التنبيه على الأخطاء الشائعة وحل مسألة اليوم أو مهارات التفكير العليا.`;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(prompt).then(() => {
alert('🎬 تم نسخ برومبت توليد الفيديو بنجاح! يمكنك الآن لصقه في NotebookLM أو منصة توليد الفيديو الذكية.');
}).catch(err => {
copyViaTextarea(prompt);
});
} else {
copyViaTextarea(prompt);
}
}
function copyViaTextarea(text) {
const t = document.createElement('textarea');
t.value = text;
document.body.appendChild(t);
t.select();
document.execCommand('copy');
document.body.removeChild(t);
alert('🎬 تم نسخ برومبت توليد الفيديو بنجاح!');
}
function escapeHtml(str) {
if (!str) return '';
const d = document.createElement('div');