Initial commit: Animexe Laravel platform

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 00:01:48 +03:00
co-authored by Claude Opus 4.8
commit a63515cfc6
366 changed files with 74773 additions and 0 deletions
@@ -0,0 +1,270 @@
<?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]);
}
}