fix(settings): allow toggle deselection and improve embedding model name matching (#661)

* fix(settings): allow toggle deselection and improve embedding model name matching

Fixes #650 - Can't select embedding model

Changes:
- Add toggle behavior: clicking selected model now deselects it
- Improve model name matching to handle Ollama quantization variants
  (e.g., qwen3-embedding:8b-q4_K_M now matches qwen3-embedding:8b)
- Added installedVersionNames set to match base:version ignoring suffixes

This fixes two issues:
1. Users couldn't unselect a model once selected (no toggle)
2. Users couldn't select models when Ollama returned names with
   quantization suffixes that didn't match the recommended models list

* refactor: remove redundant :latest check per Gemini review

The installedBaseNames set already handles the case where a model
without a tag (e.g., 'embeddinggemma') matches an installed model
with :latest suffix. This simplifies the matching logic.

---------

Co-authored-by: Alex <63423455+AlexMadera@users.noreply.github.com>
This commit is contained in:
Michael Ludlow
2026-01-05 02:08:22 -05:00
committed by GitHub
parent 65f608986b
commit 234d44f637
@@ -165,29 +165,40 @@ export function OllamaModelSelector({
if (abortSignal?.aborted) return;
if (result?.success && result?.data?.embedding_models) {
// Build a set of installed model names (both full name and normalized)
// Build a set of installed model names (full, base, and version-matched)
const installedFullNames = new Set<string>();
const installedBaseNames = new Set<string>();
const installedVersionNames = new Set<string>();
result.data.embedding_models.forEach((m: { name: string }) => {
const name = m.name;
installedFullNames.add(name);
// Only normalize :latest suffix, not version tags like :4b, :8b, :0.6b
// Normalize :latest suffix
if (name.endsWith(':latest')) {
installedBaseNames.add(name.replace(':latest', ''));
} else if (!name.includes(':')) {
installedBaseNames.add(name);
}
// Handle quantization variants (e.g., qwen3-embedding:8b-q4_K_M)
// Extract base:version without quantization suffix
const quantMatch = name.match(/^([^:]+:[^-]+)/);
if (quantMatch) {
installedVersionNames.add(quantMatch[1]);
}
});
// Update models with installation status
setModels(
RECOMMENDED_MODELS.map(model => {
// Check exact match first, then base name (for :latest normalization)
// Check multiple matching strategies:
// 1. Exact match (e.g., "qwen3-embedding:8b" === "qwen3-embedding:8b")
// 2. Base name match for :latest normalization (handles "embeddinggemma" matching "embeddinggemma:latest")
// 3. Version match ignoring quantization suffix (e.g., "qwen3-embedding:8b" matches "qwen3-embedding:8b-q4_K_M")
const isInstalled = installedFullNames.has(model.name) ||
installedBaseNames.has(model.name) ||
// Also check if model without tag is installed (e.g., "embeddinggemma" matches "embeddinggemma")
(model.name.includes(':') ? false : installedFullNames.has(model.name + ':latest'));
installedVersionNames.has(model.name);
return {
...model,
installed: isInstalled,
@@ -281,15 +292,22 @@ export function OllamaModelSelector({
};
/**
* Handles model selection by calling the parent callback.
* Handles model selection with toggle behavior.
* Clicking an already-selected model will deselect it.
* Only allows selection of installed models and when component is not disabled.
*
* @param {OllamaModel} model - The model to select
* @param {OllamaModel} model - The model to select or deselect
* @returns {void}
*/
const handleSelect = (model: OllamaModel) => {
if (!model.installed || disabled) return;
onModelSelect(model.name, model.dim);
// Toggle behavior: if already selected, deselect by passing empty values
if (selectedModel === model.name) {
onModelSelect('', 0);
} else {
onModelSelect(model.name, model.dim);
}
};
if (isLoading) {