From ef639e804d8a18578ea5a93e143dd134bc87b360 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Tue, 8 Sep 2026 23:48:05 +0300 Subject: [PATCH] Update Saqel Platform: 2026-09-08 23:48:05 --- .../app/Services/AiVideoAnalyzerService.php | 64 ++++++++++++++++--- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/backend/app/Services/AiVideoAnalyzerService.php b/backend/app/Services/AiVideoAnalyzerService.php index 35ea195..87e7795 100644 --- a/backend/app/Services/AiVideoAnalyzerService.php +++ b/backend/app/Services/AiVideoAnalyzerService.php @@ -115,23 +115,67 @@ class AiVideoAnalyzerService 'contents' => [[ 'parts' => [ ['text' => $prompt], - ['inline_data' => ['mime_type' => 'image/jpeg', 'data' => base64_encode((string)file_get_contents($frame))],], + ['inlineData' => [ + 'mimeType' => 'image/jpeg', + 'data' => base64_encode((string)file_get_contents($frame)), + ]], ], ]], 'generationConfig' => ['responseMimeType' => 'application/json', 'temperature' => 0.1], ]; $geminiModel = getenv('GEMINI_MODEL') ?: 'gemini-1.5-flash'; $ch = curl_init('https://generativelanguage.googleapis.com/v1beta/models/' . rawurlencode($geminiModel) . ':generateContent?key=' . rawurlencode($key)); - curl_setopt_array($ch, [CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>json_encode($payload),CURLOPT_HTTPHEADER=>['Content-Type: application/json'],CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>30]); - $body = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); - $text = json_decode((string)$body, true)['candidates'][0]['content']['parts'][0]['text'] ?? ''; + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($payload), + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 30, + ]); + $body = curl_exec($ch); + $curlErr = curl_error($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($body === false || !empty($curlErr)) { + error_log("AiVideoAnalyzerService cURL error: " . $curlErr); + throw new \RuntimeException("تعذر الاتصال بخدمة التحليل: {$curlErr}"); + } + + if ($code !== 200) { + error_log("AiVideoAnalyzerService Gemini HTTP {$code}: " . $body); + $errJson = json_decode((string)$body, true); + $errMsg = $errJson['error']['message'] ?? "رمز الاستجابة {$code}"; + throw new \RuntimeException("استجابة Gemini: {$errMsg}"); + } + + $rawText = json_decode((string)$body, true)['candidates'][0]['content']['parts'][0]['text'] ?? ''; + $text = trim($rawText); + if (str_starts_with($text, '```json')) { + $text = trim(substr($text, 7)); + if (str_ends_with($text, '```')) { + $text = trim(substr($text, 0, -3)); + } + } elseif (str_starts_with($text, '```')) { + $text = trim(substr($text, 3)); + if (str_ends_with($text, '```')) { + $text = trim(substr($text, 0, -3)); + } + } + $report = json_decode($text, true); - if ($code !== 200 || !is_array($report) || !in_array($report['decision'] ?? '', ['approved','needs_manual_review','rejected'], true)) throw new \RuntimeException('استجابة Gemini غير صالحة'); + if (!is_array($report) || !in_array($report['decision'] ?? '', ['approved','needs_manual_review','rejected'], true)) { + error_log("AiVideoAnalyzerService invalid report format: " . $text); + throw new \RuntimeException('تنسيق تقرير الجودة غير صالح'); + } $report['duration_seconds'] = $duration; - if (($report['visual_clarity_score'] ?? 0) < 85 || ($report['pedagogical_readiness_score'] ?? 0) < 85) $report['decision'] = 'needs_manual_review'; + if (($report['visual_clarity_score'] ?? 0) < 85 || ($report['pedagogical_readiness_score'] ?? 0) < 85) { + $report['decision'] = 'needs_manual_review'; + } return $report; } catch (\Throwable $e) { - return ['decision' => 'needs_manual_review', 'reason' => 'تعذر إكمال تحليل Gemini؛ لم يتم رفع الفيديو']; + error_log("AiVideoAnalyzerService audit exception: " . $e->getMessage()); + return ['decision' => 'needs_manual_review', 'reason' => 'تعذر إكمال تحليل Gemini: ' . $e->getMessage(), 'duration_seconds' => $duration]; } finally { @unlink($frame); } } /** @@ -285,7 +329,8 @@ class AiVideoAnalyzerService \"approval_status\": \"approved_official\" }"; - $url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-lite-latest:generateContent?key=" . $geminiKey; + $geminiModel = getenv('GEMINI_MODEL') ?: 'gemini-1.5-flash'; + $url = "https://generativelanguage.googleapis.com/v1beta/models/" . rawurlencode($geminiModel) . ":generateContent?key=" . $geminiKey; $payload = [ 'contents' => [['parts' => [['text' => $prompt]]]], 'generationConfig' => ['responseMimeType' => 'application/json', 'temperature' => 0.2] @@ -400,7 +445,8 @@ class AiVideoAnalyzerService } قاعدة صارمة: السؤال عند أي دقيقة يسأل فقط عما تم شرحه قبل ذلك التوقيت، وممنوع نهائياً الاستعانة بأي معلومة خارج المنهاج."; - $url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-lite-latest:generateContent?key=" . $geminiKey; + $geminiModel = getenv('GEMINI_MODEL') ?: 'gemini-1.5-flash'; + $url = "https://generativelanguage.googleapis.com/v1beta/models/" . rawurlencode($geminiModel) . ":generateContent?key=" . $geminiKey; $payload = [ 'contents' => [ ['parts' => [['text' => $prompt]]]