moved js files in public into js and css into css folder
This commit is contained in:
@@ -0,0 +1,597 @@
|
||||
import { del, get, post, put } from "./js/api.js?v=20260909-account-controls";
|
||||
import {
|
||||
applyTheme as applySharedTheme,
|
||||
applyThemeMode as applySharedThemeMode,
|
||||
initializeAppearance
|
||||
} from "./js/themeController.js?v=20260909-account-controls";
|
||||
import { clearStatus, setStatus } from "./js/statusNotice.js?v=20260909-account-controls";
|
||||
import { populatePersonaForm, readPersonaForm } from "./js/personaForm.js?v=20260909-account-controls";
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const settingsNotice = $("settingsNotice");
|
||||
const settingsThemeSelect = $("settingsThemeSelect");
|
||||
const themeModeSelect = $("themeModeSelect");
|
||||
const workspaceModeSelect = $("workspaceModeSelect");
|
||||
const diagnosticsNavItem = $("diagnosticsNavItem");
|
||||
const refreshDiagnosticsBtn = $("refreshDiagnostics");
|
||||
const diagnosticsStatus = $("diagnosticsStatus");
|
||||
const diagnosticsBackend = $("diagnosticsBackend");
|
||||
const diagnosticsLatency = $("diagnosticsLatency");
|
||||
const diagnosticsCheckedAt = $("diagnosticsCheckedAt");
|
||||
const diagnosticsModelCount = $("diagnosticsModelCount");
|
||||
const diagnosticsCacheAge = $("diagnosticsCacheAge");
|
||||
const diagnosticsActiveRequests = $("diagnosticsActiveRequests");
|
||||
const diagnosticsLastFailure = $("diagnosticsLastFailure");
|
||||
const diagnosticsModels = $("diagnosticsModels");
|
||||
const diagnosticsWarnings = $("diagnosticsWarnings");
|
||||
const currentUsernameInput = $("currentUsername");
|
||||
const newUsernameInput = $("newUsername");
|
||||
const usernamePasswordInput = $("usernamePassword");
|
||||
const currentPasswordInput = $("currentPassword");
|
||||
const newPasswordInput = $("newPassword");
|
||||
const repeatPasswordInput = $("repeatPassword");
|
||||
const exportAccountBtn = $("exportAccount");
|
||||
const logoutAllSessionsBtn = $("logoutAllSessions");
|
||||
const deleteAccountBtn = $("deleteAccount");
|
||||
const usernameForm = $("usernameForm");
|
||||
const passwordForm = $("passwordForm");
|
||||
const settingsNavItems = document.querySelectorAll("[data-settings-view]");
|
||||
const settingsPanels = document.querySelectorAll("[data-settings-panel]");
|
||||
const createAiCharacterBtn = $("createAiCharacter");
|
||||
const createUserPersonaBtn = $("createUserPersona");
|
||||
const settingsAiCharacterList = $("settingsAiCharacterList");
|
||||
const settingsUserPersonaList = $("settingsUserPersonaList");
|
||||
const settingsPersonaModal = $("settingsPersonaModal");
|
||||
const settingsPersonaClose = $("settingsPersonaClose");
|
||||
const settingsPersonaForm = $("settingsPersonaForm");
|
||||
const settingsPersonaFormTitle = $("settingsPersonaFormTitle");
|
||||
const settingsPersonaFormNotice = $("settingsPersonaFormNotice");
|
||||
const settingsPersonaHelper = $("settingsPersonaHelper");
|
||||
const settingsPersonaType = $("settingsPersonaType");
|
||||
const settingsPersonaName = $("settingsPersonaName");
|
||||
const settingsPersonaPronouns = $("settingsPersonaPronouns");
|
||||
const settingsPersonaAppearance = $("settingsPersonaAppearance");
|
||||
const settingsPersonaBackground = $("settingsPersonaBackground");
|
||||
const settingsPersonaDetails = $("settingsPersonaDetails");
|
||||
const settingsPersonaExamplesField = $("settingsPersonaExamplesField");
|
||||
const settingsPersonaExampleDialogues = $("settingsPersonaExampleDialogues");
|
||||
const settingsPopupModal = $("settingsPopupModal");
|
||||
const settingsPopupClose = $("settingsPopupClose");
|
||||
const settingsPopupCancel = $("settingsPopupCancel");
|
||||
const settingsPopupConfirm = $("settingsPopupConfirm");
|
||||
const settingsPopupTitle = $("settingsPopupTitle");
|
||||
const settingsPopupEyebrow = $("settingsPopupEyebrow");
|
||||
const settingsPopupDescription = $("settingsPopupDescription");
|
||||
const settingsPopupField = $("settingsPopupField");
|
||||
const settingsPopupInputLabel = $("settingsPopupInputLabel");
|
||||
const settingsPopupInput = $("settingsPopupInput");
|
||||
const settingsPersonaFields = {
|
||||
name: settingsPersonaName,
|
||||
pronouns: settingsPersonaPronouns,
|
||||
appearance: settingsPersonaAppearance,
|
||||
background: settingsPersonaBackground,
|
||||
details: settingsPersonaDetails,
|
||||
exampleDialogues: settingsPersonaExampleDialogues
|
||||
};
|
||||
|
||||
let assistantPersonas = [];
|
||||
let userPersonas = [];
|
||||
let publishedPersonaIds = new Set();
|
||||
let editingPersonaId = null;
|
||||
let settingsPopupResolver = null;
|
||||
let isAdminUser = false;
|
||||
|
||||
function setNotice(message, state = "") {
|
||||
setStatus(settingsNotice, message, state);
|
||||
}
|
||||
|
||||
function setPersonaNotice(message = "", state = "") {
|
||||
if (!message) {
|
||||
clearStatus(settingsPersonaFormNotice);
|
||||
return;
|
||||
}
|
||||
setStatus(settingsPersonaFormNotice, message, state);
|
||||
}
|
||||
|
||||
function closeSettingsPopup(value = null) {
|
||||
settingsPopupModal.classList.add("hidden");
|
||||
if (settingsPopupResolver) {
|
||||
const resolve = settingsPopupResolver;
|
||||
settingsPopupResolver = null;
|
||||
resolve(value);
|
||||
}
|
||||
}
|
||||
|
||||
function promptSettingsPopup({
|
||||
eyebrow = "Action",
|
||||
title = "Enter a value",
|
||||
description = "",
|
||||
label = "Value",
|
||||
value = "",
|
||||
placeholder = "",
|
||||
confirmLabel = "Confirm"
|
||||
} = {}) {
|
||||
settingsPopupEyebrow.textContent = eyebrow;
|
||||
settingsPopupTitle.textContent = title;
|
||||
settingsPopupDescription.textContent = description;
|
||||
settingsPopupInputLabel.textContent = label;
|
||||
settingsPopupInput.value = value;
|
||||
settingsPopupInput.placeholder = placeholder;
|
||||
settingsPopupConfirm.textContent = confirmLabel;
|
||||
settingsPopupModal.classList.remove("hidden");
|
||||
settingsPopupInput.focus();
|
||||
settingsPopupInput.select();
|
||||
return new Promise((resolve) => {
|
||||
settingsPopupResolver = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
function applyTheme(themeKey, persist = true) {
|
||||
return applySharedTheme(themeKey, { persist, title: "Elsewhere Settings", themeSelect: settingsThemeSelect });
|
||||
}
|
||||
|
||||
function applyThemeMode(modeKey, persist = true) {
|
||||
return applySharedThemeMode(modeKey, { persist, modeSelect: themeModeSelect });
|
||||
}
|
||||
|
||||
function setSettingsView(view) {
|
||||
const normalizedView = ["my-ai-characters", "my-personas"].includes(view) ? "my-roleplay-companions" : view;
|
||||
if (normalizedView === "my-roleplay-companions" && location.pathname !== "/app/personas") { location.assign("/app/personas" + location.search); return; }
|
||||
if (normalizedView === "diagnostics" && !isAdminUser) {
|
||||
setNotice("Diagnostics are available to admin users only.", "error");
|
||||
return;
|
||||
}
|
||||
settingsNavItems.forEach((item) => {
|
||||
const isActive = item.dataset.settingsView === normalizedView;
|
||||
item.classList.toggle("active", isActive);
|
||||
item.setAttribute("aria-selected", String(isActive));
|
||||
});
|
||||
settingsPanels.forEach((panel) => {
|
||||
const isActive = panel.dataset.settingsPanel === normalizedView;
|
||||
panel.classList.toggle("active", isActive);
|
||||
panel.setAttribute("aria-hidden", String(!isActive));
|
||||
});
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.set("view", normalizedView);
|
||||
history.replaceState(null, "", `${window.location.pathname}?${params.toString()}`);
|
||||
if (normalizedView === "diagnostics") void loadDiagnostics();
|
||||
}
|
||||
|
||||
function formatDuration(ms) {
|
||||
if (!Number.isFinite(ms)) return "n/a";
|
||||
if (ms < 1000) return `${Math.round(ms)} ms`;
|
||||
return `${(ms / 1000).toFixed(1)} s`;
|
||||
}
|
||||
|
||||
function renderDiagnosticsList(element, items, emptyText) {
|
||||
element.innerHTML = "";
|
||||
if (!items.length) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "status persona-status";
|
||||
empty.textContent = emptyText;
|
||||
element.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
items.forEach((item) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "diagnostics-list-item";
|
||||
row.textContent = item;
|
||||
element.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadDiagnostics() {
|
||||
if (!isAdminUser) return false;
|
||||
diagnosticsStatus.textContent = "Checking...";
|
||||
const res = await get("/admin/diagnostics");
|
||||
if (res.error) {
|
||||
setNotice(res.error, "error");
|
||||
diagnosticsStatus.textContent = "Unavailable";
|
||||
return false;
|
||||
}
|
||||
|
||||
const model = res.model || {};
|
||||
const lastFailure = model.lastFailure;
|
||||
const activeRequests = (model.activeRequests || []).reduce((sum, item) => sum + Number(item.activeRequests || 0), 0);
|
||||
diagnosticsStatus.textContent = model.ok ? "Online" : "Degraded";
|
||||
diagnosticsBackend.textContent = model.backendUrl || "Backend unavailable";
|
||||
diagnosticsLatency.textContent = formatDuration(model.latencyMs ?? model.listLatencyMs);
|
||||
diagnosticsCheckedAt.textContent = model.checkedAt ? `Checked ${new Date(model.checkedAt).toLocaleString()}` : "Not checked yet";
|
||||
diagnosticsModelCount.textContent = String(model.modelCount || 0);
|
||||
diagnosticsCacheAge.textContent = `Cache age ${formatDuration(model.cacheAgeMs)}`;
|
||||
diagnosticsActiveRequests.textContent = String(activeRequests);
|
||||
diagnosticsLastFailure.textContent = lastFailure ? `${lastFailure.error} (${new Date(lastFailure.at).toLocaleString()})` : "No recorded failures";
|
||||
renderDiagnosticsList(diagnosticsModels, model.availableModels || [], "No models returned by the backend.");
|
||||
renderDiagnosticsList(diagnosticsWarnings, res.configWarnings || [], "No config warnings.");
|
||||
setNotice("Diagnostics loaded.", model.ok ? "success" : "error");
|
||||
return true;
|
||||
}
|
||||
|
||||
function formatMeta(persona) {
|
||||
return persona.pronouns ? `Pronouns: ${persona.pronouns}` : "Pronouns: n/a";
|
||||
}
|
||||
|
||||
function syncPersonaExamplesField() {
|
||||
const isAssistant = settingsPersonaType.value === "assistant";
|
||||
settingsPersonaExamplesField.classList.toggle("hidden", !isAssistant);
|
||||
settingsPersonaHelper.textContent = isAssistant
|
||||
? "Create the character you want to roleplay with."
|
||||
: "Create the persona you want to speak as in roleplay.";
|
||||
$("settingsPersonaSave").textContent = isAssistant ? "Save AI Character" : "Save persona";
|
||||
}
|
||||
|
||||
function openPersonaModal(persona = null, personaType = "assistant") {
|
||||
editingPersonaId = persona ? persona.id : null;
|
||||
settingsPersonaType.disabled = Boolean(persona);
|
||||
settingsPersonaType.value = persona ? persona.persona_type : personaType;
|
||||
settingsPersonaFormTitle.textContent = persona
|
||||
? `Edit ${settingsPersonaType.value === "assistant" ? "AI Character" : "persona"}`
|
||||
: `Create ${personaType === "assistant" ? "AI Character" : "persona"}`;
|
||||
populatePersonaForm(settingsPersonaFields, persona || {});
|
||||
syncPersonaExamplesField();
|
||||
setPersonaNotice("");
|
||||
settingsPersonaModal.classList.remove("hidden");
|
||||
settingsPersonaName.focus();
|
||||
}
|
||||
|
||||
function closePersonaModal() {
|
||||
settingsPersonaModal.classList.add("hidden");
|
||||
editingPersonaId = null;
|
||||
setPersonaNotice("");
|
||||
}
|
||||
|
||||
function renderPersonaList(personas, listElement, type) {
|
||||
listElement.innerHTML = "";
|
||||
if (!personas.length) {
|
||||
const empty = document.createElement("div");
|
||||
const message = document.createElement("p");
|
||||
const button = document.createElement("button");
|
||||
empty.className = "persona-empty-state";
|
||||
message.className = "status persona-status";
|
||||
message.textContent = type === "assistant" ? "No AI Characters yet." : "No user personas yet.";
|
||||
button.type = "button";
|
||||
button.className = "secondary-action";
|
||||
button.textContent = type === "assistant" ? "Create AI Character" : "Create user persona";
|
||||
button.addEventListener("click", () => openPersonaModal(null, type));
|
||||
empty.append(message, button);
|
||||
listElement.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
personas.forEach((persona) => {
|
||||
const item = document.createElement("div");
|
||||
const titleRow = document.createElement("div");
|
||||
const title = document.createElement("h4");
|
||||
const tag = document.createElement("span");
|
||||
const meta = document.createElement("p");
|
||||
const actions = document.createElement("div");
|
||||
|
||||
item.className = "persona-item";
|
||||
titleRow.className = "persona-title-row";
|
||||
actions.className = "persona-item-actions";
|
||||
tag.className = `persona-role-tag ${type === "assistant" ? "ai" : "user"}`;
|
||||
tag.textContent = type === "assistant" ? "AI Character" : "User";
|
||||
title.textContent = persona.name;
|
||||
meta.textContent = formatMeta(persona);
|
||||
|
||||
[
|
||||
["Edit", () => openPersonaModal(persona)],
|
||||
["Clone", () => clonePersona(persona.id)],
|
||||
["History", () => showPersonaHistory(persona.id)],
|
||||
[publishedPersonaIds.has(persona.id) ? "Update listing" : "Publish", () => publishPersona(persona.id)],
|
||||
["Delete", () => deletePersona(persona.id)]
|
||||
]
|
||||
.filter(([label]) => Boolean(label))
|
||||
.forEach(([label, handler]) => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.textContent = label;
|
||||
button.addEventListener("click", handler);
|
||||
actions.appendChild(button);
|
||||
});
|
||||
|
||||
if (publishedPersonaIds.has(persona.id)) {
|
||||
const unpublishButton = document.createElement("button");
|
||||
unpublishButton.type = "button";
|
||||
unpublishButton.textContent = "Unpublish";
|
||||
unpublishButton.addEventListener("click", () => unpublishPersona(persona.id));
|
||||
actions.appendChild(unpublishButton);
|
||||
}
|
||||
|
||||
titleRow.append(title, tag);
|
||||
item.append(titleRow, meta, actions);
|
||||
listElement.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadProfile() {
|
||||
const res = await get("/settings/profile");
|
||||
if (res.error) {
|
||||
setNotice(res.error, "error");
|
||||
return false;
|
||||
}
|
||||
const accountName = document.querySelector(".shell-username");
|
||||
if (accountName) { accountName.textContent = res.username || ""; accountName.title = res.username || ""; }
|
||||
currentUsernameInput.value = res.username || "";
|
||||
newUsernameInput.value = res.username || "";
|
||||
isAdminUser = res.username === "admin";
|
||||
diagnosticsNavItem.classList.toggle("hidden", !isAdminUser);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function loadPersonas() {
|
||||
const res = await get("/personas");
|
||||
if (res.error) {
|
||||
setNotice(res.error, "error");
|
||||
return false;
|
||||
}
|
||||
assistantPersonas = res.assistantPersonas || [];
|
||||
userPersonas = res.userPersonas || [];
|
||||
publishedPersonaIds = new Set(res.publishedPersonaIds || []);
|
||||
renderPersonaList(assistantPersonas, settingsAiCharacterList, "assistant");
|
||||
renderPersonaList(userPersonas, settingsUserPersonaList, "user");
|
||||
return true;
|
||||
}
|
||||
|
||||
async function savePersona() {
|
||||
const wasEditing = Boolean(editingPersonaId);
|
||||
const payload = {
|
||||
...readPersonaForm(settingsPersonaFields),
|
||||
personaType: settingsPersonaType.value,
|
||||
exampleDialogues: settingsPersonaType.value === "assistant" ? settingsPersonaExampleDialogues.value.trim() : ""
|
||||
};
|
||||
|
||||
if (!payload.name) {
|
||||
setPersonaNotice("Name is required.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
const res = editingPersonaId
|
||||
? await put(`/personas/${editingPersonaId}`, payload)
|
||||
: await post("/personas", payload);
|
||||
|
||||
if (res.error) {
|
||||
setPersonaNotice(res.error, "error");
|
||||
return;
|
||||
}
|
||||
|
||||
await loadPersonas();
|
||||
closePersonaModal();
|
||||
setSettingsView("my-roleplay-companions");
|
||||
setNotice(wasEditing ? "Persona updated." : "Persona created.", "success");
|
||||
}
|
||||
|
||||
async function deletePersona(id) {
|
||||
const res = await del(`/personas/${id}`);
|
||||
if (res.error) {
|
||||
setNotice(res.error, "error");
|
||||
return;
|
||||
}
|
||||
await loadPersonas();
|
||||
setNotice("Persona deleted.", "success");
|
||||
}
|
||||
|
||||
async function publishPersona(id) {
|
||||
const tagsInput = await promptSettingsPopup({
|
||||
eyebrow: "Marketplace",
|
||||
title: "Persona tags",
|
||||
description: "Add optional comma-separated tags to improve discovery.",
|
||||
label: "Tags",
|
||||
placeholder: "mentor, fantasy, villain",
|
||||
confirmLabel: "Publish"
|
||||
});
|
||||
if (tagsInput === null) return;
|
||||
const tags = String(tagsInput || "").split(",").map((tag) => tag.trim()).filter(Boolean);
|
||||
const res = await post(`/personas/${id}/publish`, { tags });
|
||||
if (res.error) {
|
||||
setNotice(res.error, "error");
|
||||
return;
|
||||
}
|
||||
await loadPersonas();
|
||||
setNotice("Persona published.", "success");
|
||||
}
|
||||
|
||||
async function clonePersona(id) {
|
||||
const res = await post(`/personas/${id}/clone`, {});
|
||||
if (res.error) return setNotice(res.error, "error");
|
||||
await loadPersonas();
|
||||
setNotice("Persona cloned.", "success");
|
||||
}
|
||||
|
||||
async function showPersonaHistory(id) {
|
||||
const res = await get(`/personas/${id}/versions`);
|
||||
if (res.error) return setNotice(res.error, "error");
|
||||
const versions = res.versions || [];
|
||||
const summary = versions.length
|
||||
? versions.map((version) => `v${version.version_number} - ${new Date(version.created_at).toLocaleString()}`).join("\n")
|
||||
: "No saved versions yet.";
|
||||
const choice = await promptSettingsPopup({
|
||||
eyebrow: "Persona history",
|
||||
title: "Restore a version",
|
||||
description: summary,
|
||||
label: "Version number",
|
||||
placeholder: "1",
|
||||
confirmLabel: "Restore"
|
||||
});
|
||||
if (choice === null) return;
|
||||
const selected = Number(choice);
|
||||
if (!selected) return;
|
||||
const version = versions.find((item) => item.version_number === selected);
|
||||
if (!version) return setNotice("Version not found.", "error");
|
||||
const restore = await post(`/personas/${id}/versions/${version.id}/restore`, {});
|
||||
if (restore.error) return setNotice(restore.error, "error");
|
||||
await loadPersonas();
|
||||
setNotice(`Restored persona version ${selected}.`, "success");
|
||||
}
|
||||
|
||||
async function unpublishPersona(id) {
|
||||
const res = await post(`/personas/${id}/unpublish`, {});
|
||||
if (res.error) {
|
||||
setNotice(res.error, "error");
|
||||
return;
|
||||
}
|
||||
await loadPersonas();
|
||||
setNotice("Persona unpublished.", "success");
|
||||
}
|
||||
|
||||
function downloadJson(filename, payload) {
|
||||
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
async function exportAccount() {
|
||||
const res = await get("/settings/export");
|
||||
if (res.error) return setNotice(res.error, "error");
|
||||
const username = currentUsernameInput.value || "account";
|
||||
downloadJson(`account-export-${username}-${new Date().toISOString().slice(0, 10)}.json`, res.account);
|
||||
setNotice("Account export downloaded.", "success");
|
||||
}
|
||||
|
||||
async function logoutAllSessions() {
|
||||
const confirmed = await promptSettingsPopup({
|
||||
eyebrow: "Security",
|
||||
title: "Logout all sessions",
|
||||
description: "Type logout to sign out this browser and every other active session.",
|
||||
label: "Confirmation",
|
||||
placeholder: "logout",
|
||||
confirmLabel: "Logout all"
|
||||
});
|
||||
if (String(confirmed || "").trim().toLowerCase() !== "logout") return;
|
||||
const res = await post("/settings/logout-all-sessions", {});
|
||||
if (res.error) return setNotice(res.error, "error");
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
async function deleteAccount() {
|
||||
const currentPassword = await promptSettingsPopup({
|
||||
eyebrow: "Danger zone",
|
||||
title: "Delete account",
|
||||
description: "Enter your current password to permanently delete your account and all account data.",
|
||||
label: "Current password",
|
||||
placeholder: "Password",
|
||||
confirmLabel: "Delete account"
|
||||
});
|
||||
if (currentPassword === null) return;
|
||||
const res = await request("/settings/account", { currentPassword }, "DELETE");
|
||||
if (res.error) return setNotice(res.error, "error");
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
usernameForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const username = newUsernameInput.value.trim();
|
||||
const password = usernamePasswordInput.value;
|
||||
if (!username || !password) {
|
||||
setNotice("New username and current password are required.", "error");
|
||||
return;
|
||||
}
|
||||
const res = await request("/settings/username", { username, password }, "PUT");
|
||||
if (res.error) {
|
||||
setNotice(res.error, "error");
|
||||
return;
|
||||
}
|
||||
currentUsernameInput.value = res.username;
|
||||
newUsernameInput.value = res.username;
|
||||
usernamePasswordInput.value = "";
|
||||
setNotice("Username updated.", "success");
|
||||
});
|
||||
|
||||
passwordForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const currentPassword = currentPasswordInput.value;
|
||||
const nextPassword = newPasswordInput.value;
|
||||
const repeatPassword = repeatPasswordInput.value;
|
||||
if (!currentPassword || !nextPassword || !repeatPassword) {
|
||||
setNotice("Fill in all password fields.", "error");
|
||||
return;
|
||||
}
|
||||
if (nextPassword !== repeatPassword) {
|
||||
setNotice("New passwords do not match.", "error");
|
||||
return;
|
||||
}
|
||||
const res = await request("/settings/password", { currentPassword, newPassword: nextPassword }, "PUT");
|
||||
if (res.error) {
|
||||
setNotice(res.error, "error");
|
||||
return;
|
||||
}
|
||||
currentPasswordInput.value = "";
|
||||
newPasswordInput.value = "";
|
||||
repeatPasswordInput.value = "";
|
||||
setNotice("Password updated.", "success");
|
||||
});
|
||||
|
||||
settingsThemeSelect.addEventListener("change", (event) => {
|
||||
applyTheme(event.target.value);
|
||||
setNotice("Style updated.", "success");
|
||||
});
|
||||
themeModeSelect.addEventListener("change", (event) => {
|
||||
applyThemeMode(event.target.value);
|
||||
setNotice(`Appearance set to ${document.body.dataset.themeMode}.`, "success");
|
||||
});
|
||||
workspaceModeSelect.addEventListener("change", (event) => {
|
||||
const mode = event.target.value === "advanced" ? "advanced" : "basic";
|
||||
localStorage.setItem("krishd-workspace-mode", mode);
|
||||
setNotice(`Workspace mode set to ${mode}.`, "success");
|
||||
});
|
||||
exportAccountBtn.addEventListener("click", () => { void exportAccount(); });
|
||||
logoutAllSessionsBtn.addEventListener("click", () => { void logoutAllSessions(); });
|
||||
deleteAccountBtn.addEventListener("click", () => { void deleteAccount(); });
|
||||
refreshDiagnosticsBtn.addEventListener("click", () => { void loadDiagnostics(); });
|
||||
|
||||
settingsNavItems.forEach((item) => item.addEventListener("click", () => setSettingsView(item.dataset.settingsView)));
|
||||
createAiCharacterBtn.addEventListener("click", () => openPersonaModal(null, "assistant"));
|
||||
createUserPersonaBtn.addEventListener("click", () => openPersonaModal(null, "user"));
|
||||
settingsPersonaClose.addEventListener("click", closePersonaModal);
|
||||
settingsPersonaModal.addEventListener("click", (event) => {
|
||||
if (event.target === settingsPersonaModal) closePersonaModal();
|
||||
});
|
||||
settingsPersonaType.addEventListener("change", syncPersonaExamplesField);
|
||||
settingsPersonaForm.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
void savePersona();
|
||||
});
|
||||
settingsPopupConfirm.addEventListener("click", () => closeSettingsPopup(settingsPopupInput.value));
|
||||
settingsPopupCancel.addEventListener("click", () => closeSettingsPopup(null));
|
||||
settingsPopupClose.addEventListener("click", () => closeSettingsPopup(null));
|
||||
settingsPopupModal.addEventListener("click", (event) => {
|
||||
if (event.target === settingsPopupModal) closeSettingsPopup(null);
|
||||
});
|
||||
settingsPopupInput.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
closeSettingsPopup(settingsPopupInput.value);
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
event.preventDefault();
|
||||
closeSettingsPopup(null);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("load", async () => {
|
||||
initializeAppearance({ title: "Elsewhere Settings", themeSelect: settingsThemeSelect, modeSelect: themeModeSelect });
|
||||
workspaceModeSelect.value = localStorage.getItem("krishd-workspace-mode") || "basic";
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const personaPage = location.pathname === "/app/personas";
|
||||
const view = personaPage ? "my-roleplay-companions" : params.get("view") || "personal";
|
||||
if (personaPage) { document.querySelector(".market-header .eyebrow").textContent = "Your collection"; document.querySelector("h1").textContent = "My personas"; document.title = "My personas - Elsewhere"; document.querySelector(".settings-nav").hidden = true; document.querySelector(".settings-layout").style.gridTemplateColumns = "1fr"; }
|
||||
const profileOk = await loadProfile();
|
||||
setSettingsView(view);
|
||||
const personasOk = await loadPersonas();
|
||||
if (profileOk && personasOk && view !== "diagnostics") setNotice(personaPage ? "Personas loaded." : "Settings loaded.");
|
||||
|
||||
const create = params.get("create");
|
||||
if (create === "assistant") {
|
||||
setSettingsView("my-roleplay-companions");
|
||||
openPersonaModal(null, "assistant");
|
||||
} else if (create === "user") {
|
||||
setSettingsView("my-roleplay-companions");
|
||||
openPersonaModal(null, "user");
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user