Files
momentry_studio/AGENTS.md

6.8 KiB
Raw Blame History

AGENTS.md

Stack

  • Frontend: Vue 3 + TypeScript + Vite (port 5173)
  • Desktop: Tauri 2 (Rust backend in src-tauri/)
  • Runtime: Node.js + npm + Rust/Cargo all required

Commands

npm run dev          # Vite dev server (port 5173)
npm run build        # vue-tsc --noEmit && vite build (typecheck + bundle)
npm run preview      # Preview production build
npm run tauri        # Alias for `cargo tauri`
cargo tauri dev      # Full Tauri desktop dev (builds frontend + opens native window)
cargo tauri build    # Full native app build (outputs to src-tauri/target/release/bundle/)

Architecture

  • Dual-mode API: Frontend uses apiCall() from src/api/index.ts which detects window.__TAURI__ and dispatches to either Tauri IPC (invoke) or HTTP to proxy at http://0.0.0.0:8888.
  • Rust Tauri commands: src-tauri/src/main.rs — 22 Tauri commands:
    • Core API: search_llm_smart, search_agents, get_files, get_people, get_faces, get_traces, get_file_info
    • Media: get_thumbnail, get_face_thumbnail, get_identity_profile
    • Identity: update_identity_name, update_identity_status, update_identity_starred, delete_identity, search_identities, get_face_candidates, merge_identities, bind_face, unbind_face
    • File ops: register_file, process_file, unregister_file
    • Local SQLite: get_search_history, save_search_history, rename_search_history, pin_search_history, delete_search_history, get_bookmarks, save_bookmark, delete_bookmark
  • Rust HTTP proxy (src-tauri/src/proxy.rs): Axum on port 8888. Forwards /api/v1/* to Core API (localhost:3002) with API key injection. Handles /api/v1/search-history and /api/v1/bookmarks locally via db:: functions. Handles /api/v1/identity/{uuid}/profile (reads local filesystem) and /api/v1/face-thumbnail (downloads frame + crops bbox) locally — these mirror the Tauri IPC commands that can't be proxied to Core API.
  • Per-user SQLite: src-tauri/src/db.rs — Project-root data/users/demo.sqlite (outside src-tauri/ to avoid dev watcher rebuilds). WAL mode enabled. Tables: search_history, bookmarks, app_users. init_db() called on app setup.
  • Frontend routes: /search, /library, /people (redirect //search)
  • Frontend entry: src/main.tsApp.vuerouter/index.ts
  • Global directive: v-observe registered in main.ts from src/directives/vObserve.ts
  • Composables: src/composables/useSearchHistory.tsuseSearchHistory() and useBookmarks() for SQLite-backed search history and bookmarks CRUD via apiCall.

Key Details

  • withGlobalTauri: true in tauri.conf.jsonwindow.__TAURI__ is available in the webview.
  • No linter, formatter, or tests configured — do not invent them unless asked.
  • TypeScript is non-strict (strict: false in tsconfig.json).
  • CI/CD: Shell scripts only (ci-cd.sh, local-ci-cd.sh, setup-gitea-ci.sh). No GitHub Actions.
  • Full native build order: npm installnpm run buildcargo tauri build
  • Rust deps: sqlx 0.8, rusqlite 0.32 (bundled), reqwest, tokio, tauri-plugin-shell, base64, chrono, uuid
  • Core API: http://localhost:3002, API key hardcoded in Rust only.
  • Proxy: http://0.0.0.0:8888 — forwards /api/v1/* to Core API with auto-injected key; handles /api/v1/search-history and /api/v1/bookmarks locally.

Patterns

  • Search thumbnails: Use start_frame for frame-accurate thumbnails. Cache key format: ${file_uuid}:${start_frame}. Not a single file_uuid key because same file can appear in multiple search results at different time segments.
  • Store thumbnail queue: src/store.ts provides loadThumbnail(), loadFaceThumb(), loadProfile() with max 4 concurrent requests. Always use store functions for shared data across pages.
  • Page perPage limit: Core API hangs on per_page >= 100. Always use perPage ≤ 20. The Rust get_people command uses a special 2-page × 100 flow (separate from frontend).
  • PersonDetailView: Uses ensurePeople() from store instead of direct get_people call. Face thumbnails go through store's loadFaceThumb(). Faces/candidates use perPage: 20.
  • v-observe directive: Global directive (v-observe) registered in main.ts. Defined in src/directives/vObserve.ts. Uses IntersectionObserver with 200px rootMargin. Includes requestAnimationFrame fallback for elements already visible on mount.
  • SearchView empty state: When messages.length === 0, shows .ms-hero with "Momentry Studio" + "Turn Every Moment Into Intelligence" slogan. Chat area gets .chat--empty class (flex-column, justify-end) to position hero near the search bar. Once messages exist, hero disappears and .ibar stays fixed at bottom.
  • *No console.log in .vue files: All debug console.log statements have been removed. Use console.error only for actual errors.
  • Search history & bookmarks: Stored in per-user SQLite (data/users/demo.sqlite), not localStorage. History items have id, query, title, chat_state (JSON-serialized Vue messages), mode, pinned, timestamps. Max 30 items; pinned sorted first. Composable useSearchHistory() provides loadHistory, saveToHistory, renameHistory, pinHistory, deleteHistory, restoreFromHistory. Composable useBookmarks() provides loadBookmarks, saveBookmark, deleteBookmark.
  • SearchView history panel: Dropdown with ⋯ menu per item (Pin/Unpin, Rename, Delete). +New button clears chat. Click item → restores chat_state JSON or runs new search. Renames via modal dialog.
  • SearchView bookmark panel: Dropdown with items from SQLite. Click → runs search. Hover shows x button to delete.
  • API call snake_case: Frontend apiCall() sends camelCase args to Tauri IPC (auto-converted by Tauri) but snake_case in HTTP body. buildHttpRequest() converts arg names for HTTP mode (e.g., chatStatechat_state).
  • HistoryItem ID format: h_{timestamp}_{random} e.g. h_1718000000_abc123.
  • SQLite WAL mode + path outside src-tauri: Database at project-root data/users/demo.sqlite (not inside src-tauri/). WAL journal mode prevents -journal file creation that triggers cargo tauri dev watcher rebuilds. Default path ../data/users/demo.sqlite resolved from src-tauri/ cwd.
  • Video streaming: get_video_stream is NOT a Tauri command. Both Tauri and browser modes use the proxy URL directly (http://localhost:8888/api/v1/file/{uuid}/video?...). The <video> element streams from the proxy, never via IPC. Proxy streams video/* and octet-stream responses (doesn't buffer entire body).
  • Face thumbnail & identity profile in browser mode: /api/v1/face-thumbnail and /api/v1/identity/{uuid}/profile are handled locally by the proxy (same logic as Tauri IPC commands). Core API doesn't support bbox cropping for face thumbnails or local filesystem for profiles.