83 lines
3.7 KiB
PHP
83 lines
3.7 KiB
PHP
<?php
|
|
/**
|
|
* ambre-tool-react.php - React component generator with live artifact preview
|
|
* Input: JSON {topic}
|
|
* Output: JSON {ok, preview_url, code, title}
|
|
*/
|
|
header('Content-Type: application/json');
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
echo json_encode(['ok'=>false, 'error'=>'POST only']); exit;
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$topic = trim($input['topic'] ?? '');
|
|
if (strlen($topic) < 3) { echo json_encode(['ok'=>false, 'error'=>'topic too short']); exit; }
|
|
$topic = substr($topic, 0, 500);
|
|
|
|
$prompt = "Tu es un expert frontend React. Genere UN composant React autonome pour: \"$topic\"\n\n"
|
|
. "Contraintes techniques:\n"
|
|
. "- React 18 via CDN (pas d'imports externes npm)\n"
|
|
. "- TailwindCSS via CDN (class utilities)\n"
|
|
. "- Pas de Router, pas de state manager\n"
|
|
. "- TOUT le code dans UN seul fichier HTML renderable directement\n"
|
|
. "- Design ultra-premium: gradients, animations CSS, hover effects, responsive\n"
|
|
. "- Palette moderne (indigo/slate/violet/emerald)\n"
|
|
. "- Composant interactif avec au moins 1 etat useState\n"
|
|
. "- Pas de alert() ni prompt(), UX seulement\n"
|
|
. "- Icones via Unicode emojis ou SVG inline\n"
|
|
. "- Si donnees: tableau/array inline dans le composant (pas fetch externe)\n\n"
|
|
. "IMPORTANT:\n"
|
|
. "- Retourne UNIQUEMENT le code HTML complet commencant par <!DOCTYPE html>\n"
|
|
. "- Aucun texte d'explication avant ou apres\n"
|
|
. "- Pas de backticks markdown\n"
|
|
. "- Le code doit s'ouvrir directement dans un browser et fonctionner";
|
|
|
|
$ch = curl_init('http://127.0.0.1:4000/v1/chat/completions');
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_POSTFIELDS => json_encode([
|
|
'model' => 'auto',
|
|
'messages' => [['role'=>'user', 'content'=>$prompt]],
|
|
'max_tokens' => 6000, 'temperature' => 0.7
|
|
]),
|
|
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
|
CURLOPT_TIMEOUT => 120,
|
|
]);
|
|
$resp = curl_exec($ch);
|
|
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
|
|
if ($http !== 200) { echo json_encode(['ok'=>false, 'error'=>"LLM HTTP $http"]); exit; }
|
|
|
|
$data = json_decode($resp, true);
|
|
$html = $data['choices'][0]['message']['content'] ?? '';
|
|
|
|
// Strip markdown code fences if any
|
|
$html = preg_replace('/^```(?:html)?\s*\n/', '', $html);
|
|
$html = preg_replace('/\n```\s*$/', '', $html);
|
|
$html = trim($html);
|
|
|
|
// Validation: must contain DOCTYPE and a react/tailwind reference
|
|
if (stripos($html, '<!DOCTYPE') === false) {
|
|
// Wrap in minimal HTML shell if LLM just returned JSX
|
|
$html = "<!DOCTYPE html>\n<html lang='fr'>\n<head>\n<meta charset='UTF-8'>\n<script src='https://cdn.tailwindcss.com'></script>\n<script crossorigin src='https://unpkg.com/react@18/umd/react.production.min.js'></script>\n<script crossorigin src='https://unpkg.com/react-dom@18/umd/react-dom.production.min.js'></script>\n<script src='https://unpkg.com/@babel/standalone/babel.min.js'></script>\n</head>\n<body class='bg-slate-50 min-h-screen'>\n<div id='root'></div>\n<script type='text/babel'>\n" . $html . "\nReactDOM.createRoot(document.getElementById('root')).render(<App />);\n</script>\n</body>\n</html>";
|
|
}
|
|
|
|
// Save as standalone HTML
|
|
$filename = 'react-' . substr(md5($topic . microtime(true)), 0, 10) . '.html';
|
|
$outpath = '/var/www/html/files/' . $filename;
|
|
if (!is_dir('/var/www/html/files')) { mkdir('/var/www/html/files', 0755, true); }
|
|
|
|
file_put_contents($outpath, $html);
|
|
|
|
echo json_encode([
|
|
'ok' => true,
|
|
'preview_url' => '/files/' . $filename,
|
|
'url' => '/files/' . $filename,
|
|
'title' => 'React - ' . substr($topic, 0, 50),
|
|
'code_preview' => substr($html, 0, 2000),
|
|
'size' => filesize($outpath),
|
|
'size_kb' => round(filesize($outpath)/1024, 1),
|
|
'lines' => substr_count($html, "\n"),
|
|
]);
|