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 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,11 @@ const store = useSettingsStore();
|
|||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const toastStore = useToastStore();
|
const toastStore = useToastStore();
|
||||||
const assistantName = ref("");
|
const assistantName = ref("");
|
||||||
|
const defaultModel = ref("");
|
||||||
const intentModel = ref("");
|
const intentModel = ref("");
|
||||||
|
const installedModels = ref<string[]>([]);
|
||||||
|
const defaultChatModel = ref("");
|
||||||
|
const defaultIntentModel = ref("");
|
||||||
const newEmail = ref("");
|
const newEmail = ref("");
|
||||||
const emailPassword = ref("");
|
const emailPassword = ref("");
|
||||||
const changingEmail = ref(false);
|
const changingEmail = ref(false);
|
||||||
@@ -66,8 +70,19 @@ onMounted(async () => {
|
|||||||
assistantName.value = store.assistantName;
|
assistantName.value = store.assistantName;
|
||||||
newEmail.value = authStore.user?.email ?? "";
|
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
|
// Load notification preferences from user settings
|
||||||
const allSettings = await apiGet<Record<string, string>>("/api/settings");
|
const allSettings = await apiGet<Record<string, string>>("/api/settings");
|
||||||
|
defaultModel.value = allSettings.default_model ?? "";
|
||||||
intentModel.value = allSettings.intent_model ?? "";
|
intentModel.value = allSettings.intent_model ?? "";
|
||||||
if (allSettings.notify_task_reminders !== undefined) {
|
if (allSettings.notify_task_reminders !== undefined) {
|
||||||
notifyTaskReminders.value = allSettings.notify_task_reminders !== "false";
|
notifyTaskReminders.value = allSettings.notify_task_reminders !== "false";
|
||||||
@@ -157,7 +172,8 @@ async function saveAssistant() {
|
|||||||
try {
|
try {
|
||||||
await store.updateSettings({
|
await store.updateSettings({
|
||||||
assistant_name: assistantName.value.trim() || "Fable",
|
assistant_name: assistantName.value.trim() || "Fable",
|
||||||
intent_model: intentModel.value.trim(),
|
default_model: defaultModel.value,
|
||||||
|
intent_model: intentModel.value,
|
||||||
});
|
});
|
||||||
saved.value = true;
|
saved.value = true;
|
||||||
setTimeout(() => (saved.value = false), 2000);
|
setTimeout(() => (saved.value = false), 2000);
|
||||||
@@ -342,20 +358,22 @@ async function handleRestoreFile(event: Event) {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="default-model">Chat Model</label>
|
||||||
|
<select id="default-model" v-model="defaultModel" class="input">
|
||||||
|
<option value="">Default ({{ defaultChatModel || "qwen3" }})</option>
|
||||||
|
<option v-for="m in installedModels" :key="m" :value="m">{{ m }}</option>
|
||||||
|
</select>
|
||||||
|
<p class="field-hint">Model used for new conversations.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="intent-model">Intent Model</label>
|
<label for="intent-model">Intent Model</label>
|
||||||
<input
|
<select id="intent-model" v-model="intentModel" class="input">
|
||||||
id="intent-model"
|
<option value="">Default ({{ defaultIntentModel || "qwen2.5:1.5b" }})</option>
|
||||||
v-model="intentModel"
|
<option v-for="m in installedModels" :key="m" :value="m">{{ m }}</option>
|
||||||
type="text"
|
</select>
|
||||||
placeholder="Same as chat model"
|
<p class="field-hint">Smaller/faster model for intent routing. Runs before the main model to detect tool calls.</p>
|
||||||
class="input"
|
|
||||||
/>
|
|
||||||
<p class="field-hint">
|
|
||||||
Optional smaller/faster model for intent routing (e.g. <code>llama3.2:3b</code>,
|
|
||||||
<code>qwen2.5:3b</code>). Leave empty to use the same model as chat.
|
|
||||||
Can also be set via <code>OLLAMA_INTENT_MODEL</code> environment variable.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class Config:
|
|||||||
OLLAMA_MODEL: str = os.environ.get("OLLAMA_MODEL", "qwen3")
|
OLLAMA_MODEL: str = os.environ.get("OLLAMA_MODEL", "qwen3")
|
||||||
# Optional dedicated model for intent classification (should be small/fast).
|
# 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.
|
# 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.
|
# KV cache context window for generation. Lower = less VRAM, less throughput impact.
|
||||||
# 8192 is sufficient for most conversations; raise if you paste large documents.
|
# 8192 is sufficient for most conversations; raise if you paste large documents.
|
||||||
OLLAMA_NUM_CTX: int = int(os.environ.get("OLLAMA_NUM_CTX", "8192"))
|
OLLAMA_NUM_CTX: int = int(os.environ.get("OLLAMA_NUM_CTX", "8192"))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from quart import Blueprint, jsonify, request
|
from quart import Blueprint, jsonify, request
|
||||||
|
|
||||||
from fabledassistant.auth import login_required, get_current_user_id
|
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.caldav import CALDAV_SETTING_KEYS, get_caldav_config, test_connection
|
||||||
from fabledassistant.services.llm import get_installed_models
|
from fabledassistant.services.llm import get_installed_models
|
||||||
from fabledassistant.services.settings import get_all_settings, set_settings_batch
|
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):
|
if not isinstance(data, dict):
|
||||||
return jsonify({"error": "Expected a JSON object"}), 400
|
return jsonify({"error": "Expected a JSON object"}), 400
|
||||||
|
|
||||||
if "default_model" in data:
|
if "default_model" in data or "intent_model" in data:
|
||||||
model = str(data["default_model"])
|
|
||||||
installed = await get_installed_models()
|
installed = await get_installed_models()
|
||||||
if installed and model not in installed:
|
for key in ("default_model", "intent_model"):
|
||||||
return jsonify({"error": f"Model '{model}' is not installed"}), 400
|
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()})
|
await set_settings_batch(uid, {k: str(v) for k, v in data.items()})
|
||||||
settings = await get_all_settings(uid)
|
settings = await get_all_settings(uid)
|
||||||
return jsonify(settings)
|
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"])
|
@settings_bp.route("/caldav", methods=["GET"])
|
||||||
@login_required
|
@login_required
|
||||||
async def get_caldav():
|
async def get_caldav():
|
||||||
|
|||||||
Reference in New Issue
Block a user