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
+91 -2
View File
@@ -11,7 +11,9 @@
* - odidere_system_message: the system message used when the next
* conversation is created. It does not change a conversation that
* already exists.
* - odidere_provider, odidere_model, odidere_voice: selections.
* - odidere_provider, odidere_model, odidere_voice: default selections.
* - odidere_conversation_models: per-conversation provider and model
* selections, keyed by conversation ID.
* - odidere_max_turns, odidere_reasoning_budget: compose settings.
* - odidere_mute: whether speech playback is muted.
*
@@ -20,6 +22,7 @@
*/
const CONVERSATION_KEY = 'odidere_conversation';
const CONVERSATION_MODELS_KEY = 'odidere_conversation_models';
const SYSTEM_MESSAGE_KEY = 'odidere_system_message';
const PROVIDER_KEY = 'odidere_provider';
const MODEL_KEY = 'odidere_model';
@@ -100,7 +103,7 @@ export class Storage {
}
/**
* setModelSelection stores the selected provider and model.
* setModelSelection stores the default provider and model.
* @param {{provider: string, model: string}} selection
*/
setModelSelection({ provider, model }) {
@@ -108,6 +111,92 @@ export class Storage {
localStorage.setItem(MODEL_KEY, model);
}
/**
* _loadConversationModels reads the per-conversation model map.
* @returns {Object<string, {provider: string, model: string}>}
* @private
*/
_loadConversationModels() {
try {
const parsed = JSON.parse(localStorage.getItem(CONVERSATION_MODELS_KEY));
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
? parsed
: {};
} catch (error) {
console.error('failed to load conversation models:', error);
return {};
}
}
/**
* getConversationModel returns the provider and model stored for
* one conversation, or null fields when none is stored.
* @param {string} id - conversation ID.
* @returns {{provider: string | null, model: string | null}}
*/
getConversationModel(id) {
const entry = this._loadConversationModels()[id];
return {
provider: entry?.provider || null,
model: entry?.model || null,
};
}
/**
* setConversationModel stores the provider and model for one
* conversation.
* @param {string} id - conversation ID.
* @param {{provider: string, model: string}} selection
*/
setConversationModel(id, { provider, model }) {
if (!id || !provider || !model) return;
try {
const map = this._loadConversationModels();
map[id] = { provider, model };
localStorage.setItem(CONVERSATION_MODELS_KEY, JSON.stringify(map));
} catch (error) {
console.error('failed to save conversation model:', error);
}
}
/**
* deleteConversationModel removes the stored model entry for one
* conversation.
* @param {string} id - conversation ID.
*/
deleteConversationModel(id) {
if (!id) return;
try {
const map = this._loadConversationModels();
if (!(id in map)) return;
delete map[id];
localStorage.setItem(CONVERSATION_MODELS_KEY, JSON.stringify(map));
} catch (error) {
console.error('failed to delete conversation model:', error);
}
}
/**
* pruneConversationModels removes stored model entries for
* conversations that no longer exist on the server (for example,
* conversations deleted from another device or browser tab), so
* the localStorage map does not grow without bound.
* @param {Iterable<string>} validIds - conversation IDs currently
* on the server.
*/
pruneConversationModels(validIds) {
try {
const keep = new Set(validIds);
const map = this._loadConversationModels();
for (const id of Object.keys(map)) {
if (!keep.has(id)) delete map[id];
}
localStorage.setItem(CONVERSATION_MODELS_KEY, JSON.stringify(map));
} catch (error) {
console.error('failed to prune conversation models:', error);
}
}
/** @returns {string} the selected voice, or '' for automatic */
getVoice() {
return localStorage.getItem(VOICE_KEY) || '';