381 lines
22 KiB
Plaintext
Executable File
381 lines
22 KiB
Plaintext
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>HAMID - 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: #00d4ff;
|
|
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-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:#00d4ff}
|
|
.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:#00d4ff}
|
|
.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:#00d4ff}
|
|
.think-steps{margin-top:12px;padding-left:32px;max-height:0;overflow:hidden;transition:max-height .3s}
|
|
|
|
.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:#00d4ff;box-shadow:0 0 0 3px rgba(212,165,116,.15)}
|
|
.input-box.dragover{border-color:#00d4ff;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:#00d4ff;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:#00d4ff}
|
|
.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:#00d4ff;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}
|
|
</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">HAMID</div>
|
|
<div>
|
|
<select id="provider">
|
|
<option value="cerebras">Cerebras</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>
|
|
<option value="ollama-mini">Ollama Mini</option>
|
|
</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>HAMID</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 HAMID 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="this.parentElement.classList.toggle(\'exp\')">'+sparkle()+'<span class="think-title">Reflexion en cours...</span></div><div class="think-steps"><div class="step active"><span class="dot"></span>Analyse de la demande...</div><div class="step"><span class="dot"></span>Consultation Knowledge Base...</div><div class="step"><span class="dot"></span>Structuration de la reponse...</div><div class="step"><span class="dot"></span>Generation du contenu...</div></div></div>'}
|
|
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 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)});
|
|
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)+'</div>';
|
|
html+='<div class="meta">'+data.provider+' - '+data.model+' - '+data.duration+'ms';
|
|
if(data.kb_count>0)html+='<span class="kb-badge">KB:'+data.kb_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.generate_doc&&data.generate_doc.type&&data.generate_doc.title){
|
|
var doc=await generateDoc(data.generate_doc.type,data.generate_doc.title,data.response);
|
|
if(doc)html+='<a href="'+doc.url+'" target="_blank" class="file-link">📄 '+doc.filename+'</a>';
|
|
}
|
|
resp.innerHTML=html;
|
|
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>
|
|
</body>
|
|
</html>
|
|
<style>
|
|
.think-box { display:flex; align-items:center; gap:10px; padding:10px; }
|
|
.think-box .sparkle-icon {
|
|
color:#00d4ff;
|
|
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:#00d4ff !important; font-weight:500; }
|
|
</style>
|