Files
momentry_studio/AGENTS.md
Momentry Studio f3d75ebce8 feat: Library 批量注册 + PeopleView 修复 + 国际化
Library:
- 添加 checkbox 和鼠标框选功能
- Unregistered 卡片添加快速注册按钮
- 状态标签移至左下方,人脸图标移至左侧中间
- Deep Scan 改为 unregister -> register -> process
- 完整国际化(日语、繁体中文、英语)

PeopleView:
- 修复卡片不显示问题(使用 get_file_traces API)
- 移除对 identity_id 字段的依赖

其他:
- 添加日语语言包
- 添加 useDragSelect composable
- 新增文档:Face Bbox Null Bug Report
2026-10-01 00:42:13 +08:00

108 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENTS.md
## Important: File Path Convention
**All file paths in this document and code references must use full absolute paths.**
Examples:
- ✅ `/Users/accusys/momentry_studio/src/views/PeopleView.vue`
- ❌ `src/views/PeopleView.vue`
- ✅ `/Users/accusys/momentry_studio/src-tauri/src/db.rs`
- ❌ `src-tauri/src/db.rs`
## Workflow
**Before starting any task, always create a `todowrite` with all steps listed.**
Mark items `in_progress` while working and `completed` when done.
## 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 proxy # Start Rust proxy binary only (port 8888)
npm run dev # Start Rust proxy + 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/)
```
- **Browser dev flow**: `npm run dev` → kills old proxy on 8888 → starts `momentry-proxy` → sleeps 2s → starts Vite. Vite proxy forwards `/api/v1/*` to Rust proxy at `localhost:8888` and `/api/v2/*` directly to MarkBase at `localhost:11438`. Rust proxy handles face-crop, cluster-results, identity-matches, search-history, bookmarks, profile images locally; proxies rest to Core API at `localhost:3002`.
## Two-API Architecture (不可混淆)
**Two independent backend APIs. `/api/v1/*` → Momentry Core API ONLY; `/api/v2/*` → MarkBase Core API ONLY. Never cross.**
| API | Port | Namespace | Owner | Auth |
|---|---|---|---|---|
| **Momentry Core API** | `:3002` | `/api/v1/*` only (files, search, identities, faces, traces, agents, video-marks…) | `/Users/accusys/momentry_core` | `api_key` injected by Studio proxy |
| **MarkBase Core API** | `:11438` | `/api/v2/*` only (admin, myfiles, service, auth, config, tree, products…) | `/Users/accusys/markbase/markbase-core` | Bearer token (`/api/v2/admin/login`) |
**Routing rules** (`/Users/accusys/momentry_studio/src-tauri/src/proxy.rs`):
- Studio proxy `:8888` is a v1/v2 gateway: `fallback_handler` routes `/api/v2/*` → `proxy_to(MARKBASE_API, inject_api_key=false)` and any other `/api/*` → `proxy_to(CORE_API, inject_api_key=true)`. MarkBase does NOT get `api_key` injected.
- Vite dev proxy (`/Users/accusys/momentry_studio/vite.config.ts`): `/api/v1` → `localhost:8888`, `/api/v2` → `localhost:11438`.
- **Admin data (`get_system_stats`/`list_admin_users`/`list_shares`) always comes from MarkBase `/api/v2/admin/*` in ALL modes** — these are forced to `httpCall()` in `src/api/index.ts` (no Tauri commands exist for them).
- `ClientView` (`/Users/accusys/momentry_studio/src/views/ClientView.vue`) talks to MarkBase directly via `MARKBASE_URL + '/api/v2'` (bypasses Studio proxy). `start_service`/`stop_service` use `detectBaseUrl()` → MarkBase directly.
## 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` — 19 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. Routes `/api/v2/*` to MarkBase API (localhost:11438) and `/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` → `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.
- **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 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` — v1/v2 gateway: `/api/v2/*` → MarkBase (11438, no key), `/api/v1/*` → 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.
- **No dev residue allowed (`以後不再有 dev 殘留`)**: Never commit or leave scratch/dev files in the repo — this includes tracked backup snapshots (`*.bak`, `*.bak3`, `*.base`, `*.clean`, `*.work`, `*.backup_*`), standalone dev-only binaries/scripts (e.g. `gen_password.rs`), and unused dead code. Keep backups out of git (personal temp or gitignored); delete them when done. New dev features must not introduce `console.log` (even temporary — use `console.error` or remove before commit).
- **Face settings owned by Core**: `src/api/index.ts` `register_file`/`process_file` and the Rust commands in `/Users/accusys/momentry_studio/src-tauri/src/main.rs` must NOT send or read `face_settings`/`FACE_MIN_*` env vars. Core API owns the face-detection defaults server-side (see Core guide `docs_v1.0/M4_workspace/2026-08-08_face_settings_studio_guide.md`).
- **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 `<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.
- **Advanced Mode (🔒/🔓)**: Store at `/Users/accusys/momentry_studio/src/stores/advancedMode.ts`. Toggle button (🔒 closed / 🔓 open) in LibraryView toolbar persists to localStorage key `momentry_advanced_mode`. Controls visibility of advanced features:
- `LibraryView.vue:180` — Processor QC button `v-if="isAdvanced"`
- `VideoPlayer.vue:88,129,169,198` — Mark timelines, controls, panel, frame seek `v-if="isAdvanced"`
- `PeopleView.vue:19` — Data QC button `v-if="selectedFileUuid && isAdvanced"`
- `BuildVersion.vue:2` — Build version badge `v-if="isAdvanced"`
- Import: `import { useAdvancedMode } from '@/stores/advancedMode'`
- Usage: `const { isAdvanced } = useAdvancedMode()`
- **File Status (Core API 單一來源)**: 6 個狀態 values 由 Core API 回傳,前端只負責顯示不過濾:
- `registered` → 📋 已註冊(剛註冊)
- `scanning` → 🔍 掃描中(處理中,顯示 progress bar)
- `keyword_ready` → 🔎 關鍵字可用(sentence chunks 完成)
- `semantic_ready` → 🧠 語意可用(embedded chunks 完成)
- `face_mgmt_ready` → 🎭 臉部管理可用(trace_profiles + tkg_nodes 完成)
- `agent_ready` → 🤖 Agent 可用(tkg_edges 完成,終端狀態)
- 前端不做任何本地 status promotion(已移除 `pollProgress`、`loadPipelineStats`、`refreshFileStatus` 中的 `f.status =` 賦值)
- 唯一寫入點:`syncFileStatus()` 將 Core API 回傳值寫入 `fileStatusCache` + `f.status`
- 顯示函數:`LibraryView.vue` line 1274 (`statusFromDB`), 1283 (`statusBadgeClass`), 1291 (`statusBadgeIcon`), 1299 (`statusBadgeLabel`)