Add note context visibility in chat and standardize UI design tokens
Improve chat context: build_context() now returns metadata about auto-found notes, emitted as an SSE event so the frontend can display context pills showing which notes influenced the response. Users can promote notes for deeper context (+) or exclude irrelevant ones (x). A note picker lets users manually attach notes. Multi-word search uses per-term AND matching, and auto-search iterates keywords individually for broader OR-style coverage. Standardize styling: introduce CSS design tokens (--radius-sm/md/lg/pill, --color-success/warning/overlay, --focus-ring) and migrate all components to use them. Fix header alignment to full-width, add active nav link state, replace hardcoded colors with CSS variables, and normalize button padding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+47
-13
@@ -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.5: Chat UX improvements, settings page, model management, entity rendering fix
|
||||
2026-02-10 — Phase 4.7: Styling consistency pass — design tokens, header alignment, standardized UI
|
||||
|
||||
## Project Overview
|
||||
Fabled Assistant is a self-hosted note-taking and task-tracking application with
|
||||
@@ -50,6 +50,10 @@ for AI-assisted features.
|
||||
`LIKE` prefix.
|
||||
- **Dark-first theming:** CSS custom properties on `:root` (light) and
|
||||
`[data-theme="dark"]`, with `prefers-color-scheme` detection defaulting to dark.
|
||||
- **Design tokens:** Standardized CSS custom properties for border-radius
|
||||
(`--radius-sm: 6px`, `--radius-md: 8px`, `--radius-lg: 12px`), semantic colors
|
||||
(`--color-success`, `--color-warning`, `--color-overlay`), and focus ring
|
||||
(`--focus-ring`). All components reference tokens instead of hardcoded values.
|
||||
- **Unified note/task model:** A task is just a note with task attributes enabled.
|
||||
A note has `status IS NOT NULL` ⟹ it's a task. "Convert to task" sets
|
||||
`status='todo'`; "convert to note" clears `status`, `priority`, `due_date`.
|
||||
@@ -62,8 +66,11 @@ for AI-assisted features.
|
||||
need to POST the message body. Simpler than WebSockets; Quart supports async
|
||||
generators natively for SSE.
|
||||
- **Context building server-side:** Backend fetches URL content and searches notes —
|
||||
frontend just sends the message text + optional note ID. Keyword extraction uses
|
||||
simple word splitting with stopword filtering (no embeddings).
|
||||
frontend just sends the message text + optional note ID + optional exclude list.
|
||||
Keyword extraction uses simple word splitting with stopword filtering (no embeddings).
|
||||
`build_context()` returns `(messages, context_meta)` tuple; metadata includes
|
||||
auto-found note IDs/titles sent to frontend via SSE `context` event before streaming.
|
||||
Multi-word search splits terms into per-word ILIKE with AND logic (not adjacent match).
|
||||
- **No blocking long-running operations:** Any potentially slow operation (model
|
||||
pulls, LLM calls, URL fetching, etc.) must never block app startup or freeze the
|
||||
UI. Backend uses SSE streaming for incremental responses (chat messages and model
|
||||
@@ -185,7 +192,7 @@ fabledassistant/
|
||||
│ ├── 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, configurable assistant name)
|
||||
│ │ ├── llm.py # Ollama interaction: ensure_model, stream_chat, generate_completion, fetch_url_content, build_context (keyword extraction, note search with 5 keywords + 2000-char previews, exclude_note_ids support, returns (messages, context_meta) tuple, 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/
|
||||
@@ -200,7 +207,7 @@ fabledassistant/
|
||||
│ ├── 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
|
||||
│ │ └── theme.css # CSS custom properties: light/dark themes, body reset, design tokens (radius, semantic colors, focus ring, overlay), global focus-visible styles
|
||||
│ ├── api/
|
||||
│ │ └── client.ts # apiGet/apiPost/apiPut/apiPatch/apiDelete + apiStreamPost (SSE via fetch + ReadableStream)
|
||||
│ ├── composables/
|
||||
@@ -209,19 +216,19 @@ fabledassistant/
|
||||
│ ├── stores/
|
||||
│ │ ├── 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)
|
||||
│ │ ├── chat.ts # Conversation CRUD, sendMessage (SSE streaming, handles context event, accepts excludeNoteIds), lastContextMeta ref, saveMessageAsNote, summarizeAsNote, fetchModels, status polling (ollamaStatus, modelStatus, chatReady)
|
||||
│ │ ├── settings.ts # App settings: assistantName, defaultModel, fetchInstalledModels, pullModel (SSE streaming with onProgress callback), 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
|
||||
│ │ ├── chat.ts # Message, Conversation, ConversationDetail, ContextMeta, 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() (with (?<!&) to skip HTML entities), linkifyWikilinks()
|
||||
│ │ └── markdown.ts # renderMarkdown() (full) + renderPreview() (strips links/images) — decodes HTML entities before marked, replaces ' after sanitization
|
||||
│ ├── views/
|
||||
│ │ ├── ChatView.vue # Dedicated /chat page: sidebar + bubble-style messages (user right, assistant left) + floating dark input bar + auto-focus
|
||||
│ │ ├── ChatView.vue # Dedicated /chat page: sidebar + bubble-style messages (user right, assistant left) + floating dark input bar + auto-focus + note picker + context pills (auto-found notes with promote/exclude) + exclude tracking
|
||||
│ │ ├── 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
|
||||
@@ -232,7 +239,7 @@ fabledassistant/
|
||||
│ │ └── TaskViewerView.vue # Task detail: rendered markdown, badges, convert-to-note button, backlinks
|
||||
│ ├── components/
|
||||
│ │ ├── 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
|
||||
│ │ ├── ChatPanel.vue # Slide-out chat panel (right side overlay, receives contextNoteId prop), bubble-style messages, floating dark input, note picker, context pills with promote/exclude
|
||||
│ │ ├── 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
|
||||
@@ -275,7 +282,7 @@ fabledassistant/
|
||||
| GET | `/api/chat/conversations/:id` | Get conversation with all messages |
|
||||
| DELETE | `/api/chat/conversations/:id` | Delete conversation (cascades to messages) |
|
||||
| PATCH | `/api/chat/conversations/:id` | Update conversation title (body: `{title}`) |
|
||||
| POST | `/api/chat/conversations/:id/messages` | Send message + stream LLM response via SSE (body: `{content, context_note_id?}`) |
|
||||
| POST | `/api/chat/conversations/:id/messages` | Send message + stream LLM response via SSE (body: `{content, context_note_id?, exclude_note_ids?}`); emits `{context: ContextMeta}` SSE event before streaming chunks |
|
||||
| POST | `/api/chat/messages/:id/save-as-note` | Save assistant message as a new note |
|
||||
| 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}`) |
|
||||
@@ -426,7 +433,7 @@ When adding a new migration, follow these conventions:
|
||||
|
||||
### Phase 4 — LLM Chat Integration ✓
|
||||
- [x] **Ollama client:** async HTTP via httpx — `ensure_model()` (auto-pull on startup), `stream_chat()` (streaming NDJSON), `generate_completion()` (non-streaming)
|
||||
- [x] **Context building:** System prompt with note-aware context — includes current note (via `context_note_id`), keyword-extracted related note search (top 3), URL content fetching (regex detection, HTML stripping, truncated to 4000 chars)
|
||||
- [x] **Context building:** System prompt with note-aware context — includes current note (via `context_note_id`), keyword-extracted related note search (top 3, 5 keywords, 2000-char previews), URL content fetching (regex detection, HTML stripping, truncated to 4000 chars), returns `(messages, context_meta)` tuple with auto-found note IDs/titles, accepts `exclude_note_ids` to skip notes during auto-search
|
||||
- [x] **Conversation persistence:** `conversations` + `messages` tables in PostgreSQL, full CRUD
|
||||
- [x] **SSE streaming endpoint:** `POST /api/chat/conversations/:id/messages` streams LLM response chunks as SSE events, saves complete response to DB on finish
|
||||
- [x] **Save as note:** Save any assistant message as a new note (first line as title)
|
||||
@@ -451,6 +458,29 @@ When adding a new migration, follow these conventions:
|
||||
- [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 4.6 — Improved Note Context in Chat ✓
|
||||
- [x] **Multi-word search:** `list_notes()` splits multi-word queries into per-term ILIKE filters with AND logic (each word matched independently, not adjacent)
|
||||
- [x] **Context metadata:** `build_context()` returns `(messages, context_meta)` tuple with auto-found note IDs/titles and attached note info
|
||||
- [x] **Fuller auto-context:** Auto-found note previews increased from 300 to 2000 chars; uses all 5 extracted keywords instead of 3
|
||||
- [x] **Context SSE event:** Backend emits `{context: ContextMeta}` SSE event before streaming LLM response chunks
|
||||
- [x] **Exclude note IDs:** Backend accepts `exclude_note_ids` in request body, skips those notes during auto-search
|
||||
- [x] **Context pills:** ChatView and ChatPanel show auto-found notes as pills above streaming response with note title as router-link
|
||||
- [x] **Promote/exclude controls:** "+" button on context pills promotes note to attached context for next message; "x" button excludes note from future auto-searches in the session
|
||||
- [x] **Note picker:** Paperclip button in chat input opens dropdown with debounced search, selecting a note attaches it as context (shown as pill above input)
|
||||
- [x] **ContextMeta type:** New TypeScript interface for context metadata (`context_note_id`, `context_note_title`, `auto_notes`)
|
||||
- [x] **Session-scoped excludes:** Excluded note IDs tracked per conversation session, cleared on conversation switch
|
||||
|
||||
### Phase 4.7 — Styling Consistency Pass ✓
|
||||
- [x] **Design tokens:** Added `--radius-sm/md/lg/pill`, `--color-success/warning/overlay`, `--focus-ring` to theme.css
|
||||
- [x] **Header alignment fix:** Removed `max-width: 960px` from header nav — now full-width with consistent padding, matching modern app layout
|
||||
- [x] **Active nav link state:** Added `router-link-active` styling with primary color + card background
|
||||
- [x] **Hardcoded colors replaced:** Status indicator dots, settings saved message, remove/confirm-delete buttons now use CSS variables
|
||||
- [x] **Border-radius standardized:** All components migrated from mixed 3px/4px/6px/8px to `var(--radius-sm)` (6px) for controls, `var(--radius-md)` (8px) for cards/containers
|
||||
- [x] **Button padding standardized:** All primary/CTA buttons use `0.45rem 1rem`; small buttons use `0.3rem 0.75rem`
|
||||
- [x] **Settings max-width fixed:** Changed from 700px to 720px to match all other views
|
||||
- [x] **Focus-visible states:** Global `focus-visible` rule with `--focus-ring` shadow on inputs, textareas, selects, buttons
|
||||
- [x] **Modal overlays:** Replaced hardcoded `rgba(0,0,0,0.5)` with `var(--color-overlay)` across all components
|
||||
|
||||
### Phase 5 — Polish & Production Hardening
|
||||
- [ ] Authentication (single-user or multi-user, TBD)
|
||||
- [ ] Docker Swarm production stack (secrets, volumes, networking)
|
||||
@@ -476,11 +506,15 @@ When adding a new migration, follow these conventions:
|
||||
- Authentication model: single-user (password-only) vs multi-user?
|
||||
|
||||
## Current Status
|
||||
**Phase:** Phase 4.5 complete. Chat UX improvements, settings page, model management.
|
||||
**Phase:** Phase 4.7 complete. Styling consistency pass with design tokens and standardized UI.
|
||||
- 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
|
||||
- Note-aware context: auto-includes current note + searches related notes by keyword (5 keywords, 2000-char previews)
|
||||
- Context visibility: auto-found notes shown as pills with title links, promote (+) and exclude (x) controls
|
||||
- Note picker: attach any note as context from chat input via search dropdown
|
||||
- Exclude tracking: session-scoped per conversation, excluded notes skipped in future auto-searches
|
||||
- Multi-word search: splits query into per-term ILIKE with AND logic (words match independently)
|
||||
- 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 bubble-style messages (user right, assistant left), floating dark input bar
|
||||
|
||||
Reference in New Issue
Block a user