/** * 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); }); })();