From de1831f68902ef76bd6acc32009ba4ab9346cfcd Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 1 Mar 2026 13:09:01 -0500 Subject: [PATCH] Update summary.md for session: image cache, suggested notes improvements Co-Authored-By: Claude Sonnet 4.6 --- summary.md | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/summary.md b/summary.md index a0a3300..6ae40d9 100644 --- a/summary.md +++ b/summary.md @@ -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/ 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/` — 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/` — 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 60–74%, muted 45–59%. 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