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():