diff --git a/app/modules_app/voice/transcribe.php b/app/modules_app/voice/transcribe.php index 0522e22..92a8145 100644 --- a/app/modules_app/voice/transcribe.php +++ b/app/modules_app/voice/transcribe.php @@ -98,7 +98,7 @@ function detectAudioMimeType(string $path, string $fallback, string $fileName = function extractIntentFromAudio(string $base64Audio, string $mimeType, string $apiKey): array { - $model = env('GEMINI_VOICE_MODEL', env('GEMINI_MODEL', 'gemini-2.5-flash')); + $model = env('GEMINI_VOICE_MODEL', 'gemini-2.5-flash'); $systemPrompt = << true, - CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE), - CURLOPT_RETURNTRANSFER => true, - CURLOPT_HTTPHEADER => ['Content-Type: application/json'], - CURLOPT_TIMEOUT => 45, - ]); - - $response = curl_exec($ch); - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - $error = curl_error($ch); - curl_close($ch); - - if ($httpCode !== 200 || !$response) { - error_log("Voice Gemini Error: HTTP {$httpCode} | {$error} | {$response}"); - json_error('فشل تحليل الصوت بواسطة Gemini', 500); + // Some Gemini model/API combinations reject JSON mode for multimodal audio. + // Retry once with prompt-only JSON enforcement before failing the request. + if ($result['http_code'] !== 200 || !$result['body']) { + $fallbackPayload = $payload; + unset($fallbackPayload['generationConfig']['responseMimeType']); + $result = callGeminiGenerateContent($model, $fallbackPayload, $apiKey); } - $respData = json_decode($response, true); + if ($result['http_code'] !== 200 || !$result['body']) { + $geminiError = parseGeminiError($result['body']); + error_log( + "Voice Gemini Error: HTTP {$result['http_code']} | {$result['curl_error']} | {$result['body']}" + ); + + json_error( + 'فشل تحليل الصوت بواسطة Gemini: ' . $geminiError['message'], + 502, + [ + 'gemini_http_code' => $result['http_code'], + 'gemini_status' => $geminiError['status'], + 'gemini_model' => $model, + 'audio_mime_type' => $mimeType, + ] + ); + } + + $respData = json_decode($result['body'], true); if (!is_array($respData)) { json_error('تعذر قراءة رد Gemini', 500); } @@ -217,6 +224,52 @@ PROMPT; ]; } +function callGeminiGenerateContent(string $model, array $payload, string $apiKey): array +{ + $url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent"; + + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'x-goog-api-key: ' . $apiKey, + ], + CURLOPT_TIMEOUT => 45, + ]); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $error = curl_error($ch); + curl_close($ch); + + return [ + 'body' => is_string($response) ? $response : '', + 'http_code' => (int)$httpCode, + 'curl_error' => $error ?: '', + ]; +} + +function parseGeminiError(string $response): array +{ + $decoded = json_decode($response, true); + $error = is_array($decoded) ? ($decoded['error'] ?? null) : null; + + if (is_array($error)) { + return [ + 'message' => (string)($error['message'] ?? 'رد غير معروف من Gemini'), + 'status' => (string)($error['status'] ?? ''), + ]; + } + + return [ + 'message' => 'رد غير معروف من Gemini', + 'status' => '', + ]; +} + function decodeModelJson(string $rawText): ?array { $text = trim($rawText);