Update Saqel Platform: 2026-09-08 23:29:32
This commit is contained in:
@@ -18,9 +18,36 @@ namespace App\Services;
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Core\Security;
|
||||
use App\Core\RedisClient;
|
||||
|
||||
class AiVideoAnalyzerService
|
||||
{
|
||||
/**
|
||||
* Select a Gemini key from the configured comma-separated pool. Redis
|
||||
* provides a process-safe round robin counter across PHP-FPM workers.
|
||||
* No key material is logged or returned to callers.
|
||||
*/
|
||||
private static function nextGeminiApiKey(): string
|
||||
{
|
||||
$raw = trim((string)(getenv('GEMINI_API_KEYS') ?: getenv('GEMINI_API_KEY') ?: ''));
|
||||
$keys = array_values(array_filter(array_map('trim', explode(',', $raw))));
|
||||
if (empty($keys)) {
|
||||
return '';
|
||||
}
|
||||
if (count($keys) === 1) {
|
||||
return $keys[0];
|
||||
}
|
||||
|
||||
try {
|
||||
$turn = (int)RedisClient::getInstance()->incr('saqel:gemini:round_robin');
|
||||
return $keys[($turn - 1) % count($keys)];
|
||||
} catch (\Throwable $e) {
|
||||
// The analysis can still proceed during a Redis outage. The time
|
||||
// bucket alternates keys without exposing either value.
|
||||
return $keys[(int)(floor(time() / 60) % count($keys))];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fail-closed admission gate for teacher uploads. The media stays in PHP's
|
||||
* temporary upload area until Gemini and technical checks approve it.
|
||||
@@ -50,7 +77,7 @@ class AiVideoAnalyzerService
|
||||
if (!$hasVideo || !$hasAudio || $duration <= 0 || $duration > 1500) {
|
||||
return ['decision' => 'rejected', 'reason' => 'الفيديو يجب أن يحتوي صورة وصوتاً وأن لا يتجاوز 25 دقيقة', 'duration_seconds' => $duration];
|
||||
}
|
||||
$key = trim((string)getenv('GEMINI_API_KEY'));
|
||||
$key = self::nextGeminiApiKey();
|
||||
if ($key === '') return ['decision' => 'needs_manual_review', 'reason' => 'Gemini غير مهيأ؛ لم يتم رفع الفيديو', 'duration_seconds' => $duration];
|
||||
|
||||
// Extract a representative frame; Gemini receives real media evidence,
|
||||
@@ -200,7 +227,7 @@ class AiVideoAnalyzerService
|
||||
public static function evaluatePedagogicalQuality(int $lessonId, string $lessonTitle, int $duration, array $curriculum): array
|
||||
{
|
||||
try {
|
||||
$geminiKey = getenv('GEMINI_API_KEY');
|
||||
$geminiKey = self::nextGeminiApiKey();
|
||||
$alignmentScore = 0.0;
|
||||
$clarityScore = 0.0;
|
||||
$outcomes = [
|
||||
@@ -312,7 +339,7 @@ class AiVideoAnalyzerService
|
||||
*/
|
||||
private static function generateAnalysis(string $lessonTitle, int $duration, array $curriculum): array
|
||||
{
|
||||
$geminiKey = getenv('GEMINI_API_KEY');
|
||||
$geminiKey = self::nextGeminiApiKey();
|
||||
|
||||
if (!empty($geminiKey)) {
|
||||
try {
|
||||
|
||||
+21
-15
@@ -27,22 +27,28 @@ spl_autoload_register(function ($class) {
|
||||
|
||||
// 2. Load Environment Variables with Multi-Path Fallback
|
||||
try {
|
||||
$candidatePaths = [
|
||||
'/home/intaleqapp-saqel/.env',
|
||||
'/home/intaleqapp-saqel/htdocs/saqel.intaleqapp.com/.env',
|
||||
'/home/intaleqapp-saqel/htdocs/saqel.intaleqapp.com/saqel/.env',
|
||||
'/home/intaleqapp-saqel/htdocs/saqel.intaleqapp.com/saqel/backend/.env',
|
||||
APP_ROOT . '/.env',
|
||||
APP_ROOT . '/../.env'
|
||||
];
|
||||
|
||||
// Production secrets belong outside the web/document root. Do not load
|
||||
// backend/.env: it may be a placeholder and must never shadow the real
|
||||
// CloudPanel environment file.
|
||||
$envPath = getenv('SAQEL_ENV_FILE') ?: '/home/intaleqapp-saqel/.env';
|
||||
$loaded = false;
|
||||
foreach ($candidatePaths as $path) {
|
||||
if (file_exists($path)) {
|
||||
define('LOADED_ENV_PATH', $path);
|
||||
\App\Core\Env::load($path);
|
||||
$loaded = true;
|
||||
break;
|
||||
if (file_exists($envPath)) {
|
||||
\App\Core\Env::load($envPath);
|
||||
define('LOADED_ENV_PATH', $envPath);
|
||||
$loaded = true;
|
||||
|
||||
// Support either GEMINI_API_KEYS=key1,key2 or the legacy singular
|
||||
// variable containing a comma-separated pool. Legacy services receive
|
||||
// one safe key; services with round-robin support consume the pool.
|
||||
$geminiPool = trim((string)(getenv('GEMINI_API_KEYS') ?: getenv('GEMINI_API_KEY') ?: ''));
|
||||
$geminiKeys = array_values(array_filter(array_map('trim', explode(',', $geminiPool))));
|
||||
if (!empty($geminiKeys)) {
|
||||
putenv('GEMINI_API_KEYS=' . implode(',', $geminiKeys));
|
||||
$_ENV['GEMINI_API_KEYS'] = implode(',', $geminiKeys);
|
||||
$_SERVER['GEMINI_API_KEYS'] = implode(',', $geminiKeys);
|
||||
putenv('GEMINI_API_KEY=' . $geminiKeys[0]);
|
||||
$_ENV['GEMINI_API_KEY'] = $geminiKeys[0];
|
||||
$_SERVER['GEMINI_API_KEY'] = $geminiKeys[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user