diff --git a/backend/app/Controllers/CurriculumController.php b/backend/app/Controllers/CurriculumController.php
index a5ccc3e..398ed4b 100644
--- a/backend/app/Controllers/CurriculumController.php
+++ b/backend/app/Controllers/CurriculumController.php
@@ -171,24 +171,24 @@ class CurriculumController
return;
}
$content = CurriculumService::getLessonMarkdown($file);
+ $assets = CurriculumService::getLessonAiAssets($file);
$serverFullPath = realpath(__DIR__ . '/../../storage/curriculum') . '/' . ltrim($file, '/');
$response->json([
'status' => 'success',
'file' => $file,
'server_full_path' => $serverFullPath,
- 'content' => $content
+ 'content' => $content,
+ 'ai_assets' => $assets
]);
}
- /**
- * Save Lesson Markdown Content
- */
public function saveLessonContent(Request $request, Response $response): void
{
$body = $request->getBody();
$file = $body['file'] ?? '';
$content = $body['content'] ?? '';
+ $aiAssets = $body['ai_assets'] ?? null;
if (empty($file) || empty($content)) {
$response->status(400)->json(['status' => 'error', 'message' => 'بيانات الحفظ غير مكتملة']);
@@ -196,6 +196,10 @@ class CurriculumController
}
CurriculumService::saveLessonMarkdown($file, $content);
+ if ($aiAssets !== null) {
+ CurriculumService::saveLessonAiAssets($file, $aiAssets);
+ }
+
$serverFullPath = realpath(__DIR__ . '/../../storage/curriculum') . '/' . ltrim($file, '/');
$response->json([
@@ -204,4 +208,34 @@ class CurriculumController
'server_full_path' => $serverFullPath
]);
}
+
+ public function generateAiAssets(Request $request, Response $response): void
+ {
+ $body = $request->getBody();
+ $file = $body['file'] ?? '';
+ if (empty($file)) {
+ $response->status(400)->json(['status' => 'error', 'message' => 'مسار الملف مطلوب']);
+ return;
+ }
+
+ $content = CurriculumService::getLessonMarkdown($file);
+ if (empty($content)) {
+ $response->status(400)->json(['status' => 'error', 'message' => 'محتوى الدرس فارغ']);
+ return;
+ }
+
+ // Generate Assets
+ require_once __DIR__ . '/../Services/AiLessonEnhancerService.php';
+
+ // We will adapt the service call here since AiLessonEnhancerService expects lessonId.
+ // Actually, let's create a custom static method in AiLessonEnhancerService to accept raw text.
+ $assets = \App\Services\AiLessonEnhancerService::generateFromText('الدرس', $content);
+
+ if ($assets) {
+ CurriculumService::saveLessonAiAssets($file, $assets);
+ $response->json(['status' => 'success', 'ai_assets' => $assets]);
+ } else {
+ $response->status(500)->json(['status' => 'error', 'message' => 'فشل توليد المخرجات الذكية']);
+ }
+ }
}
diff --git a/backend/app/Services/AiLessonEnhancerService.php b/backend/app/Services/AiLessonEnhancerService.php
index 48f92da..300ce0e 100644
--- a/backend/app/Services/AiLessonEnhancerService.php
+++ b/backend/app/Services/AiLessonEnhancerService.php
@@ -10,6 +10,12 @@ class AiLessonEnhancerService
/**
* Extracts the lively script, cheat sheet, and Socratic quiz using Gemini.
*/
+ public static function generateFromText(string $title, string $content): ?array
+ {
+ $prompt = self::buildPrompt($title, $content);
+ return self::callGemini($prompt);
+ }
+
public static function enhanceLesson(int $lessonId): bool
{
$lesson = Database::selectOne(
diff --git a/backend/app/Services/CurriculumService.php b/backend/app/Services/CurriculumService.php
index e334aad..4b2ceca 100644
--- a/backend/app/Services/CurriculumService.php
+++ b/backend/app/Services/CurriculumService.php
@@ -156,6 +156,31 @@ class CurriculumService
return "# محتوى المنهاج\nالمحتوى المعتمد للمنهاج الرسمي.";
}
+ public static function saveLessonAiAssets(string $relativePath, array $assets): bool
+ {
+ self::ensureStorage();
+ $baseName = preg_replace('/\.md$/i', '', ltrim($relativePath, '/'));
+ $jsonPath = self::$storagePath . '/' . $baseName . '_ai_assets.json';
+
+ $dir = dirname($jsonPath);
+ if (!is_dir($dir)) {
+ mkdir($dir, 0777, true);
+ }
+ return file_put_contents($jsonPath, json_encode($assets, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)) !== false;
+ }
+
+ public static function getLessonAiAssets(string $relativePath): array
+ {
+ self::ensureStorage();
+ $baseName = preg_replace('/\.md$/i', '', ltrim($relativePath, '/'));
+ $jsonPath = self::$storagePath . '/' . $baseName . '_ai_assets.json';
+
+ if (file_exists($jsonPath)) {
+ return json_decode(file_get_contents($jsonPath), true) ?: [];
+ }
+ return [];
+ }
+
/**
* Fast Keyword Search across All Markdown Files
*/
diff --git a/backend/app/Views/CurriculumStudio.php b/backend/app/Views/CurriculumStudio.php
index fcae40a..34dae67 100644
--- a/backend/app/Views/CurriculumStudio.php
+++ b/backend/app/Views/CurriculumStudio.php
@@ -197,7 +197,31 @@ class CurriculumStudio
-
+
+
+
+
+
+
🧠 الاستوديو الذكي للدرس (AI Assets)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -487,6 +511,128 @@ class CurriculumStudio
}
}
+ // ==========================================================
+ // 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() {
+ if (!currentFilePath) return;
+ const content = document.getElementById('lesson_markdown_editor').value;
+
+ let socraticJson = null;
+ const socraticRaw = document.getElementById('ai_socratic_quiz_editor').value.trim();
+ if (socraticRaw) {
+ try {
+ socraticJson = JSON.parse(socraticRaw);
+ } catch(e) {
+ alert('⚠️ خطأ في صيغة JSON الخاصة بالفحص السقراطي');
+ return;
+ }
+ }
+
+ const aiAssets = {
+ ai_video_url: document.getElementById('ai_video_url_input').value,
+ cheat_sheet: document.getElementById('ai_cheat_sheet_editor').value,
+ socratic_quiz: socraticJson
+ };
+
+ try {
+ const res = await fetch('/api/curriculum/save-lesson', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ file: currentFilePath, content: content, ai_assets: aiAssets })
+ });
+ const data = await res.json();
+ if (res.ok && data.status === 'success') {
+ alert(`✅ تم حفظ واعتماد التعديلات بنجاح في الملف:\n${data.server_full_path || currentFilePath}`);
+ } else {
+ alert('⚠️ فشل حفظ الملف');
+ }
+ } catch (e) {
+ console.error(e);
+ alert('⚠️ حدث خطأ في الاتصال');
+ }
+ }
+
+ // ==========================================================
+ // 5. Generate AI Assets automatically
+ // ==========================================================
+ async function generateAiAssets() {
+ if (!currentFilePath) return;
+ const content = document.getElementById('lesson_markdown_editor').value;
+ if (!content) {
+ alert('يجب أن يحتوي الدرس على نص أولاً');
+ return;
+ }
+
+ const btn = window.event.currentTarget;
+ const originalText = btn.innerHTML;
+ btn.innerHTML = '⏳ جاري التوليد الذكي...';
+ btn.disabled = true;
+
+ try {
+ 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();
+
+ 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(e);
+ alert('⚠️ حدث خطأ في الاتصال بمحرك الذكاء الاصطناعي');
+ } finally {
+ btn.innerHTML = originalText;
+ btn.disabled = false;
+ }
+ }
+
function openDirectLogViewer() {
const overlay = document.getElementById('upload_progress_overlay');
overlay.style.display = 'flex';
diff --git a/backend/public/index.php b/backend/public/index.php
index e168feb..6da8f64 100644
--- a/backend/public/index.php
+++ b/backend/public/index.php
@@ -62,6 +62,7 @@ $router->get('/api/curriculum/upload-log', [\App\Controllers\CurriculumControlle
$router->get('/api/curriculum/tree', [\App\Controllers\CurriculumController::class, 'getTree']);
$router->get('/api/curriculum/lesson', [\App\Controllers\CurriculumController::class, 'getLessonContent']);
$router->post('/api/curriculum/save-lesson', [\App\Controllers\CurriculumController::class, 'saveLessonContent']);
+$router->post('/api/curriculum/generate-ai-assets', [\App\Controllers\CurriculumController::class, 'generateAiAssets']);
$router->get('/api/curriculum/search', function ($request, $response) {
$q = $request->getQueryParams()['q'] ?? '';
$response->json([