Main #15
+1
-1
@@ -174,7 +174,7 @@
|
|||||||
<div class="persona-section-actions">
|
<div class="persona-section-actions">
|
||||||
<button id="clearUserPersona"
|
<button id="clearUserPersona"
|
||||||
class="secondary-action"
|
class="secondary-action"
|
||||||
type="button">Clear</button>
|
type="button">Clear for this chat</button>
|
||||||
<a href="/app/personas"
|
<a href="/app/personas"
|
||||||
class="secondary-action">Manage</a>
|
class="secondary-action">Manage</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -125,13 +125,18 @@ export function createPersonaController({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderPersonaSection() {
|
function renderPersonaSection() {
|
||||||
updateActivePersonaStatus(state.userPersonas, state.activeUserPersonaId, activeUserPersonaStatus, "No default user persona set.", "Default user persona");
|
const activeChat = state.chatSessions.find((chat) => chat.id === state.activeChatId);
|
||||||
if (chatDrawerPersonaValue) {
|
const chatPersona = state.userPersonas.find((persona) => persona.id === activeChat?.user_persona_id);
|
||||||
const active = state.userPersonas.find((persona) => persona.id === state.activeUserPersonaId);
|
if (activeUserPersonaStatus) {
|
||||||
chatDrawerPersonaValue.textContent = active ? active.name : "None";
|
activeUserPersonaStatus.textContent = activeChat
|
||||||
if (headerPersonaName) headerPersonaName.textContent = active ? active.name : "None";
|
? `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() {
|
function maybeShowPersonaNudge() {
|
||||||
@@ -142,12 +147,6 @@ export function createPersonaController({
|
|||||||
setNotice("Want to personalize responses? Try personas in Tools.", "success");
|
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") {
|
function openPersonaForm(persona = null, personaType = "assistant") {
|
||||||
closeChatDrawer();
|
closeChatDrawer();
|
||||||
closePopup(false);
|
closePopup(false);
|
||||||
@@ -263,13 +262,17 @@ export function createPersonaController({
|
|||||||
renderPersonaSection();
|
renderPersonaSection();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const selectedId = personaType === "user"
|
||||||
|
? state.chatSessions.find((chat) => chat.id === state.activeChatId)?.user_persona_id ?? null
|
||||||
|
: activeId;
|
||||||
items.forEach((persona) => {
|
items.forEach((persona) => {
|
||||||
const item = document.createElement("div"), titleRow = document.createElement("div"), title = document.createElement("h4");
|
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");
|
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";
|
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";
|
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],
|
["Edit", () => openPersonaForm(persona), false],
|
||||||
[state.publishedPersonaIds.has(persona.id) ? "Update listing" : "Publish", () => publishPersona(persona.id), false],
|
[state.publishedPersonaIds.has(persona.id) ? "Update listing" : "Publish", () => publishPersona(persona.id), false],
|
||||||
["Delete", () => deletePersona(persona.id), false]]
|
["Delete", () => deletePersona(persona.id), false]]
|
||||||
@@ -309,13 +312,24 @@ export function createPersonaController({
|
|||||||
setNotice("User persona updated.", "success");
|
setNotice("User persona updated.", "success");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearPersona(type) {
|
async function setChatUserPersona(id) {
|
||||||
if (type !== "user") return;
|
if (!state.activeChatId) return setNotice("Open a chat before choosing a user persona.", "error");
|
||||||
const res = await post("/personas/user/clear", {});
|
const res = await put(`/chats/${state.activeChatId}/user-persona`, {userPersonaId: id});
|
||||||
if (res.error) return setNotice(res.error, "error");
|
if (res.error) return setNotice(res.error, "error");
|
||||||
state.activeUserPersonaId = null;
|
const chat = state.chatSessions.find((item) => item.id === state.activeChatId);
|
||||||
renderPersonaList(state.userPersonas, state.activeUserPersonaId, userPersonaList, "user");
|
if (chat) Object.assign(chat, res.chat);
|
||||||
setNotice("Persona cleared.", "success");
|
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() {
|
async function savePersona() {
|
||||||
@@ -406,6 +420,7 @@ export function createPersonaController({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
renderPersonaSection,
|
||||||
updateRoleplaySuggestions,
|
updateRoleplaySuggestions,
|
||||||
setRoleplayStarterNotice,
|
setRoleplayStarterNotice,
|
||||||
renderRoleplayStarterStep,
|
renderRoleplayStarterStep,
|
||||||
@@ -415,7 +430,7 @@ export function createPersonaController({
|
|||||||
openRoleplayStarter,
|
openRoleplayStarter,
|
||||||
closeRoleplayStarter,
|
closeRoleplayStarter,
|
||||||
loadPersonas,
|
loadPersonas,
|
||||||
clearPersona,
|
clearChatUserPersona,
|
||||||
savePersona,
|
savePersona,
|
||||||
startRoleplay
|
startRoleplay
|
||||||
};
|
};
|
||||||
|
|||||||
+4
-2
@@ -151,7 +151,8 @@ const {
|
|||||||
openRoleplayStarter,
|
openRoleplayStarter,
|
||||||
closeRoleplayStarter,
|
closeRoleplayStarter,
|
||||||
loadPersonas,
|
loadPersonas,
|
||||||
clearPersona,
|
clearChatUserPersona,
|
||||||
|
renderPersonaSection,
|
||||||
savePersona,
|
savePersona,
|
||||||
startRoleplay
|
startRoleplay
|
||||||
} = createPersonaController({
|
} = createPersonaController({
|
||||||
@@ -448,6 +449,7 @@ function updateWorkspaceCopy() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderDrawerSummary();
|
renderDrawerSummary();
|
||||||
|
renderPersonaSection();
|
||||||
updateComposerPlaceholder();
|
updateComposerPlaceholder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -986,7 +988,7 @@ saveGenerationSettingsBtn.addEventListener("click", () => { void saveGenerationS
|
|||||||
roleplayNewPersonaBtn.addEventListener("click", () => { closeRoleplayStarter(); openPersonaForm(null, "assistant"); });
|
roleplayNewPersonaBtn.addEventListener("click", () => { closeRoleplayStarter(); openPersonaForm(null, "assistant"); });
|
||||||
clearUserPersonaBtn.addEventListener("click", () => {
|
clearUserPersonaBtn.addEventListener("click", () => {
|
||||||
closeChatDrawer();
|
closeChatDrawer();
|
||||||
void clearPersona("user");
|
void clearChatUserPersona();
|
||||||
});
|
});
|
||||||
personaForm.addEventListener("submit", (event) => { event.preventDefault(); void savePersona(); }); personaCloseBtn.addEventListener("click", closePersonaForm);
|
personaForm.addEventListener("submit", (event) => { event.preventDefault(); void savePersona(); }); personaCloseBtn.addEventListener("click", closePersonaForm);
|
||||||
personaTypeSelect.addEventListener("change", () => {
|
personaTypeSelect.addEventListener("change", () => {
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ export function createChatStatements(db) {
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
`),
|
`),
|
||||||
updateChatSessionTitle: db.prepare("UPDATE chat_sessions SET title = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"),
|
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 = ?"),
|
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 = ?"),
|
updateChatContextSummary: db.prepare("UPDATE chat_sessions SET context_summary = ?, context_summary_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"),
|
||||||
updateChatGenerationSettings: db.prepare(`
|
updateChatGenerationSettings: db.prepare(`
|
||||||
@@ -432,6 +433,7 @@ const failChatTurnTransaction = db.transaction((chatId, userMessageId, errorMess
|
|||||||
return statements.getChatSession.get(chatId, username);
|
return statements.getChatSession.get(chatId, username);
|
||||||
},
|
},
|
||||||
updateChatTitle: (chatId, username, title) => statements.updateChatSessionTitle.run(title, 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),
|
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),
|
updateChatContextSummary: (chatId, username, summary, messageId) => statements.updateChatContextSummary.run(summary, Number(messageId || 0), chatId, username),
|
||||||
getPersonaChatState: (chatId, username) => statements.getPersonaChatState.get(chatId, username) || null,
|
getPersonaChatState: (chatId, username) => statements.getPersonaChatState.get(chatId, username) || null,
|
||||||
|
|||||||
@@ -116,6 +116,13 @@ const preferredModelValidator = (body) => {
|
|||||||
return {value: {model}};
|
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}) {
|
export function createChatsRouter({repositories, chatService, modelService, config, chatRateLimiters, attachmentService}) {
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
router.use(requireLogin);
|
router.use(requireLogin);
|
||||||
@@ -272,6 +279,32 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
|||||||
return res.json({chat: repositories.getChat(chatId, req.session.user)});
|
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) => {
|
router.post("/chats/:id/branches/:messageId/activate", (req, res) => {
|
||||||
const result = chatService.activateBranch({
|
const result = chatService.activateBranch({
|
||||||
user: req.session.user,
|
user: req.session.user,
|
||||||
|
|||||||
Reference in New Issue
Block a user