Project view:
- Add inline status advance buttons on kanban task cards (todo→in_progress,
in_progress→done); buttons reveal on hover, stop link navigation
Task viewer:
- Back button navigates to task's project instead of /tasks when project_id set
- Esc key navigates to project (or /tasks); blurs focused element first
Quick capture:
- Use user's configured model instead of hardcoded Config.OLLAMA_MODEL
- Remove create_project from classifier prompt (tool not offered, caused
task-shaped inputs to silently fall through to note fallback)
Briefing scheduler:
- Fix get_event_loop() → get_running_loop() so background thread uses the
correct hypercorn event loop (jobs were scheduling but never executing)
- Suppress bare greeting when both LLM synthesis lanes return empty
RSS feed UI (SettingsView):
- Show last-fetched age, category badge, and feed URL per row
- Category input field when adding a feed
- Refresh all button: fetches latest items, reloads list, toasts with count
- Enter key submits add-feed form; better empty-state hint with example feeds
Weather tool:
- Accept any city/region name in addition to 'home'/'work'/'all'
- Geocodes via Nominatim + fetches live from Open-Meteo for arbitrary queries
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The separate intent model (OLLAMA_INTENT_MODEL / qwen2.5:7b) is removed
from every part of the system. All classification now uses the primary model.
Changes:
- config.py: remove OLLAMA_INTENT_MODEL
- intent.py: remove classify_intent() and all supporting infrastructure
(_SYSTEM_PROMPT_TEMPLATE, _RESEARCH_PREFIX, _PRIOR_WORK_REFS); file now
only contains the quick-capture classifier
- quick_capture.py: classify_capture_intent() now called with Config.OLLAMA_MODEL
- generation_task.py: remove intent_model_setting DB lookup and get_setting import;
history summarization and research pipeline use the primary model directly
- research.py: remove intent_model parameter from run_research_pipeline() and
_generate_sub_queries(); both use the model param throughout
- routes/settings.py: remove intent_model from model-key validation and response
- app.py: remove intent model pre-warming at startup
- SettingsView.vue: remove Intent Model selector and related refs/state
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove all 6 CalDAV todo tools (create/list/update/complete/delete/search_todos)
from tools.py definitions, imports, execute_tool branches, intent routing rules,
generation_task labels/actions, and llm.py system prompt hints. CalDAV event
tools remain. Todo functions still exist in caldav.py but are no longer exposed.
- Quick-capture now uses a dedicated classify_capture_intent() with a focused
_CAPTURE_SYSTEM_PROMPT that always routes to a tool (never null). Tool set
expanded: create_note/task/event + update_note + research_topic.
- research_topic in quick-capture calls run_research_pipeline() directly (no SSE
buffer). run_research_pipeline() now accepts buf=None; all buf.append_event
calls are guarded so status events are skipped when no buffer is provided.
- Fallback note now always sets body=text (was empty for texts ≤80 chars).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Images found via SearXNG are fetched server-side, stored on disk, and
served from /api/images/<id> — the user's browser never contacts the
original image host. Original URLs are preserved for citation.
New files:
- alembic/versions/0016_add_image_cache.py — image_cache table
- src/fabledassistant/models/image_cache.py — SQLAlchemy model
- src/fabledassistant/services/images.py — fetch/store/serve logic
- src/fabledassistant/routes/images.py — GET /api/images/<id>
Modified:
- config.py: IMAGE_CACHE_DIR (/data/images), IMAGE_MAX_BYTES (5 MB)
- research.py: _search_searxng_images() — SearXNG categories=images
- tools.py: _IMAGE_TOOLS def + search_images branch in execute_tool
- intent.py: search_images routing rule (explicit visual language only)
- app.py: register images_bp
- docker-compose.yml: image_cache named volume mounted at /data/images
- ToolCallCard.vue: "image_search" label
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a Python fast-path regex (_PRIOR_WORK_REFS) in classify_intent that
detects phrases like "research you did", "note you made", "using your
research", "based on the research" etc. and returns no-tool immediately —
saving the 19s intent LLM call and correctly letting the main model answer
using search_notes/context rather than firing off a web search.
Also tighten the intent prompt rules for search_web: explicitly prohibit
using it for creative/brainstorming requests or when the user references
existing notes, and add a rule that creative/ideation questions ("think of",
"come up with", "brainstorm") always route to null (chat).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
config.py:
- Default OLLAMA_INTENT_MODEL: qwen2.5:1.5b → qwen2.5:7b
- Startup will auto-pull and warm the new model on next container restart
intent.py:
- Replaced phrase-matching examples in search_web and research_topic rules
with semantic descriptions. The 7B model doesn't need example phrases to
understand intent — it can reason from the tool's purpose. Removes implied
usage patterns that caused misclassifications on conversational phrasing
(e.g. "I've been thinking about buying shirts, can you research this?").
- research_topic rule now explicitly covers any subject regardless of phrasing,
including shopping decisions, comparisons, how-things-work questions, etc.
- search_web rule clarified as "short summary, no note" vs research_topic's
"comprehensive written reference"
The 1.5B model required prescriptive phrase examples to route correctly; the
7B model has sufficient language understanding to classify from semantic intent.
Expected improvement: ~1-2s intent calls (vs 0.4-9s for the 1.5B model which
sometimes timed out or misclassified longer/conversational messages).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two fixes for the intent model failing to route 'Research: X' messages
to research_topic:
1. Fast-path in classify_intent: if the message matches ^Research:\s+.+
(the exact format the UI Research button always sends), skip the LLM
call entirely and return research_topic with high confidence. This is
100% reliable and saves an unnecessary model call for this pattern.
2. Expanded research_topic rule examples in the system prompt to include
"Research: X" prefix format, shopping-style queries ("research where
to buy X"), and clarification that the topic is everything after the
keyword — improves LLM routing for natural-language research requests
that don't match the previous narrow examples.
Root cause: qwen2.5:1.5b misclassified "Research: where to buy three-
quarter sleeve tee shirts" as general chat (shopping query phrasing
combined with the colon confused the small model).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
research.py:
- Parallelize all 5 SearXNG queries concurrently (200ms stagger via asyncio.gather)
- Parallelize all URL fetches in parallel (asyncio.gather) — up to 15 URLs at once
instead of sequential fetches; biggest performance win (was O(n) × 15s, now ~15s flat)
- _synthesize_note accepts buf: when provided uses stream_chat (num_ctx=16384,
num_predict=8192) to emit tokens into the chat buffer in real time so users see
the note being written; falls back to generate_completion when buf=None
- Added \n\n---\n\n separator before "Research complete!" to cleanly mark boundary
after streamed synthesis content
intent.py:
- classify_intent passes num_ctx=4096 to generate_completion — reduces VRAM pressure
and prefill time for the intent model call on every single request
generation_task.py:
- _INTENT_TRIGGER_WORDS frozenset (~50 action/object/date words) + _should_skip_intent()
skips intent classification for short messages (≤10 words) with no trigger words;
saves 400-800ms model call for conversational replies ("thanks", "okay", etc.)
- Added \n\n---\n\n separator before research "done" text in research_topic branch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Context sidebar + note title:
- ChatView: replace ephemeral context pills with a persistent right-panel sidebar;
auto-found notes accumulate across turns; attached note shows with pin icon;
× button excludes a note from future auto-search; hidden on mobile
- routes/chat.py: batch-fetch note titles via get_notes_by_ids() and inject
context_note_title into each message dict at conversation load time
- notes.py: add get_notes_by_ids() batch fetch helper
- types/chat.ts: add context_note_title field to Message interface
- stores/chat.ts: sendMessage accepts optional 5th arg contextNoteTitle,
included in optimistic user message
- ChatMessage.vue: context badge shows note title instead of 'Note #N'
Expanded LLM tool suite (all with intent router rules + ToolCallCard display):
- delete_note / delete_task: permanent delete with user confirmation (write tool),
type-safe (refuse to delete wrong type), clears note context cache on success
- get_note: fetch full note body by query (search_notes returns only 200-char preview)
- list_notes: browse notes by recency/keyword/tags with limit; notes only
- update_note: add tags + tag_mode (replace/add/remove) parameters
- search_notes: add optional type filter ("note" | "task")
- search_todos (CalDAV): keyword-filter todos, companion to list_todos
- caldav.py: add search_todos() built on top of list_todos()
- generation_task.py: register new tools in _WRITE_TOOLS, _TOOL_LABELS, _TOOL_ACTIONS
- llm.py: update available actions list and guidance in system prompt
- intent.py: routing rules for all new tools
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- build_context() moved from route handler into run_generation() background task.
The 202 response now returns immediately; client connects to SSE before
note search / URL fetch begins, so 'Building context...' status is visible.
- _generate_title() runs in a fire-and-forget asyncio.create_task() after the
'done' SSE event fires. Users see their response complete 2–5s sooner on new
conversations; title appears later in the sidebar without blocking the stream.
- generate_completion() now sets think:False and accepts a max_tokens limit.
Intent classifier passes max_tokens=200 (JSON only), title generator passes
max_tokens=30 (short title), eliminating qwen3 thinking-mode overhead on these
auxiliary calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- update_note: extend with status/priority/due_date fields so task attributes
can be changed via chat (mark done, set priority, move due date). body is now
optional — task field updates work without touching content.
- list_tasks: new core tool with status/priority/due_before/due_after/limit
filters backed by list_notes(is_task=True). Enables queries like
"overdue tasks", "high priority tasks", "what's in progress".
- update_todo: new CalDAV tool to modify VTODO summary, due date, description,
and priority — follows update_event pattern (modify component, rebuild ical,
save). Completes the CalDAV todo CRUD suite.
- tools.py: add update_todo import + execute case (type: todo_updated)
- llm.py: add list_tasks and update_todo to available actions + guidance
- intent.py: routing rules for mark-done/priority/due-date → update_note,
overdue/in-progress/high-priority queries → list_tasks, CalDAV todo updates
→ update_todo
- ToolCallCard.vue: tasks list block (linked titles + due + priority badges),
todo_updated label, tool-task-priority CSS classes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mistral didn't reliably use Ollama's structured tool calling API — it wrote
tool calls as JSON text instead of invoking them. This adds an intent routing
layer that classifies user intent via a fast non-streaming LLM call before
streaming, executing detected tools directly and bypassing native tool calling.
- Change default OLLAMA_MODEL from mistral to qwen3
- Add intent.py: classify_intent() with JSON parsing and fallback regex
- Integrate intent routing into generation_task.py round 0
- Add all-day event support (iCalendar DATE values) to CalDAV service
- Add recurring event support (RRULE) to CalDAV service and tool definition
- Improve create_event tool description for descriptive titles
- Enhance system prompt with structured tool usage guidance
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>