Files
animexe/app/Services/DeepSeekService.php
2026-07-14 00:01:48 +03:00

637 lines
26 KiB
PHP
Raw Permalink 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\Services;
use App\Models\Setting;
use Illuminate\Support\Facades\Http;
class DeepSeekService
{
private string $apiKey;
public function __construct()
{
$this->apiKey = Setting::get('deepseek_api_key', '');
}
public function isConfigured(): bool
{
return !empty($this->apiKey);
}
/**
* Anime için Türkçe özet/açıklama üret.
*/
public function generateAnimeDescription(string $title, string $titleJp = '', string $genres = ''): ?string
{
$prompt = "Sen bir anime veritabanı editörüsün. Aşağıdaki anime için Türkçe, akıcı ve bilgilendirici bir özet/açıklama yaz (3-5 cümle, 120-220 kelime arası). Spoiler verme, merak uyandır.\n\n"
. "Anime adı: {$title}" . ($titleJp ? " ({$titleJp})" : '') . "\n"
. ($genres ? "Türler: {$genres}\n" : '')
. "\nSadece açıklama metnini yaz, başka hiçbir şey ekleme.";
return $this->call($prompt);
}
/**
* Bölüm için Türkçe açıklama üret.
*/
public function generateEpisodeDescription(string $animeTitle, int $episodeNumber, string $episodeTitle = ''): ?string
{
$prompt = "Sen bir anime veritabanı editörüsün. Aşağıdaki anime bölümü için kısa, akıcı ve spoiler içermeyen Türkçe bir açıklama yaz (2-4 cümle, 80-160 kelime arası).\n\n"
. "Anime: {$animeTitle}\n"
. "Bölüm: {$episodeNumber}. Bölüm" . ($episodeTitle ? " — {$episodeTitle}" : '') . "\n\n"
. "Sadece açıklama metnini yaz, başka hiçbir şey ekleme.";
return $this->call($prompt);
}
/**
* Anime için tüm meta verileri JSON olarak döndür.
* Dönen alanlar: description, release_year, studio, type, status, rating, title_en, title_jp, genres[]
*/
public function generateAnimeMeta(string $title, string $titleJp = ''): ?array
{
$prompt = <<<PROMPT
Sen bir anime veritabanı asistanısın. Aşağıdaki anime hakkında tüm bilgileri JSON formatında döndür.
Anime adı: {$title}
{$titleJp}
Döndüreceğin JSON şöyle olmalı (bilinmiyorsa null):
{
"description": "Türkçe, akıcı, spoiler içermeyen 3-5 cümlelik özet (120-220 kelime)",
"release_year": 2021,
"studio": "Stüdyo adı",
"type": "series|movie|ova|ona|special",
"status": "completed|ongoing|upcoming",
"rating": 8.5,
"title_en": "İngilizce başlık",
"title_jp": "日本語タイトル",
"genres": ["Aksiyon", "Drama"]
}
Genres listesi için sadece şu kategorileri kullan (uygunları seç): Aksiyon, Macera, Komedi, Drama, Fantezi, Bilim Kurgu, Korku, Romantizm, Spor, Supernatural, Gerilim, Slice of Life, Mecha, Müzik, Tarihsel, Okul, Isekai, Ecchi, Shounen, Shoujo, Seinen, Josei, Dövüş Sanatları.
SADECE geçerli JSON döndür, hiçbir açıklama veya markdown ekleme.
PROMPT;
$raw = $this->callJson($prompt);
return $raw;
}
public function checkSpoiler(string $text): ?array
{
$result = $this->moderateComment($text);
return ['is_spoiler' => $result['is_spoiler'], 'score' => $result['spoiler_score']];
}
/**
* Yorum moderasyonu: spoiler + küfür/hakaret kontrolü.
* Döner: ['is_spoiler'=>bool, 'spoiler_score'=>int, 'is_rude'=>bool, 'rude_score'=>int]
*/
public function moderateComment(string $text): array
{
$default = ['is_spoiler' => false, 'spoiler_score' => 0, 'is_rude' => false, 'rude_score' => 0];
$prompt = "Aşağıdaki metin bir anime platformuna yazılmış kullanıcı yorumudur. İki şeyi kontrol et:\n"
. "1) Anime bölümüne ait SPOILER içeriyor mu? (olay örgüsü açıklama, karakter ölümü, sürpriz sahne ifşası vb.)\n"
. "2) KABA/HAKARET içeriyor mu? (küfür, nefret söylemi, ağır hakaret, cinsel içerik)\n\n"
. "Sadece JSON döndür:\n"
. "{\"is_spoiler\": false, \"spoiler_score\": 10, \"is_rude\": false, \"rude_score\": 5}\n"
. "score değerleri 0-100 arası olasılık.\n\n"
. "Metin: " . mb_substr($text, 0, 400);
$raw = $this->callJson($prompt, 80);
if (!$raw) return $default;
return [
'is_spoiler' => (bool)($raw['is_spoiler'] ?? false),
'spoiler_score' => (int)($raw['spoiler_score'] ?? $raw['score'] ?? 0),
'is_rude' => (bool)($raw['is_rude'] ?? false),
'rude_score' => (int)($raw['rude_score'] ?? 0),
];
}
public string $lastError = '';
private function callJson(string $prompt, int $maxTokens = 600): ?array
{
if (!$this->isConfigured()) {
$this->lastError = 'API anahtarı ayarlanmamış';
return null;
}
try {
$response = Http::withToken($this->apiKey)
->timeout(90)
->post('https://api.deepseek.com/chat/completions', [
'model' => 'deepseek-chat',
'messages' => [['role' => 'user', 'content' => $prompt]],
'max_tokens' => $maxTokens,
'temperature' => 0.3,
'response_format' => ['type' => 'json_object'],
]);
if (!$response->successful()) {
$this->lastError = 'HTTP ' . $response->status() . ': ' . $response->json('error.message', $response->body());
\Log::error('DeepSeek API hatası', ['status' => $response->status(), 'body' => $response->body()]);
return null;
}
$content = trim($response->json('choices.0.message.content', ''));
if (!$content) {
$this->lastError = 'API boş yanıt döndürdü';
return null;
}
$content = preg_replace('/^```json\s*/i', '', $content);
$content = preg_replace('/\s*```$/i', '', $content);
$data = json_decode($content, true);
if (!is_array($data)) {
$this->lastError = 'JSON parse hatası: ' . substr($content, 0, 200);
return null;
}
return $data;
} catch (\Exception $e) {
$this->lastError = $e->getMessage();
\Log::error('DeepSeek exception', ['message' => $e->getMessage()]);
return null;
}
}
// ── Frontend AI methods ──────────────────────────────────────────────────
/**
* Anime kataloğunu AI context string olarak döndür (1 saat önbellek).
*/
public function getAnimeContext(): string
{
return \Illuminate\Support\Facades\Cache::remember('ai_anime_context', 3600, function () {
$animes = \App\Models\Anime::where('is_published', true)
->with('genres:id,name')
->get(['id', 'title', 'type', 'status', 'rating', 'release_year']);
return $animes->map(function ($a) {
$genres = $a->genres->pluck('name')->join(', ');
$type = $a->type === 'movie' ? 'Film' : 'Dizi';
return "ID:{$a->id}|{$a->title}|{$type}|{$a->release_year}|{$a->rating}"
. ($genres ? "|{$genres}" : '');
})->join("\n");
});
}
/**
* Çok turlu sohbet (sistem mesajı + anime kataloğu ile).
* $messages = [['role'=>'user','content'=>'...'], ...]
*/
public function chat(array $messages, string $animeContext = ''): ?string
{
$sys = "Sen Animexe'nin AI anime asistanısın. Animexe, Türkçe altyazılı/dublajlı ücretsiz anime izleme platformudur (animexe.com).\n\n";
$sys .= "== PLATFORM BİLGİLERİ (kullanıcı sorarsa bunları kullan) ==\n"
. "- Kayıt: Ücretsiz, e-posta ile. Kayıt olmadan bazı içerikler kısıtlı.\n"
. "- Premium üyelik: Aylık ücretli. Avantajları: reklamsız izleme, 1080p HD, erken bölüm erişimi.\n"
. "- Altyazı/Dublaj: Türkçe altyazı ve Türkçe dublaj seçenekleri mevcuttur. Player'da seçilebilir.\n"
. "- Takip/Favori: Anime sayfasında kalp veya 'Takip' butonuna tıkla. Yeni bölüm bildirimi gelir.\n"
. "- İzleme geçmişi: Otomatik kaydedilir. Profil > Geçmiş kısmından görebilirsin.\n"
. "- Arama: Üst menüdeki arama kutusuna anime adını yaz.\n"
. "- Anime isteği: 'Anime İste' sayfasından eksik animeleri talep edebilirsin.\n"
. "- Mobil: Tarayıcıdan tam destek. Android uygulaması da mevcut.\n"
. "- Dil seçimi: Player'da ses ve altyazı dili değiştirilebilir.\n"
. "- Yorumlar: Her bölümün altında yorum yapılabilir, spoiler işaretlenebilir.\n\n";
if ($animeContext) {
$sys .= "== PLATFORM KATALOĞU (ID|Başlık|Tip|Yıl|Puan|Türler) ==\n{$animeContext}\n\n";
}
$sys .= "== KURALLAR ==\n"
. "- Türkçe, samimi, kısa ve net cevap ver. Emoji kullanabilirsin.\n"
. "- Sadece platformdaki animeleri öner (katalogdan ID'si olan).\n"
. "- Spoiler verme. Merak uyandır.\n"
. "- Anime önerirken cevabının en sonuna şu formatı ekle (başka yere koyma): [SUGGEST:id1,id2,id3]\n"
. " Örnek: 'Attack on Titan harika! [SUGGEST:42]' — max 5 anime ID.\n"
. "- Eğer anime önermiyorsan [SUGGEST:...] satırını HİÇ EKLEME.\n"
. "- Site hakkında soruları yukarıdaki platform bilgilerini kullanarak cevapla.\n";
$apiMessages = array_merge(
[['role' => 'system', 'content' => $sys]],
array_slice($messages, -12)
);
return $this->callMessages($apiMessages, 700);
}
/**
* Kullanıcı tercihlerine göre 6 anime öner.
* Döner: [['id'=>1,'reason'=>'...'], ...]
*/
public function recommend(string $preferences, array $animes): ?array
{
$list = implode("\n", array_map(function ($a) {
$genres = isset($a['genres']) ? implode(', ', array_column($a['genres'], 'name')) : '';
return "ID:{$a['id']}|{$a['title']}|" . ($a['type'] === 'movie' ? 'Film' : 'Dizi')
. "|{$a['release_year']}|{$a['rating']}" . ($genres ? "|{$genres}" : '');
}, array_slice($animes, 0, 250)));
$prompt = "Anime öneri sistemi: Kullanıcı tercihlerine göre listeden EN İYİ 6 animeyi seç.\n\n"
. "Tercihler:\n{$preferences}\n\n"
. "Animeler:\n{$list}\n\n"
. "Yanıt: {\"recommendations\":[{\"id\":1,\"reason\":\"Kısa Türkçe neden (max 12 kelime)\"}]}\n"
. "SADECE JSON.";
$result = $this->callJson($prompt, 500);
if (!is_array($result)) return null;
if (isset($result['recommendations']) && is_array($result['recommendations'])) {
return $result['recommendations'];
}
if (isset($result[0]['id'])) return $result;
return null;
}
/**
* Doğal dil sorgusu ile anime ara.
* Döner: [id1, id2, ...]
*/
public function naturalSearch(string $query, array $animes): ?array
{
$list = implode("\n", array_map(function ($a) {
$genres = isset($a['genres']) ? implode(', ', array_column($a['genres'], 'name')) : '';
return "ID:{$a['id']}|{$a['title']}" . ($genres ? "|{$genres}" : '');
}, array_slice($animes, 0, 300)));
$prompt = "Kullanıcı sorgusu: \"{$query}\"\n\nAnimeler:\n{$list}\n\n"
. "En uygun max 12 animeyi bul: {\"ids\":[1,5,12]}\nSADECE JSON.";
$result = $this->callJson($prompt, 150);
if (!is_array($result)) return null;
if (isset($result['ids']) && is_array($result['ids'])) return array_map('intval', $result['ids']);
return null;
}
/**
* Bölüm hakkında spoilersız AI analizi.
*/
public function episodeInfo(string $animeTitle, int $episodeNumber, string $episodeTitle = '', string $description = ''): ?string
{
$prompt = "Sen bir anime uzmanısın. Aşağıdaki bölüm hakkında Türkçe, kısa ve ilgi çekici bir analiz yaz (3-4 cümle). "
. "Spoiler içerme. Bölümün atmosferini, önemini ve izleyiciyi neden heyecanlandırabileceğini anlat.\n\n"
. "Anime: {$animeTitle}\n"
. "Bölüm: {$episodeNumber}." . ($episodeTitle ? " — {$episodeTitle}" : '') . "\n"
. ($description ? "Açıklama: {$description}\n" : '')
. "\nSadece analiz metnini yaz.";
return $this->call($prompt, 350);
}
// ── Blog Generation ──────────────────────────────────────────────────────
/**
* Bir anime için SEO blog yazısı üret.
* Döner: ['title','slug','excerpt','content','focus_keyword','meta_description','faq','linked_slugs']
*/
public function generateBlogPost(\App\Models\Anime $anime, array $relatedAnimes = []): ?array
{
$genreList = $anime->genres->pluck('name')->join(', ');
$type = $anime->type === 'movie' ? 'anime film' : 'anime dizi';
$year = $anime->release_year ?? '';
$status = match($anime->status ?? '') {
'ongoing' => 'devam ediyor',
'completed' => 'tamamlandı',
'upcoming' => 'yakında çıkacak',
default => '',
};
$relatedList = '';
if (!empty($relatedAnimes)) {
$relatedList = "\nİlgili animeler (içerik içinde bunlara link ver, format: [LINK:slug]Anime Adı[/LINK]):\n";
foreach (array_slice($relatedAnimes, 0, 5) as $r) {
$relatedList .= "- {$r['slug']}: {$r['title']}\n";
}
}
$prompt = <<<PROMPT
Sen Animexe platformu için SEO uyumlu Türkçe blog yazıları yazan bir editörsün.
Animexe, Türkçe altyazılı/dublajlı anime izleme platformudur.
Aşağıdaki anime için çok kapsamlı, SEO uyumlu bir blog yazısı yaz:
- Anime adı: {$anime->title}
- Tür: {$type}
- Yıl: {$year}
- Türler: {$genreList}
- Durum: {$status}
- Açıklama: {$anime->description}
{$relatedList}
Blog yazısı gereksinimleri:
1. 400-600 kelime, sade ve akıcı Türkçe
2. HTML formatında yaz: <h2>, <p>, <ul>, <li>, <strong> etiketleri kullan
3. Yapı: Giriş (1-2 paragraf) → Ana içerik (2 H2 bölümü) → FAQ (2 soru-cevap, <div class="faq-item"><h3>...</h3><p>...</p></div>)
4. {$anime->title} anahtar kelimesini doğal şekilde 3-5 kez kullan
5. İlgili animelere [LINK:slug]Anime Adı[/LINK] formatında link ekle (varsa, max 2)
6. Spoiler verme, merak uyandır
7. Sonunda kısa bir CTA ekle
Yanıt JSON formatında:
{{
"title": "SEO başlığı (50-60 karakter, anime adını içermeli)",
"excerpt": "Meta description (150-160 karakter)",
"focus_keyword": "Ana anahtar kelime",
"meta_description": "SEO meta açıklaması (150-160 karakter)",
"content": "Tam HTML blog içeriği",
"faq": [
{{"q": "Soru", "a": "Cevap"}},
{{"q": "Soru", "a": "Cevap"}},
{{"q": "Soru", "a": "Cevap"}}
],
"linked_slugs": ["slug1", "slug2"]
}}
SADECE geçerli JSON döndür.
PROMPT;
return $this->callJson($prompt, 4096);
}
// ── SEO AI Methods ───────────────────────────────────────────────────────
/**
* SEO danışman sohbeti — sitenin kontekstini bilen uzman
*/
public function seoChat(array $messages, array $siteContext = []): ?string
{
$stats = $siteContext;
$sys = <<<SYS
Sen Animexe için çalışan kıdemli bir SEO danışmanısın. Animexe, Türkçe altyazılı ve dublajlı anime izleme platformudur (animexe.com).
Site istatistikleri:
- Yayında anime sayısı: {$stats['anime_count']}
- SEO başlığı olan anime: {$stats['seo_covered']} / {$stats['anime_count']}
- SEO skoru: {$stats['seo_score']}/100
- Sitemap URL sayısı: {$stats['sitemap_urls']}
Uzmanlık alanların:
- Teknik SEO (Core Web Vitals, canonicals, structured data)
- İçerik SEO (keyword araştırması, meta etiketler, başlık optimizasyonu)
- Yerel SEO (Türkiye pazarı için anime arama trendleri)
- E-A-T (Expertise, Authoritativeness, Trustworthiness)
- Schema.org (TVSeries, VideoObject, FAQPage, BreadcrumbList, Organization)
- Backlink stratejisi ve internal linking
Kurallar:
- Türkçe cevap ver, kısa ve aksiyona yönelik ol
- Spesifik, ölçülebilir öneriler sun
- Animexe'nin bulunduğu nişe (anime, Türkiye, ücretsiz izleme) odaklan
- Rakip analizi yapabilirsin: anizm.tv, turkanime.co, animeshark.net
- Markdown kullanabilirsin (bold, liste, başlık)
SYS;
$apiMessages = array_merge(
[['role' => 'system', 'content' => $sys]],
array_slice($messages, -16)
);
return $this->callMessages($apiMessages, 800);
}
/**
* Anime için AI destekli SEO başlığı + meta açıklama üret
*/
public function generateAnimeSeoMeta(\App\Models\Anime $anime): ?array
{
$genres = $anime->genres?->pluck('name')->join(', ') ?? '';
$type = $anime->type === 'movie' ? 'anime film' : ($anime->type === 'ova' ? 'OVA' : 'anime dizi');
$year = $anime->release_year ?? '';
$desc = $anime->description ? mb_substr(strip_tags($anime->description), 0, 300) : '';
$prompt = <<<PROMPT
Sen bir SEO uzmanısın. Aşağıdaki anime için Türkiye pazarına yönelik, tıklanma oranını (CTR) maksimize eden SEO başlığı ve meta açıklaması yaz.
Anime bilgileri:
- Ad: {$anime->title}
- Tür: {$type}
- Yıl: {$year}
- Kategoriler: {$genres}
- Açıklama: {$desc}
Kurallar:
- SEO Başlığı: 50-65 karakter, anahtar kelimeyi başa al, duygusal tetikleyici ekle, "Türkçe" kelimesi kullan
- Meta Açıklama: 145-158 karakter, aksiyon çağrısı içersin, "ücretsiz", "HD" gibi değer önerileri ekle
- Anahtar Kelimeler: 3-5 adet, virgülle ayrılmış, Türkçe arama trendlerine uygun
- Kullanıcı niyeti: anime izlemek isteyen Türkçe konuşan kullanıcılar
JSON formatında yanıt ver:
{
"seo_title": "...",
"seo_meta_desc": "...",
"seo_keywords": "...",
"primary_keyword": "...",
"search_intent": "transactional|informational|navigational"
}
SADECE JSON.
PROMPT;
return $this->callJson($prompt, 400);
}
/**
* Belirli bir konu için anahtar kelime önerileri
*/
public function suggestKeywords(string $topic, string $niche = 'anime'): ?array
{
$prompt = <<<PROMPT
Sen bir keyword araştırma uzmanısın. "{$topic}" konusu için Türkiye pazarında arama yapan kullanıcıların kullandığı anahtar kelimeleri bul.
Platform nişi: {$niche} izleme platformu (Türkçe altyazılı, ücretsiz)
Şu kategorilerde kelimeler öner:
1. Head keywords (yüksek hacim, yüksek rekabet) - 3 adet
2. Body keywords (orta hacim, orta rekabet) - 4 adet
3. Long-tail keywords (düşük hacim, düşük rekabet, yüksek niyet) - 5 adet
4. Question keywords (soru formatı, featured snippet fırsatı) - 3 adet
JSON formatı:
{
"head": [{"keyword":"...", "volume":"Yüksek/Orta/Düşük", "difficulty":75, "intent":"..."}],
"body": [...],
"long_tail": [...],
"questions": [...],
"content_ideas": ["Bu kelimeler için 3 içerik fikri"]
}
SADECE JSON.
PROMPT;
return $this->callJson($prompt, 1200);
}
/**
* URL'nin SEO sorunlarını AI ile analiz et
*/
public function analyzePageSeo(string $url, string $title, string $description, string $content): ?array
{
$contentSnippet = mb_substr(strip_tags($content), 0, 500);
$prompt = <<<PROMPT
Sen bir teknik SEO denetçisisin. Aşağıdaki web sayfasını SEO açısından analiz et.
URL: {$url}
Sayfa Başlığı: {$title}
Meta Açıklama: {$description}
İçerik (ilk 500 karakter): {$contentSnippet}
Şunları değerlendir ve JSON döndür:
{
"overall_score": 75,
"title_analysis": {"score":80, "issues":["Sorun1"], "suggestions":["Öneri1"]},
"description_analysis": {"score":70, "issues":[], "suggestions":[]},
"content_analysis": {"score":65, "keyword_density":"iyi/zayıf/aşırı", "issues":[], "suggestions":[]},
"technical_issues": ["Liste halinde teknik sorunlar"],
"quick_wins": ["Hemen yapılabilecek 3-5 iyileştirme"],
"priority_actions": ["Öncelikli aksiyonlar (sıralı)"]
}
SADECE JSON.
PROMPT;
return $this->callJson($prompt, 800);
}
/**
* Anime için FAQ Schema (JSON-LD) üret
*/
public function generateFaqSchema(\App\Models\Anime $anime): ?array
{
$type = $anime->type === 'movie' ? 'film' : 'dizi';
$desc = $anime->description ? mb_substr(strip_tags($anime->description), 0, 200) : $anime->title;
$genres = $anime->genres?->pluck('name')->join(', ') ?? '';
$prompt = <<<PROMPT
Sen bir schema.org uzmanısın. Aşağıdaki anime sayfası için Google'ın featured snippet'larını hedefleyen 5 adet FAQPage sorusu ve cevabı yaz.
Anime: {$anime->title} ({$type})
Kategoriler: {$genres}
Açıklama: {$desc}
Kurallar:
- Kullanıcıların gerçekten sorduğu sorular ("nerede izlenir", "kaç bölüm", "türkçe var mı" gibi)
- Cevaplar 1-3 cümle, net ve bilgilendirici
- Türkçe
- Animexe platformuna yönlendiren cevaplar (animexe.com'da izleyebilirsiniz)
JSON formatı:
{
"faqs": [
{"question": "...", "answer": "..."},
...
]
}
SADECE JSON.
PROMPT;
return $this->callJson($prompt, 600);
}
/**
* Site için genel içerik stratejisi öner
*/
public function generateContentStrategy(array $siteStats, array $weakKeywords = []): ?string
{
$kwList = !empty($weakKeywords) ? implode(', ', array_slice($weakKeywords, 0, 10)) : 'genel anime';
$score = $siteStats['seo_score'] ?? 0;
$total = $siteStats['anime_count'] ?? 0;
$covered = $siteStats['seo_covered'] ?? 0;
$prompt = <<<PROMPT
Sen Animexe için çalışan bir SEO içerik stratejisti olarak, 90 günlük bir SEO eylem planı hazırla.
Mevcut durum:
- SEO skoru: {$score}/100
- Toplam anime: {$total}
- SEO başlığı olan: {$covered}/{$total}
- Zayıf kelimeler / fırsatlar: {$kwList}
- Platform: Türkçe anime izleme (animexe.com)
- Rakipler: anizm.tv, turkanime.co
Plan şunları içersin:
1. **İlk 30 gün — Teknik temel** (hızlı kazançlar)
2. **31-60 gün — İçerik genişletme** (keyword hedefleme)
3. **61-90 gün — Otorite inşası** (backlink, sosyal sinyal)
4. **KPI hedefleri** (organik trafik artışı, sıralama hedefleri)
5. **Öncelikli içerik türleri** (anime review, top listeler, karşılaştırma yazıları)
Markdown formatında yaz. Somut, ölçülebilir ve Türkiye pazarına özgü ol.
PROMPT;
return $this->call($prompt, 1200);
}
/**
* Robots.txt için AI önerisi
*/
public function generateRobotsTxt(string $domain, array $paths = []): ?string
{
$pathList = !empty($paths) ? implode(', ', $paths) : '/admin, /api, /storage, /profile';
$prompt = "Sen bir teknik SEO uzmanısın. {$domain} domaini için optimal robots.txt içeriği oluştur. "
. "Platform bir anime izleme sitesi. Korunacak dizinler: {$pathList}. "
. "Sadece robots.txt içeriğini döndür, açıklama ekleme.";
return $this->call($prompt, 300);
}
/**
* Keşfet sayfası için anime başına kısa, çekici hook metni üret (2 cümle, spoiler yok).
*/
public function generateDiscoveryHook(\App\Models\Anime $anime): ?string
{
$genres = $anime->relationLoaded('genres')
? $anime->genres->pluck('name')->join(', ')
: '';
$info = $anime->title;
if ($anime->release_year) $info .= " ({$anime->release_year})";
if ($genres) $info .= ", {$genres}";
if ($anime->description) $info .= ". " . \Illuminate\Support\Str::limit(strip_tags($anime->description), 180);
$prompt = "Sen bir anime tanıtım yazarısın. Aşağıdaki anime için 1-2 cümlelik, merak uyandırıcı, spoiler içermeyen Türkçe bir tanıtım yaz. Emoji kullanma. Sadece tanıtım metnini yaz.\n\n{$info}";
return $this->call($prompt, 80);
}
// ── Private helpers ──────────────────────────────────────────────────────
private function callMessages(array $messages, int $maxTokens = 400): ?string
{
if (!$this->isConfigured()) return null;
try {
$r = Http::withToken($this->apiKey)->timeout(35)
->post('https://api.deepseek.com/chat/completions', [
'model' => 'deepseek-chat', 'messages' => $messages,
'max_tokens' => $maxTokens, 'temperature' => 0.75,
]);
if (!$r->successful()) return null;
return trim($r->json('choices.0.message.content', '')) ?: null;
} catch (\Exception $e) { return null; }
}
private function call(string $prompt, int $maxTokens = 400): ?string
{
if (!$this->isConfigured()) {
return null;
}
try {
$response = Http::withToken($this->apiKey)
->timeout(30)
->post('https://api.deepseek.com/chat/completions', [
'model' => 'deepseek-chat',
'messages' => [['role' => 'user', 'content' => $prompt]],
'max_tokens' => $maxTokens,
'temperature' => 0.7,
]);
if (!$response->successful()) {
return null;
}
return trim($response->json('choices.0.message.content', '')) ?: null;
} catch (\Exception $e) {
return null;
}
}
}