6.8 KiB
6.8 KiB
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()fromsrc/api/index.tswhich detectswindow.__TAURI__and dispatches to either Tauri IPC (invoke) or HTTP to proxy athttp://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
- Core API:
- 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-historyand/api/v1/bookmarkslocally viadb::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-rootdata/users/demo.sqlite(outsidesrc-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.ts→App.vue→router/index.ts - Global directive:
v-observeregistered inmain.tsfromsrc/directives/vObserve.ts - Composables:
src/composables/useSearchHistory.ts—useSearchHistory()anduseBookmarks()for SQLite-backed search history and bookmarks CRUD viaapiCall.
Key Details
withGlobalTauri: trueintauri.conf.json—window.__TAURI__is available in the webview.- No linter, formatter, or tests configured — do not invent them unless asked.
- TypeScript is non-strict (
strict: falseintsconfig.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 install→npm run build→cargo 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-historyand/api/v1/bookmarkslocally.
Patterns
- Search thumbnails: Use
start_framefor frame-accurate thumbnails. Cache key format:${file_uuid}:${start_frame}. Not a singlefile_uuidkey because same file can appear in multiple search results at different time segments. - Store thumbnail queue:
src/store.tsprovidesloadThumbnail(),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 useperPage ≤ 20. The Rustget_peoplecommand uses a special 2-page × 100 flow (separate from frontend). - PersonDetailView: Uses
ensurePeople()from store instead of directget_peoplecall. Face thumbnails go through store'sloadFaceThumb(). Faces/candidates useperPage: 20. - v-observe directive: Global directive (
v-observe) registered inmain.ts. Defined insrc/directives/vObserve.ts. UsesIntersectionObserverwith 200px rootMargin. IncludesrequestAnimationFramefallback for elements already visible on mount. - SearchView empty state: When
messages.length === 0, shows.ms-herowith "Momentry Studio" + "Turn Every Moment Into Intelligence" slogan. Chat area gets.chat--emptyclass (flex-column, justify-end) to position hero near the search bar. Once messages exist, hero disappears and.ibarstays fixed at bottom. - *No
console.login .vue files: All debug console.log statements have been removed. Useconsole.erroronly for actual errors. - Search history & bookmarks: Stored in per-user SQLite (
data/users/demo.sqlite), not localStorage. History items haveid,query,title,chat_state(JSON-serialized Vue messages),mode,pinned, timestamps. Max 30 items; pinned sorted first. ComposableuseSearchHistory()providesloadHistory,saveToHistory,renameHistory,pinHistory,deleteHistory,restoreFromHistory. ComposableuseBookmarks()providesloadBookmarks,saveBookmark,deleteBookmark. - SearchView history panel: Dropdown with ⋯ menu per item (Pin/Unpin, Rename, Delete). +New button clears chat. Click item → restores
chat_stateJSON 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()sendscamelCaseargs to Tauri IPC (auto-converted by Tauri) butsnake_casein HTTP body.buildHttpRequest()converts arg names for HTTP mode (e.g.,chatState→chat_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 insidesrc-tauri/). WAL journal mode prevents-journalfile creation that triggerscargo tauri devwatcher rebuilds. Default path../data/users/demo.sqliteresolved fromsrc-tauri/cwd. - Video streaming:
get_video_streamis 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 streamsvideo/*andoctet-streamresponses (doesn't buffer entire body). - Face thumbnail & identity profile in browser mode:
/api/v1/face-thumbnailand/api/v1/identity/{uuid}/profileare 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.