Files
hermes-hub/static/js/hub.js
T

215 lines
7.7 KiB
JavaScript

/**
* Hermes Hub — Client-side Workspace Switcher & Context Manager
* Garantit l'isolation stricte du state front-end entre univers
*/
(function () {
let activeUniverse = "tt";
const iframe = document.getElementById("workspace-iframe");
const loader = document.getElementById("hub-loader");
const activeTitle = document.getElementById("active-universe-name");
const activeScope = document.getElementById("active-universe-scope");
const activeTagline = document.getElementById("active-universe-tagline");
// Accent mappings
const ACCENT_MAP = {
tt: "var(--accent-tt)",
nyora: "var(--accent-nyora)",
perso: "var(--accent-perso)",
nabil: "var(--accent-nabil)"
};
const ACCENT_GLOW_MAP = {
tt: "var(--accent-tt-glow)",
nyora: "var(--accent-nyora-glow)",
perso: "var(--accent-perso-glow)",
nabil: "var(--accent-nabil-glow)"
};
function applyThemeTokens(universeId) {
const accent = ACCENT_MAP[universeId] || "var(--accent-tt)";
const glow = ACCENT_GLOW_MAP[universeId] || "var(--accent-tt-glow)";
document.documentElement.style.setProperty("--hub-accent-current", accent);
document.documentElement.style.setProperty("--hub-accent-glow-current", glow);
}
function switchUniverse(universeId) {
if (!universeId || (activeUniverse === universeId && iframe.src !== "about:blank")) {
return;
}
const btn = document.querySelector(`.hub-universe-btn[data-universe="${universeId}"]`);
if (!btn) return;
// 1. ISOLATION TOTALE DU STATE CLIENT
// Forcer le déchargement de l'iframe précédente pour éliminer tout résidu JS en mémoire
loader.classList.remove("hidden");
iframe.src = "about:blank";
// 2. Mettre à jour l'univers actif
activeUniverse = universeId;
// 3. Mettre à jour la classe active sur la sidebar
document.querySelectorAll(".hub-universe-btn").forEach((el) => el.classList.remove("active"));
btn.classList.add("active");
// 4. Appliquer les Design Tokens dynamiques
applyThemeTokens(universeId);
// 5. Mettre à jour les informations du header
const name = btn.dataset.name || universeId.toUpperCase();
const scope = btn.dataset.scope || universeId;
const tagline = btn.dataset.tagline || "";
if (activeTitle) activeTitle.textContent = name;
if (activeScope) activeScope.textContent = `Scope: ${scope}`;
if (activeTagline) activeTagline.textContent = tagline;
// 6. Charger le nouvel univers via le proxy dédié
const targetProxyUrl = `/u/${universeId}/`;
setTimeout(() => {
iframe.src = targetProxyUrl;
}, 50);
}
// Écouter le chargement de l'iframe
if (iframe) {
iframe.addEventListener("load", () => {
if (iframe.src !== "about:blank") {
loader.classList.add("hidden");
}
});
}
// Polling de l'état de santé des univers
async function refreshHealthStatus() {
try {
const res = await fetch("/api/universes");
if (!res.ok) return;
const data = await res.json();
data.forEach((u) => {
const dot = document.getElementById(`status-dot-${u.id}`);
if (dot) {
if (u.health && u.health.status === "online") {
dot.className = "hub-status-dot online";
dot.title = `En ligne (${u.health.latency_ms}ms)`;
} else {
dot.className = "hub-status-dot";
dot.title = `Hors ligne ou dégradé (${u.health.status})`;
}
}
});
} catch (e) {
console.warn("Health check error:", e);
}
}
// Modal Personnalité
const personaModal = document.getElementById("persona-modal");
const cloneModal = document.getElementById("clone-modal");
async function openPersonaModal() {
try {
const res = await fetch(`/api/universes/${activeUniverse}/persona`);
if (!res.ok) throw new Error("Erreur de chargement de la persona");
const persona = await res.json();
document.getElementById("persona-modal-title").textContent = `Personnalité — ${activeUniverse.toUpperCase()}`;
document.getElementById("persona-name").value = persona.name || "";
document.getElementById("persona-tagline").value = persona.tagline || "";
document.getElementById("persona-tone").value = persona.tone || "";
document.getElementById("persona-style").value = persona.style || "";
document.getElementById("persona-principles").value = (persona.principles || []).join("\n");
document.getElementById("persona-prompt").value = persona.system_prompt || "";
personaModal.classList.add("open");
} catch (e) {
alert("Impossible de charger la personnalité : " + e.message);
}
}
async function savePersona() {
const principlesText = document.getElementById("persona-principles").value;
const payload = {
universe_id: activeUniverse,
name: document.getElementById("persona-name").value,
tagline: document.getElementById("persona-tagline").value,
tone: document.getElementById("persona-tone").value,
style: document.getElementById("persona-style").value,
principles: principlesText.split("\n").map(s => s.trim()).filter(Boolean),
system_prompt: document.getElementById("persona-prompt").value,
version: 1
};
try {
const res = await fetch(`/api/universes/${activeUniverse}/persona`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
if (!res.ok) throw new Error("Erreur de sauvegarde");
personaModal.classList.remove("open");
} catch (e) {
alert("Erreur lors de la sauvegarde : " + e.message);
}
}
function openCloneModal() {
document.getElementById("clone-source-name").textContent = activeUniverse.toUpperCase();
document.getElementById("clone-name").value = `Clone ${activeUniverse.toUpperCase()} - Test`;
cloneModal.classList.add("open");
}
async function submitClone() {
const cloneName = document.getElementById("clone-name").value;
if (!cloneName) return alert("Veuillez saisir un nom pour le clone.");
try {
const res = await fetch(`/api/universes/${activeUniverse}/clone`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ clone_name: cloneName })
});
if (!res.ok) throw new Error("Erreur lors du clonage");
const data = await res.json();
alert(`Profil cloné avec succès : ${data.clone.clone_id}`);
cloneModal.classList.remove("open");
} catch (e) {
alert("Erreur de clonage : " + e.message);
}
}
// Setup Event Listeners
document.addEventListener("DOMContentLoaded", () => {
// Boutons de changement d'univers
document.querySelectorAll(".hub-universe-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const uId = btn.dataset.universe;
switchUniverse(uId);
});
});
// Outils Footer
document.getElementById("btn-edit-persona")?.addEventListener("click", openPersonaModal);
document.getElementById("btn-clone-universe")?.addEventListener("click", openCloneModal);
document.getElementById("btn-save-persona")?.addEventListener("click", savePersona);
document.getElementById("btn-submit-clone")?.addEventListener("click", submitClone);
// Boutons fermer modales
document.querySelectorAll(".hub-modal-close").forEach((btn) => {
btn.addEventListener("click", () => {
personaModal.classList.remove("open");
cloneModal.classList.remove("open");
});
});
// Démarrage : activer le premier univers (TT)
switchUniverse("tt");
// Lancer la vérification de santé
refreshHealthStatus();
setInterval(refreshHealthStatus, 15000);
});
})();