add support for switching preferred model per chat session
This commit is contained in:
@@ -368,6 +368,20 @@ async function saveGenerationSettings() {
|
|||||||
setNotice("Generation settings saved.", "success");
|
setNotice("Generation settings saved.", "success");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveSelectedModel() {
|
||||||
|
if (!activeChatId || !modelSelect.value) return;
|
||||||
|
const chatId = activeChatId;
|
||||||
|
const selectedModel = modelSelect.value;
|
||||||
|
const res = await put(`/chats/${chatId}/model`, {model: selectedModel});
|
||||||
|
if (res.error || !res.chat) return setNotice(res.error || "Unable to switch models.", "error");
|
||||||
|
const chat = getChatById(chatId);
|
||||||
|
if (chat) Object.assign(chat, res.chat);
|
||||||
|
if (chatId === activeChatId && modelSelect.value === selectedModel) {
|
||||||
|
renderModelSection();
|
||||||
|
setNotice(`Model switched to ${modelSelect.selectedOptions[0]?.textContent || selectedModel}.`, "success");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function resizeComposerInput() {
|
function resizeComposerInput() {
|
||||||
msgInput.style.height = "auto";
|
msgInput.style.height = "auto";
|
||||||
const nextHeight = Math.min(Math.max(msgInput.scrollHeight, 46), 96);
|
const nextHeight = Math.min(Math.max(msgInput.scrollHeight, 46), 96);
|
||||||
@@ -1978,6 +1992,7 @@ if (importWorkspaceFileInput) importWorkspaceFileInput.addEventListener("change"
|
|||||||
chatSearchInput.addEventListener("input", () => { void loadChatSessions(); });
|
chatSearchInput.addEventListener("input", () => { void loadChatSessions(); });
|
||||||
modelSelect.addEventListener("change", () => {
|
modelSelect.addEventListener("change", () => {
|
||||||
renderModelSection();
|
renderModelSection();
|
||||||
|
void saveSelectedModel();
|
||||||
});
|
});
|
||||||
saveGenerationSettingsBtn.addEventListener("click", () => { void saveGenerationSettings(); });
|
saveGenerationSettingsBtn.addEventListener("click", () => { void saveGenerationSettings(); });
|
||||||
roleplayNewPersonaBtn.addEventListener("click", () => openPersonaForm(null, "assistant"));
|
roleplayNewPersonaBtn.addEventListener("click", () => openPersonaForm(null, "assistant"));
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ export function createRepositories(db, config) {
|
|||||||
SET preferred_model = ?, temperature = ?, context_length = ?, response_length = ?, system_instruction = ?, updated_at = CURRENT_TIMESTAMP
|
SET preferred_model = ?, temperature = ?, context_length = ?, response_length = ?, system_instruction = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ? AND username = ?
|
WHERE id = ? AND username = ?
|
||||||
`),
|
`),
|
||||||
|
updateChatPreferredModel: db.prepare("UPDATE chat_sessions SET preferred_model = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"),
|
||||||
updateChatActiveLeaf: db.prepare("UPDATE chat_sessions SET active_leaf_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"),
|
updateChatActiveLeaf: db.prepare("UPDATE chat_sessions SET active_leaf_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"),
|
||||||
updateChatBranchSource: db.prepare("UPDATE chat_sessions SET source_chat_id = ?, branched_from_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"),
|
updateChatBranchSource: db.prepare("UPDATE chat_sessions SET source_chat_id = ?, branched_from_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND username = ?"),
|
||||||
setChatActiveLeafById: db.prepare("UPDATE chat_sessions SET active_leaf_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?"),
|
setChatActiveLeafById: db.prepare("UPDATE chat_sessions SET active_leaf_message_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?"),
|
||||||
@@ -1027,6 +1028,7 @@ export function createRepositories(db, config) {
|
|||||||
chatId,
|
chatId,
|
||||||
username
|
username
|
||||||
),
|
),
|
||||||
|
updateChatPreferredModel: (chatId, username, model) => statements.updateChatPreferredModel.run(model || null, chatId, username),
|
||||||
updateChatMessage: (chatId, messageId, content) => statements.updateChatMessage.run(content, messageId, chatId),
|
updateChatMessage: (chatId, messageId, content) => statements.updateChatMessage.run(content, messageId, chatId),
|
||||||
updateChatMessageRetryState: (chatId, messageId, payload) => statements.updateChatMessageWithRetryState.run(
|
updateChatMessageRetryState: (chatId, messageId, payload) => statements.updateChatMessageWithRetryState.run(
|
||||||
payload.content,
|
payload.content,
|
||||||
|
|||||||
@@ -91,6 +91,13 @@ const generationSettingsValidator = (body) => {
|
|||||||
}};
|
}};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const preferredModelValidator = (body) => {
|
||||||
|
const model = String(body?.model || "").trim();
|
||||||
|
if (!model) return {error: "Model is required"};
|
||||||
|
if (model.length > 200) return {error: "Model must be 200 characters or fewer"};
|
||||||
|
return {value: {model}};
|
||||||
|
};
|
||||||
|
|
||||||
export function createChatsRouter({repositories, chatService, modelService, config, chatRateLimiters}) {
|
export function createChatsRouter({repositories, chatService, modelService, config, chatRateLimiters}) {
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
router.use(requireLogin);
|
router.use(requireLogin);
|
||||||
@@ -180,6 +187,13 @@ 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/model", validateBody(preferredModelValidator), (req, res) => {
|
||||||
|
const chatId = Number(req.params.id);
|
||||||
|
if (!repositories.getChat(chatId, req.session.user)) return res.status(404).json({error: "Chat not found"});
|
||||||
|
repositories.updateChatPreferredModel(chatId, req.session.user, req.validatedBody.model);
|
||||||
|
return res.json({chat: repositories.getChat(chatId, req.session.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,
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export function createChatService(repositories, modelService, config) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getGenerationSettings = (session, requestedModel) => ({
|
const getGenerationSettings = (session, requestedModel) => ({
|
||||||
model: session.preferred_model || requestedModel || "mistral:latest",
|
model: requestedModel || session.preferred_model || "mistral:latest",
|
||||||
generation: {
|
generation: {
|
||||||
temperature: session.temperature ?? undefined,
|
temperature: session.temperature ?? undefined,
|
||||||
contextLength: session.context_length ?? undefined,
|
contextLength: session.context_length ?? undefined,
|
||||||
|
|||||||
+9
-3
@@ -434,11 +434,17 @@ test("conversation branches, edit-resend, and generation settings work", async (
|
|||||||
body: {preferredModel: "preferred:model", temperature: 0.4, contextLength: 4096, responseLength: 300, systemInstruction: "Be exact."},
|
body: {preferredModel: "preferred:model", temperature: 0.4, contextLength: 4096, responseLength: 300, systemInstruction: "Be exact."},
|
||||||
jar
|
jar
|
||||||
});
|
});
|
||||||
await request(server.baseUrl, "/chat", {method: "POST", body: {chatId, message: "first", model: "ignored:model"}, jar});
|
await request(server.baseUrl, "/chat", {method: "POST", body: {chatId, message: "first", model: "selected:model"}, jar});
|
||||||
assert.equal(calls[0].model, "preferred:model");
|
assert.equal(calls[0].model, "selected:model");
|
||||||
assert.deepEqual(calls[0].options.generation, {temperature: 0.4, contextLength: 4096, responseLength: 300});
|
assert.deepEqual(calls[0].options.generation, {temperature: 0.4, contextLength: 4096, responseLength: 300});
|
||||||
assert.match(calls[0].messages[0].content, /Be exact/);
|
assert.match(calls[0].messages[0].content, /Be exact/);
|
||||||
|
|
||||||
|
const switchedModel = await request(server.baseUrl, `/chats/${chatId}/model`, {
|
||||||
|
method: "PUT", body: {model: "selected:model"}, jar
|
||||||
|
});
|
||||||
|
assert.equal(switchedModel.status, 200);
|
||||||
|
assert.equal(switchedModel.json.chat.preferred_model, "selected:model");
|
||||||
|
|
||||||
await request(server.baseUrl, "/chat", {method: "POST", body: {chatId, message: "second", model: "ignored:model"}, jar});
|
await request(server.baseUrl, "/chat", {method: "POST", body: {chatId, message: "second", model: "ignored:model"}, jar});
|
||||||
const original = await request(server.baseUrl, `/chats/${chatId}/messages`, {jar});
|
const original = await request(server.baseUrl, `/chats/${chatId}/messages`, {jar});
|
||||||
const firstAssistant = original.json.messages[1];
|
const firstAssistant = original.json.messages[1];
|
||||||
@@ -470,7 +476,7 @@ test("conversation branches, edit-resend, and generation settings work", async (
|
|||||||
assert.equal(branched.json.chat.branched_from_message_id, firstAssistant.id);
|
assert.equal(branched.json.chat.branched_from_message_id, firstAssistant.id);
|
||||||
assert.match(branched.json.chat.title, /\(branch\)$/);
|
assert.match(branched.json.chat.title, /\(branch\)$/);
|
||||||
assert.deepEqual(branched.json.messages.map((message) => message.content), ["first", "reply-1"]);
|
assert.deepEqual(branched.json.messages.map((message) => message.content), ["first", "reply-1"]);
|
||||||
assert.equal(branched.json.chat.preferred_model, "preferred:model");
|
assert.equal(branched.json.chat.preferred_model, "selected:model");
|
||||||
assert.equal(branched.json.chat.temperature, 0.4);
|
assert.equal(branched.json.chat.temperature, 0.4);
|
||||||
|
|
||||||
const edited = await request(server.baseUrl, `/chats/${chatId}/messages/${active.json.messages[0].id}/edit-resend`, {
|
const edited = await request(server.baseUrl, `/chats/${chatId}/messages/${active.json.messages[0].id}/edit-resend`, {
|
||||||
|
|||||||
Reference in New Issue
Block a user