Files
backPRO/src/Services/OpenAIService.php
Jacek Pyziak b653cea252 Add installer functionality for WordPress with FTP and database configuration
- Create SQL migration for prompt templates used in article and image generation.
- Add migration to change publish interval from days to hours in the sites table.
- Implement InstallerController to handle installation requests and validation.
- Develop FtpService for FTP connections and file uploads.
- Create InstallerService to manage the WordPress installation process, including downloading, extracting, and configuring WordPress.
- Add index view for the installer with form inputs for FTP, database, and WordPress admin settings.
- Implement progress tracking for the installation process with AJAX polling.
2026-02-16 21:55:24 +01:00

105 lines
4.0 KiB
PHP

<?php
namespace App\Services;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use App\Core\Config;
use App\Helpers\Logger;
class OpenAIService
{
public const DEFAULT_ARTICLE_PROMPT_TEMPLATE = 'Jesteś doświadczonym copywriterem SEO. Pisz artykuły w języku polskim, optymalizowane pod SEO. Artykuł powinien mieć {min_words}-{max_words} słów, zawierać nagłówki H2 i H3, być angażujący i merytoryczny. Formatuj treść w HTML (bez tagów <html>, <body>, <head>). Zwróć odpowiedź WYŁĄCZNIE w formacie JSON: {"title": "tytuł artykułu", "content": "treść HTML artykułu"}';
private Client $client;
public function __construct()
{
$this->client = new Client([
'base_uri' => 'https://api.openai.com/v1/',
'timeout' => 120,
]);
}
public function generateArticle(string $topicName, string $topicDescription, array $existingTitles): ?array
{
$apiKey = Config::getDbSetting('openai_api_key', Config::get('OPENAI_API_KEY'));
$model = Config::getDbSetting('openai_model', Config::get('OPENAI_MODEL', 'gpt-4o'));
$minWords = Config::getDbSetting('article_min_words', '800');
$maxWords = Config::getDbSetting('article_max_words', '1200');
$systemPromptTemplate = Config::getDbSetting('article_generation_prompt', self::DEFAULT_ARTICLE_PROMPT_TEMPLATE);
if (!is_string($systemPromptTemplate) || trim($systemPromptTemplate) === '') {
$systemPromptTemplate = self::DEFAULT_ARTICLE_PROMPT_TEMPLATE;
}
if (empty($apiKey)) {
Logger::error('OpenAI API key not configured', 'openai');
return null;
}
$existingList = !empty($existingTitles)
? implode("\n- ", $existingTitles)
: '(brak - to pierwszy artykuł z tego tematu)';
$systemPrompt = strtr($systemPromptTemplate, [
'{min_words}' => (string) $minWords,
'{max_words}' => (string) $maxWords,
]);
$userPrompt = "Napisz artykuł na temat: {$topicName}\n";
if (!empty($topicDescription)) {
$userPrompt .= "Wytyczne: {$topicDescription}\n";
}
$userPrompt .= "\nWAŻNE - NIE pisz o następujących tematach, bo artykuły o nich już istnieją na stronie:\n- {$existingList}";
$fullPrompt = $systemPrompt . "\n\n" . $userPrompt;
try {
$response = $this->client->post('chat/completions', [
'headers' => [
'Authorization' => "Bearer {$apiKey}",
'Content-Type' => 'application/json',
],
'json' => [
'model' => $model,
'messages' => [
['role' => 'system', 'content' => $systemPrompt],
['role' => 'user', 'content' => $userPrompt],
],
'temperature' => 0.8,
'max_tokens' => 4000,
'response_format' => ['type' => 'json_object'],
],
]);
$data = json_decode($response->getBody()->getContents(), true);
$content = $data['choices'][0]['message']['content'] ?? null;
if (!$content) {
Logger::error('Empty response from OpenAI', 'openai');
return null;
}
$article = json_decode($content, true);
if (!isset($article['title']) || !isset($article['content'])) {
Logger::error('Invalid JSON structure from OpenAI: ' . $content, 'openai');
return null;
}
Logger::info("Generated article: {$article['title']}", 'openai');
return [
'title' => $article['title'],
'content' => $article['content'],
'model' => $model,
'prompt' => $fullPrompt,
];
} catch (GuzzleException $e) {
Logger::error('OpenAI API error: ' . $e->getMessage(), 'openai');
return null;
}
}
}