From f915aaf7940cea47bd121ffef152aadaff1e28fb Mon Sep 17 00:00:00 2001 From: Momentry Studio Date: Wed, 24 Jun 2026 02:02:33 +0800 Subject: [PATCH] feat: pending identity sorting, face detail modal, video player fps, processor counts --- AGENTS.md | 44 ++- docs/CORE_API_REFERENCE.md | 67 +++++ src-tauri/src/main.rs | 209 +++++++++----- src/api/index.ts | 61 +++- src/components/VideoPlayer.vue | 508 +++++++++++++++++++++++++++++---- src/store.ts | 137 +++++++++ src/views/LibraryView.vue | 233 +++++++++++++-- src/views/PeopleView.vue | 186 +++++++++++- src/views/PersonDetailView.vue | 358 +++++++++++++---------- 9 files changed, 1479 insertions(+), 324 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index fb5f8b6..91a351d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,18 +16,19 @@ cargo tauri build # Full native app build (outputs to src-tauri/target/releas ``` ## Architecture -- **All Core API calls go through Rust Tauri commands** — frontend NEVER contacts `localhost:3002` directly. -- **Rust proxies to external API**: `CORE_API = http://localhost:3002` (hardcoded in `src-tauri/src/main.rs`). This service must be running for the app to function. -- **API key**: Hardcoded in `src-tauri/src/main.rs` only (`API_KEY` constant). -- **Rust entrypoint**: `src-tauri/src/main.rs` — 15 Tauri commands: - - `search_llm_smart`, `get_files`, `get_people`, `get_faces`, `get_traces` (data APIs) - - `get_thumbnail`, `get_identity_profile` (return base64 image strings) - - `get_video_stream` (returns `file://` URL to temp MP4) - - `update_identity_name`, `delete_identity` (identity management) - - `search_identities`, `get_face_candidates` (search) - - `merge_identities`, `bind_face`, `unbind_face` (identity ops) +- **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.ts` → `src/App.vue` → `src/router/index.ts` +- **Frontend entry**: `src/main.ts` → `App.vue` → `router/index.ts` +- **Global directive**: `v-observe` registered in `main.ts` from `src/directives/vObserve.ts` +- **Composables**: `src/composables/useSearchHistory.ts` — `useSearchHistory()` and `useBookmarks()` for SQLite-backed search history and bookmarks CRUD via `apiCall`. ## Key Details - **`withGlobalTauri: true`** in `tauri.conf.json` — `window.__TAURI__` is available in the webview. @@ -35,4 +36,23 @@ cargo tauri build # Full native app build (outputs to src-tauri/target/releas - **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 install` → `npm run build` → `cargo tauri build` -- **Rust deps**: sqlx (postgres), reqwest, tokio, tauri-plugin-shell, base64 +- **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., `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 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 `