getMessage() . " in " . $e->getFile() . " on line " . $e->getLine() . " "; }); require_once __DIR__ . '/../vendor/autoload.php'; use App\Services\CurriculumService; use App\Core\Env; $taskId = $argv[1] ?? ''; if (empty($taskId)) { echo "Task ID required\n"; return; } $processingDir = __DIR__ . '/../storage/curriculum/processing'; $stateFile = $processingDir . '/' . $taskId . '.json'; $pdfFile = $processingDir . '/' . $taskId . '.pdf'; if (!file_exists($stateFile) || !file_exists($pdfFile)) { echo "Files missing\n"; return; } function updateState($stateFile, $status, $progress, $message, $extra = []) { $state = json_decode(file_get_contents($stateFile), true); $state['status'] = $status; $state['progress'] = $progress; $state['message'] = $message; if (!empty($extra)) { $state = array_merge($state, $extra); } file_put_contents($stateFile, json_encode($state, JSON_UNESCAPED_UNICODE)); // Log to file too $logFile = str_replace('.json', '.log', $stateFile); $timestamp = date('Y-m-d H:i:s'); file_put_contents($logFile, "[$timestamp] [$status] [$progress%] $message\n", FILE_APPEND); } $state = json_decode(file_get_contents($stateFile), true); $origName = $state['orig_name'] ?? 'Unknown.pdf'; Env::load(__DIR__ . '/../.env'); $rawKeys = Env::get('GEMINI_API_KEY') ?: getenv('GEMINI_API_KEY'); $apiKeys = array_values(array_filter(array_map('trim', explode(',', $rawKeys)))); if (empty($apiKeys)) { updateState($stateFile, 'error', 0, '⚠️ لم يتم العثور على مفتاح الذكاء الاصطناعي (GEMINI_API_KEY) في ملف .env.'); return; } // Round Robin Logic $rrFile = sys_get_temp_dir() . '/saqel_gemini_rr.txt'; $currentIndex = file_exists($rrFile) ? (int)file_get_contents($rrFile) : 0; $geminiKey = $apiKeys[$currentIndex % count($apiKeys)]; file_put_contents($rrFile, ($currentIndex + 1) % count($apiKeys)); // Add info about which key is used (masked for security) $maskedKey = substr($geminiKey, 0, 6) . '...' . substr($geminiKey, -4); updateState($stateFile, 'uploading_to_ai', 15, "تم تفعيل Round Robin. جاري استخدام المفتاح: {$maskedKey}"); if (empty($geminiKey)) { updateState($stateFile, 'error', 0, '⚠️ لم يتم العثور على مفتاح الذكاء الاصطناعي (GEMINI_API_KEY) في ملف .env. النظام يحتاج إلى الـ AI لقراءة وتفريغ الكتب (بما فيها الكتب المصورة والرياضيات) بشكل حقيقي.'); return; } updateState($stateFile, 'uploading_to_ai', 20, 'جاري رفع الكتاب إلى محرك الذكاء الاصطناعي (Gemini Vision) لقراءته بالكامل...'); // 1. Upload PDF to Gemini File API $fileSize = filesize($pdfFile); $uploadUrl = "https://generativelanguage.googleapis.com/upload/v1beta/files?uploadType=media&key=" . $geminiKey; $ch = curl_init($uploadUrl); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents($pdfFile)); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/pdf', 'Content-Length: ' . $fileSize ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 300); // 5 minutes for upload $uploadRes = curl_exec($ch); $uploadCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($uploadCode !== 200) { updateState($stateFile, 'error', 0, 'فشل رفع الملف إلى الذكاء الاصطناعي. كود الخطأ: ' . $uploadCode . ' التفاصيل: ' . $uploadRes); return; } $uploadData = json_decode($uploadRes, true); $fileUri = $uploadData['file']['uri'] ?? ''; $fileNameGemini = $uploadData['file']['name'] ?? ''; if (empty($fileUri)) { updateState($stateFile, 'error', 0, 'لم يتم الحصول على URI من الذكاء الاصطناعي.'); return; } updateState($stateFile, 'analyzing', 40, 'الكتاب الآن في عقل الذكاء الاصطناعي... جاري المعالجة البصرية واستخراج المعادلات والنصوص...'); // Wait for file to be ACTIVE (Gemini needs time to process PDF) $maxRetries = 15; $isReady = false; for ($i = 0; $i < $maxRetries; $i++) { $ch = curl_init("https://generativelanguage.googleapis.com/v1beta/{$fileNameGemini}?key={$geminiKey}"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $statRes = curl_exec($ch); curl_close($ch); $statData = json_decode($statRes, true); if (($statData['state'] ?? '') === 'ACTIVE') { $isReady = true; break; } sleep(3); // wait 3 seconds before polling again } if (!$isReady) { updateState($stateFile, 'error', 0, 'انتهى وقت الانتظار أثناء معالجة الذكاء الاصطناعي لملف الـ PDF.'); return; } updateState($stateFile, 'analyzing', 60, 'جاري تفريغ المحتوى وتشكيل دروس المارك داون والمصادر الملحقة...'); // 2. Prompt Gemini $prompt = "أنت خبير مناهج تعليمية ورياضيات. لقد قمت برفع كتاب دراسي بصيغة PDF. اقرأ هذا الكتاب بأكمله بدقة. استخرج الهيكل الشجري للمنهاج، بما في ذلك جميع الوحدات والدروس. لكل درس، استخرج **النص الحقيقي الفعلي** الموجود في الكتاب بدقة متناهية. إذا كان هناك معادلات رياضية أو أرقام أو نصوص تفسيرية، اكتبها بالكامل باستخدام تنسيق Markdown و LaTeX (مثل $x^2 + y^2 = r^2$). لا تستخدم نصوصاً وهمية أبداً! إذا لم تستطع قراءة كل الدروس، اقرأ الدروس الأولى بشكل كامل جداً من الكتاب وضع ملخصاً للباقي. أريد الإجابة بصيغة JSON صالحة 100% فقط بدون أي نص خارج الـ JSON: { \"grade_name\": \"اسم الصف (مثال: الصف العاشر)\", \"grade_key\": \"grade_10\", \"subject_name\": \"اسم المادة (مثال: الرياضيات)\", \"subject_key\": \"math_10\", \"semester_name\": \"الفصل الدراسي\", \"semester_key\": \"semester_1\", \"units\": [ { \"unit_key\": \"unit_1\", \"unit_name\": \"اسم الوحدة الحقيقي\", \"lessons\": [ { \"lesson_id\": \"lesson_1\", \"title\": \"اسم الدرس الحقيقي\", \"outcomes\": [\"نتاج 1 حقيقي\"], \"markdown_content\": \"# عنوان الدرس\\n\\n[هنا النص الحقيقي والشرح والمعادلات كما ورد في الكتاب المرفوع]\" } ] } ], \"resources\": { \"worksheets\": { \"name\": \"أوراق عمل\", \"items\": [] }, \"exams\": { \"name\": \"اختبارات\", \"items\": [] } } }"; $payload = [ 'contents' => [ [ 'parts' => [ ['fileData' => ['mimeType' => 'application/pdf', 'fileUri' => $fileUri]], ['text' => $prompt] ] ] ], 'generationConfig' => ['responseMimeType' => 'application/json', 'temperature' => 0.1] ]; // Delay 4 seconds to respect free tier rate limits updateState($stateFile, 'analyzing', 70, 'جاري تطبيقหน่วงزمني (4 ثوانٍ) لتفادي حدود الاستخدام (Rate Limit)...'); sleep(4); // Allow custom model from .env (e.g. gemini-1.5-flash or gemini-2.0-flash) $geminiModel = Env::get('GEMINI_MODEL') ?: getenv('GEMINI_MODEL'); if (empty($geminiModel)) { $geminiModel = "gemini-1.5-flash"; // standard robust model } updateState($stateFile, 'analyzing', 75, "جاري إرسال البيانات للنموذج [{$geminiModel}] لاستخراج الدروس والمفاهيم والمعادلات..."); $url = "https://generativelanguage.googleapis.com/v1beta/models/{$geminiModel}:generateContent?key=" . $geminiKey; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 300 // AI might take 2-3 minutes ]); $res = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code !== 200 || empty($res)) { $detail = !empty($res) ? $res : 'استجابة فارغة من خادم Google Gemini'; updateState($stateFile, 'error', 0, "فشل تحليل المحتوى من الذكاء الاصطناعي (HTTP {$code}). التفاصيل: {$detail}"); return; } $jsonResponse = json_decode($res, true); $text = $jsonResponse['candidates'][0]['content']['parts'][0]['text'] ?? ''; // Clean up markdown fences if Gemini returned them $text = preg_replace('/^```json\s*/i', '', $text); $text = preg_replace('/```\s*$/i', '', $text); $text = trim($text); $parsedStructure = json_decode($text, true); if (empty($parsedStructure) || empty($parsedStructure['units'])) { // Log the actual text for debugging file_put_contents($stateFile . '.debug.txt', $text); updateState($stateFile, 'error', 0, 'فشل الذكاء الاصطناعي في إرجاع بنية JSON صالحة.'); return; } updateState($stateFile, 'generating', 80, 'الذكاء الاصطناعي انتهى من القراءة! جاري بناء الملفات وحفظها على السيرفر...'); // Save to disk $updatedTree = CurriculumService::mergeExtractedCurriculum($parsedStructure); $firstLessonFile = ''; if (!empty($parsedStructure['units'][0]['lessons'][0])) { $firstLes = $parsedStructure['units'][0]['lessons'][0]; $firstLessonFile = "{$parsedStructure['grade_key']}/{$parsedStructure['subject_key']}/{$parsedStructure['semester_key']}/{$parsedStructure['units'][0]['unit_key']}/{$firstLes['lesson_id']}.md"; } updateState($stateFile, 'completed', 100, 'تم الاستخراج المعتمد على الـ AI بالكامل بنجاح!', [ 'active_file' => $firstLessonFile, 'tree' => $updatedTree, 'extracted_data' => $parsedStructure ]);