diff --git a/public/chat.html b/public/chat.html
index de3b05a..9fe66c0 100644
--- a/public/chat.html
+++ b/public/chat.html
@@ -174,7 +174,7 @@
+ type="button">Clear for this chat
Manage
diff --git a/public/js/personaController.js b/public/js/personaController.js
index 1bd6c2f..d0a6dff 100644
--- a/public/js/personaController.js
+++ b/public/js/personaController.js
@@ -125,13 +125,18 @@ export function createPersonaController({
}
function renderPersonaSection() {
- updateActivePersonaStatus(state.userPersonas, state.activeUserPersonaId, activeUserPersonaStatus, "No default user persona set.", "Default user persona");
- if (chatDrawerPersonaValue) {
- const active = state.userPersonas.find((persona) => persona.id === state.activeUserPersonaId);
- chatDrawerPersonaValue.textContent = active ? active.name : "None";
- if (headerPersonaName) headerPersonaName.textContent = active ? active.name : "None";
+ const activeChat = state.chatSessions.find((chat) => chat.id === state.activeChatId);
+ const chatPersona = state.userPersonas.find((persona) => persona.id === activeChat?.user_persona_id);
+ if (activeUserPersonaStatus) {
+ activeUserPersonaStatus.textContent = activeChat
+ ? `Using in this chat: ${chatPersona?.name || "yourself"}. Default: ${getPersonaName(state.activeUserPersonaId, state.userPersonas) || "yourself"}.`
+ : `Default user persona: ${getPersonaName(state.activeUserPersonaId, state.userPersonas) || "yourself"}.`;
}
- if (clearUserPersonaBtn) clearUserPersonaBtn.disabled = !state.activeUserPersonaId;
+ if (chatDrawerPersonaValue) {
+ chatDrawerPersonaValue.textContent = chatPersona?.name || "Yourself";
+ if (headerPersonaName) headerPersonaName.textContent = chatPersona?.name || "Yourself";
+ }
+ if (clearUserPersonaBtn) clearUserPersonaBtn.disabled = !activeChat?.user_persona_id;
}
function maybeShowPersonaNudge() {
@@ -142,12 +147,6 @@ export function createPersonaController({
setNotice("Want to personalize responses? Try personas in Tools.", "success");
}
- function updateActivePersonaStatus(items, activeId, statusEl, emptyText, prefix) {
- if (!statusEl) return;
- const active = items.find((persona) => persona.id === activeId);
- statusEl.textContent = active ? `${prefix}: ${active.name}` : emptyText;
- }
-
function openPersonaForm(persona = null, personaType = "assistant") {
closeChatDrawer();
closePopup(false);
@@ -263,13 +262,17 @@ export function createPersonaController({
renderPersonaSection();
return;
}
+ const selectedId = personaType === "user"
+ ? state.chatSessions.find((chat) => chat.id === state.activeChatId)?.user_persona_id ?? null
+ : activeId;
items.forEach((persona) => {
const item = document.createElement("div"), titleRow = document.createElement("div"), title = document.createElement("h4");
const tag = document.createElement("span"), meta = document.createElement("p"), actions = document.createElement("div");
- item.className = `persona-item${personaType === "user" && persona.id === activeId ? " active" : ""}`; titleRow.className = "persona-title-row"; actions.className = "persona-item-actions";
+ item.className = `persona-item${personaType === "user" && persona.id === selectedId ? " active" : ""}`; titleRow.className = "persona-title-row"; actions.className = "persona-item-actions";
tag.className = `persona-role-tag ${personaType === "user" ? "user" : "ai"}`; tag.textContent = personaType === "user" ? "You" : "AI";
title.textContent = persona.name; meta.textContent = persona.pronouns ? `Pronouns: ${persona.pronouns}` : "Pronouns: n/a";
- [[personaType === "assistant" ? "Start roleplay" : "Set default", () => equipPersona(persona.id, personaType), personaType === "user" && persona.id === activeId],
+ [[personaType === "assistant" ? "Start roleplay" : "Use in this chat", () => personaType === "user" ? setChatUserPersona(persona.id) : equipPersona(persona.id, personaType), personaType === "user" && persona.id === selectedId],
+ ...(personaType === "user" ? [["Set default", () => equipPersona(persona.id, personaType), persona.id === state.activeUserPersonaId]] : []),
["Edit", () => openPersonaForm(persona), false],
[state.publishedPersonaIds.has(persona.id) ? "Update listing" : "Publish", () => publishPersona(persona.id), false],
["Delete", () => deletePersona(persona.id), false]]
@@ -309,13 +312,24 @@ export function createPersonaController({
setNotice("User persona updated.", "success");
}
- async function clearPersona(type) {
- if (type !== "user") return;
- const res = await post("/personas/user/clear", {});
+ async function setChatUserPersona(id) {
+ if (!state.activeChatId) return setNotice("Open a chat before choosing a user persona.", "error");
+ const res = await put(`/chats/${state.activeChatId}/user-persona`, {userPersonaId: id});
if (res.error) return setNotice(res.error, "error");
- state.activeUserPersonaId = null;
- renderPersonaList(state.userPersonas, state.activeUserPersonaId, userPersonaList, "user");
- setNotice("Persona cleared.", "success");
+ const chat = state.chatSessions.find((item) => item.id === state.activeChatId);
+ if (chat) Object.assign(chat, res.chat);
+ renderPersonaList(state.userPersonas, res.chat.user_persona_id, userPersonaList, "user");
+ setNotice("User persona switched for this chat.", "success");
+ }
+
+ async function clearChatUserPersona() {
+ if (!state.activeChatId) return;
+ const res = await put(`/chats/${state.activeChatId}/user-persona`, {userPersonaId: null});
+ if (res.error) return setNotice(res.error, "error");
+ const chat = state.chatSessions.find((item) => item.id === state.activeChatId);
+ if (chat) Object.assign(chat, res.chat);
+ renderPersonaList(state.userPersonas, null, userPersonaList, "user");
+ setNotice("This chat will use yourself.", "success");
}
async function savePersona() {
@@ -406,6 +420,7 @@ export function createPersonaController({
}
return {
+ renderPersonaSection,
updateRoleplaySuggestions,
setRoleplayStarterNotice,
renderRoleplayStarterStep,
@@ -415,7 +430,7 @@ export function createPersonaController({
openRoleplayStarter,
closeRoleplayStarter,
loadPersonas,
- clearPersona,
+ clearChatUserPersona,
savePersona,
startRoleplay
};
diff --git a/public/js/script.js b/public/js/script.js
index bbaa28e..0a43e60 100644
--- a/public/js/script.js
+++ b/public/js/script.js
@@ -151,7 +151,8 @@ const {
openRoleplayStarter,
closeRoleplayStarter,
loadPersonas,
- clearPersona,
+ clearChatUserPersona,
+ renderPersonaSection,
savePersona,
startRoleplay
} = createPersonaController({
@@ -448,6 +449,7 @@ function updateWorkspaceCopy() {
}
renderDrawerSummary();
+ renderPersonaSection();
updateComposerPlaceholder();
}
@@ -986,7 +988,7 @@ saveGenerationSettingsBtn.addEventListener("click", () => { void saveGenerationS
roleplayNewPersonaBtn.addEventListener("click", () => { closeRoleplayStarter(); openPersonaForm(null, "assistant"); });
clearUserPersonaBtn.addEventListener("click", () => {
closeChatDrawer();
- void clearPersona("user");
+ void clearChatUserPersona();
});
personaForm.addEventListener("submit", (event) => { event.preventDefault(); void savePersona(); }); personaCloseBtn.addEventListener("click", closePersonaForm);
personaTypeSelect.addEventListener("change", () => {
diff --git a/src/db/chatRepository.js b/src/db/chatRepository.js
index 314fabe..bd3909a 100644
--- a/src/db/chatRepository.js
+++ b/src/db/chatRepository.js
@@ -111,6 +111,7 @@ export function createChatStatements(db) {
LIMIT 1
`),
updateChatSessionTitle: db.prepare("UPDATE chat_sessions SET title = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"),
+ updateChatUserPersona: db.prepare("UPDATE chat_sessions SET user_persona_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"),
updateChatSessionScene: db.prepare("UPDATE chat_sessions SET scenario_prompt = ?, scenario_summary = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"),
updateChatContextSummary: db.prepare("UPDATE chat_sessions SET context_summary = ?, context_summary_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"),
updateChatGenerationSettings: db.prepare(`
@@ -432,6 +433,7 @@ const failChatTurnTransaction = db.transaction((chatId, userMessageId, errorMess
return statements.getChatSession.get(chatId, username);
},
updateChatTitle: (chatId, username, title) => statements.updateChatSessionTitle.run(title, chatId, username),
+ updateChatUserPersona: (chatId, username, personaId) => statements.updateChatUserPersona.run(personaId ?? null, chatId, username),
updateChatScene: (chatId, username, scenarioPrompt, scenarioSummary) => statements.updateChatSessionScene.run(scenarioPrompt, scenarioSummary, chatId, username),
updateChatContextSummary: (chatId, username, summary, messageId) => statements.updateChatContextSummary.run(summary, Number(messageId || 0), chatId, username),
getPersonaChatState: (chatId, username) => statements.getPersonaChatState.get(chatId, username) || null,
diff --git a/src/routes/chats.js b/src/routes/chats.js
index daeded7..f88383e 100644
--- a/src/routes/chats.js
+++ b/src/routes/chats.js
@@ -116,6 +116,13 @@ const preferredModelValidator = (body) => {
return {value: {model}};
};
+const userPersonaValidator = (body) => {
+ if (body?.userPersonaId == null || body.userPersonaId === "") return {value: {userPersonaId: null}};
+ const userPersonaId = Number(body.userPersonaId);
+ if (!Number.isInteger(userPersonaId) || userPersonaId < 1) return {error: "Invalid user persona"};
+ return {value: {userPersonaId}};
+};
+
export function createChatsRouter({repositories, chatService, modelService, config, chatRateLimiters, attachmentService}) {
const router = express.Router();
router.use(requireLogin);
@@ -272,6 +279,32 @@ export function createChatsRouter({repositories, chatService, modelService, conf
return res.json({chat: repositories.getChat(chatId, req.session.user)});
});
+ router.put("/chats/:id/user-persona", validateBody(userPersonaValidator), (req, res) => {
+ const chatId = Number(req.params.id);
+ const user = req.session.user;
+ const chat = repositories.getChat(chatId, user);
+ if (!chat) return res.status(404).json({error: "Chat not found"});
+ const {userPersonaId} = req.validatedBody;
+ if (userPersonaId) {
+ const persona = repositories.getPersona(userPersonaId, user);
+ if (!persona || persona.persona_type !== "user") return res.status(404).json({error: "User persona not found"});
+ }
+ repositories.updateChatUserPersona(chatId, user, userPersonaId);
+ if (chat.assistant_persona_id) {
+ const assistantPersona = repositories.getPersona(chat.assistant_persona_id, user);
+ const userPersona = userPersonaId ? repositories.getPersona(userPersonaId, user) : null;
+ if (assistantPersona) {
+ repositories.updateChatScene(
+ chatId,
+ user,
+ chat.scenario_prompt,
+ chatService.buildRoleplaySceneSummary(assistantPersona, userPersona, chat.scenario_prompt)
+ );
+ }
+ }
+ return res.json({chat: repositories.getChat(chatId, user)});
+ });
+
router.post("/chats/:id/branches/:messageId/activate", (req, res) => {
const result = chatService.activateBranch({
user: req.session.user,