- /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
180 lines
8.4 KiB
JavaScript
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,"<").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(){});
|
|
});
|
|
})();
|