Add conversation and message model selection

This commit is contained in:
dwrz
2026-11-01 14:53:11 +00:00
parent 019ff64621
commit a4cc2d31ba
7 changed files with 289 additions and 28 deletions
+48 -3
View File
@@ -55,8 +55,15 @@ class Odidere {
);
},
onComposeSubmit: () => this.submit(),
onModelChange: ({ provider, model }) => {
this.storage.setModelSelection({ provider, model });
onDefaultModelChange: ({ provider, model }) =>
this.storage.setModelSelection({ provider, model }),
onComposeModelChange: ({ provider, model }) => {
if (this.conversationId) {
this.storage.setConversationModel(this.conversationId, {
provider,
model,
});
}
this.ui.updateContext(
this.totalTokens,
this.ui.getSelectedModel().contextSize,
@@ -583,7 +590,15 @@ class Odidere {
try {
const body = { stream: true };
if (message) body.message = message;
// Read the selected model once. Save it for this conversation,
// which always has an ID here, and send it with the request.
const { provider, model } = this.ui.getSelectedModel();
if (provider && model) {
this.storage.setConversationModel(this.conversationId, {
provider,
model,
});
}
if (provider) body.provider = provider;
if (model) body.model = model;
const maxIterations = this.storage.getMaxIterations();
@@ -707,6 +722,7 @@ class Odidere {
if (id) {
try {
await this.api.deleteConversation(id);
this.storage.deleteConversationModel(id);
} catch (error) {
// The conversation survives on the server, but the local
// selection is still cleared.
@@ -732,6 +748,7 @@ class Odidere {
this.ui.clearChat();
this.ui.setPendingApprovals([]);
this.ui.renderSystemDefault(this.storage.getSystemMessage());
this.ui.selectComposeModel(this.resolveConversationModel(null));
this.ui.updateContext(0, null);
this.updateSendEnabled();
}
@@ -778,6 +795,9 @@ class Odidere {
this.ui.closeConversations();
return;
}
// Remove model entries for conversations deleted in other tabs
// or on other devices.
this.storage.pruneConversationModels(this.conversations.map((c) => c.id));
this.ui.renderConversationList(this.conversations, {
currentId: this.conversationId,
});
@@ -850,6 +870,7 @@ class Odidere {
this.ui.setPendingApprovals([]);
this.ui.updateContext(0, null);
this.conversationId = id;
this.ui.selectComposeModel(this.resolveConversationModel(id));
await this.resync();
this.updateSendEnabled();
}
@@ -890,6 +911,7 @@ class Odidere {
this.ui.renderError(`Failed to delete conversation: ${error.message}`);
return;
}
this.storage.deleteConversationModel(id);
this.conversations = this.conversations.filter((c) => c.id !== id);
if (id === this.conversationId) {
// The server-side delete cancels an ongoing inference.
@@ -1009,6 +1031,9 @@ class Odidere {
* request. Used when the server reports the conversation gone.
*/
async clearSelection() {
if (this.conversationId) {
this.storage.deleteConversationModel(this.conversationId);
}
this.messages.clear();
this.drafts.clear();
this.storage.clearConversation();
@@ -1018,6 +1043,7 @@ class Odidere {
this.ui.clearChat();
this.ui.setPendingApprovals([]);
this.ui.renderSystemDefault(this.storage.getSystemMessage());
this.ui.selectComposeModel(this.resolveConversationModel(null));
this.ui.updateContext(0, null);
this.updateSendEnabled();
}
@@ -1567,10 +1593,29 @@ class Odidere {
// MODELS AND VOICES
// ====================
/**
* resolveConversationModel returns the provider and model stored
* for the given conversation ID, falling back to the default
* model selection when none is stored.
* @param {string | null} [id]
* @returns {{provider: string | null, model: string | null}}
*/
resolveConversationModel(id = this.conversationId) {
if (id) {
const stored = this.storage.getConversationModel(id);
if (stored.provider && stored.model) return stored;
}
return this.storage.getModelSelection();
}
async loadModels() {
try {
const data = await this.api.getModels();
this.ui.populateModels(data, this.storage.getModelSelection());
this.ui.populateModels(
data,
this.storage.getModelSelection(),
this.resolveConversationModel(),
);
this.ui.updateContext(
this.totalTokens,
this.ui.getSelectedModel().contextSize,