Update Saqel Platform: 2026-08-28 15:39:16
This commit is contained in:
@@ -18,7 +18,7 @@ class CurriculumStudio
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>استوديو إدارة وفهرسة المناهج الوزارية — صَقِل Enterprise</title>
|
||||
<title>استوديو إدارة وفهرسة المناهج الحية — صَقِل Enterprise</title>
|
||||
<link rel="icon" type="image/jpeg" href="/assets/images/saqel_logo.jpg">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
@@ -59,10 +59,10 @@ class CurriculumStudio
|
||||
max-height: calc(100vh - 120px); overflow-y: auto;
|
||||
}
|
||||
|
||||
.tree-node { margin-bottom: 12px; }
|
||||
.tree-node { margin-bottom: 14px; }
|
||||
.tree-header {
|
||||
font-size: 13px; font-weight: 800; color: var(--accent-gold); display: flex; align-items: center; gap: 6px;
|
||||
padding: 6px 10px; background: rgba(255, 209, 102, 0.08); border-radius: 8px; cursor: pointer;
|
||||
padding: 8px 12px; background: rgba(255, 209, 102, 0.08); border-radius: 10px; cursor: pointer;
|
||||
}
|
||||
.tree-subject {
|
||||
font-size: 12.5px; font-weight: 700; color: var(--accent-cyan); margin: 6px 12px; padding: 4px 8px;
|
||||
@@ -72,7 +72,7 @@ class CurriculumStudio
|
||||
font-size: 12px; font-weight: 600; color: var(--text-secondary); margin: 4px 20px; padding: 3px 6px;
|
||||
}
|
||||
.tree-lesson {
|
||||
font-size: 11.5px; color: var(--text-muted); margin: 3px 30px; padding: 4px 8px; border-radius: 6px;
|
||||
font-size: 11.5px; color: var(--text-muted); margin: 3px 30px; padding: 5px 10px; border-radius: 8px;
|
||||
cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.tree-lesson:hover, .tree-lesson.active {
|
||||
@@ -87,6 +87,7 @@ class CurriculumStudio
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #0284C7, #0369A1); color: #FFF; border: none; padding: 10px 20px;
|
||||
border-radius: 12px; font-size: 13px; font-weight: 700; cursor: pointer; transition: all 0.2s;
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 4px 16px rgba(2, 132, 199, 0.4); }
|
||||
|
||||
@@ -97,10 +98,14 @@ class CurriculumStudio
|
||||
.search-box:focus { border-color: var(--accent-cyan); }
|
||||
|
||||
.md-editor {
|
||||
width: 100%; height: 380px; background: rgba(0,0,0,0.5); border: 1px solid var(--border);
|
||||
width: 100%; height: 420px; 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.6; resize: vertical; outline: none;
|
||||
}
|
||||
|
||||
.empty-tree-state {
|
||||
padding: 40px 20px; text-align: center; color: var(--text-muted); font-size: 13px; line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -110,7 +115,7 @@ class CurriculumStudio
|
||||
<div style="display: flex; align-items: center; gap: 10px;">
|
||||
<img src="/assets/images/saqel_logo.jpg" alt="صَقِل" style="width: 36px; height: 36px; border-radius: 10px;">
|
||||
<span style="font-weight: 800; font-size: 18px; color: #FFF;">صَقِل Enterprise</span>
|
||||
<span style="font-size: 11px; font-weight: 700; color: var(--accent-gold); background: rgba(255,209,102,0.12); padding: 2px 10px; border-radius: 980px;">استوديو إدارة وفهرسة المناهج 📚</span>
|
||||
<span style="font-size: 11px; font-weight: 700; color: var(--accent-gold); background: rgba(255,209,102,0.12); padding: 2px 10px; border-radius: 980px;">استوديو إدارة وفهرسة المناهج الحية 📚</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<a href="/teacher" style="font-size: 12px; color: var(--accent-cyan); text-decoration: none; border: 1px solid rgba(0,245,212,0.3); padding: 5px 14px; border-radius: 980px;">استوديو المعلم 👨🏫</a>
|
||||
@@ -120,17 +125,17 @@ class CurriculumStudio
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<!-- PDF Ingestion & Extraction Top Banner -->
|
||||
<!-- Live PDF Ingestion Bar -->
|
||||
<div class="workspace-card" style="margin-bottom: 24px; background: linear-gradient(135deg, rgba(30, 41, 59, 0.7), rgba(15, 23, 42, 0.9));">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px;">
|
||||
<div>
|
||||
<h2 style="font-size: 20px; font-weight: 900; color: #FFF; margin-bottom: 4px;">محرك رفع وتحويل كتب الوزارة PDF إلى شجرة Markdown فائقة السرعة 📑</h2>
|
||||
<p style="font-size: 12.5px; color: var(--text-secondary);">ارفع كتاب المنهاج، واستخرج الوحدات والدروس ونتاجات التعلم، وقارن دقة النص قبل اعتماده في قاعدة المعرفة.</p>
|
||||
<h2 style="font-size: 20px; font-weight: 900; color: #FFF; margin-bottom: 4px;">رفع وتحليل كتاب المنهاج الوزاري PDF (معالجة حية 100%) 📑</h2>
|
||||
<p style="font-size: 12.5px; color: var(--text-secondary);" id="upload_status_desc">اختر ملف كتاب المنهاج (PDF) ليقوم الخادم والذكاء الاصطناعي بفك تشفيره واستخراج الفصول والوحدات والدروس الحقيقية فوراً.</p>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; align-items: center;">
|
||||
<input type="file" id="curriculum_pdf_input" accept=".pdf,.doc,.docx" style="display: none;" onchange="handlePdfSelected(this)">
|
||||
<button type="button" onclick="document.getElementById('curriculum_pdf_input').click()" class="btn-primary">
|
||||
📤 رفع ملف المنهاج PDF
|
||||
<input type="file" id="curriculum_pdf_input" accept=".pdf" style="display: none;" onchange="handleRealPdfUpload(this)">
|
||||
<button type="button" id="btn_upload_pdf" onclick="document.getElementById('curriculum_pdf_input').click()" class="btn-primary">
|
||||
<span>📤 رفع ملف المنهاج PDF الحقيقي</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -142,49 +147,67 @@ class CurriculumStudio
|
||||
<input type="text" class="search-box" id="curriculum_search_input" placeholder="🔍 ابحث في المناهج والوحدات ونتاجات التعلم..." oninput="handleCurriculumSearch(this.value)">
|
||||
|
||||
<div id="curriculum_tree_container">
|
||||
<!-- Rendered by JS -->
|
||||
<!-- Rendered Dynamically from Real Storage -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Workspace: Markdown Editor & Extraction Inspector -->
|
||||
<div class="workspace-card">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap; gap: 10px;">
|
||||
<div>
|
||||
<span style="font-size: 11px; font-weight: 800; color: var(--accent-cyan); background: rgba(0,245,212,0.1); padding: 2px 8px; border-radius: 6px;" id="current_breadcrumb">توجيهي 2008 ⟵ الرياضيات العلمي ⟵ الوحدة 1</span>
|
||||
<h3 style="font-size: 18px; font-weight: 900; color: #FFF; margin-top: 6px;" id="current_lesson_title_display">الدرس 1: المفهوم الهندسي والفيزيائي للمشتقة الأولى</h3>
|
||||
</div>
|
||||
<button type="button" onclick="saveCurrentMarkdown()" class="btn-primary" style="background: linear-gradient(135deg, #10B981, #059669);">
|
||||
💾 حفظ واعتماد الدرس في الشجرة
|
||||
</button>
|
||||
<div class="workspace-card" id="workspace_content_area">
|
||||
<div id="empty_selection_view" style="display: none; text-align: center; padding: 60px 20px;">
|
||||
<div style="font-size: 48px; margin-bottom: 12px;">📚</div>
|
||||
<h3 style="font-size: 18px; font-weight: 800; color: #FFF; margin-bottom: 8px;">لا يوجد درس محدد حالياً</h3>
|
||||
<p style="font-size: 13px; color: var(--text-muted); max-width: 480px; margin: 0 auto;">ارفع كتاب المنهاج PDF أو اختر درساً من الشجرة الجانبية لمعاينة نتاجات التعلم والمارك داون المعتمد.</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 14px;">
|
||||
<span style="font-size: 12px; font-weight: 700; color: var(--text-muted); display: block; margin-bottom: 6px;">نتاجات التعلم المستهدفة المعتمدة من الوزارة:</span>
|
||||
<div id="current_outcomes_tags" style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
<!-- Tags rendered by JS -->
|
||||
<div id="active_lesson_view" style="display: none;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; flex-wrap: wrap; gap: 10px;">
|
||||
<div>
|
||||
<span style="font-size: 11px; font-weight: 800; color: var(--accent-cyan); background: rgba(0,245,212,0.1); padding: 2px 8px; border-radius: 6px;" id="current_breadcrumb"></span>
|
||||
<h3 style="font-size: 18px; font-weight: 900; color: #FFF; margin-top: 6px;" id="current_lesson_title_display"></h3>
|
||||
</div>
|
||||
<button type="button" onclick="saveActiveMarkdown()" class="btn-primary" style="background: linear-gradient(135deg, #10B981, #059669);">
|
||||
💾 حفظ واعتماد الدرس في الشجرة
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Markdown Content Area -->
|
||||
<textarea id="lesson_markdown_editor" class="md-editor"></textarea>
|
||||
<div style="margin-bottom: 14px;">
|
||||
<span style="font-size: 12px; font-weight: 700; color: var(--text-muted); display: block; margin-bottom: 6px;">نتاجات التعلم والمفاهيم المستهدفة المستخرجة من الكتاب:</span>
|
||||
<div id="current_outcomes_tags" style="display: flex; gap: 8px; flex-wrap: wrap;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Markdown Content Area -->
|
||||
<textarea id="lesson_markdown_editor" class="md-editor"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentLessonId = 'math_s1_u1_l1';
|
||||
let currentFilePath = 'tawjihi_2008/math_scientific/semester_1/unit_1_calculus/lesson_1.md';
|
||||
let currentFilePath = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
renderCurriculumTree(window.CURRICULUM_TREE);
|
||||
loadSampleLessonContent();
|
||||
});
|
||||
|
||||
function renderCurriculumTree(tree) {
|
||||
const container = document.getElementById('curriculum_tree_container');
|
||||
let html = '';
|
||||
const entries = Object.entries(tree || {});
|
||||
|
||||
for (const [gradeKey, grade] of Object.entries(tree)) {
|
||||
if (entries.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-tree-state">
|
||||
<div style="font-size: 32px; margin-bottom: 8px;">📂</div>
|
||||
<strong>لا توجد مناهج مفهرسة بعد</strong>
|
||||
<p style="margin-top: 4px;">ارفع كتاب المنهاج الوزاري (PDF) عبر الزر بالأعلى ليتم بناء الشجرة وفهرستها تلقائياً.</p>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('empty_selection_view').style.display = 'block';
|
||||
document.getElementById('active_lesson_view').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
for (const [gradeKey, grade] of entries) {
|
||||
html += `
|
||||
<div class="tree-node">
|
||||
<div class="tree-header">📁 ${escapeHtml(grade.name)}</div>
|
||||
@@ -198,9 +221,9 @@ class CurriculumStudio
|
||||
html += `<div class="tree-unit">🔹 ${escapeHtml(unit.name)}</div>`;
|
||||
|
||||
for (const les of (unit.lessons || [])) {
|
||||
const activeClass = (les.id === currentLessonId) ? ' active' : '';
|
||||
const activeClass = (les.file === currentFilePath) ? ' active' : '';
|
||||
html += `
|
||||
<div class="tree-lesson${activeClass}" onclick="selectLesson('${les.id}', '${escapeHtml(les.title)}', '${les.file}', '${escapeHtml(grade.name)} ⟵ ${escapeHtml(sub.name)} ⟵ ${escapeHtml(unit.name)}', ${JSON.stringify(les.outcomes || []).replace(/"/g, '"')})">
|
||||
<div class="tree-lesson${activeClass}" onclick="selectLesson('${les.file}', '${escapeHtml(les.title)}', '${escapeHtml(grade.name)} ⟵ ${escapeHtml(sub.name)} ⟵ ${escapeHtml(unit.name)}', ${JSON.stringify(les.outcomes || []).replace(/"/g, '"')})">
|
||||
📄 ${escapeHtml(les.title)}
|
||||
</div>
|
||||
`;
|
||||
@@ -213,17 +236,31 @@ class CurriculumStudio
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
|
||||
// Auto-select first lesson if none selected
|
||||
if (!currentFilePath) {
|
||||
const firstGrade = Object.values(tree)[0];
|
||||
const firstSub = firstGrade?.subjects ? Object.values(firstGrade.subjects)[0] : null;
|
||||
const firstSem = firstSub?.semesters ? Object.values(firstSub.semesters)[0] : null;
|
||||
const firstUnit = firstSem?.units ? Object.values(firstSem.units)[0] : null;
|
||||
const firstLes = firstUnit?.lessons ? firstUnit.lessons[0] : null;
|
||||
if (firstLes) {
|
||||
selectLesson(firstLes.file, firstLes.title, `${firstGrade.name} ⟵ ${firstSub.name} ⟵ ${firstUnit.name}`, firstLes.outcomes || []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectLesson(id, title, file, breadcrumb, outcomes) {
|
||||
currentLessonId = id;
|
||||
currentFilePath = file;
|
||||
async function selectLesson(filePath, title, breadcrumb, outcomes) {
|
||||
currentFilePath = filePath;
|
||||
document.getElementById('empty_selection_view').style.display = 'none';
|
||||
document.getElementById('active_lesson_view').style.display = 'block';
|
||||
|
||||
document.getElementById('current_breadcrumb').textContent = breadcrumb;
|
||||
document.getElementById('current_lesson_title_display').textContent = title;
|
||||
|
||||
// Render Outcomes
|
||||
const tags = document.getElementById('current_outcomes_tags');
|
||||
tags.innerHTML = outcomes.map(o => `
|
||||
tags.innerHTML = (outcomes || []).map(o => `
|
||||
<span style="font-size: 11px; font-weight: 700; background: rgba(255,209,102,0.12); color: var(--accent-gold); border: 1px solid rgba(255,209,102,0.3); padding: 3px 10px; border-radius: 980px;">
|
||||
🎯 ${escapeHtml(o)}
|
||||
</span>
|
||||
@@ -231,27 +268,86 @@ class CurriculumStudio
|
||||
|
||||
// Highlight Active in Sidebar
|
||||
document.querySelectorAll('.tree-lesson').forEach(el => el.classList.remove('active'));
|
||||
event.currentTarget?.classList.add('active');
|
||||
event?.currentTarget?.classList.add('active');
|
||||
|
||||
loadSampleLessonContent();
|
||||
}
|
||||
|
||||
function loadSampleLessonContent() {
|
||||
const editor = document.getElementById('lesson_markdown_editor');
|
||||
editor.value = `# مبحث الرياضيات — الفرع العلمي (توجيهي 2008)\n## الوحدة الأولى: التفاضل وتطبيقاته\n\n### الدرس 1: المفهوم الهندسي والفيزيائي للمشتقة الأولى\n\n- **نتاجات التعلم المستهدفة:**\n 1. فهم المعنى الهندسي للمشتقة الأولى كميل للمماس عند نقطة التماس (x1, y1).\n 2. التمييز بين الاقتران المتصل والاقتران القابل لاشتقاق.\n 3. حساب المشتقة باستخدام التعريف العام للمشتقة: f'(x) = lim(h->0) [f(x+h) - f(x)] / h.\n\n- **القواعد الأساسية:**\n • مشتقة الثابت = 0\n • مشتقة x^n = n * x^(n-1)\n • ميل المماس m = f'(x1)`;
|
||||
}
|
||||
|
||||
function handlePdfSelected(input) {
|
||||
if (input.files && input.files[0]) {
|
||||
const file = input.files[0];
|
||||
alert(`✅ تم استلام ملف المنهاج: ${file.name}\n\nجارٍ فك التشفير واستخراج الوحدات والدروس تلقائياً بالذكاء الاصطناعي...`);
|
||||
// Simulate AI extraction and auto-population
|
||||
document.getElementById('lesson_markdown_editor').value = `# مستخرج تلقائي من المنهاج الوزاري: ${file.name}\n\n## الوحدة المستخرجة: التفاضل والتكامل المتقدم\n\n- تم التعرف على 4 وحدات و 14 درساً معيارياً.\n- جاهز للاعتماد والمطابقة في شجرة المناهج.`;
|
||||
// Fetch Real Markdown from Server
|
||||
try {
|
||||
const res = await fetch(`/api/curriculum/lesson?file=${encodeURIComponent(filePath)}`);
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success') {
|
||||
document.getElementById('lesson_markdown_editor').value = data.content || '';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Fetch markdown error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function saveCurrentMarkdown() {
|
||||
alert('✅ تم حفظ وتحديث ملف المنهاج بنجاح في المسار:\n' + currentFilePath);
|
||||
async function handleRealPdfUpload(input) {
|
||||
if (!input.files || !input.files[0]) return;
|
||||
const file = input.files[0];
|
||||
|
||||
const btn = document.getElementById('btn_upload_pdf');
|
||||
const desc = document.getElementById('upload_status_desc');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span>⏳ جارٍ فك تشفير الكتاب بالذكاء الاصطناعي...</span>';
|
||||
desc.textContent = `جارٍ معالجة وتفصيص ملف [${file.name}] وبناء شجرة المناهج الحقيقية...`;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('pdf_file', file);
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/curriculum/upload-pdf', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok && data.status === 'success') {
|
||||
window.CURRICULUM_TREE = data.tree;
|
||||
currentFilePath = data.active_file || '';
|
||||
renderCurriculumTree(data.tree);
|
||||
|
||||
if (data.active_file) {
|
||||
document.getElementById('empty_selection_view').style.display = 'none';
|
||||
document.getElementById('active_lesson_view').style.display = 'block';
|
||||
document.getElementById('lesson_markdown_editor').value = data.active_md || '';
|
||||
document.getElementById('current_lesson_title_display').textContent = data.extracted_data?.units?.[0]?.lessons?.[0]?.title || 'الدرس المستخرج';
|
||||
document.getElementById('current_breadcrumb').textContent = `${data.extracted_data?.grade_name} ⟵ ${data.extracted_data?.subject_name}`;
|
||||
}
|
||||
|
||||
alert(`✅ ${data.message}`);
|
||||
desc.textContent = `تم اعتماد المنهاج [${file.name}] بنجاح في قاعدة المعرفة الشجرية!`;
|
||||
} else {
|
||||
alert('⚠️ ' + (data.message || 'فشلت معالجة الملف'));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('PDF Upload error:', err);
|
||||
alert('حدث خطأ في الاتصال بالخادم أثناء رفع الملف.');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<span>📤 رفع ملف المنهاج PDF الحقيقي</span>';
|
||||
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('✅ تم حفظ واعتماد التعديلات بنجاح في المنهاج الرسمي!');
|
||||
} else {
|
||||
alert('⚠️ فشل حفظ الملف');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('تعذر الاتصال بالسيرفر');
|
||||
}
|
||||
}
|
||||
|
||||
function handleCurriculumSearch(q) {
|
||||
@@ -262,7 +358,7 @@ class CurriculumStudio
|
||||
const qClean = q.toLowerCase().trim();
|
||||
const filtered = {};
|
||||
|
||||
for (const [gKey, grade] of Object.entries(window.CURRICULUM_TREE)) {
|
||||
for (const [gKey, grade] of Object.entries(window.CURRICULUM_TREE || {})) {
|
||||
let hasMatch = false;
|
||||
const newSubs = {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user