149 lines
5.4 KiB
PHP
149 lines
5.4 KiB
PHP
<?php
|
|
/**
|
|
* WEVIA Render API — Enrichit les réponses IA avec des rendus visuels
|
|
* Mermaid → SVG, Markdown → HTML, Charts → PNG, Full → PDF
|
|
* Utilise: mmdc, matplotlib, wkhtmltopdf, CairoSVG, Graphviz
|
|
*/
|
|
set_time_limit(30);
|
|
header('Content-Type: application/json');
|
|
header('Access-Control-Allow-Origin: *');
|
|
if($_SERVER['REQUEST_METHOD']==='OPTIONS'){http_response_code(200);exit;}
|
|
if($_SERVER['REQUEST_METHOD']==='GET'){die(json_encode(['status'=>'ok','capabilities'=>['mermaid_svg','chart_png','pdf','graphviz']]));}
|
|
|
|
$in=json_decode(file_get_contents('php://input'),true)?:[];
|
|
$text=trim($in['text']??'');
|
|
$mode=trim($in['mode']??'auto'); // auto|mermaid|chart|pdf
|
|
if(!$text)die(json_encode(['error'=>'text required']));
|
|
|
|
$id=uniqid('wr_');
|
|
$assets=[];
|
|
|
|
// ═══ 1. MERMAID → SVG ═══
|
|
function render_mermaid($code,$id,$idx){
|
|
$mmd_file="/tmp/{$id}_m{$idx}.mmd";
|
|
$svg_file="/var/www/html/test-report/render/{$id}_m{$idx}.svg";
|
|
@mkdir("/var/www/html/test-report/render",0777,true);
|
|
file_put_contents($mmd_file,$code);
|
|
$png_file=str_replace('.svg','.png',$svg_file);
|
|
$out=shell_exec("python3 /opt/mermaid-render.py {$mmd_file} {$png_file} 2>&1");
|
|
$svg_file=$png_file;
|
|
if(file_exists($svg_file)){
|
|
return ['type'=>'mermaid_png','url'=>"/test-report/render/{$id}_m{$idx}.svg",'size'=>filesize($svg_file)];
|
|
}
|
|
return ['type'=>'mermaid_error','error'=>$out];
|
|
}
|
|
|
|
// ═══ 2. CHART → PNG (matplotlib) ═══
|
|
function render_chart($spec,$id,$idx){
|
|
$json_file="/tmp/{$id}_c{$idx}.json";
|
|
$png_file="/var/www/html/test-report/render/{$id}_c{$idx}.png";
|
|
@mkdir("/var/www/html/test-report/render",0777,true);
|
|
file_put_contents($json_file,json_encode($spec));
|
|
|
|
$py=<<<'PY'
|
|
import json,sys,warnings
|
|
warnings.filterwarnings("ignore")
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
import matplotlib.ticker as ticker
|
|
|
|
d=json.load(open(sys.argv[1]))
|
|
t=d.get("type","bar")
|
|
fig,ax=plt.subplots(figsize=(8,4))
|
|
ax.set_facecolor("#0d1117")
|
|
fig.patch.set_facecolor("#0d1117")
|
|
ax.tick_params(colors="#c8d6e5")
|
|
ax.xaxis.label.set_color("#c8d6e5")
|
|
ax.yaxis.label.set_color("#c8d6e5")
|
|
for s in ax.spines.values():s.set_color("#21262d")
|
|
|
|
labels=d.get("labels",[])
|
|
values=d.get("values",[])
|
|
colors=d.get("colors",["#7c3aed","#06b6d4","#10b981","#f59e0b","#ef4444","#ec4899"])
|
|
|
|
if t=="bar":
|
|
ax.bar(labels,values,color=colors[:len(labels)])
|
|
elif t=="pie":
|
|
ax.pie(values,labels=labels,colors=colors[:len(labels)],autopct="%1.0f%%",textprops={"color":"#c8d6e5"})
|
|
elif t=="line":
|
|
ax.plot(labels,values,color="#7c3aed",linewidth=2,marker="o")
|
|
ax.fill_between(range(len(labels)),values,alpha=0.2,color="#7c3aed")
|
|
|
|
if d.get("title"):ax.set_title(d["title"],color="#fff",fontsize=13,fontweight="bold")
|
|
if d.get("xlabel"):ax.set_xlabel(d["xlabel"])
|
|
if d.get("ylabel"):ax.set_ylabel(d["ylabel"])
|
|
plt.tight_layout()
|
|
plt.savefig(sys.argv[2],dpi=150,bbox_inches="tight",facecolor="#0d1117")
|
|
print("OK")
|
|
PY;
|
|
$py_file="/tmp/{$id}_c{$idx}.py";
|
|
file_put_contents($py_file,$py);
|
|
$out=shell_exec("python3 {$py_file} {$json_file} {$png_file} 2>&1");
|
|
if(file_exists($png_file)){
|
|
return ['type'=>'chart_png','url'=>"/test-report/render/{$id}_c{$idx}.png",'size'=>filesize($png_file)];
|
|
}
|
|
return ['type'=>'chart_error','error'=>$out];
|
|
}
|
|
|
|
// ═══ 3. GRAPHVIZ → SVG ═══
|
|
function render_graphviz($code,$id,$idx){
|
|
$dot_file="/tmp/{$id}_g{$idx}.dot";
|
|
$svg_file="/var/www/html/test-report/render/{$id}_g{$idx}.svg";
|
|
@mkdir("/var/www/html/test-report/render",0777,true);
|
|
file_put_contents($dot_file,$code);
|
|
shell_exec("dot -Tsvg {$dot_file} -o {$svg_file} 2>&1");
|
|
if(file_exists($svg_file)){
|
|
return ['type'=>'graphviz_svg','url'=>"/test-report/render/{$id}_g{$idx}.svg"];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// ═══ AUTO-DETECT & RENDER ═══
|
|
$rendered_text=$text;
|
|
$midx=0;
|
|
|
|
// Find mermaid code blocks
|
|
if(preg_match_all('/```(?:mermaid)?\s*((?:graph |flowchart |sequenceDiagram|classDiagram|gantt|pie|erDiagram|stateDiagram|journey)[\s\S]*?)```/i',$text,$matches)){
|
|
foreach($matches[1] as $code){
|
|
$result=render_mermaid(trim($code),$id,$midx);
|
|
$assets[]=$result;
|
|
if(isset($result['url'])){
|
|
$svg_tag='<img src="'.$result['url'].'" alt="Mermaid diagram" style="max-width:100%;background:#fff;border-radius:8px;padding:8px">';
|
|
$rendered_text=str_replace($matches[0][$midx],$svg_tag,$rendered_text);
|
|
}
|
|
$midx++;
|
|
}
|
|
}
|
|
|
|
// Find graphviz
|
|
if(preg_match_all('/```(?:dot|graphviz)\s*(digraph[\s\S]*?)```/i',$text,$gmatches)){
|
|
foreach($gmatches[1] as $i=>$code){
|
|
$result=render_graphviz(trim($code),$id,$i);
|
|
if($result)$assets[]=$result;
|
|
}
|
|
}
|
|
|
|
// Find chart requests (JSON chart spec)
|
|
if(preg_match_all('/```chart\s*(\{[\s\S]*?\})\s*```/i',$text,$cmatches)){
|
|
foreach($cmatches[1] as $i=>$spec){
|
|
$data=json_decode($spec,true);
|
|
if($data){
|
|
$result=render_chart($data,$id,$i);
|
|
$assets[]=$result;
|
|
if(isset($result['url'])){
|
|
$img_tag='<img src="'.$result['url'].'" alt="Chart" style="max-width:100%;border-radius:8px">';
|
|
$rendered_text=str_replace($cmatches[0][$i],$img_tag,$rendered_text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
die(json_encode([
|
|
'rendered_text'=>$rendered_text,
|
|
'assets'=>$assets,
|
|
'asset_count'=>count($assets),
|
|
'original_length'=>strlen($text),
|
|
'rendered_length'=>strlen($rendered_text)
|
|
]));
|