212 lines
13 KiB
PHP
Executable File
212 lines
13 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.chatbot_knowledge")->fetchColumn(); } catch(Exception $e) {}
|
|
try { $memCount = $pdo->query("SELECT COUNT(*) FROM admin.chatbot_memory")->fetchColumn(); } catch(Exception $e) {}
|
|
?><!DOCTYPE html>
|
|
<html data-theme="dark" lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
<title>WEVAL MIND</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
<style>
|
|
*{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:#d4a574}
|
|
.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:#d4a574}
|
|
.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,#d4a574,#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-box{background:linear-gradient(135deg,#fff8f2,#fef5ed);border:1px solid #f0e6dc;border-radius:12px;padding:14px 18px;margin-bottom:14px}
|
|
.think-head{display:flex;align-items:center;gap:12px;cursor:pointer}
|
|
.think-title{font-size:14px;font-weight:500;color:#d4a574}
|
|
.think-steps{margin-top:12px;padding-left:32px;max-height:0;overflow:hidden;transition:max-height .3s}
|
|
.think-box.exp .think-steps{max-height:200px}
|
|
.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:#d4a574;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:#d4a574;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:#d4a574;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:1px solid #e8ddd4;border-radius:24px;padding:12px 16px}
|
|
.input-box:focus-within{border-color:#d4a574;box-shadow:0 0 0 3px rgba(212,165,116,.15)}
|
|
#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,#d4a574,#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}
|
|
.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:#d4a574}
|
|
::-webkit-scrollbar{width:6px}
|
|
::-webkit-scrollbar-thumb{background:#d4d0c8;border-radius:3px}
|
|
</style>
|
|
|
|
</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>
|
|
<select id="provider">
|
|
<option value="ollama" selected>Ollama (Interne)</option><option value="cerebras">Cerebras</option>
|
|
<option value="ollama" selected>Ollama (Interne)</option><option value="groq">Groq</option>
|
|
<option value="chatgpt">ChatGPT</option>
|
|
<option value="deepseek">DeepSeek</option>
|
|
<option value="gemini">Gemini</option>
|
|
<option value="claude">Claude</option>
|
|
<option value="hyperbolic">Hyperbolic</option>
|
|
<option value="mistral">Mistral</option>
|
|
<option value="cohere">Cohere</option>
|
|
<option value="sambanova">SambaNova</option>
|
|
<option value="ollama">Ollama</option>
|
|
</select>
|
|
<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">
|
|
<textarea id="msg" placeholder="Message WEVAL MIND..." rows="1" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send()}"></textarea>
|
|
<button class="send" id="sendBtn" onclick="send()">Go</button>
|
|
</div>
|
|
<div class="footer"><?=$kbCount?> KB - <?=$memCount?> memoires</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
var msgs=document.getElementById('msgs'),msgIn=document.getElementById('msg'),sendBtn=document.getElementById('sendBtn'),provider=document.getElementById('provider'),convList=document.getElementById('convList');
|
|
var chatHistory=[],processing=false,sessionId='<?=$_SESSION['hamid_session']?>';
|
|
msgIn.addEventListener('input',function(){this.style.height='auto';this.style.height=Math.min(this.scrollHeight,150)+'px'});
|
|
loadConversations();
|
|
|
|
function newChat(){
|
|
chatHistory=[];sessionId='hamid_'+Math.random().toString(36).substr(2,16);
|
|
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>';
|
|
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="this.parentElement.classList.toggle(\'exp\')">'+sparkle()+'<span class="think-title">Reflexion...</span></div><div class="think-steps"><div class="step active"><span class="dot"></span>Analyse...</div><div class="step"><span class="dot"></span>Knowledge Base...</div><div class="step"><span class="dot"></span>Generation...</div></div></div>';}
|
|
function skeleton(){return '<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='OK';box.classList.remove('exp');}},400);}
|
|
|
|
function send(){
|
|
var m=msgIn.value.trim();if(!m||processing)return;
|
|
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});
|
|
fetch('hamid-api.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m,history:chatHistory.slice(-10),provider:provider.value,session_id:sessionId})})
|
|
.then(function(r){return r.json()})
|
|
.then(function(d){
|
|
setTimeout(function(){
|
|
var resp=bot.querySelector('.resp');
|
|
if(d.error){resp.innerHTML='<div class="bubble"><p>Erreur: '+d.error+'</p></div>';}
|
|
else{
|
|
var html='<div class="bubble">'+marked.parse(d.response)+'</div>';
|
|
html+='<div class="meta">'+d.provider+' - '+d.model+' - '+d.duration+'ms';
|
|
if(d.kb_count>0)html+='<span class="kb-badge">KB:'+d.kb_count+'</span>';
|
|
html+='</div>';
|
|
resp.innerHTML=html;
|
|
chatHistory.push({role:'assistant',content:d.response});
|
|
loadConversations();
|
|
}
|
|
msgs.scrollTop=msgs.scrollHeight;processing=false;sendBtn.disabled=false;
|
|
},800);
|
|
}).catch(function(e){bot.querySelector('.resp').innerHTML='<div class="bubble"><p>Erreur</p></div>';processing=false;sendBtn.disabled=false;});
|
|
}
|
|
function esc(t){var d=document.createElement('div');d.textContent=t;return d.innerHTML;}
|
|
msgIn.focus();
|
|
</script>
|
|
|
|
|
|
|
|
</body>
|
|
</html>
|