From f45b7cf9c5aeef988feb05606686cb97a1a32703 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 25 Feb 2026 22:17:45 -0500 Subject: [PATCH] Add model dropdowns to settings and set qwen2.5:1.5b as intent default - Default OLLAMA_INTENT_MODEL to qwen2.5:1.5b in code instead of empty - Add GET /api/settings/models endpoint returning installed models and defaults - Validate intent_model against installed models on save (same as default_model) - Replace intent model text input with a dropdown of installed models - Add chat model dropdown to Assistant settings section Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/views/SettingsView.vue | 44 ++++++++++++++++++-------- src/fabledassistant/config.py | 2 +- src/fabledassistant/routes/settings.py | 23 +++++++++++--- 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 4190697..c9197e1 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -9,7 +9,11 @@ const store = useSettingsStore(); const authStore = useAuthStore(); const toastStore = useToastStore(); const assistantName = ref(""); +const defaultModel = ref(""); const intentModel = ref(""); +const installedModels = ref([]); +const defaultChatModel = ref(""); +const defaultIntentModel = ref(""); const newEmail = ref(""); const emailPassword = ref(""); const changingEmail = ref(false); @@ -66,8 +70,19 @@ onMounted(async () => { assistantName.value = store.assistantName; newEmail.value = authStore.user?.email ?? ""; + // Load installed models and configured defaults + try { + const modelsData = await apiGet<{ models: string[]; default_chat_model: string; default_intent_model: string }>("/api/settings/models"); + installedModels.value = modelsData.models; + defaultChatModel.value = modelsData.default_chat_model; + defaultIntentModel.value = modelsData.default_intent_model; + } catch { + // Ollama unreachable — dropdowns will be empty + } + // Load notification preferences from user settings const allSettings = await apiGet>("/api/settings"); + defaultModel.value = allSettings.default_model ?? ""; intentModel.value = allSettings.intent_model ?? ""; if (allSettings.notify_task_reminders !== undefined) { notifyTaskReminders.value = allSettings.notify_task_reminders !== "false"; @@ -157,7 +172,8 @@ async function saveAssistant() { try { await store.updateSettings({ assistant_name: assistantName.value.trim() || "Fable", - intent_model: intentModel.value.trim(), + default_model: defaultModel.value, + intent_model: intentModel.value, }); saved.value = true; setTimeout(() => (saved.value = false), 2000); @@ -342,20 +358,22 @@ async function handleRestoreFile(event: Event) {

+
+ + +

Model used for new conversations.

+
+
- -

- Optional smaller/faster model for intent routing (e.g. llama3.2:3b, - qwen2.5:3b). Leave empty to use the same model as chat. - Can also be set via OLLAMA_INTENT_MODEL environment variable. -

+ +

Smaller/faster model for intent routing. Runs before the main model to detect tool calls.

diff --git a/src/fabledassistant/config.py b/src/fabledassistant/config.py index d592043..b02fb55 100644 --- a/src/fabledassistant/config.py +++ b/src/fabledassistant/config.py @@ -26,7 +26,7 @@ class Config: OLLAMA_MODEL: str = os.environ.get("OLLAMA_MODEL", "qwen3") # Optional dedicated model for intent classification (should be small/fast). # Falls back to OLLAMA_MODEL if not set. Can also be overridden per-user via settings. - OLLAMA_INTENT_MODEL: str = os.environ.get("OLLAMA_INTENT_MODEL", "") + OLLAMA_INTENT_MODEL: str = os.environ.get("OLLAMA_INTENT_MODEL", "qwen2.5:1.5b") # KV cache context window for generation. Lower = less VRAM, less throughput impact. # 8192 is sufficient for most conversations; raise if you paste large documents. OLLAMA_NUM_CTX: int = int(os.environ.get("OLLAMA_NUM_CTX", "8192")) diff --git a/src/fabledassistant/routes/settings.py b/src/fabledassistant/routes/settings.py index 577cdf4..8070aaf 100644 --- a/src/fabledassistant/routes/settings.py +++ b/src/fabledassistant/routes/settings.py @@ -1,6 +1,7 @@ from quart import Blueprint, jsonify, request from fabledassistant.auth import login_required, get_current_user_id +from fabledassistant.config import Config from fabledassistant.services.caldav import CALDAV_SETTING_KEYS, get_caldav_config, test_connection from fabledassistant.services.llm import get_installed_models from fabledassistant.services.settings import get_all_settings, set_settings_batch @@ -24,17 +25,31 @@ async def update_settings_route(): if not isinstance(data, dict): return jsonify({"error": "Expected a JSON object"}), 400 - if "default_model" in data: - model = str(data["default_model"]) + if "default_model" in data or "intent_model" in data: installed = await get_installed_models() - if installed and model not in installed: - return jsonify({"error": f"Model '{model}' is not installed"}), 400 + for key in ("default_model", "intent_model"): + if key in data and data[key]: + model = str(data[key]) + if installed and model not in installed: + return jsonify({"error": f"Model '{model}' is not installed"}), 400 await set_settings_batch(uid, {k: str(v) for k, v in data.items()}) settings = await get_all_settings(uid) return jsonify(settings) +@settings_bp.route("/models", methods=["GET"]) +@login_required +async def get_models_route(): + """Return installed Ollama models and the configured defaults.""" + models = sorted(await get_installed_models()) + return jsonify({ + "models": models, + "default_chat_model": Config.OLLAMA_MODEL, + "default_intent_model": Config.OLLAMA_INTENT_MODEL, + }) + + @settings_bp.route("/caldav", methods=["GET"]) @login_required async def get_caldav():