Fix: AI Blog Assistant retry logic and Subdomain routing for Akademi
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class AiBlogService
|
||||
{
|
||||
protected $geminiKey;
|
||||
protected $unsplashKey;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->geminiKey = config('services.gemini.key', env('GEMINI_API_KEY'));
|
||||
$this->unsplashKey = config('services.unsplash.access_key', env('UNSPLASH_ACCESS_KEY'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate blog content using Gemini API
|
||||
*/
|
||||
public function generateContent(string $topic, ?string $context = null): array
|
||||
{
|
||||
$prompt = $this->buildPrompt($topic, $context);
|
||||
|
||||
// Debug logging
|
||||
$keyStatus = empty($this->geminiKey) ? 'MISSING' : 'PRESENT (' . substr($this->geminiKey, 0, 5) . '...)';
|
||||
Log::info("Gemini Service: Key status: $keyStatus");
|
||||
|
||||
try {
|
||||
$response = Http::withoutVerifying()
|
||||
->retry(3, 10000, function ($exception, $request) {
|
||||
if ($exception instanceof \Illuminate\Http\Client\RequestException && $exception->response->status() === 429) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
->withHeaders(['Content-Type' => 'application/json'])
|
||||
->post("https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={$this->geminiKey}", [
|
||||
'contents' => [
|
||||
[
|
||||
'parts' => [
|
||||
['text' => $prompt]
|
||||
]
|
||||
]
|
||||
],
|
||||
'generationConfig' => [
|
||||
'temperature' => 0.7,
|
||||
'topK' => 40,
|
||||
'topP' => 0.95,
|
||||
'maxOutputTokens' => 8192,
|
||||
'responseMimeType' => 'application/json',
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->failed()) {
|
||||
Log::error('Gemini API Error Status: ' . $response->status());
|
||||
Log::error('Gemini API Error Body: ' . $response->body());
|
||||
throw new \Exception('Gemini API request failed: ' . $response->body());
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
if (!isset($data['candidates'][0]['content']['parts'][0]['text'])) {
|
||||
Log::error('Gemini API Invalid Response: ' . json_encode($data));
|
||||
throw new \Exception('Invalid response from Gemini API.');
|
||||
}
|
||||
|
||||
$jsonContent = $data['candidates'][0]['content']['parts'][0]['text'];
|
||||
|
||||
// Clean markdown json blocks if present
|
||||
$jsonContent = str_replace(['```json', '```'], '', $jsonContent);
|
||||
|
||||
return json_decode($jsonContent, true);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('AI Blog Generation Exception: ' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the prompt for Gemini
|
||||
*/
|
||||
private function buildPrompt(string $topic, ?string $context = null): string
|
||||
{
|
||||
$contextStr = $context ? "Additional Context: {$context}" : "";
|
||||
|
||||
return <<<EOT
|
||||
You are an expert content writer for "Trunçgil Teknoloji", a leading technology company specializing in web software, mobile apps, IoT, and digital transformation.
|
||||
Write a comprehensive, SEO-friendly blog post about the topic: "{$topic}".
|
||||
{$contextStr}
|
||||
|
||||
**IMPORTANT**: All content in the JSON response (title, content, excerpt, meta_data) MUST BE IN TURKISH language. Only the `image_prompt` should be in English.
|
||||
|
||||
Requirements:
|
||||
1. **Format**: Return ONLY a valid JSON object. No markdown formatting outside the JSON structure.
|
||||
2. **Language**: Turkish (Türkçe).
|
||||
3. **Tone**: Professional, authoritative, yet accessible. Enthusiastic about technology.
|
||||
4. **Brand Integration**: Naturally mention "Trunçgil Teknoloji" at least 2-3 times. Position Trunçgil Teknoloji as a solution provider or expert in this field. Use phrases like "Trunçgil Teknoloji olarak biz...", "Deneyimli ekibimizle...", etc.
|
||||
5. **Structure**:
|
||||
* `title`: Catchy, SEO-optimized title (Max 60 chars) - IN TURKISH.
|
||||
* `meta_title`: SEO Meta Title - IN TURKISH.
|
||||
* `meta_description`: SEO Meta Description (Max 160 chars) - IN TURKISH.
|
||||
* `content`: Full HTML blog post content.
|
||||
* Use <h2> and <h3> tags for headings.
|
||||
* Use <p> for paragraphs.
|
||||
* Include a bulleted list (<ul>/<li>) if appropriate.
|
||||
* Do NOT use <h1> or <html>/<body> tags.
|
||||
* Ensure the content is informative and valuable (min 600 words).
|
||||
* ALL CONTENT MUST BE TURKISH.
|
||||
* `excerpt`: A short summary (1-2 sentences) - IN TURKISH.
|
||||
* `tags`: Array of 5-8 relevant tags (Turkish).
|
||||
* `image_prompt`: A creative English prompt to generate a cover image for this article using an AI image generator.
|
||||
|
||||
JSON Schema:
|
||||
{
|
||||
"title": "string",
|
||||
"meta_title": "string",
|
||||
"meta_description": "string",
|
||||
"content": "html_string",
|
||||
"excerpt": "string",
|
||||
"tags": ["string"],
|
||||
"image_prompt": "string"
|
||||
}
|
||||
EOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and save a cover image
|
||||
*/
|
||||
public function fetchAndSaveImage(string $keyword, string $imagePrompt): ?string
|
||||
{
|
||||
$imageUrl = null;
|
||||
|
||||
// 1. Try Unsplash if key exists
|
||||
if ($this->unsplashKey) {
|
||||
try {
|
||||
$response = Http::withoutVerifying()->get('https://api.unsplash.com/search/photos', [
|
||||
'query' => $keyword,
|
||||
'orientation' => 'landscape',
|
||||
'per_page' => 1,
|
||||
'client_id' => $this->unsplashKey
|
||||
]);
|
||||
|
||||
if ($response->successful() && !empty($response['results'])) {
|
||||
$imageUrl = $response['results'][0]['urls']['regular'];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::warning('Unsplash API failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback to Pollinations AI (using the generated prompt)
|
||||
if (!$imageUrl) {
|
||||
// Encode the prompt
|
||||
$encodedPrompt = urlencode($imagePrompt . " high quality, technological, photorealistic, 4k");
|
||||
$imageUrl = "https://image.pollinations.ai/prompt/{$encodedPrompt}";
|
||||
}
|
||||
|
||||
if (!$imageUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Download and Save
|
||||
try {
|
||||
$imageContent = Http::withoutVerifying()->get($imageUrl)->body();
|
||||
$filename = 'blog/ai-generated-' . Str::slug($keyword) . '-' . time() . '.jpg';
|
||||
|
||||
Storage::disk('public')->put($filename, $imageContent);
|
||||
|
||||
return $filename;
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Image download failed: ' . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user