Add settings page, model management, and chat UX improvements

- Settings infrastructure: key-value settings table, GET/PUT API, Pinia store
- Configurable assistant name (default "Fable") in settings and LLM system prompt
- Model catalog with 18 models in 3 categories (General Purpose, Coding,
  Uncensored / Creative Writing) with download/select/remove functionality
- Move Ollama status indicator from chat views to global nav bar
- Chat bubble layout: user messages right-aligned, assistant left-aligned
- Floating dark input bar with auto-focus and circular send button
- Fix HTML entity rendering (' apostrophe issue in marked/DOMPurify pipeline)
- Fix new chat button navigation (fetchConversation before router.push)
- Recent chats section on home page with "New Chat" button
- Update summary.md with Phase 4.5 changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-10 21:32:02 -05:00
parent 834fd80640
commit 38b1ac933e
20 changed files with 1257 additions and 236 deletions
+69
View File
@@ -0,0 +1,69 @@
import { ref, computed } from "vue";
import { defineStore } from "pinia";
import { apiGet, apiPut, apiPost } from "@/api/client";
import type { AppSettings } from "@/types/settings";
export const useSettingsStore = defineStore("settings", () => {
const settings = ref<AppSettings>({});
const loading = ref(false);
const installedModels = ref<string[]>([]);
const assistantName = computed(
() => settings.value.assistant_name || "Fable"
);
const defaultModel = computed(
() => settings.value.default_model || ""
);
async function fetchSettings() {
loading.value = true;
try {
settings.value = await apiGet<AppSettings>("/api/settings");
} catch {
// Use defaults on error
} finally {
loading.value = false;
}
}
async function updateSettings(updates: AppSettings) {
loading.value = true;
try {
settings.value = await apiPut<AppSettings>("/api/settings", updates);
} finally {
loading.value = false;
}
}
async function fetchInstalledModels() {
try {
const data = await apiGet<{ models: { name: string }[] }>("/api/chat/models");
installedModels.value = data.models.map((m) => m.name);
} catch {
installedModels.value = [];
}
}
async function pullModel(model: string) {
await apiPost("/api/chat/models/pull", { model });
}
async function deleteModel(model: string) {
await apiPost("/api/chat/models/delete", { model });
await fetchInstalledModels();
}
return {
settings,
loading,
installedModels,
assistantName,
defaultModel,
fetchSettings,
updateSettings,
fetchInstalledModels,
pullModel,
deleteModel,
};
});