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

493 lines
32 KiB
PHP
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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; $convCount = 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) {}
try { $convCount = $pdo->query("SELECT COUNT(DISTINCT conversation_id) FROM admin.chatbot_history WHERE session_id = '".$_SESSION['hamid_session']."'")->fetchColumn(); } catch(Exception $e) {}
// Load providers from config
$providers = [];
try {
$stmt = $pdo->query("SELECT key, value FROM admin.commonia_config WHERE key LIKE '%_api_key' OR key LIKE '%_endpoint'");
$config = [];
while ($row = $stmt->fetch()) { $config[$row['key']] = $row['value']; }
} catch(Exception $e) { $config = []; }
// Detected provider list with timeouts from hamid-api.php
$allProviders = [
'cerebras' => ['name'=>'Cerebras','icon'=>'🧠','model'=>'llama-3.3-70b','type'=>'Gratuit','speed'=>'~2s'],
'groq' => ['name'=>'Groq','icon'=>'⚡','model'=>'llama-3.3-70b-versatile','type'=>'Gratuit','speed'=>'~3s'],
'sambanova' => ['name'=>'SambaNova','icon'=>'⚡','model'=>'Meta-Llama-3.1-8B','type'=>'Gratuit','speed'=>'~4s'],
'cloudflare' => ['name'=>'Cloudflare','icon'=>'☁️','model'=>'llama-3.1-8b-instruct','type'=>'Gratuit','speed'=>'~5s'],
'together' => ['name'=>'Together','icon'=>'🤝','model'=>'Llama-3.3-70B','type'=>'Gratuit','speed'=>'~5s'],
'fireworks' => ['name'=>'Fireworks','icon'=>'🎆','model'=>'llama-v3p1-70b','type'=>'Gratuit','speed'=>'~4s'],
'openrouter' => ['name'=>'OpenRouter','icon'=>'🔀','model'=>'auto','type'=>'Gratuit','speed'=>'~5s'],
'novita' => ['name'=>'Novita','icon'=>'✨','model'=>'llama-3.1-70b','type'=>'Gratuit','speed'=>'~5s'],
'lepton' => ['name'=>'Lepton','icon'=>'⚛️','model'=>'llama-3.3-70b','type'=>'Gratuit','speed'=>'~5s'],
'hyperbolic' => ['name'=>'Hyperbolic','icon'=>'📐','model'=>'llama-3.1-70b','type'=>'Gratuit','speed'=>'~5s'],
'huggingface' => ['name'=>'HuggingFace','icon'=>'🤗','model'=>'llama-3.1-8b','type'=>'Gratuit','speed'=>'~8s'],
'mistral' => ['name'=>'Mistral','icon'=>'🌀','model'=>'mistral-large-latest','type'=>'Gratuit limité','speed'=>'~5s'],
'deepseek' => ['name'=>'DeepSeek','icon'=>'🔍','model'=>'deepseek-chat','type'=>'Payant','speed'=>'~6s'],
'claude' => ['name'=>'Claude (Anthropic)','icon'=>'🤖','model'=>'claude-sonnet-4-20250514','type'=>'Payant','speed'=>'~8s'],
'openai' => ['name'=>'OpenAI GPT-4o','icon'=>'🟢','model'=>'gpt-4o','type'=>'Payant','speed'=>'~5s'],
'openai-mini' => ['name'=>'OpenAI GPT-4o-mini','icon'=>'🟢','model'=>'gpt-4o-mini','type'=>'Payant','speed'=>'~3s'],
'gemini' => ['name'=>'Google Gemini','icon'=>'💎','model'=>'gemini-2.0-flash','type'=>'Gratuit limité','speed'=>'~5s'],
'cohere' => ['name'=>'Cohere','icon'=>'🔷','model'=>'command-r-plus','type'=>'Gratuit limité','speed'=>'~5s'],
'ai21' => ['name'=>'AI21 Labs','icon'=>'🔬','model'=>'jamba-instruct','type'=>'Payant','speed'=>'~5s'],
'xai' => ['name'=>'xAI Grok','icon'=>'❌','model'=>'grok-beta','type'=>'Payant','speed'=>'~5s'],
'perplexity' => ['name'=>'Perplexity','icon'=>'🔮','model'=>'pplx-70b-online','type'=>'Payant','speed'=>'~5s'],
'vllm' => ['name'=>'vLLM (Custom)','icon'=>'🖥️','model'=>'custom','type'=>'Self-hosted','speed'=>'~3s'],
'ollama' => ['name'=>'Ollama (Local)','icon'=>'🦙','model'=>'phi:latest','type'=>'Local','speed'=>'~60s'],
'ollama-mini' => ['name'=>'Ollama Mini','icon'=>'🦙','model'=>'phi:latest','type'=>'Local','speed'=>'~30s'],
];
$providersJson = json_encode($allProviders);
?><!DOCTYPE html>
<html lang="fr" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>WEVAL MIND — AI Assistant</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root{--bg:#060a14;--bg2:#0a0f1a;--sf:#0c1220;--sf2:#111827;--sf3:#1a2332;--bd:#1e293b;--bd2:#2a3a4e;--tx:#e2e8f0;--tx2:#94a3b8;--mt:#64748b;--cy:#22d3ee;--cyd:rgba(34,211,238,.12);--gn:#34d399;--gnd:rgba(52,211,153,.12);--am:#fbbf24;--amd:rgba(251,191,36,.12);--rd:#f87171;--rdd:rgba(248,113,113,.12);--pu:#a78bfa;--pud:rgba(167,139,250,.12);--bl:#60a5fa;--bld:rgba(96,165,250,.12);--pk:#f472b6;--pkd:rgba(244,114,182,.12);--r:12px;--rs:8px;--f:'DM Sans',sans-serif;--m:'JetBrains Mono',monospace;--tr:.2s ease}
[data-theme="light"]{--bg:#f5f7fa;--bg2:#eef1f5;--sf:#fff;--sf2:#f8fafc;--sf3:#f1f5f9;--bd:#e2e8f0;--bd2:#cbd5e1;--tx:#0f172a;--tx2:#475569;--mt:#64748b;--cy:#0ea5e9;--cyd:rgba(14,165,233,.08);--gn:#10b981;--gnd:rgba(16,185,129,.08);--am:#f59e0b;--amd:rgba(245,158,11,.08);--rd:#ef4444;--rdd:rgba(239,68,68,.08);--pu:#8b5cf6;--pud:rgba(139,92,246,.08);--bl:#3b82f6;--bld:rgba(59,130,246,.08);--pk:#ec4899;--pkd:rgba(236,72,153,.08)}
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}html,body{height:100%;overflow:hidden}
body{background:var(--bg);color:var(--tx);font-family:var(--f);font-size:14px;line-height:1.6;transition:background var(--tr),color var(--tr)}
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:var(--bd);border-radius:10px}
/* Layout */
.app{display:grid;grid-template-columns:250px 1fr;height:100vh;transition:grid-template-columns var(--tr)}
.app.sc{grid-template-columns:0 1fr}
/* Sidebar */
.sb{background:var(--bg2);border-right:1px solid var(--bd);display:flex;flex-direction:column;overflow:hidden}
.sb-h{padding:14px 16px;border-bottom:1px solid var(--bd);display:flex;align-items:center;gap:8px}
.sb-h .br{font-size:15px;font-weight:700;flex:1}.sb-h .br b{color:var(--cy)}
.sb-new{width:calc(100% - 24px);margin:10px 12px;padding:9px;background:var(--cyd);border:1px solid rgba(34,211,238,.2);border-radius:var(--rs);color:var(--cy);font-family:var(--f);font-size:12px;font-weight:600;cursor:pointer;text-align:center;transition:all var(--tr)}
.sb-new:hover{background:rgba(34,211,238,.2)}
.sb-list{flex:1;overflow-y:auto;padding:4px 8px}
.sb-i{padding:8px 10px;border-radius:6px;cursor:pointer;font-size:12px;color:var(--tx2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:all var(--tr);margin-bottom:1px}
.sb-i:hover{background:var(--sf3);color:var(--tx)}.sb-i.a{background:var(--cyd);color:var(--cy)}
.sb-f{padding:10px 14px;border-top:1px solid var(--bd);font-size:11px;color:var(--mt);display:flex;gap:8px}
.sb-f b{color:var(--cy);font-family:var(--m)}
/* Main */
.mn{display:flex;flex-direction:column;height:100vh;overflow:hidden}
/* Topbar */
.tb{background:var(--sf);border-bottom:1px solid var(--bd);padding:6px 12px;display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap}
.tb-btn{width:30px;height:30px;background:none;border:1px solid var(--bd);border-radius:6px;color:var(--mt);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:15px;transition:all var(--tr);flex-shrink:0}
.tb-btn:hover{color:var(--tx);border-color:var(--bd2)}
.tb-title{font-size:13px;font-weight:600;margin-right:auto}.tb-title b{color:var(--cy)}
/* Capability pills */
.caps{display:flex;gap:3px;flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;flex-shrink:1;min-width:0}
.caps::-webkit-scrollbar{display:none}
.cap{padding:3px 8px;border-radius:14px;font-size:10px;font-weight:600;cursor:pointer;white-space:nowrap;border:1px solid var(--bd);background:var(--sf2);color:var(--tx2);transition:all var(--tr);display:flex;align-items:center;gap:4px}
.cap:hover{border-color:var(--bd2);color:var(--tx)}.cap.a{background:var(--cyd);border-color:var(--cy);color:var(--cy)}
.cap i{width:5px;height:5px;border-radius:50%;display:inline-block}
/* Provider selector */
.prov-sel{padding:3px 8px;border-radius:14px;border:1px solid var(--bd);background:var(--sf2);color:var(--tx2);font-family:var(--f);font-size:10px;font-weight:500;cursor:pointer;outline:none;max-width:200px;flex-shrink:0}
/* Status badge */
.badge{padding:2px 7px;border-radius:10px;font-size:9px;font-weight:700;display:inline-flex;align-items:center;gap:3px;flex-shrink:0}
.badge.ok{background:var(--gnd);color:var(--gn)}.badge.wn{background:var(--amd);color:var(--am)}
.badge::before{content:'';width:4px;height:4px;border-radius:50%;background:currentColor}
/* Theme toggle */
.thm{width:38px;height:22px;background:var(--sf3);border:1px solid var(--bd);border-radius:11px;cursor:pointer;position:relative;transition:all var(--tr);flex-shrink:0}
.thm::after{content:'';position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--cy);transition:all var(--tr)}
[data-theme="light"] .thm::after{left:18px;background:var(--am)}
.thm span{position:absolute;top:50%;transform:translateY(-50%);font-size:10px}.thm .m{left:4px}.thm .s{right:4px}
/* Tabs */
.tabs{display:flex;gap:2px;padding:6px 14px 0;border-bottom:1px solid var(--bd);background:var(--sf);flex-shrink:0}
.tab{padding:7px 14px;font-size:11px;font-weight:600;color:var(--mt);cursor:pointer;border-bottom:2px solid transparent;transition:all var(--tr);display:flex;align-items:center;gap:5px}
.tab:hover{color:var(--tx)}.tab.a{color:var(--cy);border-color:var(--cy)}
/* Chat */
.chat{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:12px}
.msg{display:flex;gap:10px;max-width:780px;width:100%;margin:0 auto;animation:fi .3s ease}
@keyframes fi{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
.msg.u{justify-content:flex-end}.msg.u .mc{order:-1}
.av{width:30px;height:30px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;flex-shrink:0}
.msg.ai .av{background:linear-gradient(135deg,var(--cyd),var(--bld));border:1px solid rgba(34,211,238,.2);color:var(--cy)}
.msg.u .av{background:var(--pud);border:1px solid rgba(167,139,250,.2);color:var(--pu)}
.bub{background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);padding:12px 16px;font-size:13px;line-height:1.7;max-width:660px;word-wrap:break-word}
.msg.u .bub{background:var(--sf3);border-color:var(--bd2)}
.bub code{background:var(--sf3);padding:1px 5px;border-radius:3px;font-family:var(--m);font-size:11.5px}
.bub pre{background:var(--bg2);border:1px solid var(--bd);border-radius:var(--rs);padding:12px;overflow-x:auto;margin:8px 0;font-family:var(--m);font-size:11.5px;line-height:1.5}
.bub img{max-width:100%;border-radius:8px;margin:8px 0}
.acts{display:flex;gap:3px;margin-top:6px;opacity:0;transition:opacity var(--tr)}.msg:hover .acts{opacity:1}
.act{padding:2px 7px;border-radius:4px;border:none;background:var(--sf3);color:var(--mt);font-size:10px;cursor:pointer;font-family:var(--f);transition:all var(--tr)}.act:hover{color:var(--tx)}
.think{background:var(--sf3);border-left:3px solid var(--pu);padding:8px 12px;margin:6px 0;border-radius:0 var(--rs) var(--rs) 0;font-size:12px;color:var(--tx2);font-style:italic}
/* Typing */
.typing{display:none;gap:4px;padding:12px 16px;align-items:center;max-width:780px;margin:0 auto}.typing.v{display:flex}
.td{width:6px;height:6px;background:var(--cy);border-radius:50%;animation:tb 1.4s infinite;opacity:.4}
.td:nth-child(2){animation-delay:.2s}.td:nth-child(3){animation-delay:.4s}
@keyframes tb{0%,60%,100%{transform:translateY(0);opacity:.4}30%{transform:translateY(-5px);opacity:1}}
/* Welcome */
.welc{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:30px;text-align:center}
.welc-logo{width:56px;height:56px;background:linear-gradient(135deg,var(--cy),var(--bl));border-radius:16px;display:flex;align-items:center;justify-content:center;font-size:24px;color:#fff;box-shadow:0 6px 24px rgba(34,211,238,.2)}
.welc h2{font-size:22px;font-weight:700}.welc h2 b{color:var(--cy)}
.welc p{color:var(--mt);font-size:13px;max-width:380px}
.welc-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:560px;width:100%;margin-top:8px}
.welc-c{background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);padding:14px;text-align:left;cursor:pointer;transition:all var(--tr)}
.welc-c:hover{border-color:var(--cy);background:var(--cyd);transform:translateY(-2px)}
.welc-c .i{font-size:18px;margin-bottom:6px}.welc-c .n{font-size:11px;font-weight:600;margin-bottom:3px}.welc-c .d{font-size:10px;color:var(--mt);line-height:1.3}
/* Input */
.inp-area{padding:10px 16px 14px;background:var(--bg);flex-shrink:0}
.inp-wrap{max-width:780px;margin:0 auto}
.tools{display:flex;gap:5px;margin-bottom:6px;flex-wrap:wrap}
.tool{padding:4px 10px;border-radius:16px;border:1px solid var(--bd);background:var(--sf);color:var(--tx2);font-family:var(--f);font-size:10.5px;font-weight:600;cursor:pointer;display:flex;align-items:center;gap:4px;transition:all var(--tr)}
.tool:hover{border-color:var(--cy);color:var(--cy)}.tool.a{background:var(--cyd);border-color:var(--cy);color:var(--cy)}
.ibox{background:var(--sf);border:1.5px solid var(--bd);border-radius:14px;padding:10px 14px;display:flex;align-items:flex-end;gap:8px;transition:border-color var(--tr),box-shadow var(--tr)}
.ibox:focus-within{border-color:var(--cy);box-shadow:0 0 0 3px var(--cyd)}
.ibox textarea{flex:1;background:none;border:none;outline:none;color:var(--tx);font-family:var(--f);font-size:13px;line-height:1.5;resize:none;max-height:140px;min-height:22px}
.ibox textarea::placeholder{color:var(--mt)}
.ib{width:30px;height:30px;border-radius:50%;border:none;background:none;color:var(--mt);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:15px;transition:all var(--tr);flex-shrink:0}
.ib:hover{color:var(--tx);background:var(--sf3)}
.send{width:34px;height:34px;border-radius:50%;border:none;background:var(--cy);color:#060a14;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:15px;font-weight:700;transition:all var(--tr);box-shadow:0 2px 8px rgba(34,211,238,.3);flex-shrink:0}
.send:hover{transform:scale(1.05)}
.inp-foot{display:flex;justify-content:space-between;margin-top:5px;font-size:10px;color:var(--mt)}
.inp-foot b{font-family:var(--m);color:var(--cy)}
/* Panels */
.panel{display:none;flex:1;overflow:hidden}.panel.a{display:flex;flex-direction:column}
.admin-p{flex:1;overflow-y:auto;padding:16px}
.admin-g{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;max-width:800px}
.acard{background:var(--sf);border:1px solid var(--bd);border-radius:var(--r);padding:16px}
.acard .l{font-size:9px;text-transform:uppercase;font-weight:700;color:var(--mt);letter-spacing:.5px;margin-bottom:6px}
.acard .v{font-family:var(--m);font-size:20px;font-weight:700}
.acard .v.c1{color:var(--cy)}.acard .v.c2{color:var(--gn)}.acard .v.c3{color:var(--pu)}.acard .v.c4{color:var(--am)}
/* File drop overlay */
.drop-overlay{display:none;position:absolute;inset:0;background:rgba(6,10,20,.9);z-index:50;align-items:center;justify-content:center;flex-direction:column;gap:12px;border:3px dashed var(--cy);border-radius:var(--r)}
.drop-overlay.v{display:flex}
.drop-overlay .ico{font-size:48px}.drop-overlay .txt{font-size:16px;font-weight:600;color:var(--cy)}
@media(max-width:768px){.app{grid-template-columns:0 1fr}.sb{position:fixed;left:-260px;z-index:100;width:250px;height:100vh}.sb.mo{left:0;box-shadow:4px 0 20px rgba(0,0,0,.5)}.welc-grid{grid-template-columns:1fr 1fr}.caps{max-width:50vw}}
</style>
</head>
<body>
<div class="app" id="app">
<!-- SIDEBAR -->
<aside class="sb" id="sidebar">
<div class="sb-h"><div class="br">WEVAL <b>MIND</b></div><span class="badge ok">Online</span></div>
<button class="sb-new" onclick="newConv()"> Nouvelle conversation</button>
<div class="sb-list" id="convList"></div>
<div class="sb-f">📚 KB: <b><?=number_format($kbCount)?></b> <span style="margin-left:auto">🧠 <b><?=number_format($memCount)?></b></span></div>
</aside>
<!-- MAIN -->
<main class="mn">
<!-- TOPBAR -->
<div class="tb">
<button class="tb-btn" onclick="toggleSB()">☰</button>
<div class="tb-title">WEVAL <b>MIND</b></div>
<div class="caps" id="capsRow">
<div class="cap a" data-c="normal" onclick="setCap(this)"><i style="background:var(--gn)"></i>Normal</div>
<div class="cap" data-c="doclong" onclick="setCap(this)"><i style="background:var(--bl)"></i>Doc Long</div>
<div class="cap" data-c="tot" onclick="setCap(this)"><i style="background:var(--rd)"></i>ToT</div>
<div class="cap" data-c="score" onclick="setCap(this)"><i style="background:var(--am)"></i>Score</div>
<div class="cap" data-c="brain" onclick="setCap(this)"><i style="background:var(--gn)"></i>Brain+</div>
<div class="cap" data-c="cot" onclick="setCap(this)"><i style="background:var(--pu)"></i>CoT</div>
<div class="cap" data-c="reflect" onclick="setCap(this)"><i style="background:var(--pk)"></i>Reflect</div>
<div class="cap" data-c="kb" onclick="setCap(this)"><i style="background:var(--bl)"></i>KB</div>
<div class="cap" data-c="rag" onclick="setCap(this)"><i style="background:var(--am)"></i>RAG</div>
<div class="cap" data-c="denise" onclick="setCap(this)"><i style="background:var(--pk)"></i>Denise</div>
</div>
<select class="prov-sel" id="provSel">
<option value="weval-mind">🧠 Weval Mind (Autonome)</option></select>
<span class="badge ok" id="statusBadge">● <?=($kbCount > 0 ? 'OK' : '—')?></span>
<div class="thm" onclick="toggleTheme()" title="Jour/Nuit"><span class="m">🌙</span><span class="s">☀️</span></div>
</div>
<!-- TABS -->
<div class="tabs">
<div class="tab a" onclick="swTab('chat',this)">💬 Conversation</div>
<div class="tab" onclick="swTab('artifacts',this)">🎨 Artifacts</div>
<div class="tab" onclick="swTab('admin',this)">⚙️ Admin</div>
</div>
<!-- CHAT PANEL -->
<div class="panel a" id="p-chat" style="position:relative">
<div class="drop-overlay" id="dropZone"><div class="ico">📁</div><div class="txt">Déposez vos fichiers ici</div></div>
<div class="chat" id="chatArea">
<div class="welc" id="welcome">
<div class="welc-logo">🧠</div>
<h2>WEVAL <b>MIND</b></h2>
<p>Assistant IA multi-provider — Vision, Code, RAG, Agents et plus.</p>
<div class="welc-grid">
<div class="welc-c" onclick="qp('Analyse cette image et décris ce que tu vois')"><div class="i">📸</div><div class="n">Vision</div><div class="d">Analyse d'images, OCR</div></div>
<div class="welc-c" onclick="qp('Crée une image artistique de')"><div class="i">🎨</div><div class="n">Créer Image</div><div class="d">Génération IA</div></div>
<div class="welc-c" onclick="qp('Écris un code Python pour')"><div class="i">💻</div><div class="n">Code</div><div class="d">Dev, debug, review</div></div>
<div class="welc-c" onclick="qp('Recherche dans la KB :')"><div class="i">📚</div><div class="n">RAG / KB</div><div class="d">Base de connaissances</div></div>
<div class="welc-c" onclick="qp('Analyse ce document')"><div class="i">📄</div><div class="n">Fichiers</div><div class="d">PDF, DOCX, vidéo</div></div>
<div class="welc-c" onclick="qp('Lance un agent autonome pour')"><div class="i">🤖</div><div class="n">Agent</div><div class="d">Tâches multi-étapes</div></div>
</div>
</div>
</div>
<div class="typing" id="typing"><div class="td"></div><div class="td"></div><div class="td"></div></div>
<!-- INPUT -->
<div class="inp-area">
<div class="inp-wrap">
<div class="tools">
<button class="tool" onclick="tt(this,'vision')">📸 Vision</button>
<button class="tool" onclick="tt(this,'file')">📎 Fichier</button>
<button class="tool" onclick="tt(this,'image')">🎨 Créer Image</button>
<button class="tool" onclick="tt(this,'agent')">🤖 Agent</button>
<button class="tool" onclick="tt(this,'stream')">▶ Stream</button>
<button class="tool" onclick="tt(this,'rag')">📚 RAG</button>
</div>
<div class="ibox">
<input type="file" id="fileIn" style="display:none" accept="image/*,video/*,.pdf,.docx,.txt,.csv,.xlsx,.json" multiple>
<button class="ib" onclick="document.getElementById('fileIn').click()" title="Joindre">📎</button>
<textarea id="msgIn" rows="1" placeholder="Message WEVAL MIND ou glissez des fichiers..." onkeydown="hk(event)" oninput="ar(this)"></textarea>
<button class="ib" onclick="startVoice()" id="voiceBtn" title="Voix">🎙</button>
<button class="ib" onclick="toggleTTS(this)" id="ttsBtn" title="TTS" style="font-size:11px">🔊</button>
<button class="send" onclick="send()" title="Envoyer">➤</button>
</div>
<div class="inp-foot">
<span>📚 <b><?=number_format($kbCount)?></b> KB · 🧠 <b><?=number_format($memCount)?></b> mém</span>
<span id="tkn">0 tokens</span>
</div>
</div>
</div>
</div>
<!-- ARTIFACTS PANEL -->
<div class="panel" id="p-artifacts">
<div style="flex:1;overflow-y:auto;padding:20px;display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:10px;align-content:start" id="artGrid">
<div style="grid-column:1/-1;text-align:center;padding:50px;color:var(--mt)"><div style="font-size:36px;margin-bottom:10px">🎨</div><div style="font-size:13px;font-weight:600">Aucun artifact</div></div>
</div>
</div>
<!-- ADMIN PANEL -->
<div class="panel" id="p-admin">
<div class="admin-p">
<h3 style="margin-bottom:14px;font-size:15px">Système WEVAL MIND</h3>
<div class="admin-g">
<div class="acard"><div class="l">Base de connaissances</div><div class="v c1"><?=number_format($kbCount)?></div></div>
<div class="acard"><div class="l">Mémoires</div><div class="v c2"><?=number_format($memCount)?></div></div>
<div class="acard"><div class="l">Conversations</div><div class="v c3"><?=number_format($convCount)?></div></div>
<div class="acard"><div class="l">Providers</div><div class="v c4"><?=count($allProviders)?></div></div>
<div class="acard"><div class="l">Session</div><div class="v" style="font-size:10px;color:var(--mt);word-break:break-all"><?=$_SESSION['hamid_session']?></div></div>
<div class="acard"><div class="l">Statut</div><div class="v c2">OK</div></div>
</div>
<h3 style="margin:20px 0 10px;font-size:15px">Providers configurés</h3>
<div class="admin-g" id="provGrid"></div>
</div>
</div>
</main>
</div>
<script>
const PROVIDERS=<?=$providersJson?>;
const SID='<?=$_SESSION['hamid_session']?>';
let curConv=null,cap='normal',activeTools=new Set(),files=[],isRecording=false,recognition=null;
// ── Init providers dropdown ──
(function(){
const sel=document.getElementById('provSel');
const groups={'Gratuit':[],'Gratuit limité':[],'Local':[],'Self-hosted':[],'Payant':[]};
Object.entries(PROVIDERS).forEach(([k,v])=>{(groups[v.type]||groups['Payant']).push([k,v])});
Object.entries(groups).forEach(([type,list])=>{
if(!list.length)return;
const og=document.createElement('optgroup');og.label=type;
list.forEach(([k,v])=>{const o=document.createElement('option');o.value=k;o.textContent=v.icon+' '+v.name+' — '+v.model;og.appendChild(o)});
sel.appendChild(og);
});
sel.value=localStorage.getItem('weval-provider')||'cerebras';
// Admin provider grid
const pg=document.getElementById('provGrid');
Object.entries(PROVIDERS).forEach(([k,v])=>{
const d=document.createElement('div');d.className='acard';
d.innerHTML='<div class="l">'+v.icon+' '+v.name+'</div><div class="v" style="font-size:11px;color:var(--tx2)">'+v.model+'</div><div style="margin-top:4px"><span class="badge '+(v.type==='Gratuit'?'ok':'wn')+'">'+v.type+'</span> <span style="font-size:10px;color:var(--mt)">'+v.speed+'</span></div>';
pg.appendChild(d);
});
})();
// ── Theme ──
function toggleTheme(){const h=document.documentElement,n=h.getAttribute('data-theme')==='dark'?'light':'dark';h.setAttribute('data-theme',n);localStorage.setItem('weval-theme',n)}
(function(){const s=localStorage.getItem('weval-theme');if(s)document.documentElement.setAttribute('data-theme',s)})();
// ── Sidebar ──
function toggleSB(){document.getElementById('app').classList.toggle('sc');document.getElementById('sidebar').classList.toggle('mo')}
function swTab(id,el){document.querySelectorAll('.tab').forEach(t=>t.classList.remove('a'));document.querySelectorAll('.panel').forEach(p=>p.classList.remove('a'));el.classList.add('a');document.getElementById('p-'+id).classList.add('a')}
function setCap(el){document.querySelectorAll('.cap').forEach(p=>p.classList.remove('a'));el.classList.add('a');cap=el.dataset.c}
function tt(b,t){b.classList.toggle('a');activeTools.has(t)?activeTools.delete(t):activeTools.add(t)}
function qp(t){document.getElementById('msgIn').value=t;document.getElementById('msgIn').focus();hideWelcome()}
// ── Conversations ──
function newConv(){curConv=null;document.getElementById('chatArea').innerHTML='';showWelcome();document.querySelectorAll('.sb-i').forEach(c=>c.classList.remove('a'))}
function loadConvs(){
fetch('hamid-api.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'list_conversations',session:SID})})
.then(r=>r.json()).then(data=>{
if(!data.conversations)return;
const l=document.getElementById('convList');l.innerHTML='';
data.conversations.forEach(c=>{
const d=document.createElement('div');d.className='sb-i'+(c.id==curConv?' a':'');
d.textContent=c.title||c.preview||'Conv #'+c.id;
d.onclick=()=>loadConv(c.id);l.appendChild(d);
});
}).catch(()=>{});
}
function loadConv(id){
curConv=id;hideWelcome();
document.querySelectorAll('.sb-i').forEach((c,i)=>c.classList.toggle('a',i==id));
fetch('hamid-api.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'get_conversation',session:SID,conversation_id:id})})
.then(r=>r.json()).then(data=>{
if(!data.messages)return;
document.getElementById('chatArea').innerHTML='';
data.messages.forEach(m=>addMsg(m.role,m.content,m.thinking));
}).catch(()=>{});
}
// ── Messages ──
function addMsg(role,content,thinking){
const a=document.getElementById('chatArea'),d=document.createElement('div');
d.className='msg '+(role==='user'?'u':'ai');
let thinkHtml='';
if(thinking)thinkHtml='<div class="think">💭 '+thinking.substring(0,300)+(thinking.length>300?'...':'')+'</div>';
d.innerHTML='<div class="av">'+(role==='user'?'Y':'H')+'</div><div class="mc">'+thinkHtml+'<div class="bub">'+fmt(content)+'</div><div class="acts"><button class="act" onclick="copyM(this)">📋 Copier</button>'+(role!=='user'?'<button class="act" onclick="regen()">🔄</button>':'')+'</div></div>';
a.appendChild(d);a.scrollTop=a.scrollHeight;
}
function fmt(t){if(!t)return'';return t.replace(/```(\w*)\n([\s\S]*?)```/g,'<pre><code>$2</code></pre>').replace(/`([^`]+)`/g,'<code>$1</code>').replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>').replace(/\n/g,'<br>')}
function hideWelcome(){const w=document.getElementById('welcome');if(w)w.style.display='none'}
function showWelcome(){const a=document.getElementById('chatArea');if(!document.getElementById('welcome')){location.reload();return}document.getElementById('welcome').style.display='flex'}
function copyM(b){const bb=b.closest('.mc').querySelector('.bub');navigator.clipboard.writeText(bb.textContent).then(()=>{b.textContent='✓';setTimeout(()=>b.textContent='📋 Copier',1500)})}
function regen(){const ms=document.querySelectorAll('.msg.u .bub');if(ms.length){document.getElementById('msgIn').value=ms[ms.length-1].textContent;send()}}
// ── Send ──
function send(){
const inp=document.getElementById('msgIn'),t=inp.value.trim();
if(!t&&!files.length)return;
hideWelcome();addMsg('user',t);inp.value='';ar(inp);
document.getElementById('typing').classList.add('v');
const prov=document.getElementById('provSel').value;
localStorage.setItem('weval-provider',prov);
const payload={
message:t,session:SID,conversation_id:curConv,
capability:cap,provider:prov,
tools:Array.from(activeTools),
use_kb:activeTools.has('rag')||cap==='kb'||cap==='rag',
stream:activeTools.has('stream')
};
// Handle file attachments
if(files.length>0){
const fd=new FormData();
fd.append('json',JSON.stringify(payload));
files.forEach((f,i)=>fd.append('file_'+i,f));
fetch('hamid-api.php',{method:'POST',body:fd})
.then(r=>r.json()).then(handleResp).catch(handleErr);
files=[];
} else {
fetch('hamid-api.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)})
.then(r=>r.json()).then(handleResp).catch(handleErr);
}
}
function handleResp(d){
document.getElementById('typing').classList.remove('v');
if(d.error){addMsg('assistant','❌ '+d.error);return}
if(d.response)addMsg('assistant',d.response,d.thinking);
if(d.conversation_id){curConv=d.conversation_id;loadConvs()}
if(d.tokens)document.getElementById('tkn').textContent=d.tokens+' tokens';
if(d.provider)document.getElementById('statusBadge').textContent='● '+d.provider;
if(d.artifact){addArtifact(d.artifact)}
if(d.response&&window.speechSynthesis&&localStorage.getItem('weval-tts')!=='off'){try{var u=new SpeechSynthesisUtterance(d.response.replace(/[#*]/g,'').substring(0,500));u.lang='fr-FR';u.rate=1.1;speechSynthesis.speak(u)}catch(e){}}
}
function handleErr(e){document.getElementById('typing').classList.remove('v');addMsg('assistant','❌ Erreur: '+e.message)}
function addArtifact(art){
const g=document.getElementById('artGrid');
if(g.querySelector('.welc-c')){g.innerHTML=''}
const d=document.createElement('div');d.className='acard';d.style.cursor='pointer';
d.innerHTML='<div class="l">'+art.type+'</div><div style="font-size:12px;font-weight:600">'+art.title+'</div><div style="font-size:11px;color:var(--mt);margin-top:4px">'+(art.preview||'')+'</div>';
d.onclick=()=>{if(art.url)window.open(art.url);else if(art.content)navigator.clipboard.writeText(art.content)};
g.appendChild(d);
}
// ── Keyboard ──
function hk(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()}}
function ar(el){el.style.height='auto';el.style.height=Math.min(el.scrollHeight,140)+'px'}
// ── Voice ──
function startVoice(){
const btn=document.getElementById('voiceBtn');
if(isRecording&&recognition){recognition.stop();isRecording=false;btn.textContent='🎙';return}
const SR=window.SpeechRecognition||window.webkitSpeechRecognition;
if(!SR){btn.textContent='❌';setTimeout(()=>btn.textContent='🎙',1500);return}
recognition=new SR();recognition.lang='fr-FR';recognition.continuous=false;recognition.interimResults=true;
recognition.onstart=()=>{isRecording=true;btn.textContent='🔴';btn.style.animation='pulse 1s infinite'};
recognition.onresult=e=>{
let final='',interim='';
for(let i=0;i<e.results.length;i++){if(e.results[i].isFinal)final+=e.results[i][0].transcript;else interim+=e.results[i][0].transcript}
document.getElementById('msgIn').value=final||interim;
};
recognition.onend=()=>{isRecording=false;btn.textContent='🎙';btn.style.animation=''};
recognition.onerror=()=>{isRecording=false;btn.textContent='🎙';btn.style.animation=''};
recognition.start();
}
// ── File handling ──
document.getElementById('fileIn').addEventListener('change',function(e){
const newFiles=Array.from(e.target.files);
files.push(...newFiles);
const inp=document.getElementById('msgIn');
const names=newFiles.map(f=>'📎 '+f.name+(f.type.startsWith('video')?'🎬':f.type.startsWith('image')?'🖼':'📄')).join('\n');
inp.value=(inp.value?inp.value+'\n':'')+names;
inp.focus();ar(inp);
this.value='';
});
// ── Drag & Drop ──
const chatPanel=document.getElementById('p-chat');
const dropZone=document.getElementById('dropZone');
let dragCounter=0;
chatPanel.addEventListener('dragenter',e=>{e.preventDefault();dragCounter++;dropZone.classList.add('v')});
chatPanel.addEventListener('dragleave',e=>{e.preventDefault();dragCounter--;if(dragCounter<=0){dropZone.classList.remove('v');dragCounter=0}});
chatPanel.addEventListener('dragover',e=>e.preventDefault());
chatPanel.addEventListener('drop',e=>{
e.preventDefault();dragCounter=0;dropZone.classList.remove('v');
const newFiles=Array.from(e.dataTransfer.files);
if(newFiles.length){
files.push(...newFiles);
const inp=document.getElementById('msgIn');
const names=newFiles.map(f=>'📎 '+f.name).join('\n');
inp.value=(inp.value?inp.value+'\n':'')+names;
inp.focus();ar(inp);
}
});
// ── Paste handling (images) ──
document.addEventListener('paste',e=>{
const items=Array.from(e.clipboardData.items||[]);
items.forEach(item=>{
if(item.type.startsWith('image')){
const f=item.getAsFile();
if(f){files.push(f);const inp=document.getElementById('msgIn');inp.value=(inp.value?inp.value+'\n':'')+'📎 Image collée';inp.focus()}
}
});
});
// ── Init ──
loadConvs();
document.getElementById('msgIn').focus();
function toggleTTS(btn){if(localStorage.getItem('weval-tts')==='off'){localStorage.removeItem('weval-tts');btn.textContent='\ud83d\udd0a';btn.title='TTS ON'}else{localStorage.setItem('weval-tts','off');speechSynthesis.cancel();btn.textContent='\ud83d\udd07';btn.title='TTS OFF'}}
</script>
</body>
</html>