Files
html/api/wevia-artifact-host.php
2026-04-12 22:57:03 +02:00

108 lines
9.0 KiB
PHP

<?php
function weviaHostArtifact($response) {
if (preg_match('/```(?:jsx|js|javascript|react)\s*\n([\s\S]*?)```/', $response, $m)) {
$jsxCode = $m[1];
// Strip ALL imports
$jsxCode = preg_replace('/^\s*import\s+.+?from\s+[\x27"].+?[\x27"];?\s*$/m', '', $jsxCode);
// Strip export statements (Babel compiles to CommonJS which fails in browser)
$jsxCode = preg_replace('/^\s*export\s+default\s+/m', '/* exported */ window.__COMP__ = ', $jsxCode);
$jsxCode = preg_replace('/^\s*export\s+/m', '/* export */ ', $jsxCode);
// React globals + simple chart components (replace Recharts)
$globals = <<<'JS'
const {useState,useEffect,useRef,useCallback,useMemo,Fragment,createElement}=React;
// Simple CSS Bar Chart component (replaces Recharts)
function SimpleChart({data, dataKey, xKey, title, color}) {
const max = Math.max(...data.map(d => d[dataKey] || 0));
return React.createElement('div', {style:{margin:'20px 0'}},
title && React.createElement('h3', {style:{marginBottom:'12px',color:'#1e293b'}}, title),
React.createElement('div', {style:{display:'flex',alignItems:'flex-end',gap:'8px',height:'200px',borderBottom:'2px solid #e2e8f0',paddingBottom:'8px'}},
...data.map((d,i) => React.createElement('div', {key:i,style:{flex:1,display:'flex',flexDirection:'column',alignItems:'center',gap:'4px'}},
React.createElement('span', {style:{fontSize:'11px',fontWeight:'600',color:color||'#6366f1'}}, d[dataKey]),
React.createElement('div', {style:{width:'100%',background:color||'#6366f1',borderRadius:'4px 4px 0 0',height:((d[dataKey]/max)*160)+'px',minHeight:'4px',transition:'height .3s'}}),
React.createElement('span', {style:{fontSize:'10px',color:'#64748b'}}, d[xKey]||d.name||d.mois||'')
))
)
);
}
// Aliases for Recharts components (render as SimpleChart)
const LineChart = SimpleChart;
const BarChart = SimpleChart;
const Line = () => null;
const Bar = () => null;
const XAxis = () => null;
const YAxis = () => null;
const CartesianGrid = () => null;
const Tooltip = () => null;
const Legend = () => null;
const ResponsiveContainer = ({children}) => children;
const PieChart = () => null;
const Pie = () => null;
const Cell = () => null;
const AreaChart = SimpleChart;
const Area = () => null;
// Reactstrap aliases
const Card = ({children,...p}) => React.createElement('div',{style:{background:'#fff',border:'1px solid #e2e8f0',borderRadius:'12px',padding:'20px',margin:'8px',...(p.style||{})}},children);
const CardBody = ({children}) => React.createElement('div',null,children);
const Row = ({children}) => React.createElement('div',{style:{display:'flex',flexWrap:'wrap',gap:'16px'}},children);
const Col = ({children}) => React.createElement('div',{style:{flex:1,minWidth:'200px'}},children);
const Button = ({children,onClick,...p}) => React.createElement('button',{onClick,style:{padding:'8px 16px',borderRadius:'8px',border:'1px solid #e2e8f0',cursor:'pointer',...(p.style||{})}},children);
const Container = ({children}) => React.createElement('div',{style:{maxWidth:'1200px',margin:'0 auto'}},children);
var Header = typeof Header !== 'undefined' ? Header : ({children,title}) => React.createElement('header',{style:{background:'linear-gradient(135deg,#1e293b,#334155)',color:'#fff',padding:'20px 24px',borderRadius:'12px',marginBottom:'20px'}},React.createElement('h1',{style:{margin:0,fontSize:'24px'}},title||children||'Dashboard'));
var Footer = typeof Footer !== 'undefined' ? Footer : ({children}) => React.createElement('footer',{style:{padding:'16px',textAlign:'center',color:'#94a3b8',fontSize:'12px',marginTop:'24px',borderTop:'1px solid #e2e8f0'}},children||'WEVIA Engine');
var Sidebar = typeof Sidebar !== 'undefined' ? Sidebar : ({children}) => React.createElement('aside',{style:{width:'250px',background:'#f8fafc',padding:'16px',borderRight:'1px solid #e2e8f0'}},children);
const NavBar = Header;
const Navbar = Header;
var Nav = typeof Nav !== 'undefined' ? Nav : Header;
const Table = ({children,...p}) => React.createElement('table',{style:{width:'100%',borderCollapse:'collapse',...(p.style||{})}},children);
const Badge = ({children,color}) => React.createElement('span',{style:{padding:'2px 8px',borderRadius:'12px',fontSize:'12px',background:color||'#e2e8f0'}},children);
var Header = typeof Header !== 'undefined' ? Header : ({children,...p}) => React.createElement('header',{style:{padding:'16px 24px',background:'#1e293b',color:'#fff',borderRadius:'12px',marginBottom:'20px',...(p.style||{})}},children);
var Footer = typeof Footer !== 'undefined' ? Footer : ({children}) => React.createElement('footer',{style:{padding:'12px',textAlign:'center',color:'#64748b',marginTop:'20px'}},children);
var Sidebar = typeof Sidebar !== 'undefined' ? Sidebar : ({children}) => React.createElement('aside',{style:{width:'250px',padding:'16px',background:'#f1f5f9',borderRadius:'12px'}},children);
var Nav = typeof Nav !== 'undefined' ? Nav : ({children}) => React.createElement('nav',{style:{display:'flex',gap:'12px',marginBottom:'16px'}},children);
var NavItem = typeof NavItem !== 'undefined' ? NavItem : ({children,active,onClick}) => React.createElement('button',{onClick,style:{padding:'8px 16px',borderRadius:'8px',border:'1px solid #e2e8f0',background:active?'#6366f1':'#fff',color:active?'#fff':'#1e293b',cursor:'pointer'}},children);
var Input = typeof Input !== 'undefined' ? Input : (p) => React.createElement('input',{...p,style:{padding:'8px 12px',borderRadius:'8px',border:'1px solid #e2e8f0',width:'100%',...(p.style||{})}});
var Select = typeof Select !== 'undefined' ? Select : ({children,...p}) => React.createElement('select',{...p,style:{padding:'8px 12px',borderRadius:'8px',border:'1px solid #e2e8f0',...(p.style||{})}},children);
var Alert = typeof Alert !== 'undefined' ? Alert : ({children,type}) => React.createElement('div',{style:{padding:'12px 16px',borderRadius:'8px',background:type==='error'?'#fef2f2':type==='success'?'#f0fdf4':'#eff6ff',border:'1px solid',borderColor:type==='error'?'#fecaca':type==='success'?'#bbf7d0':'#bfdbfe',marginBottom:'12px'}},children);
var Spinner = typeof Spinner !== 'undefined' ? Spinner : () => React.createElement('div',{style:{width:'24px',height:'24px',border:'3px solid #e2e8f0',borderTop:'3px solid #6366f1',borderRadius:'50%',animation:'spin 1s linear infinite'}});
var Progress = typeof Progress !== 'undefined' ? Progress : ({value,max}) => React.createElement('div',{style:{height:'8px',background:'#e2e8f0',borderRadius:'4px',overflow:'hidden'}},React.createElement('div',{style:{height:'100%',width:((value||0)/(max||100)*100)+'%',background:'#6366f1',borderRadius:'4px',transition:'width .3s'}}));
JS;
$jsxCode = $globals . "\n" . $jsxCode;
$hash = substr(md5($jsxCode), 0, 8);
$filename = "artifact_{$hash}_" . time() . ".html";
$path = "/var/www/weval/wevia-ia/downloads/{$filename}";
$url = "/wevia-ia/downloads/{$filename}";
$safeCode = str_replace('</script>', '<\/script>', $jsxCode);
$html = '<!DOCTYPE html><html><head><meta charset="UTF-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.23.9/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.8/axios.min.js"></script>
<style>body{font-family:system-ui,sans-serif;padding:20px;margin:0;background:#f8fafc;color:#1e293b}
h1,h2,h3{color:#1e293b;margin:12px 0}
table{width:100%;border-collapse:collapse;margin:12px 0}th,td{border:1px solid #e2e8f0;padding:8px 12px;text-align:left}th{background:#f1f5f9}
ul{margin:8px 0;padding-left:20px}li{margin:4px 0}
</style></head><body>
<div id="root"><p style="color:#6366f1;font-style:italic">Chargement du dashboard...</p></div>
<script>var exports={},module={exports:exports};function require(m){var map={"react":React,"react-dom":ReactDOM,"axios":typeof axios!=="undefined"?axios:{get:function(){return Promise.resolve({data:[]})},post:function(){return Promise.resolve({data:{}})}}}; return map[m]||{}}</script>
<script type="text/babel">' . $safeCode . '
try {
const _names=["Dashboard","DashboardKPI","SupplyChainKPI","DashboardKPISupplyChain","App","Main","__COMP__"];
let _C=window.__COMP__||null;if(!_C){for(const n of _names){try{_C=eval(n);if(_C)break}catch(e){}}}
if(_C)ReactDOM.render(React.createElement(_C),document.getElementById("root"));
else document.getElementById("root").innerHTML="<p>Composant non trouve</p>";
}catch(e){document.getElementById("root").innerHTML="<pre style=color:red>"+e.message+"</pre>";}
</script>
<script>window.onerror=function(m){document.getElementById("root").innerHTML+="<pre style=color:red>"+m+"</pre>"}</script>
</body></html>';
file_put_contents($path, $html);
$response .= "\n\n---\n\n> \xf0\x9f\x94\x97 **[Voir le dashboard en live]({$url})**\n";
return ['response' => $response, 'artifact_url' => $url, 'artifact_type' => 'jsx'];
}
return null;
}