encryption = $encryption; } /** * POST /v2/support/complaints * يحلل الشكوى باستخدام الذكاء الاصطناعي (Gemini) ويحفظ النتيجة */ public function storeComplaint(Request $request): JsonResponse { $userId = $request->attributes->get('_jwt_user_id'); $request->validate([ 'ride_id' => 'required|string', 'complaint_text' => 'required|string', 'audio_link' => 'nullable|string', ]); $rideId = $request->input('ride_id'); $complaintText = $request->input('complaint_text'); $audioLink = $request->input('audio_link'); // 1. جلب بيانات الرحلة $ride = DB::connection('ride')->table('ride') ->where('id', $rideId) ->first(); if (!$ride) { return response()->json(['status' => 'failure', 'message' => 'Ride not found'], 404); } $passengerId = $ride->passenger_id; $driverId = $ride->driver_id; // 2. جلب الملفات التعريفية والسلوك $passengerProfile = $this->getPassengerFullProfile($passengerId); $driverProfile = $this->getDriverFullProfile($driverId); $driverBehavior = DB::connection('tracking')->table('driver_behavior') ->where('trip_id', $rideId) ->where('driver_id', $driverId) ->first(); // 3. بناء الـ Prompt لـ Gemini $prompt = $this->buildGeminiPrompt($ride, $passengerProfile, $driverProfile, $driverBehavior, $complaintText, $audioLink); // 4. استدعاء Gemini API $apiKey = config('services.gemini.key') ?? env('GEMINI_API_KEY'); if (!$apiKey) { Log::error('Gemini API Key missing in SupportController'); // Fallback: save without AI if key is missing? // Better to fail if AI is expected. } try { $response = Http::timeout(60)->post("https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={$apiKey}", [ 'contents' => [ ['parts' => [['text' => $prompt]]] ] ]); if ($response->failed()) { throw new \Exception('Gemini API failed: ' . $response->body()); } $aiData = $response->json(); $analysisRaw = $aiData['candidates'][0]['content']['parts'][0]['text'] ?? ''; $analysisJson = trim(preg_replace('/```json|```/', '', $analysisRaw)); $analysis = json_decode($analysisJson, true); if (!$analysis || !isset($analysis['passengerReport'])) { throw new \Exception('Failed to parse AI response: ' . $analysisRaw); } // 5. حفظ الشكوى والتحليل $fullDescription = $complaintText . ($audioLink ? "\n\n[Audio Attached: {$audioLink}]" : ""); DB::connection('primary')->table('complaint')->insert([ 'ride_id' => $rideId, 'passenger_id' => $passengerId, 'driver_id' => $driverId, 'complaint_type' => $analysis['complaint_type'] ?? 'General', 'description' => $fullDescription, 'statusComplaint' => 'Resolved', 'resolution' => $analysisJson, 'passenger_report' => json_encode($analysis['passengerReport'], JSON_UNESCAPED_UNICODE), 'driver_report' => json_encode($analysis['driverReport'], JSON_UNESCAPED_UNICODE), 'cs_solutions' => json_encode($analysis['customerServiceSolutions'], JSON_UNESCAPED_UNICODE), 'fault_determination' => $analysis['fault_determination'] ?? 'N/A', 'complaint_nature' => $analysis['complaint_nature'] ?? 'N/A', 'date_filed' => now(), 'date_resolved' => now(), ]); return response()->json([ 'status' => 'success', 'message' => 'Complaint processed successfully.', 'passenger_response' => $analysis['passengerReport'], 'driver_response' => $analysis['driverReport'] ]); } catch (\Exception $e) { Log::error('SupportController AI Error: ' . $e->getMessage()); // Fallback: Just save as pending if AI fails DB::connection('primary')->table('complaint')->insert([ 'ride_id' => $rideId, 'passenger_id' => $passengerId, 'driver_id' => $driverId, 'complaint_type' => 'General', 'description' => $complaintText . ($audioLink ? " [Audio: $audioLink]" : ""), 'statusComplaint' => 'Open', 'date_filed' => now(), ]); return response()->json([ 'status' => 'success', 'message' => 'Complaint received (Manual Review required).', 'passenger_response' => ['title' => 'تم استلام شكواك', 'body' => 'جاري مراجعة طلبك من قبل فريق الدعم.'], 'driver_response' => ['title' => 'بلاغ جديد', 'body' => 'تم تسجيل بلاغ بخصوص رحلتك الأخيرة.'] ]); } } private function getPassengerFullProfile($id) { $p = DB::connection('primary')->table('passengers')->where('id', $id)->first(); if (!$p) return null; return [ 'info' => [ 'id' => $p->id, 'full_name' => trim($this->encryption->decrypt($p->first_name) . ' ' . $this->encryption->decrypt($p->last_name)), 'created_at' => $p->created_at ], 'ratings' => DB::connection('primary')->table('ratingPassenger')->where('passenger_id', $id) ->selectRaw('AVG(rating) as avg_rating, COUNT(id) as total_ratings')->first(), 'comments' => DB::connection('primary')->table('ratingPassenger')->where('passenger_id', $id) ->whereNotNull('comment')->orderBy('created_at', 'desc')->limit(5)->pluck('comment') ]; } private function getDriverFullProfile($id) { $d = DB::connection('primary')->table('driver')->where('id', $id)->first(); if (!$d) return null; return [ 'info' => [ 'id' => $d->id, 'full_name' => trim($this->encryption->decrypt($d->first_name) . ' ' . $this->encryption->decrypt($d->last_name)), 'created_at' => $d->created_at ], 'ratings' => DB::connection('primary')->table('ratingDriver')->where('driver_id', $id) ->selectRaw('AVG(rating) as avg_rating, COUNT(id) as total_ratings')->first(), 'comments' => DB::connection('primary')->table('ratingDriver')->where('driver_id', $id) ->whereNotNull('comment')->orderBy('created_at', 'desc')->limit(5)->pluck('comment') ]; } private function buildGeminiPrompt($ride, $pProfile, $dProfile, $behavior, $text, $audio) { return " أنت خبير في حل النزاعات في خدمات نقل الركاب لتطبيق intaleqapp.com. قم بتحليل الشكوى التالية بين راكب وسائق بناءً على البيانات الشاملة التالية: **1. تفاصيل الرحلة:** " . json_encode($ride, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . " **2. ملف الراكب:** " . json_encode($pProfile, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . " **3. ملف السائق:** " . json_encode($dProfile, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . " **4. بيانات سلوك السائق (في هذه الرحلة):** " . json_encode($behavior, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . " **5. الشكوى نفسها:** - نص الشكوى من الراكب: '" . $text . "' - رابط تسجيل صوتي للشكوى (إن وجد): " . $audio . " **مهمتك هي:** 1. تحليل جميع البيانات المتاحة لتحديد الطرف المخطئ على الأرجح. 2. تحديد ما إذا كانت الشكوى كيدية أم حقيقية. 3. **تصنيف الشكوى** (مثال: سلوك السائق، مشكلة في الأجرة، مسار الرحلة، حالة السيارة، أخرى). 4. اقتراح حلين واضحين ومختلفين لفريق خدمة العملاء. 5. كتابة تقرير موجز ومناسب للراكب. 6. كتابة تقرير موجز ومناسب للسائق. **الخرج المطلوب:** أعد الرد بصيغة JSON فقط، بدون أي نصوص إضافية، وباللغة العربية (لهجة مصرية)، بالهيكل التالي: { \"customerServiceSolutions\": [\"الحل المقترح الأول\", \"الحل المقترح الثاني\"], \"passengerReport\": { \"title\": \"بخصوص شكوتك في رحلة Intaleq\", \"body\": \"رسالة واضحة للراكب بنتيجة الشكوى\" }, \"driverReport\": { \"title\": \"بخصوص بلاغ رحلتك الأخيرة في Intaleq\", \"body\": \"رسالة واضحة للسائق بنتيجة الشكوى\" }, \"fault_determination\": \"الطرف المخطئ (الراكب/السائق/كلاهما/غير واضح)\", \"complaint_nature\": \"طبيعة الشكوى (حقيقية/كيدية/نزاع بسيط)\", \"complaint_type\": \"تصنيف الشكوى الذي حددته\" } "; } }