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

282 lines
10 KiB
JavaScript

/**
* Hermes Hub — Client-side Workspace Switcher & Context Manager
* Routage natif par sous-domaine (tt.hub.yesminedor.tn, nyora.hub..., perso.hub..., nabil.hub..., dsh.hub...)
* Résout à 100% le chargement des assets absolus sans réécriture fragile
*/
(function () {
let activeUniverse = "tt";
let activeMode = "session"; // "session" ou "files" (pour l'univers nabil)
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");
const nabilModeToggle = document.getElementById("nabil-mode-toggle");
const tabSession = document.getElementById("tab-session");
const tabFiles = document.getElementById("tab-files");
// 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 getTargetUrl(universeId, mode) {
const hostname = window.location.hostname;
// 1. Production *.hub.yesminedor.tn (Routage par sous-domaine natif)
if (hostname === "hub.yesminedor.tn" || hostname.endsWith(".hub.yesminedor.tn")) {
if (universeId === "nabil" && mode === "files") {
return "https://dsh.hub.yesminedor.tn/";
}
return `https://${universeId}.hub.yesminedor.tn/`;
}
// 2. Test local *.localhost
if (hostname.endsWith(".localhost") || hostname === "localhost") {
const port = window.location.port ? `:${window.location.port}` : "";
if (universeId === "nabil" && mode === "files") {
return `${window.location.protocol}//dsh.localhost${port}/`;
}
return `${window.location.protocol}//${universeId}.localhost${port}/`;
}
// 3. Fallback IP direct / chemin relatif (pour tests curl/SSH)
if (universeId === "nabil" && mode === "files") {
return "/u/nabil/files/";
}
return `/u/${universeId}/`;
}
function setMode(mode) {
activeMode = mode;
if (tabSession && tabFiles) {
if (mode === "session") {
tabSession.classList.add("active");
tabSession.setAttribute("aria-selected", "true");
tabFiles.classList.remove("active");
tabFiles.setAttribute("aria-selected", "false");
} else {
tabFiles.classList.add("active");
tabFiles.setAttribute("aria-selected", "true");
tabSession.classList.remove("active");
tabSession.setAttribute("aria-selected", "false");
}
}
// Recharger le canvas avec le mode choisi
loader.classList.remove("hidden");
iframe.src = "about:blank";
setTimeout(() => {
iframe.src = getTargetUrl(activeUniverse, activeMode);
}, 50);
}
function switchUniverse(universeId) {
if (!universeId) return;
const btn = document.querySelector(`.hub-universe-btn[data-universe="${universeId}"]`);
if (!btn) return;
// 1. ISOLATION TOTALE DU STATE CLIENT
loader.classList.remove("hidden");
iframe.src = "about:blank";
// 2. Mettre à jour l'univers actif
activeUniverse = universeId;
activeMode = "session";
// 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 || "";
const supportsFiles = btn.dataset.supportsFiles === "true";
if (activeTitle) activeTitle.textContent = name;
if (activeScope) activeScope.textContent = `Scope: ${scope}`;
if (activeTagline) activeTagline.textContent = tagline;
// 6. Afficher ou masquer le sélecteur toggle DSH (Claude Design)
if (nabilModeToggle) {
if (supportsFiles) {
nabilModeToggle.classList.add("visible");
if (tabSession && tabFiles) {
tabSession.classList.add("active");
tabSession.setAttribute("aria-selected", "true");
tabFiles.classList.remove("active");
tabFiles.setAttribute("aria-selected", "false");
}
} else {
nabilModeToggle.classList.remove("visible");
}
}
// 7. Charger le nouvel univers via son sous-domaine dédié
setTimeout(() => {
iframe.src = getTargetUrl(activeUniverse, activeMode);
}, 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" || u.health.status_code === 200 || u.health.status_code === 302)) {
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);
}
}
// Modales Personnalité & Clonage
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", () => {
document.querySelectorAll(".hub-universe-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const uId = btn.dataset.universe;
switchUniverse(uId);
});
});
tabSession?.addEventListener("click", () => setMode("session"));
tabFiles?.addEventListener("click", () => setMode("files"));
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);
document.querySelectorAll(".hub-modal-close").forEach((btn) => {
btn.addEventListener("click", () => {
personaModal.classList.remove("open");
cloneModal.classList.remove("open");
});
});
switchUniverse("tt");
refreshHealthStatus();
setInterval(refreshHealthStatus, 15000);
});
})();