Files
html/api/ambre-universal-chat.js
Opus 11e8be9860 feat(cdp): wave 309 chrome autoheal intent + load guard
- /api/wired-pending/intent-opus4-chrome_cdp_autoheal_w309.php
- Cron */5min installe sur /etc/cron.d/wevia-chrome-autoheal
- Load guard 45 threshold + circuit breaker +5
- 8/8 CDP stable verified
- Doctrine 309
2026-04-24 12:02:23 +02:00

180 lines
8.4 KiB
JavaScript

/**
* ambre-universal-chat.js · wave-259 · Universal chat widget for all internal pages
*
* Usage: just <script src="/api/ambre-universal-chat.js"></script>
* - Auto-creates minimal chat UI (floating button + panel)
* - Uses /api/ambre-internal-chat-api.php (persistent memory + cross-chat learning)
* - Auto chat_id = hostname + pathname
* - Works on wevia-master, all-ia-hub, orchestrator, director-chat, l99-brain, etc.
*/
(function(){
if (window.__ambreUniversalChat) return;
window.__ambreUniversalChat = true;
var chatId = "internal-" + (location.pathname.replace(/[^a-z0-9]/gi, "-").toLowerCase() || "root");
var apiUrl = "/api/ambre-internal-chat-api.php";
// Styles
var style = document.createElement("style");
style.textContent = `
.amw-btn{position:fixed;bottom:20px;left:20px;width:56px;height:56px;border-radius:50%;
background:linear-gradient(135deg,#4338ca,#6366f1);color:#fff;border:none;cursor:pointer;
box-shadow:0 4px 20px rgba(99,102,241,.4);z-index:99997;font-size:24px;transition:transform .15s}
.amw-btn:hover{transform:scale(1.08)}
.amw-panel{position:fixed;bottom:90px;left:20px;width:400px;max-width:calc(100vw - 40px);
height:560px;max-height:calc(100vh - 120px);background:#fff;border-radius:16px;
box-shadow:0 20px 60px rgba(0,0,0,.25);z-index:99998;display:none;flex-direction:column;
overflow:hidden;font-family:system-ui,-apple-system,sans-serif}
.amw-panel.open{display:flex;animation:amwSlide .2s ease}
@keyframes amwSlide{from{transform:translateY(20px);opacity:0}to{transform:translateY(0);opacity:1}}
.amw-header{padding:14px 16px;background:linear-gradient(135deg,#4338ca,#6366f1);color:#fff;
display:flex;justify-content:space-between;align-items:center}
.amw-header h3{margin:0;font-size:14px;font-weight:600}
.amw-header .meta{font-size:11px;opacity:.85;margin-top:2px}
.amw-close{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0 4px}
.amw-body{flex:1;overflow-y:auto;padding:14px;background:#f8fafc}
.amw-msg{margin:8px 0;padding:10px 12px;border-radius:12px;max-width:85%;font-size:13px;line-height:1.4;word-wrap:break-word}
.amw-msg.u{background:#eef2ff;color:#1e293b;margin-left:auto;border-bottom-right-radius:4px}
.amw-msg.a{background:#fff;color:#1e293b;border:1px solid #e2e8f0;border-bottom-left-radius:4px}
.amw-msg.a .meta{font-size:10px;color:#94a3b8;margin-top:4px}
.amw-msg.a.ma{background:linear-gradient(135deg,#f0f9ff,#eef2ff);border-color:#6366f1}
.amw-msg.a.ma::before{content:"🧠 Multi-Agent";display:block;font-size:10px;color:#4338ca;font-weight:600;margin-bottom:4px}
.amw-input-wrap{padding:10px;background:#fff;border-top:1px solid #e2e8f0;display:flex;gap:8px}
.amw-input{flex:1;padding:10px 12px;border:1px solid #cbd5e1;border-radius:10px;font-size:13px;outline:none;font-family:inherit}
.amw-input:focus{border-color:#6366f1}
.amw-send{padding:0 16px;background:linear-gradient(135deg,#4338ca,#6366f1);color:#fff;border:none;border-radius:10px;cursor:pointer;font-size:13px;font-weight:500}
.amw-send:disabled{opacity:.5;cursor:not-allowed}
.amw-typing{padding:10px;font-size:11px;color:#94a3b8;font-style:italic}
`;
document.head.appendChild(style);
// Elements
var btn = document.createElement("button");
btn.className = "amw-btn";
btn.innerHTML = "💬";
btn.title = "Chat interne WEVIA · mémoire persistante";
document.body.appendChild(btn);
var panel = document.createElement("div");
panel.className = "amw-panel";
panel.innerHTML = `
<div class="amw-header">
<div>
<h3>💬 Chat Interne · WEVIA</h3>
<div class="meta">Mémoire persistante · Multi-agent · <span id="amwTurns">0</span> tours</div>
</div>
<button class="amw-close">✕</button>
</div>
<div class="amw-body" id="amwBody"></div>
<div class="amw-input-wrap">
<input class="amw-input" id="amwInput" placeholder="Écrivez (analyse complete pour multi-agent)..." />
<button class="amw-send" id="amwSend">Envoyer</button>
</div>
`;
document.body.appendChild(panel);
btn.addEventListener("click", function(){ panel.classList.toggle("open"); });
panel.querySelector(".amw-close").addEventListener("click", function(){ panel.classList.remove("open"); });
var body = panel.querySelector("#amwBody");
var input = panel.querySelector("#amwInput");
var sendBtn = panel.querySelector("#amwSend");
var turnsSpan = panel.querySelector("#amwTurns");
function addMsg(role, text, meta){
var m = document.createElement("div");
m.className = "amw-msg " + (role === "user" ? "u" : "a") + (meta && meta.mode === "multiagent" ? " ma" : "");
var safe = (text || "").replace(/</g,"&lt;").replace(/\n/g,"<br>");
m.innerHTML = safe;
if (meta && role === "assistant") {
var mi = document.createElement("div");
mi.className = "meta";
mi.textContent = (meta.total_ms || 0) + "ms · " + (meta.mode || "std") + (meta.agents ? " · " + meta.agents + " agents" : "");
m.appendChild(mi);
}
body.appendChild(m);
body.scrollTop = body.scrollHeight;
}
function showTyping(){
var t = document.createElement("div");
t.id = "amwTyping";
t.className = "amw-typing";
t.textContent = "WEVIA réfléchit...";
body.appendChild(t);
body.scrollTop = body.scrollHeight;
}
function hideTyping(){
var t = document.getElementById("amwTyping");
if (t) t.remove();
}
function send(){
var msg = input.value.trim();
if (!msg) return;
input.value = "";
sendBtn.disabled = true;
addMsg("user", msg);
showTyping();
var ctrl = new AbortController();
var timeout = setTimeout(function(){ ctrl.abort(); }, 120000);
fetch(apiUrl, {
method: "POST",
headers: {"Content-Type":"application/json","Cache-Control":"no-cache"},
body: JSON.stringify({ chat_id: chatId, message: msg }),
signal: ctrl.signal,
cache: "no-store",
})
.then(function(r){ clearTimeout(timeout); return r.json(); })
.then(function(d){
hideTyping();
sendBtn.disabled = false;
if (d && d.ok) {
addMsg("assistant", d.response || "(empty)", {
mode: d.mode,
total_ms: d.total_ms,
agents: d.agents ? d.agents.length : 0,
});
turnsSpan.textContent = d.memory_turns || 0;
} else {
addMsg("assistant", "❌ " + ((d && d.error) || "Erreur"));
}
})
.catch(function(err){
hideTyping();
sendBtn.disabled = false;
addMsg("assistant", "❌ " + (err.name === "AbortError" ? "Timeout 2min" : String(err)));
});
}
sendBtn.addEventListener("click", send);
input.addEventListener("keydown", function(e){ if (e.key === "Enter" && !e.shiftKey){ e.preventDefault(); send(); } });
// Load prior context on open
var loaded = false;
btn.addEventListener("click", function(){
if (loaded || !panel.classList.contains("open")) return;
loaded = true;
fetch("/api/ambre-internal-memory.php", {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({ action: "load", chat_id: chatId, n: 20 }),
cache: "no-store",
})
.then(function(r){ return r.json(); })
.then(function(d){
if (d && d.ok && d.messages && d.messages.length > 0) {
d.messages.forEach(function(m){
if (m.role === "user" || m.role === "assistant") addMsg(m.role, m.content, m.metadata);
});
turnsSpan.textContent = d.messages.length;
} else {
addMsg("assistant", "Bonjour ! Je suis WEVIA interne (mémoire persistante illimitée). Chat_id: " + chatId);
}
})
.catch(function(){});
});
})();