implement various changes to styling

This commit is contained in:
2026-03-24 13:48:34 +01:00
parent 2436f34c99
commit e88c91f22e
4 changed files with 212 additions and 85 deletions
+19 -17
View File
@@ -163,12 +163,6 @@
<div id="messages" class="messages-panel messages-panel-focus"></div> <div id="messages" class="messages-panel messages-panel-focus"></div>
<div id="emptyState" class="empty-state">
<p class="eyebrow">No messages yet</p>
<h3>Write something when you're ready.</h3>
<p>This can stay a plain chat. Personas and shared presets are optional.</p>
</div>
<div class="input-shell input-shell-focus"> <div class="input-shell input-shell-focus">
<div class="input-meta"> <div class="input-meta">
<p class="composer-hint">Enter sends. Shift+Enter adds a new line.</p> <p class="composer-hint">Enter sends. Shift+Enter adds a new line.</p>
@@ -184,40 +178,48 @@
</div> </div>
<div id="personaModal" class="modal-overlay hidden"> <div id="personaModal" class="modal-overlay hidden">
<div class="modal-card"> <div class="modal-card persona-modal-card">
<div class="modal-header"> <div class="modal-header">
<div class="persona-modal-heading">
<p class="eyebrow">Persona editor</p>
<h3 id="personaFormTitle">Create persona</h3> <h3 id="personaFormTitle">Create persona</h3>
</div>
<button id="personaClose" type="button" class="icon-button">Cancel</button> <button id="personaClose" type="button" class="icon-button">Cancel</button>
</div> </div>
<form id="personaForm" class="persona-form"> <form id="personaForm" class="persona-form">
<label class="field"> <div class="persona-form-intro">
<p class="persona-helper">AI personas shape the assistant. User personas add context for how replies should read you.</p>
</div>
<p id="personaFormNotice" class="status hidden"></p>
<div class="persona-form-grid">
<label class="field field-wide">
<span>Persona for</span> <span>Persona for</span>
<select id="personaType"> <select id="personaType">
<option value="assistant">AI persona</option> <option value="assistant">AI persona</option>
<option value="user">Your persona</option> <option value="user">Your persona</option>
</select> </select>
</label> </label>
<p class="persona-helper">AI personas shape the assistant. Your persona gives the model user-side context.</p>
<label class="field"> <label class="field">
<span>Name</span> <span>Persona name</span>
<input id="personaName" placeholder="Persona name" required/> <input id="personaName" placeholder="Persona name" required/>
</label> </label>
<label class="field"> <label class="field">
<span>Pronouns</span> <span>Persona pronouns</span>
<input id="personaPronouns" placeholder="e.g. she/her, they/them"/> <input id="personaPronouns" placeholder="e.g. she/her, they/them"/>
</label> </label>
<label class="field"> <label class="field field-wide">
<span>Appearance</span> <span>Appearance</span>
<textarea id="personaAppearance" rows="2" placeholder="Describe their look"></textarea> <textarea id="personaAppearance" rows="3" placeholder="Describe their look"></textarea>
</label> </label>
<label class="field"> <label class="field field-wide">
<span>Background</span> <span>Background</span>
<textarea id="personaBackground" rows="2" placeholder="Backstory or origins"></textarea> <textarea id="personaBackground" rows="3" placeholder="Backstory, history, role, or origins"></textarea>
</label> </label>
<label class="field"> <label class="field field-wide">
<span>Details</span> <span>Details</span>
<textarea id="personaDetails" rows="3" placeholder="Personality traits, voice, quirks, goals"></textarea> <textarea id="personaDetails" rows="4" placeholder="Personality traits, voice, goals, quirks, boundaries, style"></textarea>
</label> </label>
</div>
<div class="persona-actions"> <div class="persona-actions">
<button id="personaSave" type="submit" class="primary-action">Save persona</button> <button id="personaSave" type="submit" class="primary-action">Save persona</button>
</div> </div>
+19 -9
View File
@@ -1,6 +1,6 @@
const $ = (id) => document.getElementById(id); const $ = (id) => document.getElementById(id);
const authDiv = $("auth"), chatDiv = $("chat"), authMsg = $("authMsg"); const authDiv = $("auth"), chatDiv = $("chat"), authMsg = $("authMsg");
const messagesDiv = $("messages"), emptyState = $("emptyState"), modelSelect = $("model"); const messagesDiv = $("messages"), modelSelect = $("model");
const loginForm = $("loginForm"), registerForm = $("registerForm"), msgInput = $("msgInput"), sendBtn = $("send"); const loginForm = $("loginForm"), registerForm = $("registerForm"), msgInput = $("msgInput"), sendBtn = $("send");
const loginUsernameInput = $("loginUsername"), loginPasswordInput = $("loginPassword"); const loginUsernameInput = $("loginUsername"), loginPasswordInput = $("loginPassword");
const registerUsernameInput = $("registerUsername"), registerPasswordInput = $("registerPassword"); const registerUsernameInput = $("registerUsername"), registerPasswordInput = $("registerPassword");
@@ -12,6 +12,7 @@ const personaList = $("personaList"), userPersonaList = $("userPersonaList");
const personaForm = $("personaForm"), personaFormTitle = $("personaFormTitle"), personaTypeSelect = $("personaType"); const personaForm = $("personaForm"), personaFormTitle = $("personaFormTitle"), personaTypeSelect = $("personaType");
const personaNameInput = $("personaName"), personaPronounsInput = $("personaPronouns"), personaAppearanceInput = $("personaAppearance"); const personaNameInput = $("personaName"), personaPronounsInput = $("personaPronouns"), personaAppearanceInput = $("personaAppearance");
const personaBackgroundInput = $("personaBackground"), personaDetailsInput = $("personaDetails"); const personaBackgroundInput = $("personaBackground"), personaDetailsInput = $("personaDetails");
const personaFormNotice = $("personaFormNotice");
const newPersonaBtn = $("newPersona"), newUserPersonaBtn = $("newUserPersona"), clearPersonaBtn = $("clearPersona"), clearUserPersonaBtn = $("clearUserPersona"); const newPersonaBtn = $("newPersona"), newUserPersonaBtn = $("newUserPersona"), clearPersonaBtn = $("clearPersona"), clearUserPersonaBtn = $("clearUserPersona");
const activePersonaStatus = $("activePersonaStatus"), activeUserPersonaStatus = $("activeUserPersonaStatus"); const activePersonaStatus = $("activePersonaStatus"), activeUserPersonaStatus = $("activeUserPersonaStatus");
const personaModal = $("personaModal"), personaCloseBtn = $("personaClose"), personaMenuButton = $("personaMenuButton"), personaPopover = $("personaPopover"); const personaModal = $("personaModal"), personaCloseBtn = $("personaClose"), personaMenuButton = $("personaMenuButton"), personaPopover = $("personaPopover");
@@ -46,6 +47,16 @@ const del = (url) => request(url, null, "DELETE");
function setAuthMessage(message, state = "") { authMsg.textContent = message; authMsg.className = state ? `status ${state}` : "status"; } function setAuthMessage(message, state = "") { authMsg.textContent = message; authMsg.className = state ? `status ${state}` : "status"; }
function setNotice(message, state = "") { return {message, state}; } function setNotice(message, state = "") { return {message, state}; }
function setPersonaFormNotice(message = "", state = "") {
if (!personaFormNotice) return;
if (!message) {
personaFormNotice.textContent = "";
personaFormNotice.className = "status hidden";
return;
}
personaFormNotice.textContent = message;
personaFormNotice.className = state ? `status ${state}` : "status";
}
function setMessageContent(el, content) { function setMessageContent(el, content) {
if (window.marked && window.DOMPurify) { if (window.marked && window.DOMPurify) {
el.innerHTML = DOMPurify.sanitize(marked.parse(content, {breaks: true})); el.innerHTML = DOMPurify.sanitize(marked.parse(content, {breaks: true}));
@@ -53,7 +64,6 @@ function setMessageContent(el, content) {
el.textContent = content; el.textContent = content;
} }
} }
function updateEmptyState() { emptyState.classList.toggle("visible", currentMessages.length === 0); }
function getChatById(id) { return chatSessions.find((chat) => chat.id === id); } function getChatById(id) { return chatSessions.find((chat) => chat.id === id); }
function addMessage(content, isUser = false, isLoading = false) { function addMessage(content, isUser = false, isLoading = false) {
@@ -87,7 +97,6 @@ function addMessage(content, isUser = false, isLoading = false) {
function renderMessages() { function renderMessages() {
messagesDiv.innerHTML = ""; messagesDiv.innerHTML = "";
currentMessages.forEach((msg) => addMessage(msg.content, msg.role === "user")); currentMessages.forEach((msg) => addMessage(msg.content, msg.role === "user"));
updateEmptyState();
updateChatActionState(); updateChatActionState();
} }
@@ -178,10 +187,11 @@ function openPersonaForm(persona = null, personaType = "assistant") {
personaTypeSelect.value = personaType; personaTypeSelect.disabled = false; personaTypeSelect.value = personaType; personaTypeSelect.disabled = false;
personaNameInput.value = ""; personaPronounsInput.value = ""; personaAppearanceInput.value = ""; personaBackgroundInput.value = ""; personaDetailsInput.value = ""; personaNameInput.value = ""; personaPronounsInput.value = ""; personaAppearanceInput.value = ""; personaBackgroundInput.value = ""; personaDetailsInput.value = "";
} }
setPersonaFormNotice("");
personaNameInput.focus(); personaNameInput.focus();
} }
function closePersonaForm() { personaModal.classList.add("hidden"); editingPersonaId = null; editingPersonaType = "assistant"; personaTypeSelect.disabled = false; } function closePersonaForm() { personaModal.classList.add("hidden"); editingPersonaId = null; editingPersonaType = "assistant"; personaTypeSelect.disabled = false; setPersonaFormNotice(""); }
function closePersonaPopover() { personaPopover.classList.add("hidden"); personaMenuButton.setAttribute("aria-expanded", "false"); } function closePersonaPopover() { personaPopover.classList.add("hidden"); personaMenuButton.setAttribute("aria-expanded", "false"); }
function renderPersonaList(items, activeId, listElement, personaType) { function renderPersonaList(items, activeId, listElement, personaType) {
@@ -252,9 +262,9 @@ async function clearPersona(type) {
async function savePersona() { async function savePersona() {
const payload = {personaType: personaTypeSelect.value, name: personaNameInput.value.trim(), pronouns: personaPronounsInput.value.trim(), appearance: personaAppearanceInput.value.trim(), background: personaBackgroundInput.value.trim(), details: personaDetailsInput.value.trim()}; const payload = {personaType: personaTypeSelect.value, name: personaNameInput.value.trim(), pronouns: personaPronounsInput.value.trim(), appearance: personaAppearanceInput.value.trim(), background: personaBackgroundInput.value.trim(), details: personaDetailsInput.value.trim()};
if (!payload.name) return setNotice("Persona name is required.", "error"); if (!payload.name) return setPersonaFormNotice("Persona name is required.", "error");
const res = editingPersonaId ? await put(`/personas/${editingPersonaId}`, payload) : await post("/personas", payload); const res = editingPersonaId ? await put(`/personas/${editingPersonaId}`, payload) : await post("/personas", payload);
if (res.error) return setNotice(res.error, "error"); if (res.error) return setPersonaFormNotice(res.error, "error");
const wasEditing = Boolean(editingPersonaId); const wasEditing = Boolean(editingPersonaId);
await loadPersonas(); closePersonaForm(); setNotice(wasEditing ? "Persona updated." : "Persona created.", "success"); await loadPersonas(); closePersonaForm(); setNotice(wasEditing ? "Persona updated." : "Persona created.", "success");
} }
@@ -391,7 +401,7 @@ async function sendMessage() {
const autoTitle = message.length > 40 ? `${message.slice(0, 40)}...` : message; const autoTitle = message.length > 40 ? `${message.slice(0, 40)}...` : message;
currentChat.title = autoTitle; updateWorkspaceCopy(); renderChatList(); await put(`/chats/${activeChatId}`, {title: autoTitle}); currentChat.title = autoTitle; updateWorkspaceCopy(); renderChatList(); await put(`/chats/${activeChatId}`, {title: autoTitle});
} }
currentMessages.push({role: "user", content: message}); addMessage(message, true); updateEmptyState(); updateChatActionState(); currentMessages.push({role: "user", content: message}); addMessage(message, true); updateChatActionState();
msgInput.value = ""; msgInput.style.height = "auto"; setLoadingState(true); setNotice("Generating reply..."); msgInput.value = ""; msgInput.style.height = "auto"; setLoadingState(true); setNotice("Generating reply...");
const loadingMsg = addMessage("", false, true); const loadingMsg = addMessage("", false, true);
try { try {
@@ -423,7 +433,7 @@ registerForm.addEventListener("submit", (event) => { event.preventDefault(); han
$("logout").addEventListener("click", async () => { $("logout").addEventListener("click", async () => {
await post("/logout", {}); chatDiv.style.display = "none"; authDiv.style.display = "grid"; await post("/logout", {}); chatDiv.style.display = "none"; authDiv.style.display = "grid";
activeChatId = null; currentUsername = ""; currentSummary = null; chatSessions = []; currentMessages = []; assistantPersonas = []; userPersonas = []; activeChatId = null; currentUsername = ""; currentSummary = null; chatSessions = []; currentMessages = []; assistantPersonas = []; userPersonas = [];
activePersonaId = null; activeUserPersonaId = null; publishedPersonaIds = new Set(); messagesDiv.innerHTML = ""; renderChatList(); updateEmptyState(); updateChatActionState(); activePersonaId = null; activeUserPersonaId = null; publishedPersonaIds = new Set(); messagesDiv.innerHTML = ""; renderChatList(); updateChatActionState();
showAuthScreen("login"); setAuthMessage("Logged out.", "success"); setNotice("Ready."); showAuthScreen("login"); setAuthMessage("Logged out.", "success"); setNotice("Ready.");
}); });
sendBtn.addEventListener("click", sendMessage); sendBtn.addEventListener("click", sendMessage);
@@ -440,5 +450,5 @@ personaMenuButton.addEventListener("click", togglePersonaPopover); personaPopove
document.addEventListener("keydown", (event) => { if (event.key === "Escape") { closePersonaPopover(); closePersonaForm(); } }); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { closePersonaPopover(); closePersonaForm(); } });
window.addEventListener("load", async () => { window.addEventListener("load", async () => {
applyTheme(localStorage.getItem("krishd-theme") || "fakegpt", false); applyTheme(localStorage.getItem("krishd-theme") || "fakegpt", false);
setNotice("Ready."); updateEmptyState(); updateChatActionState(); await checkSession(); setNotice("Ready."); updateChatActionState(); await checkSession();
}); });
+96 -25
View File
@@ -334,8 +334,7 @@ h2 {
.subtitle, .subtitle,
.persona-guidance, .persona-guidance,
.persona-helper, .persona-helper,
.composer-hint, .composer-hint {
.empty-state p:last-child {
margin: 0; margin: 0;
color: var(--text-soft); color: var(--text-soft);
line-height: 1.6; line-height: 1.6;
@@ -390,7 +389,6 @@ h2 {
.input-shell, .input-shell,
.chat-sidebar, .chat-sidebar,
.messages-panel, .messages-panel,
.empty-state,
.persona-popover, .persona-popover,
.modal-card, .modal-card,
.market-shell { .market-shell {
@@ -758,7 +756,6 @@ select:focus {
.workspace-overview, .workspace-overview,
.messages-panel, .messages-panel,
.empty-state,
.input-shell { .input-shell {
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
padding: 14px; padding: 14px;
@@ -863,18 +860,6 @@ select:focus {
min-height: 0; min-height: 0;
} }
.empty-state {
display: none;
align-content: center;
justify-items: start;
gap: 8px;
background: rgba(250, 244, 235, 0.75);
}
.empty-state.visible {
display: grid;
}
.msg { .msg {
max-width: min(78%, 780px); max-width: min(78%, 780px);
padding: 14px 16px; padding: 14px 16px;
@@ -1175,22 +1160,83 @@ select:focus {
display: grid; display: grid;
place-items: center; place-items: center;
padding: 24px; padding: 24px;
background: rgba(24, 32, 31, 0.16); background: rgba(12, 16, 18, 0.3);
backdrop-filter: blur(4px); backdrop-filter: blur(10px);
z-index: 50; z-index: 50;
overflow: auto;
} }
.modal-card { .modal-card {
width: min(560px, 95vw); width: min(760px, 95vw);
max-height: calc(100vh - 48px);
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
padding: 18px; padding: 20px;
overflow: auto;
} }
.persona-form { .persona-form {
display: grid; display: grid;
gap: 12px; gap: 18px;
padding: 0;
border: none;
background: transparent;
}
.persona-modal-card {
display: grid;
gap: 18px;
}
.persona-modal-heading {
display: grid;
gap: 4px;
}
.persona-form-intro {
padding: 16px 18px;
border-radius: 16px;
background: rgba(24, 32, 31, 0.04);
border: 1px solid rgba(24, 32, 31, 0.08);
}
.persona-form > .status {
margin: 0;
}
.persona-form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
.field-wide {
grid-column: 1 / -1;
}
.persona-form .field,
.persona-form .field-wide {
padding: 14px; padding: 14px;
border-radius: var(--radius-md); border-radius: var(--radius-md);
background: rgba(24, 32, 31, 0.03);
border: 1px solid rgba(24, 32, 31, 0.06);
}
.persona-form .field textarea {
min-height: 112px;
}
.persona-actions {
display: flex;
justify-content: flex-end;
position: sticky;
bottom: 0;
padding-top: 10px;
background: inherit;
}
.persona-actions .primary-action {
width: auto;
min-width: 180px;
} }
.market-shell { .market-shell {
@@ -1432,7 +1478,9 @@ body[data-theme="fakegpt"] .chat-item button,
body[data-theme="fakegpt"] .session-chip, body[data-theme="fakegpt"] .session-chip,
body[data-theme="fakegpt"] .settings-nav, body[data-theme="fakegpt"] .settings-nav,
body[data-theme="fakegpt"] .settings-nav-item:not(.active), body[data-theme="fakegpt"] .settings-nav-item:not(.active),
body[data-theme="fakegpt"] .settings-theme-note { body[data-theme="fakegpt"] .settings-theme-note,
body[data-theme="fakegpt"] .persona-form-intro,
body[data-theme="fakegpt"] .persona-form .field {
background: rgba(18, 24, 21, 0.96); background: rgba(18, 24, 21, 0.96);
color: #d7fff3; color: #d7fff3;
border: 1px solid rgba(36, 255, 180, 0.16); border: 1px solid rgba(36, 255, 180, 0.16);
@@ -1515,7 +1563,9 @@ body[data-theme="fraud"] .icon-button,
body[data-theme="fraud"] .session-chip, body[data-theme="fraud"] .session-chip,
body[data-theme="fraud"] .settings-nav, body[data-theme="fraud"] .settings-nav,
body[data-theme="fraud"] .settings-nav-item:not(.active), body[data-theme="fraud"] .settings-nav-item:not(.active),
body[data-theme="fraud"] .settings-theme-note { body[data-theme="fraud"] .settings-theme-note,
body[data-theme="fraud"] .persona-form-intro,
body[data-theme="fraud"] .persona-form .field {
background: rgba(255, 249, 242, 0.88); background: rgba(255, 249, 242, 0.88);
color: #4b3d31; color: #4b3d31;
border: 1px solid rgba(84, 62, 43, 0.08); border: 1px solid rgba(84, 62, 43, 0.08);
@@ -1628,7 +1678,9 @@ body[data-theme="slopilot"] .session-chip {
body[data-theme="slopilot"] .settings-nav, body[data-theme="slopilot"] .settings-nav,
body[data-theme="slopilot"] .settings-nav-item:not(.active), body[data-theme="slopilot"] .settings-nav-item:not(.active),
body[data-theme="slopilot"] .settings-theme-note { body[data-theme="slopilot"] .settings-theme-note,
body[data-theme="slopilot"] .persona-form-intro,
body[data-theme="slopilot"] .persona-form .field {
background: #ffffff; background: #ffffff;
border-color: rgba(13, 104, 255, 0.12); border-color: rgba(13, 104, 255, 0.12);
} }
@@ -1714,7 +1766,9 @@ body[data-theme="confusity"] .session-chip {
body[data-theme="confusity"] .settings-nav, body[data-theme="confusity"] .settings-nav,
body[data-theme="confusity"] .settings-nav-item:not(.active), body[data-theme="confusity"] .settings-nav-item:not(.active),
body[data-theme="confusity"] .settings-theme-note { body[data-theme="confusity"] .settings-theme-note,
body[data-theme="confusity"] .persona-form-intro,
body[data-theme="confusity"] .persona-form .field {
background: #ffffff; background: #ffffff;
border-color: rgba(17, 24, 39, 0.08); border-color: rgba(17, 24, 39, 0.08);
} }
@@ -1795,6 +1849,19 @@ body[data-theme="confusity"] .settings-nav-item.active {
border: 1px solid rgba(17, 24, 39, 0.16); border: 1px solid rgba(17, 24, 39, 0.16);
} }
body[data-theme="germini"] .persona-form-intro,
body[data-theme="germini"] .persona-form .field {
background: rgba(255, 255, 255, 0.26);
border: 1px solid rgba(255, 255, 255, 0.3);
backdrop-filter: blur(18px);
}
body[data-theme="beta-ai"] .persona-form-intro,
body[data-theme="beta-ai"] .persona-form .field {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(243, 238, 255, 0.92));
border: 1px solid rgba(90, 103, 255, 0.08);
}
@keyframes gemFloat { @keyframes gemFloat {
0%, 100% { 0%, 100% {
transform: translateY(0); transform: translateY(0);
@@ -1923,6 +1990,10 @@ body[data-theme="confusity"] .settings-nav-item.active {
justify-self: stretch; justify-self: stretch;
} }
.persona-form-grid {
grid-template-columns: 1fr;
}
.settings-shell .market-header-simple { .settings-shell .market-header-simple {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
+59 -15
View File
@@ -782,6 +782,56 @@ const normalizePersonaField = (value) => {
return trimmed ? trimmed : null; return trimmed ? trimmed : null;
}; };
const PERSONA_FIELD_LIMITS = {
name: 80,
pronouns: 40,
appearance: 400,
background: 1000,
details: 1200
};
const blockedPersonaPatterns = [
/\b(child|children|kid|kids|minor|underage|under-aged|teenager|young girl|young boy|loli|shota)\b/i,
/\b(nazi|hitler|third reich|kkk|ku klux klan|white power|nigger|nigga)\b/i,
/\b(rape|rapist|sexual assault|molest|molestation|incest|bestiality|zoophilia|necrophilia)\b/i,
/\b(self-harm|self harm|suicide fetish)\b/i
];
const validatePersonaPayload = (payload) => {
const sanitized = {
name: (payload?.name || "").trim(),
pronouns: normalizePersonaField(payload?.pronouns),
appearance: normalizePersonaField(payload?.appearance),
background: normalizePersonaField(payload?.background),
details: normalizePersonaField(payload?.details)
};
if (!sanitized.name) {
return {error: "Name is required"};
}
for (const [key, limit] of Object.entries(PERSONA_FIELD_LIMITS)) {
const value = sanitized[key];
if (value && value.length > limit) {
return {error: `${key.charAt(0).toUpperCase() + key.slice(1)} is too long`};
}
}
const combinedText = [
sanitized.name,
sanitized.pronouns,
sanitized.appearance,
sanitized.background,
sanitized.details
].filter(Boolean).join("\n");
if (blockedPersonaPatterns.some((pattern) => pattern.test(combinedText))) {
return {error: "This persona contains disallowed content. Remove sexual content involving minors, extreme sexual violence, or hateful/extremist material."};
}
return {sanitized};
};
const buildPersonaPrompt = (persona) => { const buildPersonaPrompt = (persona) => {
const lines = [ const lines = [
"SYSTEM ROLE:", "SYSTEM ROLE:",
@@ -921,18 +971,15 @@ app.get("/personas", requireLogin, (req, res) => {
app.post("/personas", requireLogin, (req, res) => { app.post("/personas", requireLogin, (req, res) => {
const user = req.session.user; const user = req.session.user;
const name = (req.body?.name || "").trim();
if (!name) {
return res.status(400).json({error: "Name is required"});
}
const personaType = (req.body?.personaType || "assistant").trim(); const personaType = (req.body?.personaType || "assistant").trim();
if (!["assistant", "user"].includes(personaType)) { if (!["assistant", "user"].includes(personaType)) {
return res.status(400).json({error: "Invalid persona type"}); return res.status(400).json({error: "Invalid persona type"});
} }
const pronouns = normalizePersonaField(req.body?.pronouns); const validation = validatePersonaPayload(req.body);
const appearance = normalizePersonaField(req.body?.appearance); if (validation.error) {
const background = normalizePersonaField(req.body?.background); return res.status(400).json({error: validation.error});
const details = normalizePersonaField(req.body?.details); }
const {name, pronouns, appearance, background, details} = validation.sanitized;
const personaId = insertPersonaStmt.run( const personaId = insertPersonaStmt.run(
user, user,
name, name,
@@ -949,14 +996,11 @@ app.post("/personas", requireLogin, (req, res) => {
app.put("/personas/:id", requireLogin, (req, res) => { app.put("/personas/:id", requireLogin, (req, res) => {
const user = req.session.user; const user = req.session.user;
const personaId = Number(req.params.id); const personaId = Number(req.params.id);
const name = (req.body?.name || "").trim(); const validation = validatePersonaPayload(req.body);
if (!name) { if (validation.error) {
return res.status(400).json({error: "Name is required"}); return res.status(400).json({error: validation.error});
} }
const pronouns = normalizePersonaField(req.body?.pronouns); const {name, pronouns, appearance, background, details} = validation.sanitized;
const appearance = normalizePersonaField(req.body?.appearance);
const background = normalizePersonaField(req.body?.background);
const details = normalizePersonaField(req.body?.details);
const result = updatePersonaStmt.run( const result = updatePersonaStmt.run(
name, name,
pronouns, pronouns,