/** * ShibaClaw WebUI — Profile Selector * Handles agent profile switching per session. */ // ── Profile state ──────────────────────────────────────────── let _profilesCache = null; const profileBtn = document.getElementById("btn-profile"); const profileDropdown = document.getElementById("profile-dropdown"); const profileLabel = document.getElementById("profile-label"); // ── API helpers ────────────────────────────────────────────── async function fetchProfiles() { try { const res = await authFetch("/api/profiles"); if (!res.ok) return []; const data = await res.json(); _profilesCache = data.profiles || []; return _profilesCache; } catch { return _profilesCache || []; } } async function switchProfile(profileId) { if (!state.sessionId || profileId === state.profileId) return; try { await authFetch(`/api/sessions/${encodeURIComponent(state.sessionId)}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ profile_id: profileId }), }); await syncProfileSelection(profileId); closeProfileDropdown(); } catch (e) { console.error("Failed to switch profile:", e); } } function _applyProfileAvatar(profileId) { const profiles = _profilesCache || []; const current = profiles.find(p => p.id === profileId); const avatarUrl = (current && current.avatar) ? current.avatar : DEFAULT_AVATAR; state.profileAvatar = avatarUrl; document.querySelectorAll(".agent-avatar-img").forEach(img => { img.src = avatarUrl; }); const sidebarLogo = document.querySelector(".logo img"); if (sidebarLogo) sidebarLogo.src = avatarUrl; const welcomeLogo = document.querySelector(".welcome-logo"); if (welcomeLogo) welcomeLogo.src = avatarUrl; } // ── UI helpers ─────────────────────────────────────────────── function updateProfileLabel() { if (!profileLabel) return; const profiles = _profilesCache || []; const current = profiles.find(p => p.id === state.profileId); profileLabel.textContent = current ? current.label : (state.profileId || "Default"); } async function syncProfileSelection(profileId) { if (!_profilesCache) { await fetchProfiles(); } state.profileId = profileId || "default"; _applyProfileAvatar(state.profileId); updateProfileLabel(); } window.syncProfileSelection = syncProfileSelection; function closeProfileDropdown() { if (profileDropdown) profileDropdown.classList.remove("active"); } async function renderProfileDropdown() { if (!profileDropdown) return; const profiles = await fetchProfiles(); let html = ""; for (const p of profiles) { const isActive = p.id === state.profileId; html += `
${isActive ? "radio_button_checked" : "radio_button_unchecked"}
${escapeHtml(p.label)}
${p.description ? `
${escapeHtml(p.description)}
` : ""}
${p.builtin ? 'built-in' : ""}
`; } html += '
'; html += `
add_circle_outline Create custom profile
`; profileDropdown.innerHTML = html; profileDropdown.querySelectorAll(".profile-option").forEach(el => { el.addEventListener("click", () => switchProfile(el.dataset.profileId)); }); const createBtn = profileDropdown.querySelector("#profile-action-create"); if (createBtn) createBtn.addEventListener("click", () => startProfileCreationSession()); } function escapeHtml(text) { const d = document.createElement("div"); d.textContent = text; return d.innerHTML; } // ── Toggle dropdown ────────────────────────────────────────── if (profileBtn) { profileBtn.addEventListener("click", async (e) => { e.stopPropagation(); const isOpen = profileDropdown.classList.contains("active"); if (isOpen) { closeProfileDropdown(); } else { await renderProfileDropdown(); profileDropdown.classList.add("active"); } }); } document.addEventListener("click", (e) => { if (profileDropdown && !profileDropdown.contains(e.target) && e.target !== profileBtn) { closeProfileDropdown(); } }); function startProfileCreationSession() { closeProfileDropdown(); if (!state.socket) return; const prompt = [ "I want to create a new custom agent profile for ShibaClaw.", "Walk me through defining it step by step:", "1. Ask me what kind of assistant I need (role, specialty, tone).", "2. Based on my answers, generate a complete SOUL.md file.", "3. Once I'm happy with it, save it as a new profile using write_file to `profiles//SOUL.md` in the workspace.", "4. Also update `profiles/manifest.json` to register the new profile with id, label, description, and `\"builtin\": false`.", "", "Start by asking me what kind of agent I'd like to create." ].join("\n"); const onReset = (data) => { realtime.off("session_reset", onReset); setTimeout(() => { const chatInput = document.getElementById("chat-input"); if (chatInput) { chatInput.value = prompt; chatInput.dispatchEvent(new Event("input", { bubbles: true })); } const btnSend = document.getElementById("btn-send"); if (btnSend) btnSend.click(); }, 300); }; realtime.on("session_reset", onReset); realtime.emit("new_session"); } function initProfileSocket() { realtime.on("connected", (data) => { if (data.profile_id) { syncProfileSelection(data.profile_id); } }); realtime.on("session_reset", (data) => { if (data.profile_id) { syncProfileSelection(data.profile_id); } }); } if (typeof realtime !== "undefined") { initProfileSocket(); } else { const _checkSocket = setInterval(() => { if (typeof realtime !== "undefined") { clearInterval(_checkSocket); initProfileSocket(); } }, 200); } if (state.profileId) { syncProfileSelection(state.profileId); }