implemented step 14 by Gemini

- added LLMpool, worker script
This commit is contained in:
2026-06-14 14:39:51 +02:00
parent ec698f3f34
commit d07bc89eea
5 changed files with 281 additions and 2 deletions

View File

@ -9,7 +9,7 @@ use Exception;
class DAIClient
{
private string $apiUrl;
private int $timeout = 60;
private int $timeout = 120;
public function __construct(?string $apiUrl = null)
{

164
src/Services/LLMpool.php Normal file
View File

@ -0,0 +1,164 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Actions\ProjectActions;
use App\Prompts\ContentPrompt;
use Exception;
use Throwable;
class LLMpool
{
private FileStorage $storage;
private DAIClient $daiClient;
private ContentPrompt $promptGenerator;
public function __construct()
{
$this->storage = new FileStorage();
$this->daiClient = new DAIClient();
$this->promptGenerator = new ContentPrompt();
}
/**
* Processes all queued tasks.
*/
public function processQueue(): void
{
$tasksDir = realpath(__DIR__ . '/../../data/llm');
$files = glob($tasksDir . '/t_*.json');
foreach ($files as $file) {
$this->processTask(basename($file));
}
}
/**
* Processes a single task by its filename.
*/
public function processTask(string $taskFilename): bool
{
$taskPath = "llm/$taskFilename";
$task = $this->storage->get($taskPath);
if (!$task || $task['status'] !== 'queued') {
return false;
}
// 1. Lock task (atomic update to 'generating')
$task['status'] = 'generating';
$task['attempt_count']++;
$task['started_at'] = date('c');
$this->storage->put($taskPath, $task);
$projectId = $task['project_id'];
// Update project status to 'generating'
$projectData = $this->storage->get("projects/{$projectId}.json");
if ($projectData) {
$projectData['status'] = 'generating';
$this->storage->put("projects/{$projectId}.json", $projectData);
}
$projectActions = new ProjectActions();
try {
// 2. Generate Prompt
$prompt = $this->promptGenerator->generate($task['wizard_data']);
// 3. Call AI
$aiResponse = $this->daiClient->sendRequest($prompt);
if ($aiResponse === null) {
throw new Exception("AI API returned no answer.");
}
// Update project status to 'rendering'
if ($projectData) {
$projectData['status'] = 'rendering';
$this->storage->put("projects/{$projectId}.json", $projectData);
}
// 4. Validate and Parse
$content = $this->validateAndParseResponse($aiResponse);
// 5. Update Project
$projectData = $this->storage->get("projects/{$projectId}.json");
if (!$projectData) {
throw new Exception("Project $projectId not found during processing.");
}
$projectData['content']['generated'] = $content;
$projectData['status'] = 'ready'; // Transition to ready
$projectData['current_step'] = 7; // Automatically ready for preview
$projectData['updated_at'] = date('c');
$this->storage->put("projects/{$projectId}.json", $projectData);
// 6. Finish Task (Delete or Archive)
$this->storage->delete($taskPath);
error_log("LLMpool: Task $taskFilename processed successfully for project $projectId.");
return true;
} catch (Throwable $e) {
error_log("LLMpool Error processing task $taskFilename: " . $e->getMessage());
$task['status'] = 'queued'; // Back to queue for retry
$task['last_error'] = $e->getMessage();
if ($task['attempt_count'] >= $task['max_attempts']) {
$task['status'] = 'failed';
// Update project status to failed as well
$projectData = $this->storage->get("projects/{$projectId}.json");
if ($projectData) {
$projectData['status'] = 'failed';
$projectData['last_error'] = $e->getMessage();
$this->storage->put("projects/{$projectId}.json", $projectData);
}
}
$this->storage->put($taskPath, $task);
return false;
}
}
/**
* Validates AI output: must be JSON, no HTML, has required sections.
*/
private function validateAndParseResponse(string $rawResponse): array
{
// Strip potential markdown code blocks if AI included them
$jsonStr = trim($rawResponse);
if (strpos($jsonStr, '```json') === 0) {
$jsonStr = substr($jsonStr, 7);
}
if (str_ends_with($jsonStr, '```')) {
$jsonStr = substr($jsonStr, 0, -3);
}
$jsonStr = trim($jsonStr);
$data = json_decode($jsonStr, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new Exception("Invalid JSON format from AI: " . json_last_error_msg());
}
// Check for HTML tags
if (preg_match('/<[a-z\/][^>]*>/i', $jsonStr)) {
throw new Exception("AI response contains prohibited HTML tags.");
}
// Check required sections
$required = ['seo', 'hero', 'about', 'services'];
foreach ($required as $section) {
if (!isset($data[$section])) {
throw new Exception("AI response is missing required section: $section");
}
}
return $data;
}
}