Library: - 添加 checkbox 和鼠标框选功能 - Unregistered 卡片添加快速注册按钮 - 状态标签移至左下方,人脸图标移至左侧中间 - Deep Scan 改为 unregister -> register -> process - 完整国际化(日语、繁体中文、英语) PeopleView: - 修复卡片不显示问题(使用 get_file_traces API) - 移除对 identity_id 字段的依赖 其他: - 添加日语语言包 - 添加 useDragSelect composable - 新增文档:Face Bbox Null Bug Report
12 KiB
12 KiB
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 → startsmomentry-proxy→ sleeps 2s → starts Vite. Vite proxy forwards/api/v1/*to Rust proxy atlocalhost:8888and/api/v2/*directly to MarkBase atlocalhost:11438. Rust proxy handles face-crop, cluster-results, identity-matches, search-history, bookmarks, profile images locally; proxies rest to Core API atlocalhost: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
:8888is a v1/v2 gateway:fallback_handlerroutes/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 getapi_keyinjected. - 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 tohttpCall()insrc/api/index.ts(no Tauri commands exist for them). ClientView(/Users/accusys/momentry_studio/src/views/ClientView.vue) talks to MarkBase directly viaMARKBASE_URL + '/api/v2'(bypasses Studio proxy).start_service/stop_serviceusedetectBaseUrl()→ MarkBase directly.
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— 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
- Core API:
- 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-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— v1/v2 gateway:/api/v2/*→ MarkBase (11438, no key),/api/v1/*→ 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. - 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 introduceconsole.log(even temporary — useconsole.erroror remove before commit). - Face settings owned by Core:
src/api/index.tsregister_file/process_fileand the Rust commands in/Users/accusys/momentry_studio/src-tauri/src/main.rsmust NOT send or readface_settings/FACE_MIN_*env vars. Core API owns the face-detection defaults server-side (see Core guidedocs_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 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. - Advanced Mode (🔒/🔓): Store at
/Users/accusys/momentry_studio/src/stores/advancedMode.ts. Toggle button (🔒 closed / 🔓 open) in LibraryView toolbar persists to localStorage keymomentry_advanced_mode. Controls visibility of advanced features:LibraryView.vue:180— Processor QC buttonv-if="isAdvanced"VideoPlayer.vue:88,129,169,198— Mark timelines, controls, panel, frame seekv-if="isAdvanced"PeopleView.vue:19— Data QC buttonv-if="selectedFileUuid && isAdvanced"BuildVersion.vue:2— Build version badgev-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.vueline 1274 (statusFromDB), 1283 (statusBadgeClass), 1291 (statusBadgeIcon), 1299 (statusBadgeLabel)