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