diff --git a/api/ambre-tool-3d.php b/api/ambre-tool-3d.php new file mode 100644 index 000000000..2e889196c --- /dev/null +++ b/api/ambre-tool-3d.php @@ -0,0 +1,69 @@ +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, 400); + +$prompt = "Expert Three.js r128. Genere une scene 3D interactive pour: \"$topic\"\n\n" + . "Contraintes:\n" + . "- Three.js via CDN https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js\n" + . "- OrbitControls via https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js\n" + . "- Fichier HTML UNIQUE complet avec \n" + . "- Scene anime (animation loop)\n" + . "- OrbitControls actifs (souris)\n" + . "- Lumiere + ombre realistes\n" + . "- 5-10 objets 3D differents avec geometries/materiaux varies\n" + . "- Background degrade ou skybox\n" + . "- Fog pour profondeur\n" + . "- Resize responsive\n" + . "- Pas de NO_CAPSULE_GEOMETRY (utiliser CylinderGeometry/SphereGeometry)\n" + . "- Code propre et commente\n\n" + . "RETOURNE UNIQUEMENT LE CODE HTML sans backticks ni texte explicatif"; + +$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'] ?? ''; +$html = preg_replace('/^```(?:html)?\s*\n/', '', $html); +$html = preg_replace('/\n```\s*$/', '', trim($html)); + +if (stripos($html, 'false,'error'=>'invalid HTML output','preview'=>substr($html,0,300)]); exit; +} + +$filename = 'scene3d-' . 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, + 'url'=>'/files/'.$filename, + 'preview_url'=>'/files/'.$filename, + 'title'=>'Scene 3D - ' . substr($topic, 0, 50), + 'topic'=>$topic, + 'size'=>filesize($outpath), + 'size_kb'=>round(filesize($outpath)/1024, 1), + 'lines'=>substr_count($html, "\n"), +]); diff --git a/api/ambre-tool-brainstorm.php b/api/ambre-tool-brainstorm.php new file mode 100644 index 000000000..f751f2f2f --- /dev/null +++ b/api/ambre-tool-brainstorm.php @@ -0,0 +1,114 @@ +false,'error'=>'POST only']); exit; } + +$input = json_decode(file_get_contents('php://input'), true); +$topic = trim($input['topic'] ?? $input['query'] ?? ''); +if (strlen($topic) < 5) { echo json_encode(['ok'=>false,'error'=>'topic too short']); exit; } +$topic = substr($topic, 0, 800); + +// Providers to query in parallel (sovereign cascade exposes these) +$providers = [ + 'cerebras' => 'llama-3.3-70b', + 'groq' => 'llama-3.3-70b-versatile', + 'sambanova' => 'Meta-Llama-3.3-70B-Instruct', + 'gemini' => 'gemini-2.0-flash-exp', + 'cloudflare' => 'llama-3.3-70b-instruct', +]; + +$prompt = "Perspective sur: \"$topic\"\n\nDonne UNE idee/angle/insight unique et original en 3-5 phrases maximum. Pas d'intro, va direct a l'insight."; + +$mh = curl_multi_init(); +$handles = []; +foreach ($providers as $prov => $model) { + $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' => $model, + 'provider' => $prov, + 'messages' => [['role'=>'user', 'content'=>$prompt]], + 'max_tokens' => 400, + 'temperature' => 0.85 + ]), + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_TIMEOUT => 30, + ]); + curl_multi_add_handle($mh, $ch); + $handles[$prov] = $ch; +} + +// Execute parallel +$running = null; +do { + curl_multi_exec($mh, $running); + curl_multi_select($mh, 0.1); +} while ($running > 0); + +$responses = []; +$successful = 0; +foreach ($handles as $prov => $ch) { + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $body = curl_multi_getcontent($ch); + if ($code === 200) { + $data = json_decode($body, true); + $content = $data['choices'][0]['message']['content'] ?? ''; + if ($content) { + $responses[$prov] = substr(trim($content), 0, 800); + $successful++; + } + } + curl_multi_remove_handle($mh, $ch); + curl_close($ch); +} +curl_multi_close($mh); + +if ($successful === 0) { + echo json_encode(['ok'=>false, 'error'=>'All providers failed']); + exit; +} + +// Synthesis via 1 additional provider +$synth_input = "Synthetise les perspectives suivantes en 1 resume structure et enrichi:\n\n"; +foreach ($responses as $prov => $resp) { + $synth_input .= "### $prov\n$resp\n\n"; +} +$synth_input .= "\n\nFormat reponse:\n- 3-5 points cles majeurs (bullets)\n- 1 paragraphe synthese (4-6 phrases)\n- Pas d'intro type 'voici la synthese'"; + +$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'=>$synth_input]], + 'max_tokens' => 1200, + 'temperature' => 0.5 + ]), + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_TIMEOUT => 45, +]); +$synth_raw = curl_exec($ch); +$synth_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); +curl_close($ch); + +$synthesis = ''; +if ($synth_code === 200) { + $synth_data = json_decode($synth_raw, true); + $synthesis = $synth_data['choices'][0]['message']['content'] ?? ''; +} + +echo json_encode([ + 'ok' => true, + 'summary' => $synthesis ?: 'Synthese indisponible - voir raw_responses', + 'providers_used' => array_keys($responses), + 'providers_count' => $successful, + 'raw_responses' => $responses, + 'topic' => $topic, +]); diff --git a/api/ambre-tool-dataviz.php b/api/ambre-tool-dataviz.php new file mode 100644 index 000000000..37e738987 --- /dev/null +++ b/api/ambre-tool-dataviz.php @@ -0,0 +1,67 @@ +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, 400); + +$prompt = "Expert data-viz Plotly.js. Genere un dashboard interactif pour: \"$topic\"\n\n" + . "Contraintes:\n" + . "- Plotly.js via CDN https://cdn.plot.ly/plotly-2.27.0.min.js\n" + . "- Tailwind CSS via CDN\n" + . "- HTML complet standalone\n" + . "- 3-4 graphiques differents (line+bar+pie+scatter OU area+heatmap+radar etc)\n" + . "- Chaque chart dans une card avec titre\n" + . "- Grid responsive (2x2 desktop, 1 col mobile)\n" + . "- Donnees inline cohrentes avec le sujet (15-30 points minimum par chart)\n" + . "- Couleurs modernes (indigo/emerald/amber/rose)\n" + . "- Design premium (gradient header, shadows, spacing)\n" + . "- KPI summary cards en haut (3-4 cards avec chiffres cles)\n" + . "- Pas d'API externe, pas de fetch\n\n" + . "RETOURNE UNIQUEMENT LE CODE HTML complet sans backticks"; + +$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' => 7000, 'temperature' => 0.7 + ]), + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_TIMEOUT => 140, +]); +$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'] ?? ''; +$html = preg_replace('/^```(?:html)?\s*\n/', '', $html); +$html = preg_replace('/\n```\s*$/', '', trim($html)); + +if (stripos($html, 'false,'error'=>'invalid HTML','preview'=>substr($html,0,300)]); exit; +} + +$filename = 'dataviz-' . 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, + 'url'=>'/files/'.$filename, + 'preview_url'=>'/files/'.$filename, + 'title'=>'Dashboard - ' . substr($topic, 0, 50), + 'topic'=>$topic, + 'size_kb'=>round(filesize($outpath)/1024, 1), + 'lines'=>substr_count($html, "\n"), +]); diff --git a/api/ambre-tool-image-gen.php b/api/ambre-tool-image-gen.php new file mode 100644 index 000000000..53fcd73b8 --- /dev/null +++ b/api/ambre-tool-image-gen.php @@ -0,0 +1,95 @@ +false,'error'=>'POST only']); exit; } + +$input = json_decode(file_get_contents('php://input'), true); +$prompt = trim($input['prompt'] ?? $input['topic'] ?? ''); +$style = trim($input['style'] ?? ''); +if (strlen($prompt) < 3) { echo json_encode(['ok'=>false,'error'=>'prompt too short']); exit; } +$prompt = substr($prompt, 0, 500); + +// Style augmentation +$style_suffix = [ + 'photorealistic' => ', highly detailed, 8k, photorealistic, professional photography, sharp focus', + 'art' => ', digital art, trending on artstation, concept art, vibrant colors', + 'minimalist' => ', minimalist, clean design, simple, elegant', + 'corporate' => ', corporate professional, clean modern, premium quality', + 'default' => ', high quality, detailed, professional', +][$style] ?? ', high quality, detailed, professional'; + +$full_prompt = $prompt . $style_suffix; + +// Try sovereign image endpoint first (if exists) +$sovereign_url = 'http://127.0.0.1:4000/v1/images/generations'; +$ch = curl_init($sovereign_url); +curl_setopt_array($ch, [ + CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, + CURLOPT_POSTFIELDS => json_encode(['prompt'=>$full_prompt,'n'=>1,'size'=>'1024x1024']), + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_TIMEOUT => 120, +]); +$r1 = curl_exec($ch); +$c1 = curl_getinfo($ch, CURLINFO_HTTP_CODE); +curl_close($ch); + +if ($c1 === 200) { + $d1 = json_decode($r1, true); + $img_url = $d1['data'][0]['url'] ?? $d1['data'][0]['b64_json'] ?? null; + if ($img_url) { + // Save locally + $filename = 'img-' . substr(md5($prompt . microtime(true)), 0, 10) . '.png'; + $outpath = '/var/www/html/files/' . $filename; + if (!is_dir('/var/www/html/files')) { mkdir('/var/www/html/files', 0755, true); } + + if (strpos($img_url, 'http') === 0) { + file_put_contents($outpath, file_get_contents($img_url)); + } else { + // base64 + file_put_contents($outpath, base64_decode($img_url)); + } + + if (file_exists($outpath) && filesize($outpath) > 100) { + echo json_encode([ + 'ok'=>true, 'url'=>'/files/'.$filename, 'prompt'=>$prompt, + 'style'=>$style ?: 'default', + 'size_kb'=>round(filesize($outpath)/1024, 1), + 'provider'=>'sovereign' + ]); + exit; + } + } +} + +// Fallback: existing ambre-tool-image.php (already wired in platform) +$ch = curl_init('http://127.0.0.1/api/ambre-tool-image.php'); +curl_setopt_array($ch, [ + CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, + CURLOPT_POSTFIELDS => json_encode(['prompt'=>$full_prompt,'topic'=>$full_prompt]), + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_TIMEOUT => 90, +]); +$r2 = curl_exec($ch); +$c2 = curl_getinfo($ch, CURLINFO_HTTP_CODE); +curl_close($ch); + +if ($c2 === 200) { + $d2 = json_decode($r2, true); + $url = $d2['url'] ?? $d2['image'] ?? null; + if ($url) { + echo json_encode([ + 'ok'=>true, 'url'=>$url, 'prompt'=>$prompt, + 'style'=>$style ?: 'default', + 'provider'=>'fallback-ambre-image' + ]); + exit; + } +} + +echo json_encode(['ok'=>false, 'error'=>'image gen unavailable', 'attempted'=>['sovereign','ambre-image']]); diff --git a/api/ambre-tool-site.php b/api/ambre-tool-site.php new file mode 100644 index 000000000..ef396ae47 --- /dev/null +++ b/api/ambre-tool-site.php @@ -0,0 +1,79 @@ +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, 400); + +$prompt = "Expert frontend designer SaaS. Genere une landing page COMPLETE premium pour: \"$topic\"\n\n" + . "Sections obligatoires (dans cet ordre):\n" + . "1. Header sticky avec logo, menu (5-6 items), CTA button\n" + . "2. Hero section avec headline + sub-headline + 2 CTA + visual mockup/illustration\n" + . "3. Logo bar (6-8 companies trust)\n" + . "4. Features grid (6 features avec icons SVG, titres, descriptions)\n" + . "5. How it works (3-4 etapes numerotees)\n" + . "6. Testimonials (3 cards avec rating 5 etoiles, photo avatar circulaire initiales, nom, entreprise)\n" + . "7. Pricing table (3 tiers: Starter/Pro/Enterprise) avec features check/cross\n" + . "8. FAQ accordeon (5-6 questions)\n" + . "9. CTA final section\n" + . "10. Footer riche (4 colonnes links + newsletter + social)\n\n" + . "Tech:\n" + . "- Tailwind CSS via CDN\n" + . "- HTML complet standalone \n" + . "- Mobile responsive (breakpoints sm/md/lg)\n" + . "- Dark/light mode toggle avec localStorage ... NON, pas localStorage. Juste toggle simple via class.\n" + . "- Palette: indigo/purple/slate pour bg, emerald pour succes CTAs\n" + . "- Hover effects (scale, shadow, color transitions)\n" + . "- Smooth scroll anchors\n" + . "- Animations CSS (fade-in, slide-up)\n" + . "- Typography: Inter / system-ui\n" + . "- Design ultra moderne 2026\n" + . "- Contenu realiste et coherent avec le sujet\n" + . "- Pas de localStorage, pas de fetch externe\n\n" + . "RETOURNE UNIQUEMENT LE CODE HTML complet (sans backticks)"; + +$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' => 12000, 'temperature' => 0.75 + ]), + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_TIMEOUT => 180, +]); +$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'] ?? ''; +$html = preg_replace('/^```(?:html)?\s*\n/', '', $html); +$html = preg_replace('/\n```\s*$/', '', trim($html)); + +if (stripos($html, 'false,'error'=>'invalid HTML','preview'=>substr($html,0,300)]); exit; +} + +$filename = 'site-' . 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, + 'url'=>'/files/'.$filename, + 'preview_url'=>'/files/'.$filename, + 'title'=>'Landing Page - ' . substr($topic, 0, 50), + 'topic'=>$topic, + 'size_kb'=>round(filesize($outpath)/1024, 1), + 'lines'=>substr_count($html, "\n"), +]); diff --git a/api/ambre-tool-sql.php b/api/ambre-tool-sql.php new file mode 100644 index 000000000..964d2ab53 --- /dev/null +++ b/api/ambre-tool-sql.php @@ -0,0 +1,79 @@ +false,'error'=>'POST only']); exit; } + +$input = json_decode(file_get_contents('php://input'), true); +$query = trim($input['query'] ?? $input['topic'] ?? ''); +$dialect = $input['dialect'] ?? 'postgresql'; +if (strlen($query) < 3) { echo json_encode(['ok'=>false,'error'=>'query too short']); exit; } +$query = substr($query, 0, 800); + +$prompt = "Expert SQL $dialect. Traduis la demande en langue naturelle en SQL:\n\n\"$query\"\n\n" + . "Retourne UNIQUEMENT un JSON:\n" + . "{\n" + . " \"sql\": \"SELECT ... FROM ... WHERE ...;\",\n" + . " \"explanation\": \"Bref explication de ce que fait la requete\",\n" + . " \"tables_needed\": [\"table1\",\"table2\"],\n" + . " \"dialect\": \"$dialect\",\n" + . " \"complexity\": \"simple|medium|complex\",\n" + . " \"suggested_indexes\": [\"CREATE INDEX ...\"]\n" + . "}\n\n" + . "IMPORTANT:\n" + . "- SQL valide et optimise\n" + . "- Utiliser jointures appropriees (INNER/LEFT/RIGHT)\n" + . "- Mettre ORDER BY si sens\n" + . "- Preciser LIMIT si pertinent\n" + . "- Si agrecation, utiliser GROUP BY + HAVING\n" + . "- Explanation en francais\n" + . "- JSON UNIQUEMENT, aucun texte avant/apres"; + +$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' => 2000, 'temperature' => 0.3 + ]), + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_TIMEOUT => 60, +]); +$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); +$content_raw = $data['choices'][0]['message']['content'] ?? ''; + +// Balanced JSON extract +if (preg_match('/```(?:json)?\s*\n?(.*?)\n?```/s', $content_raw, $m)) { $content_raw = $m[1]; } +$jstart = strpos($content_raw, '{'); +if ($jstart !== false) { + $depth = 0; $jend = -1; + for ($i = $jstart; $i < strlen($content_raw); $i++) { + if ($content_raw[$i] === '{') $depth++; + elseif ($content_raw[$i] === '}') { $depth--; if ($depth === 0) { $jend = $i; break; } } + } + if ($jend > $jstart) $content_raw = substr($content_raw, $jstart, $jend - $jstart + 1); +} +$result = json_decode($content_raw, true); + +if (!$result || !isset($result['sql'])) { + echo json_encode(['ok'=>false,'error'=>'invalid JSON','raw'=>substr($content_raw,0,300)]); exit; +} + +echo json_encode([ + 'ok' => true, + 'sql' => $result['sql'], + 'explanation' => $result['explanation'] ?? '', + 'tables_needed' => $result['tables_needed'] ?? [], + 'dialect' => $result['dialect'] ?? $dialect, + 'complexity' => $result['complexity'] ?? 'medium', + 'suggested_indexes' => $result['suggested_indexes'] ?? [], + 'result' => $result['sql'], // for inline kind render +]); diff --git a/api/ambre-tool-translate-code.php b/api/ambre-tool-translate-code.php new file mode 100644 index 000000000..055062318 --- /dev/null +++ b/api/ambre-tool-translate-code.php @@ -0,0 +1,91 @@ +Python, Python->JS, etc. + */ +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); +$code = trim($input['code'] ?? ''); +$from_lang = $input['from'] ?? 'auto'; +$to_lang = $input['to'] ?? 'python'; + +// If topic was sent instead (from intent router), parse "translate X from Y to Z" +if (!$code && isset($input['topic'])) { + $topic = $input['topic']; + // Try extract code block if present + if (preg_match('/```(?:\w+)?\s*\n(.*?)\n```/s', $topic, $m)) { + $code = $m[1]; + } else { + $code = $topic; // treat topic as code + } + // Detect to_lang + foreach (['python','javascript','typescript','go','rust','java','csharp','php','ruby','kotlin','swift'] as $lang) { + if (stripos($topic, $lang) !== false) { $to_lang = $lang; break; } + } +} + +if (strlen($code) < 5) { echo json_encode(['ok'=>false,'error'=>'code too short']); exit; } +$code = substr($code, 0, 4000); + +$prompt = "Expert polyglot programmer. Traduis ce code" . ($from_lang !== 'auto' ? " de $from_lang" : "") . " vers $to_lang:\n\n" + . "```\n$code\n```\n\n" + . "Retourne UNIQUEMENT un JSON:\n" + . "{\n" + . " \"from\": \"langue detectee\",\n" + . " \"to\": \"$to_lang\",\n" + . " \"code\": \"\",\n" + . " \"notes\": \"Notes sur differences idiomatiques importantes\",\n" + . " \"dependencies\": [\"package1\", \"package2\"]\n" + . "}\n\n" + . "IMPORTANT:\n" + . "- Code traduit IDIOMATIQUE dans la langue cible (pas traduction literale)\n" + . "- Utiliser les conventions modernes (ES2024/Python3.12/Go1.22/etc)\n" + . "- Preserver les commentaires si presents, traduits en FR\n" + . "- JSON valide uniquement, aucun texte avant/apres\n" + . "- dependencies = liste des libs a installer"; + +$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' => 3500, 'temperature' => 0.3 + ]), + CURLOPT_HTTPHEADER => ['Content-Type: application/json'], + CURLOPT_TIMEOUT => 90, +]); +$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); +$content_raw = $data['choices'][0]['message']['content'] ?? ''; + +if (preg_match('/```(?:json)?\s*\n?(.*?)\n?```/s', $content_raw, $m)) { $content_raw = $m[1]; } +$jstart = strpos($content_raw, '{'); +if ($jstart !== false) { + $depth = 0; $jend = -1; + for ($i = $jstart; $i < strlen($content_raw); $i++) { + if ($content_raw[$i] === '{') $depth++; + elseif ($content_raw[$i] === '}') { $depth--; if ($depth === 0) { $jend = $i; break; } } + } + if ($jend > $jstart) $content_raw = substr($content_raw, $jstart, $jend - $jstart + 1); +} +$result = json_decode($content_raw, true); +if (!$result || !isset($result['code'])) { + echo json_encode(['ok'=>false,'error'=>'invalid JSON','raw'=>substr($content_raw,0,300)]); exit; +} + +echo json_encode([ + 'ok' => true, + 'from' => $result['from'] ?? $from_lang, + 'to' => $result['to'] ?? $to_lang, + 'code' => $result['code'], + 'notes' => $result['notes'] ?? '', + 'dependencies' => $result['dependencies'] ?? [], + 'result' => $result['code'], // for inline render +]); diff --git a/js/wevia-gen-router.js b/js/wevia-gen-router.js index 1b97ac5ed..6d46a7b44 100644 --- a/js/wevia-gen-router.js +++ b/js/wevia-gen-router.js @@ -1,202 +1,272 @@ /** - * WEVIA Public - Generation Router V1 - * Auto-detects user intent (DOCX/XLSX/PPTX/REACT/PDF) from message - * Calls corresponding API + shows premium banner + opens preview panel right - * - * Wiring: loaded as module, hooks into window.sendMsg BEFORE it streams chat - * Safe: NO access to vaults, credentials, servers. Public-grade. + * wevia-gen-router V3 GODMODE + * 17 générateurs safe public - docs + intel + GODMODE extensions + * Rétro-compatible V1/V2 (idempotent) */ (function(){ - if (window.__weviaGenRouter) return; - window.__weviaGenRouter = true; + if (window.__weviaGenRouterV3) return; + window.__weviaGenRouterV3 = true; - // ============================================================ - // INTENT DETECTION — regex patterns for natural language - // ============================================================ var GENERATORS = [ - { - id: 'docx', - patterns: [ + // ==== Documents (V1) ==== + { id: 'docx', patterns: [ /\b(gen[eè]re?z?|cr[eé]er?|fai[st]|produi[st]?|r[eé]dige?)\s+(un\s+)?(document\s+)?(word|docx|document\s+word)\b/i, /^\s*(word|docx|document\s+word)\s*[:\-]?\s+/i, - ], - api: '/api/ambre-tool-docx.php', - label: 'Document Word', - icon: '📄', - color: '#2563eb', - }, - { - id: 'xlsx', - patterns: [ + ], api: '/api/ambre-tool-docx.php', payloadKey: 'topic', + label: 'Document Word', icon: '📄', color: '#2563eb', kind: 'docx' }, + { id: 'xlsx', patterns: [ /\b(gen[eè]re?z?|cr[eé]er?|fai[st]|produi[st]?)\s+(un\s+)?(tableau|fichier|document)?\s*(excel|xlsx|tableur|spreadsheet)\b/i, /^\s*(excel|xlsx|tableau\s+excel)\s*[:\-]?\s+/i, - ], - api: '/api/ambre-tool-xlsx.php', - label: 'Tableau Excel', - icon: '📊', - color: '#10b981', - }, - { - id: 'pptx', - patterns: [ + ], api: '/api/ambre-tool-xlsx.php', payloadKey: 'topic', + label: 'Tableau Excel', icon: '📊', color: '#10b981', kind: 'xlsx' }, + { id: 'pptx', patterns: [ /\b(gen[eè]re?z?|cr[eé]er?|fai[st]|produi[st]?)\s+(une?\s+)?(pr[eé]sentation|slide|deck|ppt|pptx|powerpoint)\b/i, /^\s*(ppt|pptx|powerpoint|présentation)\s*[:\-]?\s+/i, - ], - api: '/api/ambre-tool-pptx.php', - label: 'Présentation PowerPoint', - icon: '🎬', - color: '#f59e0b', - }, - { - id: 'react', - patterns: [ - /\b(gen[eè]re?z?|cr[eé]er?|fai[st]|build|construis)\s+(un\s+)?(composant|component|page|app|widget|landing|dashboard|pricing|form)\s+(react|frontend|front[- ]end|web|html)/i, - /\b(compose|gen[eè]re?)\s+(un\s+)?(react|html|front[- ]end)\b/i, - ], - api: '/api/ambre-tool-react.php', - label: 'Composant React', - icon: '⚛️', - color: '#06b6d4', - }, + ], api: '/api/ambre-tool-pptx.php', payloadKey: 'topic', + label: 'Présentation PowerPoint', icon: '🎬', color: '#f59e0b', kind: 'pptx' }, + { id: 'react', patterns: [ + /\b(gen[eè]re?z?|cr[eé]er?|fai[st]|build|construis)\s+(un\s+)?(composant|component|widget|pricing|card|button|form)\s+(react|frontend|front[- ]end)/i, + /\b(compose|gen[eè]re?z?)\s+(un\s+)?(react|composant\s+react)\b/i, + ], api: '/api/ambre-tool-react.php', payloadKey: 'topic', + label: 'Composant React', icon: '⚛️', color: '#06b6d4', kind: 'react' }, + + // ==== GODMODE Extensions (V3) ==== + { id: 'site', patterns: [ + /\b(gen[eè]re?z?|cr[eé]er?|build|construis)\s+(une?\s+)?(site|landing|page|saas|website|mini[- ]?site|webapp)\b/i, + /^\s*(site|landing|page)\s*[:\-]?\s+/i, + ], api: '/api/ambre-tool-site.php', payloadKey: 'topic', + label: 'Landing Page Complète', icon: '🌐', color: '#8b5cf6', kind: 'react' }, + { id: '3d', patterns: [ + /\b(gen[eè]re?z?|cr[eé]er?|fai[st])\s+(une?\s+)?(sc[eè]ne|modele)\s+3d\b/i, + /\b(three[. ]?js|3d\s+scene|animation\s+3d)/i, + ], api: '/api/ambre-tool-3d.php', payloadKey: 'topic', + label: 'Scène 3D Three.js', icon: '🎲', color: '#ec4899', kind: 'react' }, + { id: 'dataviz', patterns: [ + /\b(gen[eè]re?z?|cr[eé]er?|fai[st])\s+(un\s+)?(dashboard|graphique|chart|visualisation|plotly|data[- ]?viz)\b/i, + /\b(dataviz|visualise?)\s+(des|les)?\s*(donnees|data)/i, + ], api: '/api/ambre-tool-dataviz.php', payloadKey: 'topic', + label: 'Dashboard Interactif', icon: '📈', color: '#f97316', kind: 'react' }, + { id: 'image-gen', patterns: [ + /\b(gen[eè]re?z?|cr[eé]er?|fai[st]|dessine?z?)\s+(une?\s+)?(image|illustration|visuel|picture|photo|dessin)\s+(de|pour|sur|representant|d[ue'])/i, + /^\s*(image|dessine|illustre)\s+/i, + ], api: '/api/ambre-tool-image-gen.php', payloadKey: 'prompt', + label: 'Image IA', icon: '🎨', color: '#d946ef', kind: 'image' }, + { id: 'brainstorm', patterns: [ + /\b(brainstorm|multi[- ]?ia|toutes\s+les\s+ia|plusieurs?\s+perspective)/i, + /\b(donne[- ]?moi\s+)?(plusieurs|differentes?|multiples?)\s+(idee|perspective|angle|opinion)/i, + ], api: '/api/ambre-tool-brainstorm.php', payloadKey: 'topic', + label: 'Brainstorm Multi-IA', icon: '🧠', color: '#6366f1', kind: 'json' }, + { id: 'sql', patterns: [ + /\b(gen[eè]re?z?|cr[eé]er?|traduis)\s+(une?\s+)?(requ[eê]te\s+)?sql\b/i, + /\bsql\s+(pour|de|qui)/i, + /^\s*sql\s*[:\-]?\s+/i, + ], api: '/api/ambre-tool-sql.php', payloadKey: 'query', + label: 'Requête SQL', icon: '🗃️', color: '#0891b2', kind: 'code' }, + { id: 'translate-code', patterns: [ + /\b(traduis|convertis|translate|convert)\s+.*(code|en\s+python|en\s+js|en\s+javascript|en\s+go|en\s+rust|en\s+typescript|en\s+java|en\s+ruby)/i, + /\b(python|javascript|typescript|go|rust)\s+en\s+(python|javascript|typescript|go|rust)/i, + ], api: '/api/ambre-tool-translate-code.php', payloadKey: 'topic', + label: 'Traduction Code', icon: '🔄', color: '#14b8a6', kind: 'code' }, + + // ==== V2 Utilities ==== + { id: 'web-search', patterns: [ + /\b(cherche?z?|recherche?z?|trouve?z?|search)\s+(sur\s+(le|l\x27)?web|web|en\s+ligne|online|internet)/i, + /\b(que\s+dit|quoi\s+de\s+neuf|actualit[eé])\s+/i, + /^\s*(web[- ]?search|search)\s*[:\-]?\s+/i, + ], api: '/api/ambre-tool-web-search.php', payloadKey: 'query', + label: 'Recherche Web', icon: '🔍', color: '#7c3aed', kind: 'json' }, + { id: 'url-summary', patterns: [ + /\b(r[eé]sume?z?|summarize|analyse?z?)\s+.*\b(https?:\/\/\S+)/i, + ], api: '/api/ambre-tool-url-summary.php', payloadKey: 'url', extractUrl: true, + label: 'Résumé URL', icon: '🔗', color: '#a855f7', kind: 'json' }, + { id: 'youtube-summary', patterns: [ + /\b(r[eé]sume?z?|summarize)\s+.*(youtube\.com|youtu\.be)/i, + /youtube\.com\/watch|youtu\.be\//i, + ], api: '/api/ambre-tool-youtube-summary.php', payloadKey: 'url', extractUrl: true, + label: 'Résumé YouTube', icon: '▶️', color: '#dc2626', kind: 'json' }, + { id: 'qr', patterns: [ + /\b(gen[eè]re?z?|cr[eé]er?|fai[st])\s+(un\s+)?qr[- ]?code\b/i, + /^\s*qr[- ]?code\s*[:\-]?\s+/i, + ], api: '/api/ambre-tool-qr.php', payloadKey: 'text', + label: 'QR Code', icon: '🔲', color: '#0ea5e9', kind: 'image' }, + { id: 'calc', patterns: [ + /\b(calcule?z?|compute|combien\s+fait)\s+/i, + /^\s*=\s*\S+/, + ], api: '/api/ambre-tool-calc.php', payloadKey: 'expr', + label: 'Calcul', icon: '🧮', color: '#64748b', kind: 'inline' }, + { id: 'tts', patterns: [ + /\b(lis|lit|prononce|text[- ]?to[- ]?speech|tts|synth[eé]tise)\s+/i, + /^\s*(tts|lire)\s*[:\-]?\s+/i, + ], api: '/api/ambre-tool-tts.php', payloadKey: 'text', + label: 'Synthèse Vocale', icon: '🔊', color: '#14b8a6', kind: 'audio' }, ]; function detectIntent(text) { - if (!text || text.length < 8) return null; + if (!text || text.length < 4) return null; for (var i = 0; i < GENERATORS.length; i++) { - var g = GENERATORS[i]; - for (var j = 0; j < g.patterns.length; j++) { - if (g.patterns[j].test(text)) return g; + for (var j = 0; j < GENERATORS[i].patterns.length; j++) { + if (GENERATORS[i].patterns[j].test(text)) return GENERATORS[i]; } } return null; } - // ============================================================ - // TOPIC EXTRACTION — clean prompt from intent keywords - // ============================================================ - function extractTopic(text, gen) { + function extractPayload(text, gen) { + if (gen.extractUrl) { + var m = text.match(/https?:\/\/[^\s]+/); + if (m) return m[0]; + } var t = text; - // Strip common generation verbs - t = t.replace(/^\s*(gen[eè]re?z?|cr[eé]er?|fai[st]?|produi[st]?|r[eé]dige?|build|compose|construis)\s+/i, ''); - // Strip format keywords - t = t.replace(/\b(un|une|le|la|des)\s+/i, ''); - t = t.replace(/\b(document|tableau|fichier|composant|component|presentation|slide|deck)\s+/i, ''); - t = t.replace(/\b(word|docx|excel|xlsx|ppt|pptx|powerpoint|react|html|front[- ]?end)\b\s*/gi, ''); - // Strip "sur", "pour", "de", ":" at start - t = t.replace(/^\s*(sur|pour|de|du|:|\-|about|propos)\s+/i, ''); - t = t.replace(/^[:\-\s]+/, ''); - return t.trim(); + t = t.replace(/^\s*(gen[eè]re?z?|cr[eé]er?|fai[st]?|produi[st]?|r[eé]dige?|build|compose|construis|cherche?z?|r[eé]sume?z?|trouve?z?|search|analyse?z?|calcule?z?|lis|lit|prononce|dessine?z?|illustre|traduis|convertis)\s+/i, ''); + t = t.replace(/\b(un|une|le|la|des|sur|pour|de|du|:|\-|web|internet|web[- ]search|moi)\s+/gi, ' '); + t = t.replace(/\b(document|tableau|fichier|composant|component|pr[eé]sentation|slide|deck|qr[- ]?code|image|illustration|requete|code|site|landing|page|sc[eè]ne)\s+/gi, ' '); + t = t.replace(/\b(word|docx|excel|xlsx|ppt|pptx|powerpoint|react|html|front[- ]?end|3d|sql|plotly|dashboard|three[. ]?js)\b\s*/gi, ''); + t = t.replace(/^[:\-\s]+/, '').trim(); + return t.length > 2 ? t : text; } - // ============================================================ - // BANNER UI — progress + result with download - // ============================================================ - function createBanner(gen, topic) { + function createBanner(gen, payload) { var el = document.createElement('div'); el.className = 'wgen-banner'; - el.setAttribute('role','status'); el.style.cssText = 'margin:14px 0;padding:14px 18px;border-radius:14px;' + - 'background:linear-gradient(135deg,' + gen.color + ',' + shade(gen.color, -15) + ');' + + 'background:linear-gradient(135deg,' + gen.color + ',' + shade(gen.color,-15) + ');' + 'color:#fff;display:flex;align-items:center;gap:14px;' + 'box-shadow:0 4px 20px ' + gen.color + '44;' + 'animation:wgenSlide .4s cubic-bezier(.4,0,.2,1);font-family:inherit'; el.innerHTML = '
' + gen.icon + '
' + '
' + - '
Génération ' + gen.label + '…
' + - '
' + escapeHtml(topic) + '
' + + '
' + gen.label + '…
' + + '
' + escapeHtml(payload) + '
' + '
' + '
'; return el; } - function finalizeBanner(el, gen, result, error) { + function finalizeBanner(el, gen, data, error) { var spin = el.querySelector('.wgen-spin'); if (spin) spin.remove(); + if (error) { el.style.background = 'linear-gradient(135deg,#dc2626,#991b1b)'; el.innerHTML = - '
' + - '
Erreur génération ' + gen.label + '
' + + '
' + + '
Erreur ' + gen.label + '
' + '
' + escapeHtml(error) + '
' + ''; return; } - el.style.background = 'linear-gradient(135deg,#10b981,#059669)'; - var details = []; - if (result.sections) details.push(result.sections + ' sections'); - if (result.slides) details.push(result.slides + ' slides'); - if (result.sheets) details.push(result.sheets + ' feuilles'); - if (result.rows) details.push(result.rows + ' lignes'); - if (result.lines) details.push(result.lines + ' lignes code'); - if (result.size_kb) details.push(result.size_kb + ' KB'); - var detailsTxt = details.length ? details.join(' · ') : 'Fichier prêt'; - el.innerHTML = - '
' + gen.icon + '
' + - '
' + - '
' + gen.label + ' généré ✓
' + - '
' + escapeHtml(result.title || '') + ' · ' + detailsTxt + '
' + - '
' + - '' + - '⬇ Télécharger'; + el.style.background = 'linear-gradient(135deg,#10b981,#059669)'; + + if (['docx','xlsx','pptx','react'].indexOf(gen.kind) !== -1) { + var details = []; + if (data.sections) details.push(data.sections + ' sections'); + if (data.slides) details.push(data.slides + ' slides'); + if (data.sheets) details.push(data.sheets + ' feuilles'); + if (data.rows) details.push(data.rows + ' lignes'); + if (data.lines) details.push(data.lines + ' lignes'); + if (data.size_kb) details.push(data.size_kb + ' KB'); + var url = data.url || data.preview_url; + el.innerHTML = + '
' + gen.icon + '
' + + '
' + + '
' + gen.label + ' généré ✓
' + + '
' + escapeHtml(data.title || '') + ' · ' + details.join(' · ') + '
' + + '
' + + '' + + '⬇ Télécharger'; + } + else if (gen.kind === 'image') { + var imgUrl = data.url || data.image || ''; + el.innerHTML = + '
' + gen.icon + '
' + + '
' + gen.label + ' ✓
' + + '
' + escapeHtml(data.prompt || data.style || '') + '
' + + '' + + ''; + } + else if (gen.kind === 'audio') { + var audUrl = data.url || data.audio_url; + el.innerHTML = + '
' + gen.icon + '
' + + '
' + + '
' + gen.label + ' ✓
' + + '' + + '
' + + ''; + } + else if (gen.kind === 'inline') { + el.innerHTML = + '
' + gen.icon + '
' + + '
' + + '
' + gen.label + '
' + + '
' + escapeHtml(String(data.result || data.value || JSON.stringify(data))) + '
' + + '
'; + } + else if (gen.kind === 'code') { + var codeTxt = data.code || data.sql || data.result || ''; + el.style.flexDirection = 'column'; + el.style.alignItems = 'stretch'; + el.innerHTML = + '
' + + '
' + gen.icon + '
' + + '
' + gen.label + ' ✓
' + + (data.to || data.dialect ? '
→ ' + escapeHtml(data.to || data.dialect) + '
' : '') + + '
' + + '' + + '
' + + '
' + escapeHtml(codeTxt) + '
' + + (data.explanation || data.notes ? '
' + escapeHtml(data.explanation || data.notes) + '
' : ''); + } + else if (gen.kind === 'json') { + var summary = data.summary || data.answer || data.result || data.content || ''; + // If brainstorm with providers_used, show them + if (data.providers_used) { + el.style.flexDirection = 'column'; + el.style.alignItems = 'stretch'; + el.innerHTML = + '
' + + '
' + gen.icon + '
' + + '
' + gen.label + ' · ' + data.providers_used.length + ' IA consultées
' + + '
' + data.providers_used.join(' · ') + '
' + + '
' + + '
' + + '
' + escapeHtml(summary) + '
'; + } else { + var source = data.source || data.url || ''; + el.innerHTML = + '
' + gen.icon + '
' + + '
' + + '
' + gen.label + (source ? ' · ' + escapeHtml(source.slice(0,40)) : '') + '
' + + '
' + escapeHtml(String(summary).slice(0, 600)) + (summary.length > 600 ? '…' : '') + '
' + + '
'; + } + } } - // ============================================================ - // PREVIEW PANEL — use existing .preview-panel infra - // ============================================================ - window.weviaOpenPreview = function(url, kind, title) { + window.weviaOpenPreview = window.weviaOpenPreview || function(url, kind, title) { try { var layout = document.querySelector('.main-layout'); var body = document.getElementById('previewBody'); var titleEl = document.getElementById('prevTitleText'); if (!layout || !body) { window.open(url, '_blank'); return; } - if (titleEl) titleEl.textContent = title || 'Document'; - var html = ''; if (kind === 'react') { html = ''; - } else if (kind === 'docx' || kind === 'xlsx' || kind === 'pptx') { - // Google Docs Viewer for Office files + } else if (['docx','xlsx','pptx'].indexOf(kind) !== -1) { var absUrl = url.startsWith('http') ? url : window.location.origin + url; - html = - ''; - } else if (kind === 'pdf') { - html = ''; + html = ''; } else { html = ''; } body.innerHTML = html; layout.classList.add('panel-open'); - - // Setup download button window.__lastPreviewUrl = url; - } catch (e) { - console.warn('openPreview err', e); - window.open(url, '_blank'); - } + } catch (e) { window.open(url, '_blank'); } }; - // Wire existing downloadPreview button - var oldDl = window.downloadPreview; - window.downloadPreview = function() { - if (window.__lastPreviewUrl) { - var a = document.createElement('a'); - a.href = window.__lastPreviewUrl; - a.download = ''; - a.target = '_blank'; - document.body.appendChild(a); - a.click(); - setTimeout(function(){ a.remove(); }, 100); - return; - } - if (typeof oldDl === 'function') oldDl(); - }; - - // ============================================================ - // HOOK into sendMsg — intercept before normal chat flow - // ============================================================ function injectCSS() { if (document.getElementById('wgen-style')) return; var s = document.createElement('style'); @@ -207,126 +277,86 @@ '.wgen-banner{font-family:inherit}'; document.head.appendChild(s); } - - function escapeHtml(s) { - return String(s || '').replace(/[&<>"']/g, function(c) { - return ({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]; - }); + function escapeHtml(s) { return String(s||'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''})[c]); } + function escapeAttr(s) { return escapeHtml(s).replace(/'/g,'''); } + function shade(hex, p) { + var n = parseInt(hex.replace('#',''),16); + var r = Math.max(0,Math.min(255,(n>>16) + Math.floor(p*2.55))); + var g = Math.max(0,Math.min(255,((n>>8)&0xff) + Math.floor(p*2.55))); + var b = Math.max(0,Math.min(255,(n&0xff) + Math.floor(p*2.55))); + return '#'+((r<<16)|(g<<8)|b).toString(16).padStart(6,'0'); } - function escapeAttr(s) { return escapeHtml(s).replace(/'/g, '''); } - - function shade(hex, percent) { - var num = parseInt(hex.replace('#',''), 16); - var r = (num >> 16) + Math.floor(percent * 2.55); - var g = ((num >> 8) & 0xff) + Math.floor(percent * 2.55); - var b = (num & 0xff) + Math.floor(percent * 2.55); - r = Math.max(0, Math.min(255, r)); - g = Math.max(0, Math.min(255, g)); - b = Math.max(0, Math.min(255, b)); - return '#' + ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0'); + function getContainer() { + return document.getElementById('messages') || document.querySelector('.chat-messages') || document.querySelector('.messages-container') || document.querySelector('main') || document.body; } - // Get messages container to append banner - function getMessagesContainer() { - return document.getElementById('messages') || - document.querySelector('.chat-messages') || - document.querySelector('.messages-container') || - document.querySelector('main') || - document.body; - } - - // ============================================================ - // GENERATE — async call + banner lifecycle - // ============================================================ - async function generateArtifact(gen, topic) { + async function generateArtifact(gen, payload) { injectCSS(); - var banner = createBanner(gen, topic); - var container = getMessagesContainer(); - container.appendChild(banner); + var banner = createBanner(gen, payload); + getContainer().appendChild(banner); banner.scrollIntoView({ behavior: 'smooth', block: 'center' }); - try { + var body = {}; body[gen.payloadKey] = payload; var r = await fetch(gen.api, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ topic: topic }) + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) }); var data = await r.json(); - - if (data.ok) { + var ok = data.ok !== false && !data.error; + if (ok) { finalizeBanner(banner, gen, data, null); - // Auto-open preview - setTimeout(function() { - weviaOpenPreview(data.url || data.preview_url, gen.id, data.title); - }, 600); + if (['docx','xlsx','pptx','react'].indexOf(gen.kind) !== -1) { + var url = data.url || data.preview_url; + if (url) setTimeout(function(){ weviaOpenPreview(url, gen.kind, data.title); }, 600); + } } else { - finalizeBanner(banner, gen, null, data.error || 'Erreur inconnue'); + finalizeBanner(banner, gen, null, data.error || 'Erreur'); } } catch (e) { finalizeBanner(banner, gen, null, e.message || 'Réseau'); } } - // ============================================================ - // INTERCEPT sendMsg — wrapper - // ============================================================ function wrapSendMsg() { - if (typeof window.sendMsg !== 'function') { - setTimeout(wrapSendMsg, 500); - return; - } - if (window.__sendMsgWrapped) return; - window.__sendMsgWrapped = true; - - var originalSend = window.sendMsg; + if (typeof window.sendMsg !== 'function') { setTimeout(wrapSendMsg, 500); return; } + if (window.__sendMsgWrappedV3) return; + window.__sendMsgWrappedV3 = true; + var original = window.sendMsg; window.sendMsg = function() { try { var input = document.getElementById('msgInput'); - if (!input) return originalSend.apply(this, arguments); + if (!input) return original.apply(this, arguments); var text = input.value.trim(); - if (!text) return originalSend.apply(this, arguments); - + if (!text) return original.apply(this, arguments); var intent = detectIntent(text); if (intent) { - var topic = extractTopic(text, intent); - if (topic.length < 3) topic = text; - - // Echo user message first via normal flow - then intercept before API call - // Clear input + call generator + var payload = extractPayload(text, intent); input.value = ''; try { input.style.height = 'auto'; } catch(e) {} - - // Show user bubble (minimal) - var container = getMessagesContainer(); var userBubble = document.createElement('div'); userBubble.style.cssText = 'margin:12px 0;padding:10px 14px;background:#6366f1;color:#fff;border-radius:14px 14px 4px 14px;max-width:80%;margin-left:auto;font-size:14px;word-wrap:break-word'; userBubble.textContent = text; - container.appendChild(userBubble); - - // Trigger generator - generateArtifact(intent, topic); - return; // don't call original (would double-post) + getContainer().appendChild(userBubble); + generateArtifact(intent, payload); + return; } - } catch(e) { - console.warn('wgen hook err', e); - } - return originalSend.apply(this, arguments); + } catch(e) { console.warn('wgen-v3 hook err', e); } + return original.apply(this, arguments); }; - console.log('[wevia-gen-router] hooked sendMsg'); + console.log('[wevia-gen-router v3 GODMODE] hooked · ' + GENERATORS.length + ' generators'); } - // Start if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', wrapSendMsg); } else { wrapSendMsg(); } - // Expose for manual debug window.weviaGenRouter = { detectIntent: detectIntent, - extractTopic: extractTopic, + extractPayload: extractPayload, generate: generateArtifact, generators: GENERATORS, + version: 3, }; })();