From 0ead7fa30e9b0d0e649a3cb218d6753833f9bbc1 Mon Sep 17 00:00:00 2001 From: fretuks Date: Tue, 6 Oct 2026 22:30:02 +0200 Subject: [PATCH] Enable automatic web search by default; improve toggle behavior and search planning. --- public/chat.html | 2 +- public/js/personaController.js | 57 +++++++++++++++++++++------------- public/js/script.js | 6 ++-- src/db/chatRepository.js | 2 ++ src/routes/chats.js | 33 ++++++++++++++++++++ 5 files changed, 76 insertions(+), 24 deletions(-) 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,