Files
animexe/app/Http/Controllers/Frontend/AiController.php
T
2026-07-14 00:01:48 +03:00

271 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers\Frontend;
use App\Http\Controllers\Controller;
use App\Models\Anime;
use App\Models\Genre;
use App\Models\Analytics\AiQuery;
use App\Services\DeepSeekService;
use Illuminate\Http\Request;
class AiController extends Controller
{
/**
* AI Hub sayfası — kişisel öneri + doğal dil arama.
*/
public function index()
{
$genres = Genre::orderBy('name')->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]);
}
}