init
This commit is contained in:
190
index.php
Normal file
190
index.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||||
if ($method !== 'GET') {
|
||||
http_response_code(405);
|
||||
header('Allow: GET');
|
||||
echo 'Method Not Allowed';
|
||||
exit;
|
||||
}
|
||||
|
||||
$secPurpose = strtolower((string) ($_SERVER['HTTP_SEC_PURPOSE'] ?? ''));
|
||||
$purpose = strtolower((string) ($_SERVER['HTTP_PURPOSE'] ?? ''));
|
||||
$xMoz = strtolower((string) ($_SERVER['HTTP_X_MOZ'] ?? ''));
|
||||
|
||||
$isSpeculative = str_contains($secPurpose, 'prefetch')
|
||||
|| str_contains($secPurpose, 'prerender')
|
||||
|| str_contains($purpose, 'prefetch')
|
||||
|| str_contains($purpose, 'prerender')
|
||||
|| str_contains($xMoz, 'prefetch');
|
||||
|
||||
if (! $isSpeculative) {
|
||||
$secFetchMode = strtolower((string) ($_SERVER['HTTP_SEC_FETCH_MODE'] ?? ''));
|
||||
$secFetchDest = strtolower((string) ($_SERVER['HTTP_SEC_FETCH_DEST'] ?? ''));
|
||||
$secFetchUser = (string) ($_SERVER['HTTP_SEC_FETCH_USER'] ?? '');
|
||||
|
||||
// Treat obvious non-navigation fetches as non-user document requests.
|
||||
if (($secFetchMode !== '' && $secFetchMode !== 'navigate')
|
||||
|| ($secFetchDest !== '' && $secFetchDest !== 'document')
|
||||
|| ($secFetchUser !== '' && $secFetchUser !== '?1')) {
|
||||
$isSpeculative = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isSpeculative) {
|
||||
http_response_code(204);
|
||||
header('X-Skipped-Generation: speculative-request');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
$startedAt = microtime(true);
|
||||
|
||||
$apiKey = getenv('LLAMA_API_KEY') ?: '';
|
||||
$model = getenv('LLAMA_MODEL') ?: 'local-model';
|
||||
$maxTokens = (int) (getenv('LLAMA_MAX_TOKENS') ?: 4096);
|
||||
$reasoningEffort = getenv('LLAMA_REASONING_EFFORT') ?: 'none';
|
||||
|
||||
$topicHint = isset($_GET['topic']) ? trim((string) $_GET['topic']) : '';
|
||||
$topicHint = mb_substr($topicHint, 0, 120);
|
||||
|
||||
$seed = sprintf('seed-%08x%08x', random_int(0, 0xffffffff), random_int(0, 0xffffffff));
|
||||
|
||||
$topicCategories = [
|
||||
'local restaurant landing page',
|
||||
'fitness coaching brand site',
|
||||
'travel destination mini-guide',
|
||||
'event festival one-page promo',
|
||||
'indie game launch page',
|
||||
'architect portfolio page',
|
||||
'music artist release page',
|
||||
'bookstore seasonal campaign',
|
||||
'pet adoption center homepage',
|
||||
'artisan coffee roaster website',
|
||||
'online course product page',
|
||||
'nonprofit donation campaign page',
|
||||
];
|
||||
$selectedCategory = $topicCategories[array_rand($topicCategories)];
|
||||
|
||||
$bannedTerms = [
|
||||
'chrono',
|
||||
'temporal',
|
||||
'chrono-',
|
||||
'timewarp',
|
||||
'timescape',
|
||||
'timeshift',
|
||||
'quantum',
|
||||
'aether',
|
||||
'epoch',
|
||||
'singularity',
|
||||
];
|
||||
$bannedTermsList = implode(', ', $bannedTerms);
|
||||
|
||||
$factory = OpenAI::factory()->withBaseUri('http://localhost:8080/v1');
|
||||
if ($apiKey !== '') {
|
||||
$factory = $factory->withApiKey($apiKey);
|
||||
}
|
||||
|
||||
$client = $factory->make();
|
||||
|
||||
$systemPrompt = <<<PROMPT
|
||||
You are a senior creative web designer and front-end engineer.
|
||||
|
||||
Task:
|
||||
- On every run, first choose a unique page concept with a specific topic, visual theme, and style direction.
|
||||
- Then generate one complete, production-ready HTML document from scratch.
|
||||
|
||||
Hard output rules:
|
||||
- Return only raw HTML. No markdown. No code fences. No explanations.
|
||||
- Output must begin with <!doctype html> and include full <html>, <head>, and <body>.
|
||||
- Use Tailwind CSS via CDN in the same HTML document.
|
||||
- Do not rely on local/external project files. Everything required must be inside this single HTML file.
|
||||
- Inline any custom CSS and JavaScript in <style> and <script> tags.
|
||||
- Make the page responsive and fully usable on mobile and desktop.
|
||||
- Include accessible semantic structure and visible focus states.
|
||||
|
||||
Creative direction:
|
||||
- Avoid generic templates.
|
||||
- Use bold, intentional typography and layout.
|
||||
- Include subtle but meaningful animation/interaction.
|
||||
- Provide complete content, not placeholder lorem ipsum.
|
||||
|
||||
Diversity constraints:
|
||||
- Do not make a sci-fi, futuristic, or time-themed website unless the user explicitly asks for it.
|
||||
- Avoid these words in brand names, title, headings, and body copy: {$bannedTermsList}
|
||||
- If no topic is provided by user, choose a grounded real-world business/topic from the requested category.
|
||||
PROMPT;
|
||||
|
||||
$userPrompt = 'Create a complete one-page website now. Use random token: ' . $seed . '.';
|
||||
if ($topicHint === '') {
|
||||
$userPrompt .= ' Required category for this run: ' . $selectedCategory . '.';
|
||||
}
|
||||
if ($topicHint !== '') {
|
||||
$userPrompt .= ' Topic request: ' . $topicHint . '.';
|
||||
}
|
||||
|
||||
$request = [
|
||||
'model' => $model,
|
||||
'temperature' => 0.95,
|
||||
'top_p' => 0.95,
|
||||
'max_tokens' => $maxTokens,
|
||||
'messages' => [
|
||||
['role' => 'system', 'content' => $systemPrompt],
|
||||
['role' => 'user', 'content' => $userPrompt],
|
||||
],
|
||||
'reasoning_effort' => $reasoningEffort,
|
||||
];
|
||||
|
||||
try {
|
||||
$response = $client->chat()->create($request);
|
||||
$html = trim((string) ($response->choices[0]->message->content ?? ''));
|
||||
|
||||
if ($topicHint === '' && preg_match('/\b(?:chrono\w*|temporal\w*|quantum\w*|aether\w*|epoch\w*|singularity\w*|timeshift\w*|timescape\w*|timewarp\w*)\b/i', $html) === 1) {
|
||||
$request['messages'][] = [
|
||||
'role' => 'system',
|
||||
'content' => 'Retry with a completely different, non-sci-fi, non-time-themed concept. Do not use banned words.',
|
||||
];
|
||||
$response = $client->chat()->create($request);
|
||||
$html = trim((string) ($response->choices[0]->message->content ?? ''));
|
||||
}
|
||||
|
||||
$elapsedSeconds = number_format(microtime(true) - $startedAt, 2);
|
||||
|
||||
// If the model wraps output in a markdown fence, unwrap it safely.
|
||||
if (preg_match('/^```(?:html)?\s*(.*?)\s*```$/is', $html, $matches) === 1) {
|
||||
$html = $matches[1];
|
||||
}
|
||||
|
||||
if ($html === '' || stripos($html, '<html') === false) {
|
||||
throw new RuntimeException('Model did not return a full HTML document.');
|
||||
}
|
||||
|
||||
$timingBlock = '<div style="position:fixed;right:12px;bottom:12px;z-index:2147483647;background:rgba(17,24,39,.92);color:#fff;padding:8px 10px;border-radius:10px;font:12px/1.3 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;box-shadow:0 8px 20px rgba(0,0,0,.28)">Generated in ' . $elapsedSeconds . 's</div>';
|
||||
if (stripos($html, '</body>') !== false) {
|
||||
$html = preg_replace('/<\/body>/i', $timingBlock . '</body>', $html, 1) ?? ($html . $timingBlock);
|
||||
} else {
|
||||
$html .= $timingBlock;
|
||||
}
|
||||
|
||||
echo $html;
|
||||
|
||||
file_put_contents(__DIR__ . '/pages/' . sha1($seed) . '.html', $html);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
http_response_code(500);
|
||||
$message = htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
echo '<!doctype html><html><head><meta charset="UTF-8"><title>Generation Error</title>';
|
||||
echo '<script src="https://cdn.tailwindcss.com"></script></head><body class="min-h-screen bg-zinc-950 text-zinc-100 p-8">';
|
||||
echo '<h1 class="text-2xl font-bold mb-4">Page generation failed</h1>';
|
||||
echo '<p class="text-zinc-300 mb-3">' . $message . '</p>';
|
||||
echo '<p class="text-zinc-400 text-sm">Tip: set LLAMA_MODEL to a loaded model and optionally pass ?topic=your-idea in the URL.</p>';
|
||||
echo '</body></html>';
|
||||
}
|
||||
Reference in New Issue
Block a user