get(['id', 'name']); return view('frontend.ai.index', compact('genres')); } /** * POST /ai/chat — sohbet turu. * Body: { messages: [{role, content}, ...] } */ public function chat(Request $request) { $ai = new DeepSeekService(); if (!$ai->isConfigured()) { return response()->json(['error' => 'AI servisi şu an kullanılamıyor.'], 503); } $messages = $request->input('messages', []); if (empty($messages)) { return response()->json(['error' => 'Mesaj boş.'], 422); } // Validate structure $messages = array_filter($messages, fn($m) => isset($m['role'], $m['content']) && in_array($m['role'], ['user', 'assistant'])); $messages = array_values($messages); $context = $ai->getAnimeContext(); // Sayfa bağlamı — kullanıcı anime/player sayfasındaysa AI'ya söyle $pageCtx = trim($request->input('page_context', '')); if ($pageCtx) { $context .= "\n\n== KULLANICI ŞU AN BU SAYFADA ==\n{$pageCtx}"; } $rawReply = $ai->chat($messages, $context); if (!$rawReply) { return response()->json(['error' => 'AI yanıt vermedi, tekrar dene.'], 500); } // [SUGGEST:id1,id2,id3] satırını parse et $animeCards = []; $cleanReply = $rawReply; if (preg_match('/\[SUGGEST:([\d,\s]+)\]\s*$/m', $rawReply, $m)) { $cleanReply = trim(str_replace($m[0], '', $rawReply)); $ids = array_filter(array_map('intval', explode(',', $m[1]))); if ($ids) { $animes = Anime::whereIn('id', $ids) ->where('is_published', true) ->with('genres:id,name') ->get(['id', 'title', 'slug', 'cover_image', 'rating', 'type', 'episode_count']); $animeMap = $animes->keyBy('id'); foreach ($ids as $id) { if ($a = $animeMap[$id] ?? null) { $animeCards[] = [ 'id' => $a->id, 'title' => $a->title, 'slug' => $a->slug, 'cover' => $a->cover_url, 'rating' => $a->rating, 'type' => $a->type, 'episode_count' => $a->episode_count, 'genres' => $a->genres->pluck('name')->take(3)->join(', '), ]; } } } } // Log $lastUser = collect($messages)->last(fn($m) => $m['role'] === 'user'); AiQuery::create(['user_id'=>auth()->id(),'query_type'=>'chat','query_text'=>substr($lastUser['content']??'',0,500),'created_at'=>now()]); return response()->json(['reply' => $cleanReply, 'anime_cards' => $animeCards]); } /** * POST /ai/recommend — kişisel öneri. * Body: { mood?, genres[]?, type? } */ public function recommend(Request $request) { $ai = new DeepSeekService(); if (!$ai->isConfigured()) { return response()->json(['error' => 'AI servisi şu an kullanılamıyor.'], 503); } $mood = trim($request->input('mood', '')); $genres = $request->input('genres', []); $type = $request->input('type', ''); $prefs = []; if ($mood) $prefs[] = "Ruh hali / tema: {$mood}"; if ($genres) $prefs[] = 'Tercih edilen türler: ' . implode(', ', array_slice((array)$genres, 0, 6)); if ($type) $prefs[] = 'İçerik tipi: ' . ($type === 'movie' ? 'Film' : 'Dizi'); $prefStr = $prefs ? implode("\n", $prefs) : 'Genel tavsiye, en beğenilen animeler'; $animes = Anime::where('is_published', true) ->with('genres:id,name') ->get(['id', 'title', 'slug', 'type', 'status', 'rating', 'release_year', 'cover_image', 'episode_count']); $result = $ai->recommend($prefStr, $animes->toArray()); if (!$result) { return response()->json(['error' => 'Öneri üretilemedi.'], 500); } $animeMap = $animes->keyBy('id'); $recs = array_values(array_filter(array_map(function ($item) use ($animeMap) { $anime = $animeMap[$item['id'] ?? 0] ?? null; if (!$anime) return null; return [ 'id' => $anime->id, 'title' => $anime->title, 'slug' => $anime->slug, 'cover' => $anime->cover_url, 'rating' => $anime->rating, 'type' => $anime->type, 'episode_count' => $anime->episode_count, 'reason' => $item['reason'] ?? '', ]; }, $result))); AiQuery::create(['user_id'=>auth()->id(),'query_type'=>'recommend','query_text'=>substr($prefStr,0,500),'created_at'=>now()]); return response()->json(['recommendations' => $recs]); } /** * POST /ai/search — doğal dil ile anime ara. * Body: { query } */ public function search(Request $request) { $query = trim($request->input('query', '')); if (!$query) { return response()->json(['error' => 'Sorgu boş.'], 422); } $ai = new DeepSeekService(); if (!$ai->isConfigured()) { return response()->json(['error' => 'AI servisi şu an kullanılamıyor.'], 503); } $animes = Anime::where('is_published', true) ->with('genres:id,name') ->get(['id', 'title', 'slug', 'type', 'rating', 'release_year', 'cover_image']); $ids = $ai->naturalSearch($query, $animes->toArray()); if (!$ids) { return response()->json(['results' => []]); } $animeMap = $animes->keyBy('id'); $results = array_values(array_filter(array_map(function ($id) use ($animeMap) { $anime = $animeMap[(int)$id] ?? null; if (!$anime) return null; return [ 'id' => $anime->id, 'title' => $anime->title, 'slug' => $anime->slug, 'cover' => $anime->cover_url, 'rating' => $anime->rating, 'type' => $anime->type, ]; }, $ids))); AiQuery::create(['user_id'=>auth()->id(),'query_type'=>'search','query_text'=>substr($query,0,500),'created_at'=>now()]); return response()->json(['results' => $results]); } /** * POST /ai/episode-info — bölüm hakkında AI analizi. * Body: { anime_title, episode_number, episode_title?, description? } */ public function episodeInfo(Request $request) { $ai = new DeepSeekService(); if (!$ai->isConfigured()) { return response()->json(['error' => 'AI servisi şu an kullanılamıyor.'], 503); } $animeTitle = trim($request->input('anime_title', '')); $episodeNumber = (int) $request->input('episode_number', 1); $episodeTitle = trim($request->input('episode_title', '')); $description = trim($request->input('description', '')); if (!$animeTitle) { return response()->json(['error' => 'Anime adı gerekli.'], 422); } $info = $ai->episodeInfo($animeTitle, $episodeNumber, $episodeTitle, $description); if (!$info) { return response()->json(['error' => 'Analiz yapılamadı.'], 500); } AiQuery::create(['user_id'=>auth()->id(),'query_type'=>'episode_info','query_text'=>"{$animeTitle} E{$episodeNumber}",'created_at'=>now()]); return response()->json(['info' => $info]); } /** * POST /ai/similar — benzer animeler. * Body: { anime_id } */ public function similar(Request $request) { $ai = new DeepSeekService(); if (!$ai->isConfigured()) { return response()->json(['error' => 'AI servisi şu an kullanılamıyor.'], 503); } $anime = Anime::with('genres:id,name')->find($request->input('anime_id')); if (!$anime) { return response()->json(['error' => 'Anime bulunamadı.'], 404); } $genres = $anime->genres->pluck('name')->join(', '); $prefStr = "Şu anime ile benzer: {$anime->title}\n" . "Türler: {$genres}\n" . "Tip: " . ($anime->type === 'movie' ? 'Film' : 'Dizi') . "\n" . "Bu animeyi beğenen izleyicilere benzer içerik öner. Aynı animeyi önerme!"; $animes = Anime::where('is_published', true) ->where('id', '!=', $anime->id) ->with('genres:id,name') ->get(['id', 'title', 'slug', 'type', 'rating', 'release_year', 'cover_image']); $result = $ai->recommend($prefStr, $animes->toArray()); if (!$result) { return response()->json(['similar' => []]); } $animeMap = $animes->keyBy('id'); $similar = array_values(array_filter(array_map(function ($item) use ($animeMap) { $a = $animeMap[$item['id'] ?? 0] ?? null; if (!$a) return null; return [ 'id' => $a->id, 'title' => $a->title, 'slug' => $a->slug, 'cover' => $a->cover_url, 'rating' => $a->rating, 'type' => $a->type, 'reason' => $item['reason'] ?? '', ]; }, $result))); AiQuery::create(['user_id'=>auth()->id(),'query_type'=>'similar','query_text'=>$anime->title,'created_at'=>now()]); return response()->json(['similar' => $similar]); } }