Update summary.md for session: image cache, suggested notes improvements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 13:09:01 -05:00
parent c962e86039
commit de1831f689
+30 -3
View File
@@ -12,7 +12,7 @@
> Include file-level details in the commit body when the change is non-trivial.
## Last Updated
2026-02-28 — Quick-capture endpoint for Android/external clients; bug fixes: create_note list-title crash, AI Assist 400 error, note picker persistent context, intent routing
2026-03-01 — Image search with local cache (Phase 23); suggested notes improvements (limit 8, threshold 0.45, relevance scores); suggested notes UX; quick-capture endpoint
## Project Overview
Fabled Assistant is a self-hosted note-taking and task-tracking application with
@@ -276,6 +276,7 @@ fabledassistant/
│ │ ├── auth.py # /api/auth blueprint: register, login, logout, me, password, email change, status, password reset, invitation registration, OAuth login+callback — rate limiting, LOCAL_AUTH_ENABLED guards, _client_ip() helper
│ │ ├── admin.py # /api/admin blueprint: backup, restore, user management, registration toggle, invitations, base URL, SMTP (admin only)
│ │ ├── chat.py # /api/chat blueprint: conversations CRUD, SSE message streaming (all @login_required)
│ │ ├── images.py # /api/images/<id> GET — serve locally-cached images (no auth required; IDs are opaque)
│ │ ├── notes.py # /api/notes CRUD + wikilinks + backlinks + tag suggestions (all @login_required)
│ │ ├── quick_capture.py # /api/quick-capture POST — mobile/external single-shot item creation (session auth)
│ │ ├── tasks.py # /api/tasks CRUD + PATCH status (all @login_required)
@@ -290,8 +291,9 @@ fabledassistant/
│ │ ├── generation_buffer.py # In-memory SSE event buffer with cancel_event, reconnect support, auto-cleanup; supports chat (int keys) and assist (string keys)
│ │ ├── generation_task.py # Background asyncio tasks: run_generation (chat, DB flush, titles, intent-first pipeline + tool loop) + run_assist_generation (lightweight, no DB); _INTENT_TRIGGER_WORDS + _should_skip_intent() for skipping intent on conversational messages
│ │ ├── intent.py # Intent routing: classify_intent() makes fast non-streaming LLM call (num_ctx=4096); IntentResult has ack field (one-sentence acknowledgment streamed as TTFT)
│ │ ├── tools.py # LLM tool definitions (create/delete note+task, update_note w/tag management, get_note, list_notes, search_notes w/type filter, list_tasks, full CalDAV suite incl. search_todos, search_web, research_topic when SearXNG enabled) + execute_tool dispatcher
│ │ ├── research.py # SearXNG research pipeline: parallel query/fetch, streaming synthesis; run_research_pipeline() → Note; constants SEARXNG_QUERIES=5, PAGES_PER_QUERY=3, MAX_SYNTHESIS_SOURCES=12, CHARS_PER_SOURCE=2000
│ │ ├── images.py # Image cache service: fetch_and_store_image (SHA-256 dedup, content-type validation, 5MB cap), get_image_record, get_image_path
│ │ ├── tools.py # LLM tool definitions (create/delete note+task, update_note w/tag management, get_note, list_notes, search_notes w/type filter, list_tasks, full CalDAV suite incl. search_todos, search_web, research_topic, search_images when SearXNG enabled) + execute_tool dispatcher
│ │ ├── research.py # SearXNG research pipeline: parallel query/fetch, streaming synthesis; run_research_pipeline() → Note; _search_searxng_images() for image category search; constants SEARXNG_QUERIES=5, PAGES_PER_QUERY=3, MAX_SYNTHESIS_SOURCES=12, CHARS_PER_SOURCE=2000
│ │ ├── tag_suggestions.py # LLM-powered tag suggestions: suggest_tags() builds prompt with existing tags, calls generate_completion, parses JSON response
│ │ ├── caldav.py # CalDAV integration: full event lifecycle (create/list/search/update/delete), todos (create/list/search/update/complete/delete), list_calendars, timezone (ZoneInfo), reminders (VALARM), attendees, multi-calendar search
│ │ ├── settings.py # Settings CRUD with user_id isolation: get_setting, set_setting, set_settings_batch, get_all_settings
@@ -669,6 +671,31 @@ When adding a new migration, follow these conventions:
brainstorming requests, game design, writing help, or when the user references existing notes.
Creative/ideation requests ("think of", "come up with", "brainstorm") always route to null (chat).
`search_web` is only for genuinely new real-time facts not in the user's notes.
- **Image search with local cache (Phase 23):** `search_images` tool (SearXNG `categories=images`) fetches
images server-side, stores them on disk, and serves them from `/api/images/<id>` — the user's browser
never contacts the original image host. Original URLs preserved for citation.
- New migration `0016_add_image_cache.py`: `image_cache` table (`id`, `url_hash`, `original_url`,
`source_domain`, `title`, `content_type`, `file_size`, `file_ext`, `fetched_at`)
- `services/images.py`: `fetch_and_store_image()` — SHA-256 dedup, content-type validation (`image/*`
only), 5 MB cap, same-origin `Referer` header to bypass hotlink protection, `IntegrityError` race
handling. `get_image_record()`, `get_image_path()`.
- `routes/images.py`: `GET /api/images/<id>` — no auth (IDs are opaque SHA-256-derived); `Cache-Control:
max-age=86400`; 404 if file missing from disk.
- `research.py`: `_search_searxng_images(query)` — SearXNG with `categories=images`, returns
`[{img_src, page_url, title, source_domain}]` with same 429-retry logic as text search.
- `tools.py`: `_IMAGE_TOOLS` list + `search_images` branch in `execute_tool` — fetches top 2 images,
returns `{type: "image_search", data: {images: [{local_url, page_url, source_domain, title}]}}`.
- `intent.py`: routing rule — only triggers on explicit visual language ("show me", "what does X look
like", "picture of", "photo of"). Factual/description questions stay as null (chat).
- Config: `IMAGE_CACHE_DIR` (default `/data/images`), `IMAGE_MAX_BYTES` (default 5 MB).
- Docker: named volume `app_data` mounted at `/data` (covers all future persistent storage, not just
images). Bind-mount alternative `./data:/data` documented in compose comments.
- No CSP changes needed — `/api/images/` is same-origin, already covered by `img-src 'self'`.
- **Suggested notes improvements:** `semantic_search_notes` now returns `list[tuple[float, Note]]`
(was `list[Note]`); scores threaded through `context_meta["auto_notes"]` to frontend. Sidebar shows
`%` badge per note: green ≥75%, amber 6074%, muted 4559%. Threshold raised 0.30 → 0.45 (only
genuinely relevant notes shown). Limit raised 3 → 8 (no added compute — threshold is the real gate).
Keyword fallback notes carry `score: null` (no badge shown).
- **Quick-capture endpoint:** `POST /api/quick-capture` in `routes/quick_capture.py` (session auth via
cookie). Takes `{"text": "natural language string"}`, runs `classify_intent` filtered to creation-only
tools (`create_note`, `create_task`, `create_event`, `create_todo`), calls `execute_tool`, and returns