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

12 KiB
Raw Blame History

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)