feat: STEM Virtual Lab, English TTS companion, dynamic textbooks and zero-demo video fallback
This commit is contained in:
@@ -347,4 +347,137 @@ class CurriculumController
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available simulations for a subject or overall
|
||||
* GET /api/curriculum/simulations
|
||||
*/
|
||||
public function listSimulations(Request $request, Response $response): void
|
||||
{
|
||||
$subject = $request->getQueryParams()['subject'] ?? 'physics_10';
|
||||
$simDir = realpath(__DIR__ . '/../../storage/curriculum/simulations') . "/{$subject}";
|
||||
|
||||
$simulations = [];
|
||||
if (is_dir($simDir)) {
|
||||
$files = glob("{$simDir}/*.html");
|
||||
foreach ($files as $f) {
|
||||
$slug = basename($f, '.html');
|
||||
$title = ($slug === 'vectors_lab') ? 'مختبر جمع وتحليل المتجهات التفاعلي' :
|
||||
(($slug === 'motion_1d_lab') ? 'مختبر الحركة في بُعد واحد والتسارع' : $slug);
|
||||
$unit = ($slug === 'vectors_lab') ? 'الوحدة 1: المتجهات والكميات' :
|
||||
(($slug === 'motion_1d_lab') ? 'الوحدة 2: الحركة والقوى' : 'تجارب تفاعلية');
|
||||
|
||||
$simulations[] = [
|
||||
'slug' => $slug,
|
||||
'title' => $title,
|
||||
'unit_title' => $unit,
|
||||
'url' => "/api/curriculum/simulations/{$subject}/{$slug}",
|
||||
'has_canvas' => true,
|
||||
'watermark' => 'منصة صَقِل التعليمية الذكية © Saqel Lab',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'data' => $simulations
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get / Serve Interactive HTML5 Canvas Simulation
|
||||
* GET /api/curriculum/simulations/{subject}/{simName}
|
||||
*/
|
||||
public function getSimulation(Request $request, Response $response): void
|
||||
{
|
||||
$subject = preg_replace('/[^a-zA-Z0-9_-]/', '', $request->getParam('subject') ?? '');
|
||||
$simName = preg_replace('/[^a-zA-Z0-9_-]/', '', $request->getParam('simName') ?? '');
|
||||
|
||||
$simPath = realpath(__DIR__ . '/../../storage/curriculum/simulations') . "/{$subject}/{$simName}.html";
|
||||
|
||||
if (!file_exists($simPath)) {
|
||||
// Fallback check in root simulations
|
||||
$simPath = realpath(__DIR__ . '/../../storage/curriculum/simulations') . "/{$simName}.html";
|
||||
}
|
||||
|
||||
if (!file_exists($simPath)) {
|
||||
$response->status(404)->json(['status' => 'error', 'message' => 'المحاكي التفاعلي غير موجود']);
|
||||
return;
|
||||
}
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
header('X-Frame-Options: SAMEORIGIN');
|
||||
readfile($simPath);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get real document or textbook markdown content dynamically per subject
|
||||
* GET /api/curriculum/document
|
||||
*/
|
||||
public function getDocumentContent(Request $request, Response $response): void
|
||||
{
|
||||
$params = $request->getQueryParams();
|
||||
$file = $params['file'] ?? '';
|
||||
$subject = strtolower($params['subject'] ?? '');
|
||||
$type = $params['type'] ?? 'textbook';
|
||||
|
||||
$storage = realpath(__DIR__ . '/../../storage/curriculum');
|
||||
$resolvedFile = null;
|
||||
|
||||
if (!empty($file) && !str_ends_with($file, '.pdf')) {
|
||||
$cleanFile = ltrim($file, '/');
|
||||
if (file_exists("{$storage}/{$cleanFile}")) {
|
||||
$resolvedFile = "{$storage}/{$cleanFile}";
|
||||
}
|
||||
}
|
||||
|
||||
// If not resolved by exact path, resolve by subject
|
||||
if (!$resolvedFile && !empty($subject)) {
|
||||
if (str_contains($subject, 'english') || str_contains($subject, 'إنجليز')) {
|
||||
$mds = glob("{$storage}/grade_10/english_10/semester_1/*/*.md");
|
||||
if (!empty($mds)) $resolvedFile = $mds[0];
|
||||
} elseif (str_contains($subject, 'physic') || str_contains($subject, 'فيزياء')) {
|
||||
$mds = glob("{$storage}/grade_10/physics_10/semester_1/*/*.md");
|
||||
if (!empty($mds)) $resolvedFile = $mds[1] ?? $mds[0];
|
||||
} elseif (str_contains($subject, 'math') || str_contains($subject, 'رياضيات')) {
|
||||
$mds = glob("{$storage}/grade_10/math_10/semester_1/*/*.md");
|
||||
if (!empty($mds)) $resolvedFile = $mds[1] ?? $mds[0];
|
||||
}
|
||||
}
|
||||
|
||||
if ($resolvedFile && file_exists($resolvedFile)) {
|
||||
$content = file_get_contents($resolvedFile);
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'file' => str_replace("{$storage}/", '', $resolvedFile),
|
||||
'subject' => $subject,
|
||||
'type' => $type,
|
||||
'content' => $content
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tailored subject fallback
|
||||
$content = self::getDefaultSubjectDocument($subject, $type);
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'subject' => $subject,
|
||||
'type' => $type,
|
||||
'content' => $content
|
||||
]);
|
||||
}
|
||||
|
||||
private static function getDefaultSubjectDocument(string $subject, string $type): string
|
||||
{
|
||||
$s = strtolower($subject);
|
||||
if (str_contains($s, 'english') || str_contains($s, 'إنجليز')) {
|
||||
return "## Unit 01: Looking Good — Vocabulary & Reading\n\n### 1. Key Vocabulary & Word Formation\n- **Casual clothing:** Everyday informal garments (jeans, sneakers, hoodie).\n- **Traditional attire:** Cultural heritage wear (Jordanian Thobe and Keffiyeh).\n- **Subconscious influence:** The way external appearance affects cognitive confidence.\n\n### 2. Reading Text: The Power of First Impressions\nResearch shows that within the first seven seconds of meeting someone, people make subconscious judgments about character, competence, and reliability. In a famous experiment, doctors wearing clean white coats demonstrated higher diagnostic focus.\n\n### 3. Grammar Workshop: Articles (a, an, the, zero article)\n- Use **a / an** for non-specific singular countable nouns.\n- Use **the** when the listener knows which specific thing is meant.\n- Use **zero article** with plural or uncountable nouns spoken in general terms.";
|
||||
}
|
||||
if (str_contains($s, 'physic') || str_contains($s, 'فيزياء')) {
|
||||
return "## الوحدة الأولى: المتجهات والكميات الفيزيائية\n\n### 1. التمييز بين الكميات القياسية والمتجهة\n- **الكميات القياسية:** تُحدد بالمقدار ووحدة القياس فقط (الكتلة، الزمن، الطاقة، درجة الحرارة).\n- **الكميات المتجهة:** تُحدد بالمقدار والاتجاه ونقطة التأثير (القوة، السرعة المتجهة، التسارع، الإزاحة).\n\n### 2. جمع وتحليل المتجهات بيانياً وتحليلياً\n- **المركبة الأفقية:** A_x = A cos θ\n- **المركبة الرأسية:** A_y = A sin θ\n- **المحصلة:** R = √(R_x² + R_y²)\n- **زاوية الاتجاه:** tan θ = |R_y / R_x|";
|
||||
}
|
||||
return "## ملخص الوحدة والنتاجات التعليمية المقررة\n\n### 1. الأهداف العامة للمادة\n- استيعاب المفاهيم والمصطلحات الأساسية وفق المنهاج الوزاري المعتمد.\n- التدرب على حل النماذج والتطبيقات العملية والأسئلة الوزارية.\n- التحقق من اكتساب المهارات من خلال بنك الأسئلة التكيفي والمختبر.";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user