Files
wevads-platform/scripts/weval-mind-fullscreen.php
2026-02-26 04:53:11 +01:00

855 lines
45 KiB
PHP
Executable File

<?php
session_start();
$_SESSION['hamid_session'] = $_SESSION['hamid_session'] ?? 'hamid_' . bin2hex(random_bytes(8));
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123");
$kbCount = 0; $memCount = 0;
try { $kbCount = $pdo->query("SELECT COUNT(*) FROM admin.commonia_knowledge")->fetchColumn(); } catch(Exception $e) {}
try { $memCount = $pdo->query("SELECT COUNT(*) FROM admin.chatbot_memory")->fetchColumn(); } catch(Exception $e) {}
?><!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>WEVAL MIND - Assistant IA</title>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
.sparkle-container {
display: inline-flex;
align-items: center;
gap: 8px;
}
.sparkle {
width: 20px;
height: 20px;
position: relative;
}
.sparkle svg {
width: 100%;
height: 100%;
animation: sparkle-rotate 2s linear infinite;
}
.sparkle-star {
fill: none;
stroke: #b48cff;
stroke-width: 2;
stroke-linecap: round;
animation: sparkle-pulse 1.5s ease-in-out infinite;
}
@keyframes sparkle-rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes sparkle-pulse {
0%, 100% { opacity: 0.4; stroke-width: 1.5; }
50% { opacity: 1; stroke-width: 2.5; }
}
.sparkle-dots {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
.sparkle-dot {
position: absolute;
width: 3px;
height: 3px;
background: #00d4ff;
border-radius: 50%;
animation: sparkle-dot 1.5s ease-in-out infinite;
}
.sparkle-dot:nth-child(1) { top: 0; left: 50%; transform: translateX(-50%); animation-delay: 0s; }
.sparkle-dot:nth-child(2) { top: 50%; right: 0; transform: translateY(-50%); animation-delay: 0.2s; }
.sparkle-dot:nth-child(3) { bottom: 0; left: 50%; transform: translateX(-50%); animation-delay: 0.4s; }
.sparkle-dot:nth-child(4) { top: 50%; left: 0; transform: translateY(-50%); animation-delay: 0.6s; }
@keyframes sparkle-dot {
0%, 100% { opacity: 0.3; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1.2); }
}
/* === THINKING BOX CLAUDE STYLE === */
.think-box{background:linear-gradient(135deg,rgba(180,140,255,0.06),rgba(140,180,255,0.03));border:1px solid rgba(180,140,255,0.2);border-radius:12px;margin-bottom:12px;overflow:hidden}
.think-box .think-head{display:flex;align-items:center;gap:10px;padding:12px 16px;cursor:pointer;user-select:none}
.think-box .think-head:hover{background:rgba(180,140,255,0.05)}
.think-box .sparkle{width:18px;height:18px;display:flex;align-items:center;justify-content:center}
.think-box .sparkle svg{width:16px;height:16px;fill:#b48cff;animation:sparkle-spin 2s linear infinite}
.think-box.done .sparkle svg{animation:none;opacity:0.6}
@keyframes sparkle-spin{0%{transform:rotate(0deg) scale(1);opacity:0.7}50%{transform:rotate(180deg) scale(1.1);opacity:1}100%{transform:rotate(360deg) scale(1);opacity:0.7}}
.think-box .think-title{font-size:14px;font-weight:500;color:#b48cff}
.think-box .think-chevron{margin-left:auto;color:#b48cff;transition:transform 0.3s;font-size:12px}
.think-box.exp .think-chevron{transform:rotate(180deg)}
.think-box .think-steps{max-height:0;overflow:hidden;transition:max-height 0.3s ease}
.think-box.exp .think-steps{max-height:400px}
.think-box .think-content{padding:12px 16px;font-size:13px;color:#64748b;line-height:1.7;border-top:1px solid rgba(180,140,255,0.15);background:rgba(0,0,0,0.02);white-space:pre-wrap}
/* === FIN THINKING === */
.thinking-text {
color: #00d4ff;
font-size: 14px;
font-weight: 500;
}
.thinking-details {
font-size: 12px;
color: #888;
margin-top: 4px;
}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:Inter,-apple-system,sans-serif;background:linear-gradient(135deg,#fef7f0,#fdf2e9);height:100vh;display:flex;overflow:hidden}
.sidebar{width:260px;background:#fdf8f4;border-right:1px solid #f0e6dc;padding:16px;display:flex;flex-direction:column}
.new-btn{display:flex;align-items:center;gap:8px;padding:12px;background:#fff;border:1px solid #e8ddd4;border-radius:8px;cursor:pointer;font-size:14px;color:#5d4e37;width:100%}
.new-btn:hover{background:#fff8f2;border-color:#b48cff}
.conv-list{margin-top:16px;flex:1;overflow-y:auto}
.conv-item{padding:10px;border-radius:8px;cursor:pointer;margin-bottom:4px;font-size:13px;color:#5d4e37;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.conv-item:hover{background:#f5ebe0}
.no-conv{color:#9a8b7a;font-size:13px;text-align:center;padding:30px}
.main{flex:1;display:flex;flex-direction:column}
.header{padding:12px 24px;background:#fdf8f4;border-bottom:1px solid #f0e6dc;display:flex;align-items:center;justify-content:space-between}
.title{font-size:18px;font-weight:600;color:#5d4e37}
select{padding:8px 12px;border:1px solid #e8ddd4;border-radius:8px;background:#fff;font-size:13px;color:#5d4e37;cursor:pointer}
.hbtn{padding:8px 12px;background:0;border:1px solid #e8ddd4;border-radius:6px;cursor:pointer;color:#7a6b5a;margin-left:8px}
.hbtn:hover{background:#fff;border-color:#b48cff}
.messages{flex:1;overflow-y:auto;padding:24px}
.msg{display:flex;gap:16px;margin-bottom:24px;max-width:800px;margin-left:auto;margin-right:auto;animation:fadeIn .3s}
@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
.msg.user{flex-direction:row-reverse}
.avatar{width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
.msg.bot .avatar{background:linear-gradient(135deg,#00d4ff,#c49464);color:#fff}
.msg.user .avatar{background:#e8ddd4}
.msg-content{flex:1;min-width:0}
.msg.user .msg-content{text-align:right}
.bubble{display:inline-block;padding:14px 18px;border-radius:18px;max-width:100%;text-align:left;line-height:1.7;font-size:15px}
.msg.bot .bubble{background:#fff;border:1px solid #f0e6dc;border-radius:18px 18px 18px 4px}
.msg.user .bubble{background:#f5ebe0;border-radius:18px 18px 4px 18px}
.bubble p{margin:0 0 10px;color:#3d3429}.bubble p:last-child{margin:0}
.bubble code{background:#f5ebe0;padding:2px 6px;border-radius:4px;font-size:13px}
.bubble pre{background:#2d2a26;color:#f5ebe0;padding:14px;border-radius:8px;overflow-x:auto;margin:12px 0}
.bubble pre code{background:0;padding:0;color:inherit}
.bubble ul,.bubble ol{margin:10px 0;padding-left:24px}
.meta{font-size:11px;color:#9a8b7a;margin-top:8px}
.kb-badge{background:#e8f5e9;color:#2e7d32;padding:2px 8px;border-radius:10px;font-size:10px;margin-left:8px}
.think-head{display:flex;align-items:center;gap:12px;cursor:pointer}
.think-title{font-size:14px;font-weight:500;color:#b48cff}
.think-steps{margin-top:12px;padding-left:32px;max-height:0;overflow:hidden;transition:max-height .3s}
.think-content{font-size:13px;color:#6b7280;line-height:1.6;padding:8px 0;white-space:pre-wrap}
.think-box.exp .think-steps{max-height:300px;padding:12px;background:linear-gradient(135deg,rgba(180,140,255,0.08),rgba(140,180,255,0.05));border-radius:0 0 12px 12px;border-top:1px solid rgba(180,140,255,0.15)}
.step{display:flex;align-items:center;gap:10px;padding:5px 0;font-size:12px;color:#9a8b7a}
.step.active{color:#5d4e37;font-weight:500}
.step.done{color:#4caf50}
.dot{width:6px;height:6px;border-radius:50%;background:#d4d0c8}
.step.active .dot{background:#00d4ff;animation:pulse 1s infinite}
.step.done .dot{background:#4caf50}
@keyframes pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.6);opacity:.5}}
.sparkle{position:relative;width:20px;height:20px;display:flex;align-items:center;justify-content:center}
.sparkle svg{width:20px;height:20px;fill:#00d4ff;animation:sparkRotate 2s ease-in-out infinite}
@keyframes sparkRotate{0%,100%{transform:scale(1) rotate(0)}25%{transform:scale(1.25) rotate(15deg)}50%{transform:scale(.85) rotate(-10deg)}75%{transform:scale(1.15) rotate(8deg)}}
.sparkle::before,.sparkle::after{content:'';position:absolute;width:5px;height:5px;background:#00d4ff;border-radius:50%;animation:sparkPop 1.5s ease-in-out infinite}
.sparkle::before{top:-5px;right:-3px}
.sparkle::after{bottom:-5px;left:-3px;animation-delay:.7s}
@keyframes sparkPop{0%,100%{transform:scale(0);opacity:0}50%{transform:scale(1.2);opacity:1}}
.skel{height:18px;margin-bottom:14px;border-radius:9px;background:linear-gradient(90deg,#f5ebe0,#faf5f0,#f5ebe0);background-size:400% 100%;animation:shimmer 1.4s ease infinite}
.skel:nth-child(1){width:70%}.skel:nth-child(2){width:100%}.skel:nth-child(3){width:85%}.skel:nth-child(4){width:55%}
@keyframes shimmer{0%{background-position:100% 50%}100%{background-position:0 50%}}
.input-area{padding:16px 24px 24px;background:#fdf8f4;border-top:1px solid #f0e6dc}
.input-wrap{max-width:800px;margin:0 auto}
.input-box{display:flex;align-items:flex-end;gap:12px;background:#fff;border:2px dashed transparent;border-radius:24px;padding:12px 16px;transition:all .2s;position:relative}
.input-box:focus-within{border-color:#b48cff;box-shadow:0 0 0 3px rgba(212,165,116,.15)}
.input-box.dragover{border-color:#b48cff;background:#fff8f2}
#msg{flex:1;border:0;outline:0;font-size:15px;resize:none;max-height:150px;line-height:1.5;background:0;font-family:inherit}
.send{width:40px;height:40px;border-radius:50%;border:0;background:linear-gradient(135deg,#00d4ff,#c49464);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center}
.send:hover{transform:scale(1.08)}
.send:disabled{background:#d4d0c8;cursor:not-allowed;transform:none}
.attach-btn{width:36px;height:36px;border-radius:50%;border:1px solid #e8ddd4;background:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#7a6b5a}
.attach-btn:hover{border-color:#b48cff;background:#fff8f2}
.footer{text-align:center;margin-top:10px;font-size:12px;color:#9a8b7a}
.file-link{display:inline-flex;align-items:center;gap:8px;padding:10px 16px;background:#fff8f2;border:1px solid #e8ddd4;border-radius:10px;margin-top:12px;color:#5d4e37;text-decoration:none;font-size:13px}
.file-link:hover{border-color:#b48cff}
.art-panel{width:0;background:#fdf8f4;border-left:1px solid #f0e6dc;display:flex;flex-direction:column;transition:width .3s;overflow:hidden}
.art-panel.open{width:400px}
.art-head{padding:16px;border-bottom:1px solid #f0e6dc;display:flex;align-items:center;justify-content:space-between}
.art-title{font-size:15px;font-weight:600;color:#5d4e37}
.art-close{width:30px;height:30px;border:0;background:0;cursor:pointer;border-radius:6px;color:#7a6b5a;font-size:18px}
.art-close:hover{background:#f5ebe0}
.art-list{padding:12px;overflow-y:auto;max-height:200px}
.art-item{display:flex;align-items:center;gap:12px;padding:12px;background:#fff;border:1px solid #e8ddd4;border-radius:10px;margin-bottom:8px;cursor:pointer}
.art-item:hover{border-color:#b48cff;background:#fff8f2}
.art-icon{width:40px;height:40px;background:#f5ebe0;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#7a6b5a}
.art-info{flex:1;min-width:0}
.art-name{font-size:13px;color:#3d3429;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.art-type{font-size:11px;color:#9a8b7a;text-transform:uppercase}
.art-preview{flex:1;background:#fff;display:flex;align-items:center;justify-content:center;color:#9a8b7a;font-size:13px;flex-direction:column;gap:10px;padding:20px}
.art-preview iframe{width:100%;height:100%;border:0}
.upload-preview{display:flex;flex-wrap:wrap;gap:8px;margin-top:8px}
.upload-item{background:#f5ebe0;padding:6px 12px;border-radius:6px;font-size:12px;color:#5d4e37;display:flex;align-items:center;gap:6px}
.upload-item .remove{cursor:pointer;color:#c49464}
::-webkit-scrollbar{width:6px}::-webkit-scrollbar-thumb{background:#d4d0c8;border-radius:3px}
.mermaid {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
padding: 25px;
border-radius: 12px;
margin: 20px 0;
overflow-x: auto;
border: 2px solid #00d4ff;
box-shadow: 0 4px 15px rgba(0,212,255,0.2);
}
.mermaid svg {
max-width: 100%;
height: auto;
}
.mermaid .node rect,
.mermaid .node polygon,
.mermaid .node circle {
fill: #00d4ff !important;
stroke: #0099cc !important;
stroke-width: 2px !important;
}
.mermaid .nodeLabel {
color: #000 !important;
font-weight: 600 !important;
}
.mermaid .edgePath .path {
stroke: #0099cc !important;
stroke-width: 2px !important;
}
.mermaid .edgeLabel {
background: #fff !important;
color: #333 !important;
}
.mermaid .cluster rect {
fill: #e0f7ff !important;
stroke: #b48cff !important;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>mermaid.initialize({
startOnLoad:false,
theme:"base",
themeVariables:{
primaryColor:"#00d4ff",
primaryTextColor:"#000",
primaryBorderColor:"#0099cc",
lineColor:"#00d4ff",
secondaryColor:"#e0f7ff",
tertiaryColor:"#b3ecff",
background:"#ffffff",
mainBkg:"#00d4ff",
nodeBorder:"#0099cc",
clusterBkg:"#e0f7ff",
titleColor:"#000",
edgeLabelBackground:"#fff",
nodeTextColor:"#000"
},
flowchart:{
nodeSpacing:50,
rankSpacing:50,
curve:"basis",
htmlLabels:true
}
});</script>
<link rel="manifest" href="hamid-manifest.json">
<meta name="theme-color" content="#00d4ff">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="apple-touch-icon" href="hamid-icon-192.png">
</head>
<body>
<div class="sidebar">
<button class="new-btn" onclick="newChat()">+ Nouvelle conversation</button>
<div class="conv-list" id="convList"><div class="no-conv">Chargement...</div></div>
</div>
<div class="main">
<div class="header">
<div class="title">WEVAL MIND</div>
<div>
<button onclick="openLongDocModal()" style="background:#16213e;border:1px solid #00d4ff;color:#b48cff;padding:8px 15px;border-radius:8px;cursor:pointer;font-size:12px;margin-left:10px;" title="Générer document long">📚 Doc Long</button><span id="systemStatus" style="background:#16213e;border:1px solid #00d4ff;color:#00ff00;padding:5px 10px;border-radius:8px;font-size:11px;margin-left:10px;cursor:pointer" onclick="showProviderStatus()" title="Statut providers">● 8 OK</span>
<script>
async function updateSystemStatus(){
try{
var res=await fetch("hamid-status.php");
var data=await res.json();
var ready=Object.values(data.providers).filter(p=>p.status==="ready").length;
var down=Object.values(data.providers).filter(p=>p.status==="down").length;
var el=document.getElementById("systemStatus");
if(down>0){el.style.color="#ffa500";el.innerHTML="● "+ready+" OK / "+down+" DOWN";}
else{el.style.color="#00ff00";el.innerHTML="● "+ready+" OK";}
}catch(e){}
}
async function showProviderStatus(){
var res=await fetch("hamid-status.php");
var data=await res.json();
var msg="=== STATUT PROVIDERS ===\n\nMeilleur disponible: "+data.best_available+"\n\n";
for(var p in data.providers){
var info=data.providers[p];
var icon=info.status==="ready"?"✅":info.status==="down"?"❌":"⚠️";
msg+=icon+" "+p+" ("+info.type+") - "+info.speed;
if(info.down_reason)msg+=" ["+info.down_reason+"]";
msg+="\n";
}
alert(msg);
}
setInterval(updateSystemStatus,30000);
updateSystemStatus();
</script><select id="provider">
<optgroup label="🥇 GRATUITS ILLIMITÉS + RAPIDES">
<option value="cerebras" selected>⭐ Cerebras (Llama 3.1) - Gratuit∞</option>
<option value="ollama" selected>Ollama (Interne)</option><option value="groq">⭐ Groq (Llama 3.1) - 100k/jour</option>
<option value="sambanova">⭐ SambaNova (Llama 3.1) - Gratuit</option>
</optgroup>
<optgroup label="🥈 GRATUITS LIMITÉS">
<option value="mistral">Mistral Large - Gratuit limité</option>
<option value="cohere">Cohere Command - Gratuit limité</option>
<option value="huggingface">HuggingFace - Gratuit limité</option>
<option value="together">Together AI - Gratuit limité</option>
<option value="fireworks">Fireworks AI - Gratuit limité</option>
<option value="openrouter">OpenRouter - Multi-providers</option>
<option value="novita">Novita AI - Gratuit limité</option>
<option value="lepton">Lepton AI - Gratuit limité</option>
</optgroup>
<optgroup label="🥉 PREMIUM (Payants)">
<option value="claude">👁️ Claude Sonnet 4 - Vision!</option>
<option value="openai">OpenAI GPT-4o</option>
<option value="openai-mini">OpenAI GPT-4o-mini</option>
<option value="deepseek">DeepSeek V3</option>
<option value="perplexity">🔍 Perplexity - Web Search</option>
<option value="xai">xAI Grok</option>
<option value="ai21">AI21 Jamba</option>
</optgroup>
<optgroup label="🏠 LOCAL (Gratuit, CPU lent)">
<option value="ollama">Ollama (Mistral 7B)</option>
<option value="ollama-mini">Ollama Mini (TinyLlama)</option>
<option value="vllm">vLLM</option>
<option value="lmstudio">LM Studio</option>
<option value="localai">LocalAI</option>
</optgroup>
<optgroup label="🏢 ENTERPRISE">
<option value="azure">Azure OpenAI</option>
<option value="bedrock">AWS Bedrock</option>
<option value="cloudflare">Cloudflare AI</option>
</optgroup>
<optgroup label="🔗 WORKFLOWS">
<option value="n8n">n8n Workflow</option>
<option value="replicate">Replicate</option>
</optgroup>
<optgroup label="⚠️ HORS SERVICE">
<option value="gemini" disabled>Gemini (quota épuisé)</option>
<option value="hyperbolic" disabled>Hyperbolic (plus crédits)</option>
</optgroup>
</select>
<button class="hbtn" onclick="toggleArt()">Artifacts</button>
<button class="hbtn" onclick="location.href='hamid-admin.php'">Admin</button>
</div>
</div>
<div class="messages" id="msgs">
<div class="msg bot"><div class="avatar">H</div><div class="msg-content"><div class="bubble"><p>Bonjour ! Je suis <b>WEVAL MIND</b>. Comment puis-je t'aider ?</p></div></div></div>
</div>
<div class="input-area">
<div class="input-wrap">
<div class="input-box" id="inputBox">
<input type="file" id="fileInput" multiple hidden>
<button class="attach-btn" onclick="document.getElementById('fileInput').click()" title="Joindre fichier">📎</button>
<textarea id="msg" placeholder="Message WEVAL MIND ou glissez des fichiers..." rows="1" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send()}"></textarea>
<button class="send" id="sendBtn" onclick="send()">➤</button>
</div>
<div class="upload-preview" id="uploadPreview"></div>
<div class="footer"><?=$kbCount?> KB - <?=$memCount?> memoires</div>
</div>
</div>
</div>
<div class="art-panel" id="artPanel">
<div class="art-head"><span class="art-title">Artifacts</span><button class="art-close" onclick="toggleArt()">✕</button></div>
<div class="art-list" id="artList"></div>
<div class="art-preview" id="artPrev"><span style="font-size:48px">📄</span><span>Aucun artifact</span></div>
</div>
<script>
var msgs=document.getElementById('msgs'),msgIn=document.getElementById('msg'),sendBtn=document.getElementById('sendBtn'),provider=document.getElementById('provider'),convList=document.getElementById('convList'),artPanel=document.getElementById('artPanel'),artList=document.getElementById('artList'),artPrev=document.getElementById('artPrev'),inputBox=document.getElementById('inputBox'),fileInput=document.getElementById('fileInput'),uploadPreview=document.getElementById('uploadPreview');
var chatHistory=[],processing=false,sessionId='<?=$_SESSION['hamid_session']?>',uploadedFiles=[];
msgIn.addEventListener('input',function(){this.style.height='auto';this.style.height=Math.min(this.scrollHeight,150)+'px'});
loadConversations();
// Drag & Drop
inputBox.addEventListener('dragover',function(e){e.preventDefault();inputBox.classList.add('dragover')});
inputBox.addEventListener('dragleave',function(){inputBox.classList.remove('dragover')});
inputBox.addEventListener('drop',function(e){e.preventDefault();inputBox.classList.remove('dragover');handleFiles(e.dataTransfer.files)});
fileInput.addEventListener('change',function(){handleFiles(this.files)});
function handleFiles(files){
for(var i=0;i<files.length;i++){
var f=files[i];
if(f.size>10*1024*1024){alert('Fichier trop gros (max 10MB)');continue}
uploadedFiles.push(f);
var div=document.createElement('div');div.className='upload-item';
div.innerHTML=f.name.substring(0,20)+(f.name.length>20?'...':'')+' <span class="remove" onclick="removeFile('+i+')">✕</span>';
uploadPreview.appendChild(div);
}
}
function removeFile(idx){uploadedFiles.splice(idx,1);renderUploads()}
function renderUploads(){uploadPreview.innerHTML='';uploadedFiles.forEach(function(f,i){var div=document.createElement('div');div.className='upload-item';div.innerHTML=f.name.substring(0,20)+(f.name.length>20?'...':'')+' <span class="remove" onclick="removeFile('+i+')">✕</span>';uploadPreview.appendChild(div)})}
function toggleArt(){artPanel.classList.toggle('open')}
function newChat(){
chatHistory=[];sessionId='hamid_'+Math.random().toString(36).substr(2,16);uploadedFiles=[];uploadPreview.innerHTML='';
msgs.innerHTML='<div class="msg bot"><div class="avatar">H</div><div class="msg-content"><div class="bubble"><p>Nouvelle conversation !</p></div></div></div>';
artList.innerHTML='';loadConversations();
}
function loadConversations(){
fetch('hamid-api.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'list_conversations'})})
.then(function(r){return r.json()})
.then(function(d){
if(d.conversations&&d.conversations.length>0){
convList.innerHTML=d.conversations.map(function(c){return '<div class="conv-item" onclick="loadConv('+c.id+')">'+(c.title||'Conv #'+c.id)+'</div>'}).join('');
}else{convList.innerHTML='<div class="no-conv">Aucune conversation</div>';}
}).catch(function(){convList.innerHTML='<div class="no-conv">Erreur</div>';});
}
function loadConv(id){
fetch('hamid-api.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'load_conversation',id:id})})
.then(function(r){return r.json()})
.then(function(d){
if(d.conversation){
sessionId=d.conversation.session_id;
var messages=JSON.parse(d.conversation.messages||'[]');
chatHistory=messages.filter(function(m){return m.role});
msgs.innerHTML='';
messages.forEach(function(m){
if(m.role==='user')msgs.innerHTML+='<div class="msg user"><div class="avatar">U</div><div class="msg-content"><div class="bubble">'+esc(m.content)+'</div></div></div>';
else if(m.role==='assistant')msgs.innerHTML+='<div class="msg bot"><div class="avatar">H</div><div class="msg-content"><div class="bubble">'+marked.parse(m.content)+'</div></div></div>';
});
msgs.scrollTop=msgs.scrollHeight;
}
});
}
function sparkle(){return '<div class="sparkle"><svg viewBox="0 0 24 24"><path d="M12 0L14.59 9.41L24 12L14.59 14.59L12 24L9.41 14.59L0 12L9.41 9.41L12 0Z"/></svg></div>'}
function thinkBox(id){return '<div class="think-box exp" id="'+id+'"><div class="think-head" onclick="toggleThink(this)"><div class="sparkle"><svg viewBox="0 0 24 24"><path d="M12 0L14.59 9.41L24 12L14.59 14.59L12 24L9.41 14.59L0 12L9.41 9.41L12 0Z"/></svg></div><span class="think-title">Réflexion en cours...</span><span class="think-chevron">▼</span></div><div class="think-steps"></div></div>'}
function toggleThink(el){el.parentElement.classList.toggle("exp")}
function skeleton(){return '<div class="skel"></div><div class="skel"></div><div class="skel"></div><div class="skel"></div>'}
function animThink(id){var box=document.getElementById(id);if(!box)return;var steps=box.querySelectorAll('.step');var i=0;var iv=setInterval(function(){if(i<steps.length){steps[i].classList.remove('active');steps[i].classList.add('done');i++;if(steps[i])steps[i].classList.add('active');}else{clearInterval(iv);box.querySelector('.think-title').textContent='Reflexion terminee';setTimeout(function(){box.classList.remove('exp')},500)}},400)}
function updateThinkBox(id, thinkingText) {
var box = document.getElementById(id);
if(!box) return;
box.classList.add("done");
box.classList.remove("exp");
var stepsDiv = box.querySelector(".think-steps");
var title = box.querySelector(".think-title");
if (thinkingText && thinkingText.trim()) {
stepsDiv.innerHTML = '<div class="think-content">' + thinkingText.replace(/\n/g, "<br>") + '</div>';
title.textContent = "Réflexion terminée";
} else {
title.textContent = "Réflexion terminée";
}
}
function addArt(name,type,url){
if(!artPanel.classList.contains('open'))artPanel.classList.add('open');
var icons={pdf:'PDF',doc:'DOC',html:'HTM',txt:'TXT',md:'MD',png:'IMG',jpg:'IMG',jpeg:'IMG'};
artList.innerHTML+='<div class="art-item" onclick="showArt(\''+url+'\',\''+type+'\')"><div class="art-icon">'+(icons[type]||'FILE')+'</div><div class="art-info"><div class="art-name">'+name+'</div><div class="art-type">'+type+'</div></div></div>';
showArt(url,type);
}
function showArt(url,type){
if(type==='pdf'||type==='html')artPrev.innerHTML='<iframe src="'+url+'"></iframe>';
else if(type==='png'||type==='jpg'||type==='jpeg')artPrev.innerHTML='<img src="'+url+'" style="max-width:100%;max-height:100%">';
else artPrev.innerHTML='<div style="text-align:center"><span style="font-size:48px">📄</span><br><a href="'+url+'" target="_blank" class="file-link">Telecharger '+type.toUpperCase()+'</a></div>';
}
async function generateDoc(type,title,content){
try{
var res=await fetch('hamid-generate.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type:type,title:title,content:content,session_id:sessionId})});
var data=await res.json();
if(data.success){addArt(data.filename,data.type,data.url);return data}
return null;
}catch(e){return null}
}
function createFormData(m){
if(uploadedFiles.length===0)return JSON.stringify({message:m,history:chatHistory.slice(-10),provider:provider.value,session_id:sessionId});
var fd=new FormData();
fd.append("message",m);
fd.append("history",JSON.stringify(chatHistory.slice(-10)));
fd.append("provider",provider.value);
fd.append("session_id",sessionId);
uploadedFiles.forEach(function(f,i){fd.append("files[]",f)});
return fd;
}
function buildRequest(m){
if(uploadedFiles.length===0){
return JSON.stringify({message:m,history:chatHistory.slice(-10),provider:provider.value,session_id:sessionId});
}
var fd=new FormData();
fd.append("message",m);
fd.append("history",JSON.stringify(chatHistory.slice(-10)));
fd.append("provider",provider.value);
fd.append("session_id",sessionId);
uploadedFiles.forEach(function(f){fd.append("files[]",f)});
return fd;
}
async function send(){
var m=msgIn.value.trim();if((!m&&uploadedFiles.length===0)||processing)return;if(!m&&uploadedFiles.length>0)m="Analyse ce fichier";
processing=true;sendBtn.disabled=true;
msgs.innerHTML+='<div class="msg user"><div class="avatar">U</div><div class="msg-content"><div class="bubble">'+esc(m)+'</div></div></div>';
msgIn.value='';msgIn.style.height='auto';
var tid='t'+Date.now();
var bot=document.createElement('div');bot.className='msg bot';
bot.innerHTML='<div class="avatar">H</div><div class="msg-content">'+thinkBox(tid)+'<div class="resp">'+skeleton()+'</div></div>';
msgs.appendChild(bot);msgs.scrollTop=msgs.scrollHeight;
animThink(tid);
chatHistory.push({role:'user',content:m});
try{
console.log("uploadedFiles:", uploadedFiles, "length:", uploadedFiles.length);var req=buildRequest(m);console.log("Request type:", typeof req, req instanceof FormData ? "FormData" : "JSON");var opts={method:"POST",body:req};if(typeof req==="string")opts.headers={"Content-Type":"application/json"};var res=await fetch("hamid-api.php",opts);
var data=await res.json();
await new Promise(function(r){setTimeout(r,800)});
if(data.thinking){updateThinkBox(tid,data.thinking);}else{var box=document.getElementById(tid);if(box)box.classList.remove("exp");}
var resp=bot.querySelector('.resp');
if(data.error){
resp.innerHTML='<div class="bubble"><p>Erreur: '+data.error+'</p></div>';
}else{
var html='<div class="bubble">'+marked.parse(data.response.replace(/```mermaid\n([\s\S]*?)```/g, '<div class="mermaid">$1</div>'))+'</div>';
var provInfo = data.provider+' - '+data.model+' - '+data.duration+'ms';
if(data.failover_used) provInfo += ' <span style="color:#ffa500;font-size:10px">(failover depuis '+data.original_provider+')</span>';
html+='<div class="meta">'+provInfo;
if(data.kb_count>0)html+='<span class="kb-badge">KB:'+data.kb_count+'</span>';
if(data.memory_count>0)html+='<span class="kb-badge" style="background:#9f7aea">MEM:'+data.memory_count+'</span>';
if(data.kb_titles&&data.kb_titles.length)html+='<span style="font-size:10px;margin-left:5px">('+data.kb_titles.join(', ')+')</span>';
html+='</div>';
if(data.generated_doc&&data.generated_doc.url){
html+='<div style="margin-top:10px;padding:12px;background:linear-gradient(135deg,rgba(0,212,255,0.15),rgba(0,153,204,0.1));border-radius:8px;border-left:3px solid #00d4ff;">';
html+='<strong style="color:#b48cff">📄 PDF Généré</strong><br>';
html+='<a href="'+data.generated_doc.url+'" target="_blank" style="display:inline-block;margin-top:8px;padding:8px 16px;background:#00d4ff;color:#fff;border-radius:6px;text-decoration:none;font-weight:bold;">⬇️ Télécharger '+data.generated_doc.name+'</a>';
html+='<span style="color:#888;margin-left:10px;font-size:12px">('+Math.round(data.generated_doc.size/1024)+' KB)</span>';
html+='</div>';
addArt(data.generated_doc.name,'pdf',data.generated_doc.url);
}
resp.innerHTML=html;
try{mermaid.run({nodes:resp.querySelectorAll('.mermaid')})}catch(e){}
chatHistory.push({role:'assistant',content:data.response});
loadConversations();
}
}catch(e){bot.querySelector('.resp').innerHTML='<div class="bubble"><p>Erreur: '+e.message+'</p></div>'}
msgs.scrollTop=msgs.scrollHeight;processing=false;sendBtn.disabled=false;uploadedFiles=[];uploadPreview.innerHTML='';
}
function esc(t){var d=document.createElement('div');d.textContent=t;return d.innerHTML}
msgIn.focus();
</script>
<div id="longDocModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.8);z-index:9999;display:none;align-items:center;justify-content:center;">
<div style="background:#1a1a2e;padding:30px;border-radius:15px;max-width:500px;width:90%;border:2px solid #00d4ff;">
<h3 style="color:#b48cff;margin:0 0 20px 0;">📚 Générer Document Long (20-50 pages)</h3>
<input type="text" id="docTopic" placeholder="Sujet du document (ex: PowerMTA, Email Marketing...)" style="width:100%;padding:12px;border-radius:8px;border:1px solid #00d4ff;background:#16213e;color:#fff;margin-bottom:15px;box-sizing:border-box;">
<select id="docPages" style="width:100%;padding:12px;border-radius:8px;border:1px solid #00d4ff;background:#16213e;color:#fff;margin-bottom:15px;">
<option value="20">20 pages (~10 000 mots)</option>
<option value="30">30 pages (~15 000 mots)</option>
<option value="40">40 pages (~20 000 mots)</option>
<option value="50">50 pages (~25 000 mots)</option>
</select>
<div id="docProgress" style="display:none;margin:15px 0;padding:15px;background:#16213e;border-radius:8px;">
<div style="color:#b48cff;margin-bottom:10px;">⏳ Génération en cours...</div>
<div id="docProgressBar" style="height:8px;background:#333;border-radius:4px;overflow:hidden;">
<div id="docProgressFill" style="height:100%;background:#00d4ff;width:0%;transition:width 0.5s;"></div>
</div>
<div id="docProgressText" style="color:#888;font-size:12px;margin-top:8px;">Initialisation...</div>
</div>
<div style="display:flex;gap:10px;margin-top:20px;">
<button onclick="generateLongDoc()" style="flex:1;padding:12px;background:#00d4ff;color:#000;border:none;border-radius:8px;cursor:pointer;font-weight:bold;">🚀 Générer</button>
<button onclick="closeLongDocModal()" style="padding:12px 20px;background:#333;color:#fff;border:none;border-radius:8px;cursor:pointer;">Annuler</button>
</div>
<div id="docResult" style="display:none;margin-top:20px;padding:15px;background:#16213e;border-radius:8px;border-left:3px solid #00d4ff;"></div>
</div>
</div>
<script>
function openLongDocModal(){document.getElementById("longDocModal").style.display="flex";}
function closeLongDocModal(){document.getElementById("longDocModal").style.display="none";}
async function generateLongDoc(){
var topic=document.getElementById("docTopic").value.trim();
if(!topic){alert("Entrez un sujet");return;}
var pages=document.getElementById("docPages").value;
var progress=document.getElementById("docProgress");
var progressFill=document.getElementById("docProgressFill");
var progressText=document.getElementById("docProgressText");
var result=document.getElementById("docResult");
progress.style.display="block";
result.style.display="none";
progressFill.style.width="5%";
progressText.textContent="Connexion à l'API...";
// Simuler progression pendant le fetch
var pct=5;
var iv=setInterval(function(){
if(pct<90){pct+=Math.random()*5;progressFill.style.width=pct+"%";
var msgs=["Génération introduction...","Rédaction chapitre 1...","Rédaction chapitre 2...","Ajout exemples...","Rédaction chapitre 3...","Compilation sections...","Conversion PDF..."];
progressText.textContent=msgs[Math.floor(Math.random()*msgs.length)];}
},3000);
try{
var res=await fetch("hamid-generate-long.php",{
method:"POST",
headers:{"Content-Type":"application/json"},
body:JSON.stringify({topic:topic,pages:parseInt(pages),provider:document.getElementById("provider").value})
});
var data=await res.json();
clearInterval(iv);
progressFill.style.width="100%";
progressText.textContent="Terminé!";
if(data.success&&data.document){
result.style.display="block";
result.innerHTML="<strong style='color:#b48cff'>✅ Document généré!</strong><br><br>"+
"<b>"+data.document.name+"</b><br>"+
"Taille: "+Math.round(data.document.size/1024)+" KB<br>"+
"Mots: "+data.document.total_words+"<br>"+
"Sections: "+data.document.sections+"<br><br>"+
"<a href='"+data.document.url+"' target='_blank' style='display:inline-block;padding:10px 20px;background:#00d4ff;color:#000;border-radius:8px;text-decoration:none;font-weight:bold;'>⬇️ Télécharger le PDF</a>";
addArt(data.document.name,"pdf",data.document.url);
}else{
result.style.display="block";
result.innerHTML="<span style='color:#ff5555'>❌ Erreur: "+(data.error||"Génération échouée")+"</span>";
}
}catch(e){
clearInterval(iv);
result.style.display="block";
result.innerHTML="<span style='color:#ff5555'>❌ Erreur: "+e.message+"</span>";
}
}
</script>
<div id="codeExecModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.85);z-index:9999;align-items:center;justify-content:center;">
<div style="background:#1a1a2e;padding:25px;border-radius:15px;width:90%;max-width:700px;max-height:80vh;overflow:auto;border:2px solid #48bb78;">
<h3 style="color:#48bb78;margin:0 0 15px 0;">▶️ Exécuter du Code</h3>
<select id="codeLang" style="width:100%;padding:10px;border-radius:8px;border:1px solid #48bb78;background:#16213e;color:#fff;margin-bottom:10px;">
<option value="python">Python</option>
<option value="javascript">JavaScript</option>
<option value="php">PHP</option>
<option value="bash">Bash (limité)</option>
</select>
<textarea id="codeInput" style="width:100%;height:200px;padding:15px;border-radius:8px;border:1px solid #48bb78;background:#0d1117;color:#c9d1d9;font-family:monospace;font-size:13px;resize:vertical;box-sizing:border-box;" placeholder="# Votre code ici..."></textarea>
<div style="display:flex;gap:10px;margin-top:15px;">
<button onclick="executeCode()" style="flex:1;padding:12px;background:#48bb78;color:#000;border:none;border-radius:8px;cursor:pointer;font-weight:bold;">▶️ Exécuter</button>
<button onclick="closeCodeExec()" style="padding:12px 20px;background:#333;color:#fff;border:none;border-radius:8px;cursor:pointer;">Fermer</button>
</div>
<div id="codeOutput" style="display:none;margin-top:15px;padding:15px;background:#0d1117;border-radius:8px;border:1px solid #333;font-family:monospace;white-space:pre-wrap;max-height:200px;overflow:auto;"></div>
</div>
</div>
<script>
function openCodeExec(){document.getElementById("codeExecModal").style.display="flex";}
function closeCodeExec(){document.getElementById("codeExecModal").style.display="none";}
async function executeCode(){
var code=document.getElementById("codeInput").value;
var lang=document.getElementById("codeLang").value;
var output=document.getElementById("codeOutput");
output.style.display="block";
output.innerHTML="<span style=color:#888>Exécution en cours...</span>";
try{
var res=await fetch("hamid-execute.php",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({code:code,language:lang})});
var data=await res.json();
if(data.success){
output.innerHTML="<span style=color:#48bb78>✓ Succès ("+data.execution_time+"ms)</span>\n\n"+data.output;
}else{
output.innerHTML="<span style=color:#f56565>✗ Erreur</span>\n\n"+(data.error||data.output);
}
}catch(e){output.innerHTML="<span style=color:#f56565>Erreur: "+e.message+"</span>";}
}
async function webSearch(){
var query=prompt("Recherche Web:","");
if(!query)return;
var res=await fetch("hamid-search.php?q="+encodeURIComponent(query));
var data=await res.json();
var msg="🔍 Résultats pour: "+query+"\n\n";
if(data.instant_answer){
msg+="💡 "+data.instant_answer.answer+"\n("+data.instant_answer.source+")\n\n";
}
msg+="📄 "+data.count+" résultats:\n";
data.results.forEach(function(r,i){
msg+=(i+1)+". "+r.title+"\n "+r.url+"\n";
});
alert(msg);
}
</script>
<div id="imageGenModal" style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.85);z-index:9999;align-items:center;justify-content:center;">
<div style="background:#1a1a2e;padding:25px;border-radius:15px;width:90%;max-width:600px;border:2px solid #f687b3;">
<h3 style="color:#f687b3;margin:0 0 15px 0;">🎨 Générer une Image</h3>
<textarea id="imagePrompt" style="width:100%;height:100px;padding:15px;border-radius:8px;border:1px solid #f687b3;background:#16213e;color:#fff;resize:none;box-sizing:border-box;" placeholder="Décrivez l'image souhaitée en anglais...&#10;Ex: A futuristic city with flying cars, cyberpunk style, neon lights"></textarea>
<select id="imageProvider" style="width:100%;padding:10px;border-radius:8px;border:1px solid #f687b3;background:#16213e;color:#fff;margin-top:10px;">
<option value="pollinations">🆓 Pollinations (Gratuit, rapide)</option>
<option value="together">🆓 Together/FLUX (Gratuit, HD)</option>
<option value="stability">💰 Stability AI (Payant, pro)</option>
</select>
<div style="display:flex;gap:10px;margin-top:15px;">
<button onclick="generateImage()" style="flex:1;padding:12px;background:#f687b3;color:#000;border:none;border-radius:8px;cursor:pointer;font-weight:bold;">🎨 Générer</button>
<button onclick="closeImageGen()" style="padding:12px 20px;background:#333;color:#fff;border:none;border-radius:8px;cursor:pointer;">Fermer</button>
</div>
<div id="imageResult" style="display:none;margin-top:15px;text-align:center;"></div>
</div>
</div>
<script>
var voiceEnabled = false;
var recognition = null;
var synthesis = window.speechSynthesis;
function openImageGen(){document.getElementById("imageGenModal").style.display="flex";}
function closeImageGen(){document.getElementById("imageGenModal").style.display="none";}
async function generateImage(){
var prompt = document.getElementById("imagePrompt").value;
var provider = document.getElementById("imageProvider").value;
var result = document.getElementById("imageResult");
if(!prompt){alert("Entrez une description");return;}
result.style.display="block";
result.innerHTML="<div style=color:#f687b3>⏳ Génération en cours (30-60s)...</div>";
try{
var res = await fetch("hamid-image.php", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({prompt: prompt, provider: provider})
});
var data = await res.json();
if(data.success){
result.innerHTML = "<img src=\""+data.url+"\" style=\"max-width:100%;border-radius:10px;margin-top:10px\"><br><a href=\""+data.url+"\" download style=\"color:#f687b3\">⬇️ Télécharger</a><br><small style=color:#888>"+data.provider+"</small>";
addArt("image_"+Date.now()+".png", "png", data.url);
}else{
result.innerHTML = "<div style=color:#f56565>❌ "+data.error+"</div>";
}
}catch(e){
result.innerHTML = "<div style=color:#f56565>❌ Erreur: "+e.message+"</div>";
}
}
function toggleVoice(){
voiceEnabled = !voiceEnabled;
var btn = document.getElementById("voiceBtn");
if(voiceEnabled){
btn.style.background = "#fc8181";
btn.style.color = "#000";
btn.innerHTML = "🎤 ON";
startVoiceRecognition();
}else{
btn.style.background = "#16213e";
btn.style.color = "#fc8181";
btn.innerHTML = "🎤 Voice";
if(recognition) recognition.stop();
}
}
function startVoiceRecognition(){
if(!("webkitSpeechRecognition" in window)){
alert("Voice recognition non supporté par ce navigateur");
return;
}
recognition = new webkitSpeechRecognition();
recognition.continuous = true;
recognition.interimResults = true;
recognition.lang = "fr-FR";
recognition.onresult = function(e){
var transcript = "";
for(var i = e.resultIndex; i < e.results.length; i++){
if(e.results[i].isFinal){
transcript = e.results[i][0].transcript;
document.getElementById("msg").value = transcript;
// Auto-send après reconnaissance
setTimeout(function(){
if(voiceEnabled && transcript.length > 3){
send();
}
}, 500);
}
}
};
recognition.onerror = function(e){
console.error("Voice error:", e.error);
};
recognition.start();
}
// Text-to-Speech pour les réponses
function speakText(text){
if(!voiceEnabled || !synthesis) return;
// Nettoyer le texte (enlever markdown, etc.)
var cleanText = text.replace(/\*\*([^*]+)\*\*/g, "$1")
.replace(/```[\s\S]*?```/g, "code omis")
.replace(/`([^`]+)`/g, "$1")
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.substring(0, 1000); // Limiter la longueur
var utterance = new SpeechSynthesisUtterance(cleanText);
utterance.lang = "fr-FR";
utterance.rate = 1.1;
synthesis.speak(utterance);
}
// Hook pour parler les réponses automatiquement
var originalAddResponse = null;
</script>
<script>
// DISABLED: if("serviceWorker" in navigator){
// navigator.serviceWorker.register("hamid-sw.js");
}
</script>
</body>
</html>
<style>
.think-box { display:flex; align-items:center; gap:10px; padding:10px; }
.think-box .sparkle-icon {
color:#b48cff;
font-size:18px;
animation: sparkle-anim 1.5s ease-in-out infinite;
}
@keyframes sparkle-anim {
0%, 100% { opacity:0.4; transform:scale(0.9) rotate(0deg); }
50% { opacity:1; transform:scale(1.2) rotate(180deg); }
}
.think-text { color:#b48cff !important; font-weight:500; }
.mermaid {
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
padding: 25px;
border-radius: 12px;
margin: 20px 0;
overflow-x: auto;
border: 2px solid #00d4ff;
box-shadow: 0 4px 15px rgba(0,212,255,0.2);
}
.mermaid svg {
max-width: 100%;
height: auto;
}
.mermaid .node rect,
.mermaid .node polygon,
.mermaid .node circle {
fill: #00d4ff !important;
stroke: #0099cc !important;
stroke-width: 2px !important;
}
.mermaid .nodeLabel {
color: #000 !important;
font-weight: 600 !important;
}
.mermaid .edgePath .path {
stroke: #0099cc !important;
stroke-width: 2px !important;
}
.mermaid .edgeLabel {
background: #fff !important;
color: #333 !important;
}
.mermaid .cluster rect {
fill: #e0f7ff !important;
stroke: #b48cff !important;
}
</style>