implemented step 15 by Gemini
- Rendering HTML
This commit is contained in:
@ -14,12 +14,14 @@ class LLMpool
|
|||||||
private FileStorage $storage;
|
private FileStorage $storage;
|
||||||
private DAIClient $daiClient;
|
private DAIClient $daiClient;
|
||||||
private ContentPrompt $promptGenerator;
|
private ContentPrompt $promptGenerator;
|
||||||
|
private Renderer $renderer;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->storage = new FileStorage();
|
$this->storage = new FileStorage();
|
||||||
$this->daiClient = new DAIClient();
|
$this->daiClient = new DAIClient();
|
||||||
$this->promptGenerator = new ContentPrompt();
|
$this->promptGenerator = new ContentPrompt();
|
||||||
|
$this->renderer = new Renderer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,12 +93,21 @@ class LLMpool
|
|||||||
}
|
}
|
||||||
|
|
||||||
$projectData['content']['generated'] = $content;
|
$projectData['content']['generated'] = $content;
|
||||||
|
$this->storage->put("projects/{$projectId}.json", $projectData);
|
||||||
|
|
||||||
|
// 5. Render static site
|
||||||
|
if (!$this->renderer->render($projectId)) {
|
||||||
|
throw new Exception("Rendering failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Update Project Status
|
||||||
|
$projectData = $this->storage->get("projects/{$projectId}.json");
|
||||||
$projectData['status'] = 'ready'; // Transition to ready
|
$projectData['status'] = 'ready'; // Transition to ready
|
||||||
$projectData['current_step'] = 7; // Automatically ready for preview
|
$projectData['current_step'] = 7; // Automatically ready for preview
|
||||||
$projectData['updated_at'] = date('c');
|
$projectData['updated_at'] = date('c');
|
||||||
$this->storage->put("projects/{$projectId}.json", $projectData);
|
$this->storage->put("projects/{$projectId}.json", $projectData);
|
||||||
|
|
||||||
// 6. Finish Task (Delete or Archive)
|
// 7. Finish Task (Delete or Archive)
|
||||||
$this->storage->delete($taskPath);
|
$this->storage->delete($taskPath);
|
||||||
|
|
||||||
error_log("LLMpool: Task $taskFilename processed successfully for project $projectId.");
|
error_log("LLMpool: Task $taskFilename processed successfully for project $projectId.");
|
||||||
|
|||||||
69
src/Services/Renderer.php
Normal file
69
src/Services/Renderer.php
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class Renderer
|
||||||
|
{
|
||||||
|
private FileStorage $storage;
|
||||||
|
private string $exportsPath;
|
||||||
|
private string $templatesPath;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->storage = new FileStorage();
|
||||||
|
$this->exportsPath = realpath(__DIR__ . '/../../exports');
|
||||||
|
$this->templatesPath = realpath(__DIR__ . '/../Templates');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a complete static website for a project.
|
||||||
|
*/
|
||||||
|
public function render(string $projectId): bool
|
||||||
|
{
|
||||||
|
$projectData = $this->storage->get("projects/{$projectId}.json");
|
||||||
|
if (!$projectData || empty($projectData['content']['generated'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$projectExportDir = $this->exportsPath . DIRECTORY_SEPARATOR . $projectId;
|
||||||
|
$assetsDir = $projectExportDir . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'css';
|
||||||
|
|
||||||
|
// 1. Create directory structure
|
||||||
|
if (!is_dir($assetsDir)) {
|
||||||
|
mkdir($assetsDir, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Copy static assets
|
||||||
|
$siteCssSource = $this->templatesPath . DIRECTORY_SEPARATOR . 'css' . DIRECTORY_SEPARATOR . 'site.css';
|
||||||
|
copy($siteCssSource, $assetsDir . DIRECTORY_SEPARATOR . 'site.css');
|
||||||
|
|
||||||
|
// 3. Render HTML
|
||||||
|
$html = $this->renderTemplate('base', [
|
||||||
|
'project' => $projectData,
|
||||||
|
'content' => '<!-- Final content sections will go here -->'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 4. Save to exports
|
||||||
|
return file_put_contents($projectExportDir . DIRECTORY_SEPARATOR . 'index.html', $html) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal helper to render a PHP template with variables.
|
||||||
|
*/
|
||||||
|
private function renderTemplate(string $templateName, array $vars = []): string
|
||||||
|
{
|
||||||
|
$templateFile = $this->templatesPath . DIRECTORY_SEPARATOR . $templateName . '.php';
|
||||||
|
if (!file_exists($templateFile)) {
|
||||||
|
throw new Exception("Template not found: $templateName");
|
||||||
|
}
|
||||||
|
|
||||||
|
extract($vars);
|
||||||
|
ob_start();
|
||||||
|
include $templateFile;
|
||||||
|
return ob_get_clean();
|
||||||
|
}
|
||||||
|
}
|
||||||
87
src/Templates/base.php
Normal file
87
src/Templates/base.php
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @var array $project
|
||||||
|
* @var string $content
|
||||||
|
*/
|
||||||
|
$seo = $project['content']['generated']['seo'] ?? [];
|
||||||
|
$identity = $project['wizard_data']['identity'] ?? [];
|
||||||
|
$contact = $project['wizard_data']['contact'] ?? [];
|
||||||
|
$visuals = $project['wizard_data']['visuals'] ?? [];
|
||||||
|
$assets = $project['wizard_data']['assets'] ?? [];
|
||||||
|
|
||||||
|
// Helper function for safe output
|
||||||
|
if (!function_exists('e')) {
|
||||||
|
function e($value) {
|
||||||
|
return htmlspecialchars((string)($value ?? ''), ENT_QUOTES, 'UTF-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="sk">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<title><?= e($seo['title'] ?? $identity['business_name'] ?? 'Môj Web') ?></title>
|
||||||
|
<meta name="description" content="<?= e($seo['description'] ?? '') ?>">
|
||||||
|
|
||||||
|
<!-- Open Graph -->
|
||||||
|
<meta property="og:title" content="<?= e($seo['title'] ?? '') ?>">
|
||||||
|
<meta property="og:description" content="<?= e($seo['description'] ?? '') ?>">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
|
||||||
|
<!-- Assets -->
|
||||||
|
<link rel="stylesheet" href="assets/css/site.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
<?php if (!empty($visuals['palette'])): ?>
|
||||||
|
/* Palette overrides will go here in later steps */
|
||||||
|
<?php endif; ?>
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<div class="container">
|
||||||
|
<div class="logo">
|
||||||
|
<?php if (!empty($assets['logo'])): ?>
|
||||||
|
<img src="<?= e($assets['logo']) ?>" alt="<?= e($identity['business_name']) ?>">
|
||||||
|
<?php else: ?>
|
||||||
|
<strong><?= e($identity['business_name']) ?></strong>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<ul class="nav-links">
|
||||||
|
<!-- Navigation will be dynamic in later steps -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="content">
|
||||||
|
<?= $content ?>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<div class="container">
|
||||||
|
<div class="footer-grid">
|
||||||
|
<div class="footer-info">
|
||||||
|
<h4><?= e($identity['business_name']) ?></h4>
|
||||||
|
<p><?= e($identity['tagline']) ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-contact">
|
||||||
|
<p><?= e($contact['email']) ?></p>
|
||||||
|
<p><?= e($contact['phone']) ?></p>
|
||||||
|
<p><?= e($contact['address']) ?>, <?= e($contact['city']) ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-bottom">
|
||||||
|
<p>© <?= date('Y') ?> <?= e($identity['business_name']) ?>. Všetky práva vyhradené.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
83
src/Templates/css/site.css
Normal file
83
src/Templates/css/site.css
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/* Reset & Base Styles */
|
||||||
|
:root {
|
||||||
|
--primary-color: #2563eb;
|
||||||
|
--text-color: #1f2937;
|
||||||
|
--bg-color: #ffffff;
|
||||||
|
--section-padding: 5rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3 {
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: var(--section-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Common Components */
|
||||||
|
.btn {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header & Nav */
|
||||||
|
header {
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav .container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo img {
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
footer {
|
||||||
|
background-color: #111827;
|
||||||
|
color: white;
|
||||||
|
padding: 4rem 0;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
@ -1,72 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
|
|
||||||
// Load configuration
|
|
||||||
\App\Utils\Config::load(__DIR__ . '/../.env');
|
|
||||||
|
|
||||||
use App\Services\LLMpool;
|
|
||||||
use App\Services\FileStorage;
|
|
||||||
use App\Actions\ProjectActions;
|
|
||||||
|
|
||||||
echo "Testing LLMpool Worker (Step 14)..." . PHP_EOL . PHP_EOL;
|
|
||||||
|
|
||||||
$storage = new FileStorage();
|
|
||||||
$projectActions = new ProjectActions();
|
|
||||||
|
|
||||||
// 1. Create a dummy project
|
|
||||||
$userId = 'u_test_worker';
|
|
||||||
$userData = [
|
|
||||||
'user_id' => $userId,
|
|
||||||
'created_at' => date('c'),
|
|
||||||
'projects' => []
|
|
||||||
];
|
|
||||||
$storage->put("users/{$userId}.json", $userData);
|
|
||||||
|
|
||||||
$project = $projectActions->createProject($userId);
|
|
||||||
$projectId = $project['project_id'];
|
|
||||||
|
|
||||||
// Mock some wizard data
|
|
||||||
$project['wizard_data'] = [
|
|
||||||
'business_category' => ['group' => 'gastro', 'subcategory' => 'cafe'],
|
|
||||||
'identity' => ['business_name' => 'Kaviareň u Robota', 'tagline' => 'Káva s dušou'],
|
|
||||||
'services' => ['items' => [['name' => 'Espresso', 'description' => 'Silná káva', 'price_from' => '2']]],
|
|
||||||
'smart_answers' => ['terrace' => true]
|
|
||||||
];
|
|
||||||
$storage->put("projects/{$projectId}.json", $project);
|
|
||||||
|
|
||||||
// 2. Create a dummy task
|
|
||||||
$taskId = 't_test_worker';
|
|
||||||
$taskData = [
|
|
||||||
'task_id' => $taskId,
|
|
||||||
'project_id' => $projectId,
|
|
||||||
'status' => 'queued',
|
|
||||||
'attempt_count' => 0,
|
|
||||||
'max_attempts' => 3,
|
|
||||||
'created_at' => date('c'),
|
|
||||||
'wizard_data' => $project['wizard_data']
|
|
||||||
];
|
|
||||||
$storage->put("llm/{$taskId}.json", $taskData);
|
|
||||||
|
|
||||||
echo "[INFO] Task created: $taskId for project $projectId" . PHP_EOL;
|
|
||||||
|
|
||||||
// 3. Run Worker
|
|
||||||
$worker = new LLMpool();
|
|
||||||
echo "[PROCESS] Running worker for task $taskId.json..." . PHP_EOL;
|
|
||||||
|
|
||||||
// Since we call real AI, it might take time
|
|
||||||
$success = $worker->processTask($taskId . '.json');
|
|
||||||
|
|
||||||
if ($success) {
|
|
||||||
echo "[SUCCESS] Worker finished successfully." . PHP_EOL;
|
|
||||||
$updatedProject = $storage->get("projects/{$projectId}.json");
|
|
||||||
echo "[INFO] Project status: " . $updatedProject['status'] . PHP_EOL;
|
|
||||||
echo "[INFO] Generated content keys: " . implode(', ', array_keys($updatedProject['content']['generated'])) . PHP_EOL;
|
|
||||||
} else {
|
|
||||||
echo "[FAIL] Worker failed to process task." . PHP_EOL;
|
|
||||||
$task = $storage->get("llm/{$taskId}.json");
|
|
||||||
if ($task) {
|
|
||||||
echo "[DEBUG] Task status: " . $task['status'] . PHP_EOL;
|
|
||||||
echo "[DEBUG] Last error: " . ($task['last_error'] ?? 'N/A') . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user