Files
html/api/ambre-tool-react.php

93 lines
4.4 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');
/* SESSION_CONTEXT_INJECTED v1 */
require_once __DIR__ . '/ambre-session-context.php';
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";
$prompt = wvia_context_for_prompt() . $prompt;
$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);
// SESSION LINK DOC
if (isset($outpath) && isset($filename)) {
$__wvia_title = isset($doc) && isset($doc['title']) ? $doc['title'] : (isset($spec) && isset($spec['title']) ? $spec['title'] : (isset($deck) && isset($deck['title']) ? $deck['title'] : (isset($topic) ? $topic : 'Document')));
wvia_link_doc('/files/' . $filename, 'react', $__wvia_title);
if (isset($topic)) wvia_append_turn('user', $topic);
wvia_append_turn('assistant', '[REACT generated: ' . ($__wvia_title ?: 'doc') . ']');
}
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"),
]);