Files
larablog/app/Domain/Ai/Jobs/OptimizeArticleContentJob.php
T
gouki 3cec4c5e18
CI / PHPUnit (PHP 8.3) (push) Failing after 4s
CI / PHPUnit (PHP 8.2) (push) Failing after 1m9s
CI / Deploy (manual gate) (push) Skipped
wip: article AI polish, category SEO fields, cover generator, membership plan seeder
2026-09-07 18:48:37 +00:00

66 lines
1.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Domain\Ai\Jobs;
use App\Contracts\LlmProvider;
use App\Models\Article;
use App\Settings\AiSettings;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\Log;
class OptimizeArticleContentJob implements ShouldQueue
{
use Queueable;
public function __construct(
public int $articleId,
) {
$this->onQueue('ai-content');
}
public function handle(LlmProvider $llm, AiSettings $settings): void
{
if (! $settings->content_optimization_enabled) {
return;
}
$article = Article::query()->find($this->articleId);
if ($article === null) {
return;
}
try {
$result = $llm->complete((string) $article->content, [
'title' => $article->title,
'article_id' => $article->id,
'content_format' => $article->content_format,
]);
$updates = [
'ai_summary' => filled($result['summary'] ?? null) ? (string) $result['summary'] : $article->ai_summary,
'ai_suggestions' => array_values($result['suggestions'] ?? []),
];
if (filled($result['polished_content'] ?? null)) {
$updates['ai_polished_content'] = (string) $result['polished_content'];
}
// Fill empty SEO description from the model when the author left it blank.
if (blank($article->description) && filled($result['description'] ?? null)) {
$updates['description'] = (string) $result['description'];
}
$article->forceFill($updates)->save();
} catch (\Throwable $exception) {
Log::warning('Article content optimization failed.', [
'article_id' => $this->articleId,
'message' => $exception->getMessage(),
]);
}
}
}