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:
+54
-21
@@ -12,7 +12,7 @@
|
||||
> Include file-level details in the commit body when the change is non-trivial.
|
||||
|
||||
## Last Updated
|
||||
2026-02-10 — Phase 4 + Ollama health check (status indicator on chat views showing Ollama/model readiness)
|
||||
2026-02-10 — Phase 4.5: Chat UX improvements, settings page, model management, entity rendering fix
|
||||
|
||||
## Project Overview
|
||||
Fabled Assistant is a self-hosted note-taking and task-tracking application with
|
||||
@@ -128,6 +128,11 @@ for AI-assisted features.
|
||||
- "Convert to note" = `update_note(id, status=None, priority=None, due_date=None)`
|
||||
- Task body field is `body` (not `description`) — standardized with notes
|
||||
|
||||
### Settings
|
||||
- `key` (text PK), `value` (text)
|
||||
- Key-value store for app-wide settings (assistant name, default model, etc.)
|
||||
- CRUD via `services/settings.py`: `get_setting(key, default)`, `set_setting(key, value)`, `get_all_settings()`
|
||||
|
||||
### Conversations
|
||||
- `id` (int PK), `title` (str), `model` (str), `created_at`, `updated_at`
|
||||
- Has many messages (cascade delete)
|
||||
@@ -156,27 +161,31 @@ fabledassistant/
|
||||
│ ├── 0002_create_tasks_table.py # Tasks table + enums (raw SQL, idempotent)
|
||||
│ ├── 0003_task_note_companion.py # Data migration: create companion notes for existing tasks
|
||||
│ ├── 0004_merge_tasks_into_notes.py # Add task columns to notes, migrate data, drop tasks table
|
||||
│ └── 0005_add_chat_tables.py # Conversations + messages tables with FKs and indexes
|
||||
│ ├── 0005_add_chat_tables.py # Conversations + messages tables with FKs and indexes
|
||||
│ └── 0006_add_settings_table.py # Settings key-value table
|
||||
├── src/
|
||||
│ └── fabledassistant/
|
||||
│ ├── __init__.py
|
||||
│ ├── app.py # Quart app factory: SPA via 404 handler, JSON 404/500 for API
|
||||
│ ├── config.py # Config from env vars
|
||||
│ ├── models/
|
||||
│ │ ├── __init__.py # async_session factory, Base, imports Note + TaskStatus + TaskPriority + Conversation + Message
|
||||
│ │ ├── __init__.py # async_session factory, Base, imports Note + TaskStatus + TaskPriority + Conversation + Message + Setting
|
||||
│ │ ├── note.py # Note model (unified: id, title, body, tags[], parent_id, status, priority, due_date, timestamps) + TaskStatus/TaskPriority enums + is_task property
|
||||
│ │ └── conversation.py # Conversation + Message models with relationships and to_dict()
|
||||
│ │ ├── conversation.py # Conversation + Message models with relationships and to_dict()
|
||||
│ │ └── setting.py # Setting model (key TEXT PK, value TEXT)
|
||||
│ ├── routes/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── api.py # /api blueprint with /health endpoint
|
||||
│ │ ├── chat.py # /api/chat blueprint: conversations CRUD, SSE message streaming, save/summarize as note, models list, status check
|
||||
│ │ ├── chat.py # /api/chat blueprint: conversations CRUD, SSE message streaming, save/summarize as note, models list/pull/delete, status check
|
||||
│ │ ├── notes.py # /api/notes CRUD + /by-title + /resolve-title + /convert-to-task + /convert-to-note + /backlinks
|
||||
│ │ └── tasks.py # /api/tasks CRUD + PATCH status (thin wrappers, accepts body not description)
|
||||
│ │ ├── tasks.py # /api/tasks CRUD + PATCH status (thin wrappers, accepts body not description)
|
||||
│ │ └── settings.py # /api/settings GET/PUT — app-wide settings
|
||||
│ ├── services/
|
||||
│ │ ├── notes.py # CRUD, is_task filter, status/priority filters, convert_note_to_task, convert_task_to_note, get_backlinks
|
||||
│ │ ├── tasks.py # Thin wrappers around notes.py (create_task, list_tasks with is_task=True, etc.)
|
||||
│ │ ├── llm.py # Ollama interaction: ensure_model, stream_chat, generate_completion, fetch_url_content, build_context (keyword extraction, note search, URL fetching)
|
||||
│ │ └── chat.py # Conversation CRUD, add_message, save_response_as_note, summarize_conversation_as_note
|
||||
│ │ ├── llm.py # Ollama interaction: ensure_model, stream_chat, generate_completion, fetch_url_content, build_context (keyword extraction, note search, URL fetching, configurable assistant name)
|
||||
│ │ ├── chat.py # Conversation CRUD, add_message, save_response_as_note, summarize_conversation_as_note
|
||||
│ │ └── settings.py # Settings CRUD: get_setting, set_setting, get_all_settings
|
||||
│ ├── utils/
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── tags.py # extract_tags() — regex #tag extraction, skips code fences
|
||||
@@ -186,7 +195,7 @@ fabledassistant/
|
||||
├── vite.config.ts
|
||||
├── tsconfig.json
|
||||
├── src/
|
||||
│ ├── App.vue # Shell: AppHeader + router-view + ChatPanel (slide-out) + ToastNotification
|
||||
│ ├── App.vue # Shell: AppHeader + router-view + ChatPanel (slide-out) + ToastNotification; starts status polling + loads settings on mount
|
||||
│ ├── main.ts # App init, imports theme.css
|
||||
│ ├── assets/
|
||||
│ │ └── theme.css # CSS custom properties: light/dark themes, body reset
|
||||
@@ -199,17 +208,20 @@ fabledassistant/
|
||||
│ │ ├── notes.ts # CRUD + tag filter, resolveTitle, convertToTask, convertToNote, fetchBacklinks, fetchAllTags
|
||||
│ │ ├── tasks.ts # CRUD + status/priority filter, patchStatus (uses body not description)
|
||||
│ │ ├── chat.ts # Conversation CRUD, sendMessage (SSE streaming), saveMessageAsNote, summarizeAsNote, fetchModels, status polling (ollamaStatus, modelStatus, chatReady)
|
||||
│ │ ├── settings.ts # App settings: assistantName, defaultModel, fetchInstalledModels, pullModel, deleteModel
|
||||
│ │ └── toast.ts # Toast notification state, 3s auto-dismiss
|
||||
│ ├── types/
|
||||
│ │ ├── note.ts # Note interface (with status, priority, due_date, is_task) + TaskStatus, TaskPriority types + NoteListResponse
|
||||
│ │ ├── chat.ts # Message, Conversation, ConversationDetail, OllamaModel, OllamaStatus interfaces
|
||||
│ │ ├── settings.ts # AppSettings interface, ModelInfo interface (name, description, size, bestFor, category)
|
||||
│ │ └── task.ts # Task = re-export of Note; TaskListResponse
|
||||
│ ├── utils/
|
||||
│ │ ├── tags.ts # extractTags(), linkifyTags(), linkifyWikilinks()
|
||||
│ │ └── markdown.ts # renderMarkdown() (full), renderPreview() (strips links/images for cards)
|
||||
│ │ └── markdown.ts # renderMarkdown() (full) + renderPreview() (strips links/images) — decodes HTML entities before marked, replaces ' after sanitization
|
||||
│ ├── views/
|
||||
│ │ ├── ChatView.vue # Dedicated /chat page: sidebar (conversation list) + message thread + input + summarize + Ollama status indicator
|
||||
│ │ ├── HomeView.vue # Landing page: recent notes + tasks (independent error handling)
|
||||
│ │ ├── ChatView.vue # Dedicated /chat page: sidebar + bubble-style messages (user right, assistant left) + floating dark input bar + auto-focus
|
||||
│ │ ├── HomeView.vue # Landing page: recent notes + tasks + recent chats (3 most recent) + new chat button
|
||||
│ │ ├── SettingsView.vue # Settings page: assistant name config + model catalog (18 models in 3 categories) with download/select/remove
|
||||
│ │ ├── NotesListView.vue # Note list: search, sort, tag filter pills, pagination
|
||||
│ │ ├── NoteEditorView.vue # Create/edit: Ctrl+S, unsaved guard, toasts, autocomplete
|
||||
│ │ ├── NoteViewerView.vue # Markdown render, wikilink auto-create, convert-to-task (only when !is_task), backlinks
|
||||
@@ -217,9 +229,9 @@ fabledassistant/
|
||||
│ │ ├── TaskEditorView.vue # Create/edit task: fields (body not description), Ctrl+S, dirty guard, autocomplete
|
||||
│ │ └── TaskViewerView.vue # Task detail: rendered markdown, badges, convert-to-note button, backlinks
|
||||
│ ├── components/
|
||||
│ │ ├── AppHeader.vue # Nav bar: brand, Notes + Tasks + Chat links, chat panel toggle, theme toggle
|
||||
│ │ ├── ChatPanel.vue # Slide-out chat panel (right side overlay, receives contextNoteId prop) + Ollama status indicator
|
||||
│ │ ├── ChatMessage.vue # Message bubble: markdown rendering, role label, "Save as Note" action on assistant messages
|
||||
│ │ ├── AppHeader.vue # Nav bar: brand, Notes + Tasks + Chat + Settings links, status indicator dot, chat panel toggle, theme toggle
|
||||
│ │ ├── ChatPanel.vue # Slide-out chat panel (right side overlay, receives contextNoteId prop), bubble-style messages, floating dark input
|
||||
│ │ ├── ChatMessage.vue # Message bubble: markdown rendering, configurable assistant name label, "Save as Note" action on assistant messages
|
||||
│ │ ├── NoteCard.vue # Card with rendered markdown preview (v-html), TagPill, tag-click emit
|
||||
│ │ ├── TaskCard.vue # Card with rendered preview (body not description), StatusBadge (clickable), PriorityBadge, due date, tags
|
||||
│ │ ├── StatusBadge.vue # Color-coded status badge, optional clickable cycling
|
||||
@@ -230,7 +242,7 @@ fabledassistant/
|
||||
│ │ ├── PaginationBar.vue # Prev/next + page numbers
|
||||
│ │ └── ToastNotification.vue # Fixed-position toast container
|
||||
│ └── router/
|
||||
│ └── index.ts # Routes: /, /notes/*, /tasks/*, /chat, /chat/:id
|
||||
│ └── index.ts # Routes: /, /notes/*, /tasks/*, /chat, /chat/:id, /settings
|
||||
└── public/
|
||||
```
|
||||
|
||||
@@ -266,6 +278,10 @@ fabledassistant/
|
||||
| POST | `/api/chat/conversations/:id/summarize` | Summarize conversation via LLM, save as note |
|
||||
| GET | `/api/chat/status` | Check Ollama availability and model readiness (`{ollama, model, default_model}`) |
|
||||
| GET | `/api/chat/models` | List available Ollama models |
|
||||
| POST | `/api/chat/models/pull` | Pull/download a model from Ollama (background, body: `{model}`) |
|
||||
| POST | `/api/chat/models/delete` | Delete a model from Ollama (body: `{model}`) |
|
||||
| GET | `/api/settings` | Get all app settings as `{key: value}` dict |
|
||||
| PUT | `/api/settings` | Update settings (body: `{key: value, ...}`) |
|
||||
|
||||
## Alembic Migrations
|
||||
|
||||
@@ -276,7 +292,7 @@ container startup.
|
||||
|
||||
### Migration Chain
|
||||
```
|
||||
0001_create_notes_table.py → 0002_create_tasks_table.py → 0003_task_note_companion.py → 0004_merge_tasks_into_notes.py → 0005_add_chat_tables.py
|
||||
0001_create_notes_table.py → 0002_create_tasks_table.py → 0003_task_note_companion.py → 0004_merge_tasks_into_notes.py → 0005_add_chat_tables.py → 0006_add_settings_table.py
|
||||
```
|
||||
|
||||
### How Migrations Run
|
||||
@@ -419,6 +435,20 @@ When adding a new migration, follow these conventions:
|
||||
- [x] **Frontend SSE client:** `apiStreamPost()` uses fetch + ReadableStream to parse SSE data lines
|
||||
- [x] **Auto-title:** Conversation title auto-set from first user message content
|
||||
|
||||
### Phase 4.5 — Chat UX + Settings + Model Management ✓
|
||||
- [x] **Settings infrastructure:** Key-value `settings` table, `GET/PUT /api/settings`, Pinia store with defaults
|
||||
- [x] **Configurable assistant name:** Default "Fable", editable in settings, injected into LLM system prompt and chat message labels
|
||||
- [x] **Settings page:** `/settings` route with assistant name form + model catalog
|
||||
- [x] **Model catalog:** 18 models across 3 categories (General Purpose, Coding, Uncensored / Creative Writing) with descriptions, sizes, and best-for labels
|
||||
- [x] **Model management:** Download (pull), select (set as default), and remove (delete from Ollama) with confirmation UI
|
||||
- [x] **Status indicator in nav bar:** Moved from per-component (ChatView/ChatPanel) to global AppHeader; green/yellow/red dot with label
|
||||
- [x] **Chat bubble layout:** User messages right-aligned (primary color bg), assistant messages left-aligned (card bg), speech bubble tails
|
||||
- [x] **Floating dark input bar:** `#1c1c1e` background, rounded corners, circular send button with arrow
|
||||
- [x] **Auto-focus input:** Chat input auto-focuses on mount, conversation switch, and after sending
|
||||
- [x] **HTML entity fix:** Three-layer approach — decode entities before marked, DOMPurify sanitization, explicit `'` → `'` replacement after sanitization
|
||||
- [x] **New chat navigation fix:** `fetchConversation()` called before `router.push()` so `currentConversation` is set immediately
|
||||
- [x] **Recent chats on home page:** 3 most recent conversations shown as clickable cards + "New Chat" button
|
||||
|
||||
### Phase 5 — Polish & Production Hardening
|
||||
- [ ] Authentication (single-user or multi-user, TBD)
|
||||
- [ ] Docker Swarm production stack (secrets, volumes, networking)
|
||||
@@ -444,16 +474,19 @@ When adding a new migration, follow these conventions:
|
||||
- Authentication model: single-user (password-only) vs multi-user?
|
||||
|
||||
## Current Status
|
||||
**Phase:** Phase 4 complete. LLM chat integration with streaming responses.
|
||||
**Phase:** Phase 4.5 complete. Chat UX improvements, settings page, model management.
|
||||
- Full note-taking and task-tracking CRUD with markdown, wikilinks, backlinks, tags
|
||||
- Unified note/task model (a task is a note with `status IS NOT NULL`)
|
||||
- LLM chat via Ollama with SSE streaming responses
|
||||
- Note-aware context: auto-includes current note + searches related notes by keyword
|
||||
- URL content fetching: detects URLs in messages, fetches and includes content
|
||||
- Save assistant messages as notes; summarize entire conversations as notes
|
||||
- Dedicated `/chat` page with conversation sidebar + message thread
|
||||
- Dedicated `/chat` page with bubble-style messages (user right, assistant left), floating dark input bar
|
||||
- Slide-out chat panel accessible from any page, auto-includes note/task context
|
||||
- Auto-pull default model (`llama3.1`) on startup
|
||||
- Ollama health check: status indicator on chat views (green/yellow/red dot) with 30s polling; disables send when not ready
|
||||
- Settings page: configurable assistant name (default "Fable"), model catalog with 18 models in 3 categories
|
||||
- Model management: download, select, and remove models from the settings page
|
||||
- Ollama status indicator in global nav bar (green/yellow/red dot) with 30s polling
|
||||
- Recent chats section on home page with quick "New Chat" button
|
||||
- HTML entity rendering fix (apostrophes in marked → DOMPurify pipeline)
|
||||
- Dark/light theme with CSS custom properties
|
||||
- Ready for Phase 5: Polish & Production Hardening
|
||||
|
||||
Reference in New Issue
Block a user