Compare commits
22 Commits
f5cf12409b
...
3d13d1390e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d13d1390e | ||
|
|
04cbb71ca0 | ||
|
|
e96cc8c8de | ||
|
|
127d646ef1 | ||
|
|
87dead7f65 | ||
|
|
20dae387ee | ||
|
|
b9e93c6293 | ||
|
|
de88fd4e44 | ||
|
|
d7f89a962b | ||
|
|
25ec1625df | ||
|
|
0806d44df4 | ||
|
|
29eabf6d88 | ||
|
|
a2b71fef0d | ||
|
|
8fdd1d741b | ||
|
|
78923a8973 | ||
|
|
932e43518d | ||
|
|
5d8449b07c | ||
|
|
0856b92ec6 | ||
|
|
f8bcc0356c | ||
|
|
dddb5d4cbd | ||
|
|
a008bb865b | ||
|
|
1c30af9557 |
@@ -23,8 +23,8 @@ MONGODB_URL=mongodb://localhost:27017
|
||||
MONGODB_DATABASE=momentry_dev
|
||||
|
||||
# Redis (already isolated via prefix)
|
||||
REDIS_URL=redis://:accusys@localhost:6379
|
||||
REDIS_PASSWORD=accusys
|
||||
REDIS_URL=redis://127.0.0.1:6379
|
||||
# REDIS_PASSWORD not set - Redis has no password configured
|
||||
|
||||
# Qdrant Vector Database - Collection isolation
|
||||
QDRANT_URL=http://localhost:6333
|
||||
@@ -37,8 +37,8 @@ MOMENTRY_BACKUP_DIR=/Users/accusys/momentry/backup/momentry_dev
|
||||
MOMENTRY_SFTP_ROOT=/Users/accusys/momentry/var/sftpgo/data/demo/
|
||||
|
||||
# Python (for processing scripts)
|
||||
MOMENTRY_PYTHON_PATH=/opt/homebrew/bin/python3.11
|
||||
MOMENTRY_SCRIPTS_DIR=/Users/accusys/momentry_core_0.1/scripts
|
||||
MOMENTRY_PYTHON_PATH=/Users/accusys/momentry_core/venv/bin/python
|
||||
MOMENTRY_SCRIPTS_DIR=/Users/accusys/momentry_core/scripts
|
||||
|
||||
# Logging
|
||||
RUST_LOG=debug
|
||||
@@ -73,7 +73,7 @@ REDIS_CACHE_TTL_VIDEO_META=3600
|
||||
TMDB_API_KEY=e9cde52197f6f8df4d9db99da93db1fb
|
||||
MOMENTRY_TMDB_PROBE_ENABLED=true
|
||||
# LLM for 5W1H summary (points to M5 Gemma4)
|
||||
MOMENTRY_LLM_SUMMARY_URL=http://localhost:8082/v1/chat/completions
|
||||
MOMENTRY_LLM_SUMMARY_URL=http://127.0.0.1:8082/v1/chat/completions
|
||||
MOMENTRY_LLM_SUMMARY_MODEL=google_gemma-4-26B-A4B-it-Q5_K_M.gguf
|
||||
MOMENTRY_LLM_SUMMARY_ENABLED=true
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,3 +16,5 @@ node_modules/
|
||||
*.log
|
||||
/tmp/
|
||||
*.log
|
||||
|
||||
scripts/swift_processors/.build/
|
||||
|
||||
@@ -581,8 +581,7 @@ git push origin main
|
||||
|
||||
| 機器 | Token |
|
||||
|------|-------|
|
||||
| M5Max128 | `a7cf946148063c2bfa8d59ad629ae541813f0db8` (write:repository) |
|
||||
| M5Max128 (admin) | `b388aec114a93ae3ce752acf16a9ce678144541b` (write:repository + write:user) |
|
||||
| M5Max128 | `c33768c4cc26c0f4c575dcce832e92e5cf192773` (write:repository + write:user) |
|
||||
|
||||
**注意**: Token 有 write:repository scope,勿外洩。如需新增 token 給其他機器,各自產自己的 token。
|
||||
|
||||
|
||||
22
config/port_registry.tsv
Normal file
22
config/port_registry.tsv
Normal file
@@ -0,0 +1,22 @@
|
||||
# Port Registry - Momentry Core
|
||||
# Each port must have exactly one owner.
|
||||
# Before adding a service: pick a free port, add a row here, then configure.
|
||||
#
|
||||
# Port Service Owner Config Key Default Source
|
||||
22 ssh sshd - - macOS
|
||||
80 http Caddy - - Caddyfile
|
||||
443 https Caddy - - Caddyfile
|
||||
2019 caddy-admin Caddy - - Caddyfile (internal)
|
||||
3000 gitea gitea - 3000 start_momentry.sh
|
||||
3002 production momentry MOMENTRY_SERVER_PORT 3002 run-server-3002.sh
|
||||
3003 playground momentry_playground MOMENTRY_SERVER_PORT 3003 start_momentry.sh
|
||||
3200 dashboard Caddy - - Caddyfile
|
||||
3306 mariadb mariadbd - 3306 start_momentry.sh
|
||||
5432 postgresql postgres DATABASE_URL postgres://...:5432 start_momentry.sh
|
||||
6379 redis redis-server REDIS_URL redis://...:6379 start_momentry.sh
|
||||
6333 qdrant qdrant QDRANT_URL http://...:6333 start_momentry.sh
|
||||
8081 wordpress Caddy - - Caddyfile
|
||||
8082 llm llama-server MOMENTRY_LLM_CHAT_URL http://...:8082 start_momentry.sh
|
||||
9000 php-fpm php-fpm - 9000 brew services
|
||||
11434 ollama ollama MOMENTRY_OLLAMA_URL http://...:11434 start_momentry.sh
|
||||
11436 embedding embeddinggemma MOMENTRY_EMBED_URL http://...:11436 start_momentry.sh
|
||||
|
@@ -2,15 +2,15 @@
|
||||
document_type: "reference_doc"
|
||||
service: "MOMENTRY_CORE"
|
||||
title: "Momentry Core Release API Reference v1.0.0"
|
||||
date: "2026-05-14"
|
||||
version: "V4.1"
|
||||
date: "2026-05-25"
|
||||
version: "V4.2"
|
||||
status: "active"
|
||||
owner: "Warren"
|
||||
---
|
||||
|
||||
# Momentry Core API Reference v1.0.0
|
||||
|
||||
58 endpoints across 10 categories, with real curl examples and responses.
|
||||
55 endpoints across 10 categories, with real curl examples and responses.
|
||||
|
||||
## Base
|
||||
|
||||
@@ -30,12 +30,13 @@ owner: "Warren"
|
||||
|---|--------|------|-------------|
|
||||
| 1 | GET | `/health` | Server status (ok/degraded) |
|
||||
| 2 | GET | `/health/detailed` | Per-service health + latency |
|
||||
| 3 | POST | `/api/v1/auth/login` | Username/password → API key |
|
||||
| 4 | POST | `/api/v1/auth/logout` | Invalidate session |
|
||||
| 5 | GET | `/api/v1/stats/ingest` | Ingest statistics |
|
||||
| 3 | GET | `/health/consistency` | Data consistency check |
|
||||
| 4 | POST | `/api/v1/auth/login` | Username/password → API key |
|
||||
| 5 | POST | `/api/v1/auth/logout` | Invalidate session |
|
||||
| 6 | GET | `/api/v1/stats/sftpgo` | SFTPGo status |
|
||||
| 7 | GET | `/api/v1/stats/inference` | LLM/Embedding health |
|
||||
| 8 | POST | `/api/v1/config/cache` | Toggle Redis cache |
|
||||
| 7 | POST | `/api/v1/config/cache` | Toggle Redis cache |
|
||||
| 8 | POST | `/api/v1/config/auto-pipeline` | Toggle auto-pipeline on register |
|
||||
| 9 | POST | `/api/v1/config/watcher-auto-register` | Toggle watcher auto-register |
|
||||
|
||||
```bash
|
||||
curl http://localhost:3002/health
|
||||
@@ -44,8 +45,8 @@ curl http://localhost:3002/health
|
||||
{
|
||||
"status": "ok",
|
||||
"version": "1.0.0",
|
||||
"build_git_hash": "26f2434",
|
||||
"build_timestamp": "2026-05-14T09:09:17Z",
|
||||
"build_git_hash": "de88fd4e",
|
||||
"build_timestamp": "2026-05-25",
|
||||
"uptime_ms": 7052517
|
||||
}
|
||||
```
|
||||
@@ -68,8 +69,8 @@ Supports all file types (video, image, document, audio). SHA256 content_hash com
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"build_git_hash": "26f2434",
|
||||
"build_timestamp": "2026-05-14T09:09:17Z",
|
||||
"build_git_hash": "de88fd4e",
|
||||
"build_timestamp": "2026-05-25",
|
||||
"services": {
|
||||
"postgres": {"status": "ok", "latency_ms": 6},
|
||||
"redis": {"status": "ok", "latency_ms": 0},
|
||||
@@ -103,17 +104,17 @@ Supports all file types (video, image, document, audio). SHA256 content_hash com
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 9 | POST | `/api/v1/files/register` | Register file → file_uuid. Body: `{"file_path":"...", "content_hash":"optional"}` |
|
||||
| 10 | GET | `/api/v1/files/lookup?file_name=` | Pre-upload name conflict check. Returns matches + `next_name` for auto-rename |
|
||||
| 11 | POST | `/api/v1/unregister` | Unregister file(s): by `file_uuid` or pattern match (`file_path`+`pattern`) |
|
||||
| 12 | GET | `/api/v1/files/scan` | Scan directory for new files |
|
||||
| 13 | GET | `/api/v1/files` | List files (paginated) |
|
||||
| 14 | GET | `/api/v1/file/:file_uuid` | Single file detail |
|
||||
| 15 | GET | `/api/v1/file/:file_uuid/probe` | ffprobe metadata |
|
||||
| 16 | POST | `/api/v1/file/:file_uuid/process` | Start pipeline |
|
||||
| 17 | GET | `/api/v1/file/:file_uuid/chunk/:chunk_id` | Single chunk detail (V1.0.2+) |
|
||||
| 18 | GET | `/api/v1/progress/:file_uuid` | Processing progress |
|
||||
| 19 | GET | `/api/v1/jobs` | Monitor jobs (filterable) |
|
||||
| 10 | POST | `/api/v1/files/register` | Register file → file_uuid. Body: `{"file_path":"...", "content_hash":"optional"}` |
|
||||
| 11 | GET | `/api/v1/files/lookup?file_name=` | Pre-upload name conflict check. Returns matches + `next_name` for auto-rename |
|
||||
| 12 | POST | `/api/v1/unregister` | Unregister file(s): by `file_uuid` or pattern match (`file_path`+`pattern`) |
|
||||
| 13 | GET | `/api/v1/files/scan` | Scan directory for new files |
|
||||
| 14 | GET | `/api/v1/files` | List files (paginated) |
|
||||
| 15 | GET | `/api/v1/file/:file_uuid` | Single file detail |
|
||||
| 16 | GET | `/api/v1/file/:file_uuid/probe` | ffprobe metadata |
|
||||
| 17 | POST | `/api/v1/file/:file_uuid/process` | Start pipeline |
|
||||
| 18 | POST | `/api/v1/file/:file_uuid/chunk/:chunk_id` | Single chunk detail (V1.0.2+) |
|
||||
| 19 | POST | `/api/v1/progress/:file_uuid` | Processing progress |
|
||||
| 20 | POST | `/api/v1/jobs` | Monitor jobs (filterable) |
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3002/api/v1/files/register -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"file_path":"/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"}'
|
||||
@@ -154,14 +155,14 @@ curl "http://localhost:3002/api/v1/files?page=1&page_size=2" -H "X-API-Key: muse
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 20 | POST | `/api/v1/search/visual` | Visual chunk search |
|
||||
| 21 | POST | `/api/v1/search/visual/class` | By object class |
|
||||
| 22 | POST | `/api/v1/search/visual/density` | By spatial density |
|
||||
| 23 | POST | `/api/v1/search/visual/combination` | Combined visual search |
|
||||
| 24 | POST | `/api/v1/search/visual/stats` | Visual stats |
|
||||
| 25 | POST | `/api/v1/search/smart` | Semantic (EmbeddingGemma + pgvector) |
|
||||
| 26 | POST | `/api/v1/search/universal` | BM25 keyword (requires file_uuid) |
|
||||
| 27 | POST | `/api/v1/search/frames` | Frame-level search |
|
||||
| 21 | POST | `/api/v1/search/visual` | Visual chunk search |
|
||||
| 22 | POST | `/api/v1/search/visual/class` | By object class |
|
||||
| 23 | POST | `/api/v1/search/visual/density` | By spatial density |
|
||||
| 24 | POST | `/api/v1/search/visual/combination` | Combined visual search |
|
||||
| 25 | POST | `/api/v1/search/visual/stats` | Visual stats |
|
||||
| 26 | POST | `/api/v1/search/smart` | Semantic (EmbeddingGemma + pgvector) |
|
||||
| 27 | POST | `/api/v1/search/universal` | BM25 keyword (requires file_uuid) |
|
||||
| 28 | POST | `/api/v1/search/frames` | Frame-level search |
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3002/api/v1/search/universal -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"query":"name","limit":2,"mode":"bm25","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
|
||||
@@ -183,10 +184,10 @@ curl -X POST http://localhost:3002/api/v1/search/universal -H "X-API-Key: muser
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 28 | POST | `/api/v1/file/:file_uuid/face_trace/sortby` | List traces (sorted/filtered) |
|
||||
| 29 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/faces` | Trace detections (+ interpolation) |
|
||||
| 29 | POST | `/api/v1/file/:file_uuid/traces` | List traces (sorted/filtered) |
|
||||
| 30 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/faces` | Trace detections (+ interpolation) |
|
||||
|
||||
### sortby — list traces
|
||||
### traces — list traces
|
||||
|
||||
Parameters:
|
||||
- `sort_by`: `face_count` | `duration` | `first_appearance`
|
||||
@@ -194,7 +195,7 @@ Parameters:
|
||||
- `limit`: max results
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:3002/api/v1/file/3abeee81d94597629ed8cb943f182e94/face_trace/sortby" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"sort_by":"face_count","limit":2}'
|
||||
curl -X POST "http://localhost:3002/api/v1/file/3abeee81d94597629ed8cb943f182e94/traces" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"sort_by":"face_count","limit":2}'
|
||||
```
|
||||
```json
|
||||
{"success":true,"total_traces":6892,"total_faces":108204,"traces":[
|
||||
@@ -224,10 +225,10 @@ curl "http://localhost:3002/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/2
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 30 | GET | `/api/v1/file/:file_uuid/thumbnail` | Frame JPEG (?frame=&x=&y=&w=&h=) |
|
||||
| 31 | GET | `/api/v1/file/:file_uuid/video` | Raw video stream. Dual input: `?start_time=&end_time=` (seconds) or `?start_frame=&end_frame=` (frames). |
|
||||
| 32 | GET | `/api/v1/file/:file_uuid/video/bbox` | Bbox overlay. `?start_frame=&end_frame=&face_uuid=&duration=` (all frame numbers). Dual input via `start_time`/`end_time`. |
|
||||
| 33 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | Trace clip (?mode=&padding=&audio=) |
|
||||
| 31 | GET | `/api/v1/file/:file_uuid/thumbnail` | Frame JPEG (?frame=&x=&y=&w=&h=) |
|
||||
| 32 | GET | `/api/v1/file/:file_uuid/video` | Raw video stream. Dual input: `?start_time=&end_time=` (seconds) or `?start_frame=&end_frame=` (frames). |
|
||||
| 33 | GET | `/api/v1/file/:file_uuid/video/bbox` | Bbox overlay. `?start_frame=&end_frame=&face_uuid=&duration=` (all frame numbers). Dual input via `start_time`/`end_time`. |
|
||||
| 34 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | Trace clip (?mode=&padding=&audio=) |
|
||||
|
||||
All video endpoints support:
|
||||
- `mode=normal|debug` (default: `normal`)
|
||||
@@ -260,16 +261,16 @@ Green bbox per face detection: actual frames `thickness=4`, interpolated `thickn
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 33 | GET | `/api/v1/identities` | List all identities |
|
||||
| 34 | GET | `/api/v1/file/:file_uuid/identities` | Identities in a file |
|
||||
| 35 | POST | `/api/v1/identity` | Register new identity |
|
||||
| 36 | GET | `/api/v1/identity/:identity_uuid` | Identity detail |
|
||||
| 37 | DELETE | `/api/v1/identity/:identity_uuid` | Delete identity |
|
||||
| 38 | GET | `/api/v1/identity/:identity_uuid/files` | Files for identity |
|
||||
| 39 | GET | `/api/v1/identity/:identity_uuid/chunks` | Chunks for identity |
|
||||
| 40 | GET | `/api/v1/faces/candidates` | Unbound face gallery |
|
||||
| 41 | GET | `/api/v1/identities/search?q=` | Search identities by name → chunks |
|
||||
| 42 | GET | `/api/v1/search/identity_text?q=&file_uuid=` | Full-text search → identity-bound chunks |
|
||||
| 35 | GET | `/api/v1/identities` | List all identities |
|
||||
| 36 | GET | `/api/v1/file/:file_uuid/identities` | Identities in a file |
|
||||
| 37 | POST | `/api/v1/identity` | Register new identity |
|
||||
| 38 | GET | `/api/v1/identity/:identity_uuid` | Identity detail |
|
||||
| 39 | DELETE | `/api/v1/identity/:identity_uuid` | Delete identity |
|
||||
| 40 | GET | `/api/v1/identity/:identity_uuid/files` | Files for identity |
|
||||
| 41 | GET | `/api/v1/identity/:identity_uuid/chunks` | Chunks for identity |
|
||||
| 42 | GET | `/api/v1/faces/candidates` | Unbound face gallery |
|
||||
| 43 | GET | `/api/v1/identities/search?q=` | Search identities by name → chunks |
|
||||
| 44 | GET | `/api/v1/search/identity_text?q=&file_uuid=` | Full-text search → identity-bound chunks |
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3002/api/v1/identities?page=1&page_size=3" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
|
||||
@@ -307,9 +308,9 @@ curl "http://localhost:3002/api/v1/faces/candidates?page=1&page_size=2" -H "X-A
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 43 | POST | `/api/v1/identity/:identity_uuid/bind` | Bind face → identity |
|
||||
| 44 | POST | `/api/v1/identity/:identity_uuid/unbind` | Unbind face from identity |
|
||||
| 45 | POST | `/api/v1/identity/:identity_uuid/mergeinto` | Merge into another identity |
|
||||
| 45 | POST | `/api/v1/identity/:identity_uuid/bind` | Bind face → identity |
|
||||
| 46 | POST | `/api/v1/identity/:identity_uuid/unbind` | Unbind face from identity |
|
||||
| 47 | POST | `/api/v1/identity/:identity_uuid/mergeinto` | Merge into another identity |
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:3002/api/v1/identity/a9a90105-6d6b-46ff-92da-0c3c1a57dff4/bind" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"file_uuid":"3abeee81d94597629ed8cb943f182e94","face_id":"face_42"}'
|
||||
@@ -324,9 +325,9 @@ curl -X POST "http://localhost:3002/api/v1/identity/a9a90105-6d6b-46ff-92da-0c3c
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 46 | POST | `/api/v1/resource/register` | Register processing resource |
|
||||
| 47 | POST | `/api/v1/resource/heartbeat` | Resource heartbeat |
|
||||
| 48 | GET | `/api/v1/resources` | List all resources |
|
||||
| 48 | POST | `/api/v1/resource/register` | Register processing resource |
|
||||
| 49 | POST | `/api/v1/resource/heartbeat` | Resource heartbeat |
|
||||
| 50 | GET | `/api/v1/resources` | List all resources |
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3002/api/v1/resources" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
|
||||
@@ -341,10 +342,10 @@ curl "http://localhost:3002/api/v1/resources" -H "X-API-Key: muser_686008560363
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 49 | POST | `/api/v1/agents/translate` | AI text translation |
|
||||
| 50 | POST | `/api/v1/agents/5w1h/analyze` | Single chunk analysis |
|
||||
| 51 | POST | `/api/v1/agents/5w1h/batch` | Batch analysis |
|
||||
| 52 | GET | `/api/v1/agents/5w1h/status` | Job status |
|
||||
| 51 | POST | `/api/v1/agents/translate` | AI text translation |
|
||||
| 52 | POST | `/api/v1/agents/5w1h/analyze` | Single chunk analysis |
|
||||
| 53 | POST | `/api/v1/agents/5w1h/batch` | Batch analysis |
|
||||
| 54 | GET | `/api/v1/agents/5w1h/status` | Job status |
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:3002/api/v1/agents/translate" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"text":"Hello world","target_language":"zh-TW"}'
|
||||
@@ -359,11 +360,10 @@ curl -X POST "http://localhost:3002/api/v1/agents/translate" -H "X-API-Key: mus
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 53 | POST | `/api/v1/agents/identity/analyze` | Identify faces in file |
|
||||
| 54 | GET | `/api/v1/agents/identity/status` | Analysis status |
|
||||
| 55 | POST | `/api/v1/agents/identity/suggest` | Name suggestions |
|
||||
| 56 | POST | `/api/v1/agents/suggest/merge` | Suggest merge |
|
||||
| 57 | POST | `/api/v1/agents/suggest/clustering` | Suggest re-clustering |
|
||||
| 55 | POST | `/api/v1/agents/identity/match-from-photo` | Match face from photo |
|
||||
| 56 | POST | `/api/v1/agents/identity/match-from-trace` | Match face from trace |
|
||||
| 57 | POST | `/api/v1/agents/suggest/merge` | Suggest merge |
|
||||
| 58 | POST | `/api/v1/agents/suggest/clustering` | Suggest re-clustering |
|
||||
|
||||
---
|
||||
|
||||
@@ -371,10 +371,11 @@ curl -X POST "http://localhost:3002/api/v1/agents/translate" -H "X-API-Key: mus
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| V4.2 | 2026-05-25 | Removed phantom routes (stats/ingest, stats/inference, agents/identity/status); fixed HTTP methods (chunk, progress, jobs → POST); renamed endpoints (face_trace/sortby → traces, analyze → match-from-photo, suggest → match-from-trace); added config endpoints (consistency, auto-pipeline, watcher-auto-register); updated git hash to de88fd4e |
|
||||
| V4.1 | 2026-05-14 | Added `build_timestamp` + `resources` + `pipeline` to health APIs; identity search endpoints; trace debug rework (green bbox, text overlay, all traces listed) |
|
||||
|
||||
## Related
|
||||
|
||||
- `API_DICTIONARY_V1.0.0.md` — Quick reference (58 endpoints)
|
||||
- `API_DICTIONARY_V1.0.0.md` — Quick reference (55 endpoints)
|
||||
- `API_DOCUMENTATION_v1.0.0.md` — Detailed spec with examples
|
||||
- `TRACE/TRACE_API_REFERENCE_V1.0.0.md` — Trace-specific reference
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
document_type: "reference_doc"
|
||||
service: "MOMENTRY_CORE"
|
||||
title: "Momentry Core Release API Reference v1.0.0"
|
||||
date: "2026-05-14"
|
||||
version: "V4.1"
|
||||
date: "2026-05-25"
|
||||
version: "V4.2"
|
||||
status: "active"
|
||||
owner: "Warren"
|
||||
---
|
||||
|
||||
# Momentry Core API Reference v1.0.0
|
||||
|
||||
58 endpoints across 10 categories, with real curl examples and responses.
|
||||
55 endpoints across 10 categories, with real curl examples and responses.
|
||||
|
||||
## Base
|
||||
|
||||
| Environment | URL |
|
||||
|-------------|-----|
|
||||
| Production | `http://localhost:3002` or `https://m5api.momentry.ddns.net` |
|
||||
| Production | `http://localhost:3002` or `https://api.momentry.ddns.net` |
|
||||
| Development | `http://localhost:3003` |
|
||||
| Auth | Header `X-API-Key: <key>` (login endpoint unprotected) |
|
||||
|
||||
@@ -30,14 +30,13 @@ owner: "Warren"
|
||||
|---|--------|------|-------------|
|
||||
| 1 | GET | `/health` | Server status (ok/degraded) |
|
||||
| 2 | GET | `/health/detailed` | Per-service health + latency |
|
||||
| 3 | POST | `/api/v1/auth/login` | Username/password → API key |
|
||||
| 4 | POST | `/api/v1/auth/logout` | Invalidate session |
|
||||
| 5 | GET | `/api/v1/stats/ingest` | Ingest statistics |
|
||||
| 3 | GET | `/health/consistency` | Data consistency check |
|
||||
| 4 | POST | `/api/v1/auth/login` | Username/password → API key |
|
||||
| 5 | POST | `/api/v1/auth/logout` | Invalidate session |
|
||||
| 6 | GET | `/api/v1/stats/sftpgo` | SFTPGo status |
|
||||
| 7 | GET | `/api/v1/stats/inference` | LLM/Embedding health |
|
||||
| 8 | POST | `/api/v1/config/cache` | Toggle Redis cache |
|
||||
| 9 | POST | `/api/v1/config/auto-pipeline` | Toggle auto-pipeline on register |
|
||||
| 10 | POST | `/api/v1/config/watcher-auto-register` | Toggle watcher auto-register |
|
||||
| 7 | POST | `/api/v1/config/cache` | Toggle Redis cache |
|
||||
| 8 | POST | `/api/v1/config/auto-pipeline` | Toggle auto-pipeline on register |
|
||||
| 9 | POST | `/api/v1/config/watcher-auto-register` | Toggle watcher auto-register |
|
||||
|
||||
```bash
|
||||
curl http://localhost:3002/health
|
||||
@@ -46,8 +45,8 @@ curl http://localhost:3002/health
|
||||
{
|
||||
"status": "ok",
|
||||
"version": "1.0.0",
|
||||
"build_git_hash": "26f2434",
|
||||
"build_timestamp": "2026-05-14T09:09:17Z",
|
||||
"build_git_hash": "de88fd4e",
|
||||
"build_timestamp": "2026-05-25",
|
||||
"uptime_ms": 7052517
|
||||
}
|
||||
```
|
||||
@@ -70,8 +69,8 @@ Supports all file types (video, image, document, audio). SHA256 content_hash com
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"build_git_hash": "26f2434",
|
||||
"build_timestamp": "2026-05-14T09:09:17Z",
|
||||
"build_git_hash": "de88fd4e",
|
||||
"build_timestamp": "2026-05-25",
|
||||
"services": {
|
||||
"postgres": {"status": "ok", "latency_ms": 6},
|
||||
"redis": {"status": "ok", "latency_ms": 0},
|
||||
@@ -105,17 +104,17 @@ Supports all file types (video, image, document, audio). SHA256 content_hash com
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 9 | POST | `/api/v1/files/register` | Register file → file_uuid. Body: `{"file_path":"...", "content_hash":"optional"}` |
|
||||
| 10 | GET | `/api/v1/files/lookup?file_name=` | Pre-upload name conflict check. Returns matches + `next_name` for auto-rename |
|
||||
| 11 | POST | `/api/v1/unregister` | Unregister file(s): by `file_uuid` or pattern match (`file_path`+`pattern`) |
|
||||
| 12 | GET | `/api/v1/files/scan` | Scan directory for new files |
|
||||
| 13 | GET | `/api/v1/files` | List files (paginated) |
|
||||
| 14 | GET | `/api/v1/file/:file_uuid` | Single file detail |
|
||||
| 15 | GET | `/api/v1/file/:file_uuid/probe` | ffprobe metadata |
|
||||
| 16 | POST | `/api/v1/file/:file_uuid/process` | Start pipeline |
|
||||
| 17 | GET | `/api/v1/file/:file_uuid/chunk/:chunk_id` | Single chunk detail (V1.0.2+) |
|
||||
| 18 | GET | `/api/v1/progress/:file_uuid` | Processing progress |
|
||||
| 19 | GET | `/api/v1/jobs` | Monitor jobs (filterable) |
|
||||
| 10 | POST | `/api/v1/files/register` | Register file → file_uuid. Body: `{"file_path":"...", "content_hash":"optional"}` |
|
||||
| 11 | GET | `/api/v1/files/lookup?file_name=` | Pre-upload name conflict check. Returns matches + `next_name` for auto-rename |
|
||||
| 12 | POST | `/api/v1/unregister` | Unregister file(s): by `file_uuid` or pattern match (`file_path`+`pattern`) |
|
||||
| 13 | GET | `/api/v1/files/scan` | Scan directory for new files |
|
||||
| 14 | GET | `/api/v1/files` | List files (paginated) |
|
||||
| 15 | GET | `/api/v1/file/:file_uuid` | Single file detail |
|
||||
| 16 | GET | `/api/v1/file/:file_uuid/probe` | ffprobe metadata |
|
||||
| 17 | POST | `/api/v1/file/:file_uuid/process` | Start pipeline |
|
||||
| 18 | POST | `/api/v1/file/:file_uuid/chunk/:chunk_id` | Single chunk detail (V1.0.2+) |
|
||||
| 19 | POST | `/api/v1/progress/:file_uuid` | Processing progress |
|
||||
| 20 | POST | `/api/v1/jobs` | Monitor jobs (filterable) |
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3002/api/v1/files/register -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"file_path":"/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"}'
|
||||
@@ -156,14 +155,14 @@ curl "http://localhost:3002/api/v1/files?page=1&page_size=2" -H "X-API-Key: muse
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 20 | POST | `/api/v1/search/visual` | Visual chunk search |
|
||||
| 21 | POST | `/api/v1/search/visual/class` | By object class |
|
||||
| 22 | POST | `/api/v1/search/visual/density` | By spatial density |
|
||||
| 23 | POST | `/api/v1/search/visual/combination` | Combined visual search |
|
||||
| 24 | POST | `/api/v1/search/visual/stats` | Visual stats |
|
||||
| 25 | POST | `/api/v1/search/smart` | Semantic (EmbeddingGemma + pgvector) |
|
||||
| 26 | POST | `/api/v1/search/universal` | BM25 keyword (requires file_uuid) |
|
||||
| 27 | POST | `/api/v1/search/frames` | Frame-level search |
|
||||
| 21 | POST | `/api/v1/search/visual` | Visual chunk search |
|
||||
| 22 | POST | `/api/v1/search/visual/class` | By object class |
|
||||
| 23 | POST | `/api/v1/search/visual/density` | By spatial density |
|
||||
| 24 | POST | `/api/v1/search/visual/combination` | Combined visual search |
|
||||
| 25 | POST | `/api/v1/search/visual/stats` | Visual stats |
|
||||
| 26 | POST | `/api/v1/search/smart` | Semantic (EmbeddingGemma + pgvector) |
|
||||
| 27 | POST | `/api/v1/search/universal` | BM25 keyword (requires file_uuid) |
|
||||
| 28 | POST | `/api/v1/search/frames` | Frame-level search |
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3002/api/v1/search/universal -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"query":"name","limit":2,"mode":"bm25","file_uuid":"3abeee81d94597629ed8cb943f182e94"}'
|
||||
@@ -185,10 +184,10 @@ curl -X POST http://localhost:3002/api/v1/search/universal -H "X-API-Key: muser
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 28 | POST | `/api/v1/file/:file_uuid/face_trace/sortby` | List traces (sorted/filtered) |
|
||||
| 29 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/faces` | Trace detections (+ interpolation) |
|
||||
| 29 | POST | `/api/v1/file/:file_uuid/traces` | List traces (sorted/filtered) |
|
||||
| 30 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/faces` | Trace detections (+ interpolation) |
|
||||
|
||||
### sortby — list traces
|
||||
### traces — list traces
|
||||
|
||||
Parameters:
|
||||
- `sort_by`: `face_count` | `duration` | `first_appearance`
|
||||
@@ -196,7 +195,7 @@ Parameters:
|
||||
- `limit`: max results
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:3002/api/v1/file/3abeee81d94597629ed8cb943f182e94/face_trace/sortby" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"sort_by":"face_count","limit":2}'
|
||||
curl -X POST "http://localhost:3002/api/v1/file/3abeee81d94597629ed8cb943f182e94/traces" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"sort_by":"face_count","limit":2}'
|
||||
```
|
||||
```json
|
||||
{"success":true,"total_traces":6892,"total_faces":108204,"traces":[
|
||||
@@ -226,10 +225,10 @@ curl "http://localhost:3002/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace/2
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 30 | GET | `/api/v1/file/:file_uuid/thumbnail` | Frame JPEG (?frame=&x=&y=&w=&h=) |
|
||||
| 31 | GET | `/api/v1/file/:file_uuid/video` | Raw video stream. Dual input: `?start_time=&end_time=` (seconds) or `?start_frame=&end_frame=` (frames). |
|
||||
| 32 | GET | `/api/v1/file/:file_uuid/video/bbox` | Bbox overlay. `?start_frame=&end_frame=&face_uuid=&duration=` (all frame numbers). Dual input via `start_time`/`end_time`. |
|
||||
| 33 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | Trace clip (?mode=&padding=&audio=) |
|
||||
| 31 | GET | `/api/v1/file/:file_uuid/thumbnail` | Frame JPEG (?frame=&x=&y=&w=&h=) |
|
||||
| 32 | GET | `/api/v1/file/:file_uuid/video` | Raw video stream. Dual input: `?start_time=&end_time=` (seconds) or `?start_frame=&end_frame=` (frames). |
|
||||
| 33 | GET | `/api/v1/file/:file_uuid/video/bbox` | Bbox overlay. `?start_frame=&end_frame=&face_uuid=&duration=` (all frame numbers). Dual input via `start_time`/`end_time`. |
|
||||
| 34 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | Trace clip (?mode=&padding=&audio=) |
|
||||
|
||||
All video endpoints support:
|
||||
- `mode=normal|debug` (default: `normal`)
|
||||
@@ -262,16 +261,16 @@ Green bbox per face detection: actual frames `thickness=4`, interpolated `thickn
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 33 | GET | `/api/v1/identities` | List all identities |
|
||||
| 34 | GET | `/api/v1/file/:file_uuid/identities` | Identities in a file |
|
||||
| 35 | POST | `/api/v1/identity` | Register new identity |
|
||||
| 36 | GET | `/api/v1/identity/:identity_uuid` | Identity detail |
|
||||
| 37 | DELETE | `/api/v1/identity/:identity_uuid` | Delete identity |
|
||||
| 38 | GET | `/api/v1/identity/:identity_uuid/files` | Files for identity |
|
||||
| 39 | GET | `/api/v1/identity/:identity_uuid/chunks` | Chunks for identity |
|
||||
| 40 | GET | `/api/v1/faces/candidates` | Unbound face gallery |
|
||||
| 41 | GET | `/api/v1/identities/search?q=` | Search identities by name → chunks |
|
||||
| 42 | GET | `/api/v1/search/identity_text?q=&file_uuid=` | Full-text search → identity-bound chunks |
|
||||
| 35 | GET | `/api/v1/identities` | List all identities |
|
||||
| 36 | GET | `/api/v1/file/:file_uuid/identities` | Identities in a file |
|
||||
| 37 | POST | `/api/v1/identity` | Register new identity |
|
||||
| 38 | GET | `/api/v1/identity/:identity_uuid` | Identity detail |
|
||||
| 39 | DELETE | `/api/v1/identity/:identity_uuid` | Delete identity |
|
||||
| 40 | GET | `/api/v1/identity/:identity_uuid/files` | Files for identity |
|
||||
| 41 | GET | `/api/v1/identity/:identity_uuid/chunks` | Chunks for identity |
|
||||
| 42 | GET | `/api/v1/faces/candidates` | Unbound face gallery |
|
||||
| 43 | GET | `/api/v1/identities/search?q=` | Search identities by name → chunks |
|
||||
| 44 | GET | `/api/v1/search/identity_text?q=&file_uuid=` | Full-text search → identity-bound chunks |
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3002/api/v1/identities?page=1&page_size=3" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
|
||||
@@ -309,9 +308,9 @@ curl "http://localhost:3002/api/v1/faces/candidates?page=1&page_size=2" -H "X-A
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 43 | POST | `/api/v1/identity/:identity_uuid/bind` | Bind face → identity |
|
||||
| 44 | POST | `/api/v1/identity/:identity_uuid/unbind` | Unbind face from identity |
|
||||
| 45 | POST | `/api/v1/identity/:identity_uuid/mergeinto` | Merge into another identity |
|
||||
| 45 | POST | `/api/v1/identity/:identity_uuid/bind` | Bind face → identity |
|
||||
| 46 | POST | `/api/v1/identity/:identity_uuid/unbind` | Unbind face from identity |
|
||||
| 47 | POST | `/api/v1/identity/:identity_uuid/mergeinto` | Merge into another identity |
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:3002/api/v1/identity/a9a90105-6d6b-46ff-92da-0c3c1a57dff4/bind" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"file_uuid":"3abeee81d94597629ed8cb943f182e94","face_id":"face_42"}'
|
||||
@@ -326,9 +325,9 @@ curl -X POST "http://localhost:3002/api/v1/identity/a9a90105-6d6b-46ff-92da-0c3c
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 46 | POST | `/api/v1/resource/register` | Register processing resource |
|
||||
| 47 | POST | `/api/v1/resource/heartbeat` | Resource heartbeat |
|
||||
| 48 | GET | `/api/v1/resources` | List all resources |
|
||||
| 48 | POST | `/api/v1/resource/register` | Register processing resource |
|
||||
| 49 | POST | `/api/v1/resource/heartbeat` | Resource heartbeat |
|
||||
| 50 | GET | `/api/v1/resources` | List all resources |
|
||||
|
||||
```bash
|
||||
curl "http://localhost:3002/api/v1/resources" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
|
||||
@@ -343,10 +342,10 @@ curl "http://localhost:3002/api/v1/resources" -H "X-API-Key: muser_686008560363
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 49 | POST | `/api/v1/agents/translate` | AI text translation |
|
||||
| 50 | POST | `/api/v1/agents/5w1h/analyze` | Single chunk analysis |
|
||||
| 51 | POST | `/api/v1/agents/5w1h/batch` | Batch analysis |
|
||||
| 52 | GET | `/api/v1/agents/5w1h/status` | Job status |
|
||||
| 51 | POST | `/api/v1/agents/translate` | AI text translation |
|
||||
| 52 | POST | `/api/v1/agents/5w1h/analyze` | Single chunk analysis |
|
||||
| 53 | POST | `/api/v1/agents/5w1h/batch` | Batch analysis |
|
||||
| 54 | GET | `/api/v1/agents/5w1h/status` | Job status |
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:3002/api/v1/agents/translate" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -H "Content-Type: application/json" -d '{"text":"Hello world","target_language":"zh-TW"}'
|
||||
@@ -361,11 +360,10 @@ curl -X POST "http://localhost:3002/api/v1/agents/translate" -H "X-API-Key: mus
|
||||
|
||||
| # | Method | Path | Description |
|
||||
|---|--------|------|-------------|
|
||||
| 53 | POST | `/api/v1/agents/identity/analyze` | Identify faces in file |
|
||||
| 54 | GET | `/api/v1/agents/identity/status` | Analysis status |
|
||||
| 55 | POST | `/api/v1/agents/identity/suggest` | Name suggestions |
|
||||
| 56 | POST | `/api/v1/agents/suggest/merge` | Suggest merge |
|
||||
| 57 | POST | `/api/v1/agents/suggest/clustering` | Suggest re-clustering |
|
||||
| 55 | POST | `/api/v1/agents/identity/match-from-photo` | Match face from photo |
|
||||
| 56 | POST | `/api/v1/agents/identity/match-from-trace` | Match face from trace |
|
||||
| 57 | POST | `/api/v1/agents/suggest/merge` | Suggest merge |
|
||||
| 58 | POST | `/api/v1/agents/suggest/clustering` | Suggest re-clustering |
|
||||
|
||||
---
|
||||
|
||||
@@ -373,10 +371,11 @@ curl -X POST "http://localhost:3002/api/v1/agents/translate" -H "X-API-Key: mus
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| V4.2 | 2026-05-25 | Removed phantom routes (stats/ingest, stats/inference, agents/identity/status); fixed HTTP methods (chunk, progress, jobs → POST); renamed endpoints (face_trace/sortby → traces, analyze → match-from-photo, suggest → match-from-trace); added config endpoints (consistency, auto-pipeline, watcher-auto-register); updated git hash to de88fd4e |
|
||||
| V4.1 | 2026-05-14 | Added `build_timestamp` + `resources` + `pipeline` to health APIs; identity search endpoints; trace debug rework (green bbox, text overlay, all traces listed) |
|
||||
|
||||
## Related
|
||||
|
||||
- `API_DICTIONARY_V1.0.0.md` — Quick reference (58 endpoints)
|
||||
- `API_DICTIONARY_V1.0.0.md` — Quick reference (55 endpoints)
|
||||
- `API_DOCUMENTATION_v1.0.0.md` — Detailed spec with examples
|
||||
- `TRACE/TRACE_API_REFERENCE_V1.0.0.md` — Trace-specific reference
|
||||
|
||||
@@ -158,6 +158,8 @@ related_documents:
|
||||
| 51 | GET | `/api/v1/stats/sftpgo` | SFTPGo 使用者狀態 | ✅ |
|
||||
| 52 | GET | `/api/v1/stats/inference` | 推理叢集健康狀態 | ✅ |
|
||||
| 53 | POST | `/api/v1/config/cache` | 切換快取開關 | ✅ |
|
||||
| 54 | POST | `/api/v1/config/auto-pipeline` | 註冊後自動處理 | ✅ |
|
||||
| 55 | POST | `/api/v1/config/watcher-auto-register` | Watcher 自動註冊 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -234,6 +234,11 @@ Bind a face detection to an identity. Associates the face trace with the identit
|
||||
| `file_uuid` | string | Yes | File where face is detected |
|
||||
| `face_id` | string | Yes | Face ID (format: `{frame}_{idx}`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 清除該 face detection row 的 `stranger_id`(設為 NULL)
|
||||
- 不影響 `identities` 表中原有的 stranger auto-identity 記錄
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
@@ -259,6 +264,11 @@ Bind all face detections of a trace to an identity. Updates all rows in `face_de
|
||||
| `file_uuid` | string | Yes | File where trace exists |
|
||||
| `trace_id` | integer | Yes | Trace ID (from `face_detections.trace_id`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 清除該 trace 所有 face detection rows 的 `stranger_id`(設為 NULL)
|
||||
- 不影響 `identities` 表中原有的 stranger auto-identity 記錄
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
@@ -287,6 +297,78 @@ curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/bind/trace" \
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/identity/:identity_uuid/traces`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: identity-level
|
||||
|
||||
Get paginated face traces (continuous tracking segments) associated with this identity across all files.
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `page` | integer | No | `1` | Page number |
|
||||
| `page_size` | integer | No | `20` | Items per page |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s "$API/api/v1/identity/$IDENTITY_UUID/traces?page=1&page_size=3" \
|
||||
-H "X-API-Key: $KEY" | jq '{total, total_faces, traces}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
|
||||
"name": "Cary Grant",
|
||||
"total": 18,
|
||||
"page": 1,
|
||||
"page_size": 3,
|
||||
"total_faces": 542,
|
||||
"traces": [
|
||||
{
|
||||
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692",
|
||||
"trace_id": 906,
|
||||
"frame_count": 52,
|
||||
"first_frame": 37974,
|
||||
"last_frame": 38127,
|
||||
"first_sec": 1519.0,
|
||||
"last_sec": 1525.1,
|
||||
"avg_confidence": 0.8254
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `success` | bool | Always `true` |
|
||||
| `identity_uuid` | string | Identity UUID |
|
||||
| `name` | string | Identity display name |
|
||||
| `total` | integer | Total number of traces (across all pages) |
|
||||
| `total_faces` | integer | Sum of all face detections in returned traces |
|
||||
| `traces[].file_uuid` | string | File where trace exists |
|
||||
| `traces[].trace_id` | integer | Trace tracking ID |
|
||||
| `traces[].frame_count` | integer | Number of frames in this trace |
|
||||
| `traces[].first_frame` | integer | Start frame number |
|
||||
| `traces[].last_frame` | integer | End frame number |
|
||||
| `traces[].first_sec` | float | Start time in seconds |
|
||||
| `traces[].last_sec` | float | End time in seconds |
|
||||
| `traces[].avg_confidence` | float | Average detection confidence (0.0–1.0) |
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `404` | Identity not found |
|
||||
| `500` | Database error |
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/identity/:identity_uuid/unbind`
|
||||
|
||||
**Auth**: Required
|
||||
@@ -294,6 +376,61 @@ curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/bind/trace" \
|
||||
|
||||
Unbind a face detection from an identity. Removes the identity association from the face record.
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 只清除 `identity_id`(設為 NULL),**不會恢復 `stranger_id`**
|
||||
- 被 unbind 的 face 不會自動成為 stranger
|
||||
- 要重新標記為 stranger 需重新跑 Agent API(`identity/analyze`)
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/identity/:identity_uuid/mergeinto`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: identity-level
|
||||
|
||||
Transfer all face bindings from this identity to another identity, then optionally delete or mark the source as merged.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `into_uuid` | string | Yes | — | Target identity UUID to merge into |
|
||||
| `keep_history` | bool | No | `true` | Keep source identity record with `status='merged'` (`true`) or delete it (`false`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 轉移所有 `face_detections.identity_id` 到目標 identity
|
||||
- 同時清除所有被轉移 rows 的 `stranger_id`
|
||||
- `keep_history: true`(預設):source identity 設為 `status='merged'`,保留記錄
|
||||
- `keep_history: false`:**刪除** source identity 及其 identity JSON 檔案
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/identity/$SOURCE_UUID/mergeinto" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"into_uuid": "'"$TARGET_UUID"'", "keep_history": false}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Merged 'stranger_13894' into 'Louis Viret' (52 faces transferred, source deleted)",
|
||||
"data": { "faces_transferred": 52 }
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `404` | Source or target identity not found |
|
||||
| `500` | Database error |
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/identities/search`
|
||||
@@ -491,4 +628,4 @@ PATCH /api/v1/identity/:identity_uuid
|
||||
This **replaces** the entire `aliases` array. To add to existing aliases, include all existing entries in the request.
|
||||
|
||||
---
|
||||
*Updated: 2026-05-22
|
||||
*Updated: 2026-05-25
|
||||
|
||||
492
docs_v1.0/DESIGN/FRAME_TIME_PIPELINE_V1.0.md
Normal file
492
docs_v1.0/DESIGN/FRAME_TIME_PIPELINE_V1.0.md
Normal file
@@ -0,0 +1,492 @@
|
||||
---
|
||||
title: "Frame / Time 雙產線分流協作設計 v1.0"
|
||||
version: "1.0"
|
||||
date: "2026-05-23"
|
||||
author: "M5"
|
||||
status: "draft"
|
||||
scope: "architecture, worker, storage"
|
||||
---
|
||||
|
||||
# Frame / Time 雙產線分流協作設計 v1.0
|
||||
|
||||
| Scope | Status | Applies to |
|
||||
|---------|--------|------------|
|
||||
| architecture / worker / storage | draft | momentry_core worker pipeline |
|
||||
|
||||
---
|
||||
|
||||
## 1. 緣起與問題
|
||||
|
||||
### 1.1 現狀問題
|
||||
|
||||
worker 將所有 processor 混在一起平行執行,導致:
|
||||
|
||||
| 問題 | 說明 |
|
||||
|------|------|
|
||||
| OOM | `max_concurrent=6` 時 6 個 Python 行程同時載入模型 → 記憶體不足被 kill |
|
||||
| 資源競爭 | 多個 processor 各自開 ffmpeg decode 同一部影片 → 6 倍 decode |
|
||||
| 重試粒度粗 | 一個 processor 失敗 → 整部片全部重來 |
|
||||
| 進度不精確 | 0% → 100%,中間無細粒度進度 |
|
||||
|
||||
### 1.2 手動 vs Worker 差異
|
||||
|
||||
| 面向 | 手動執行 | Worker 自動化 |
|
||||
|------|---------|--------------|
|
||||
| 執行方式 | 循序,一次一個 processor | 平行,最多 max_concurrent 個 |
|
||||
| 產出檢查 | 人工確認 JSON 內容正確 | `output_path.exists()` 僅檢查檔案存在 |
|
||||
| 資源 | 單一模型在記憶體 | 多個模型競爭記憶體 |
|
||||
|
||||
### 1.3 核心結論
|
||||
|
||||
兩個根本問題必須解決:
|
||||
|
||||
1. **產線分流** — Frame-base 與 Time-base processor 不應混合排程
|
||||
2. **Frame-level resource management** — 透過 MarkbaseFMS 統一 frame 存取
|
||||
|
||||
---
|
||||
|
||||
## 2. 雙產線架構
|
||||
|
||||
### 2.1 Pipeline Overview
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Input[Input Video] --> Probe
|
||||
Probe -->|frame info| FMS[MarkbaseFMS]
|
||||
Probe -->|audio track| TimePipe[Time Pipeline]
|
||||
|
||||
FMS -->|frame batches| FramePipe[Frame Pipeline]
|
||||
FMS -->|cache / align / convert| FMS
|
||||
|
||||
subgraph FramePipe [Frame Pipeline]
|
||||
CUT[CUT - scene detection]
|
||||
YOLO[YOLO - object detection]
|
||||
Face[Face - face detection]
|
||||
OCR[OCR - text detection]
|
||||
Pose[Pose - pose estimation]
|
||||
end
|
||||
|
||||
subgraph TimePipe [Time Pipeline]
|
||||
ASR[ASR - speech recognition]
|
||||
ASRX[ASRX - speaker diarization]
|
||||
end
|
||||
|
||||
CUT --> YOLO
|
||||
CUT --> Face
|
||||
CUT --> OCR
|
||||
CUT --> Pose
|
||||
|
||||
ASR --> ASRX
|
||||
|
||||
YOLO & Face & OCR & Pose --> Merge[Merge Processor Results]
|
||||
ASR & ASRX --> Merge
|
||||
|
||||
Face --> Lip[Lip Sync]
|
||||
ASR --> Lip
|
||||
ASRX --> Lip
|
||||
|
||||
Merge -->|all essential done| PostProcess
|
||||
Lip --> PostProcess
|
||||
|
||||
subgraph PostProcess [Post-processing]
|
||||
R1[Rule 1 Chunking]
|
||||
R3[Rule 3 Chunking]
|
||||
TK[TKG Build]
|
||||
W1[5W1H Agent]
|
||||
ID[Identity Agent]
|
||||
end
|
||||
```
|
||||
|
||||
### 2.2 各產線定義
|
||||
|
||||
#### Frame Pipeline(frame-based)
|
||||
|
||||
| Processor | 輸入 | 輸出 | 瓶頸資源 | 產線 |
|
||||
|-----------|------|------|---------|------|
|
||||
| CUT | frame (降解析) | scene.json | CPU | Frame |
|
||||
| YOLO | frame batch | yolo.json | GPU | Frame |
|
||||
| Face | frame batch | face.json | ANE/GPU | Frame |
|
||||
| OCR | frame batch | ocr.json | CPU | Frame |
|
||||
| Pose | frame batch | pose.json | GPU | Frame |
|
||||
|
||||
#### Time Pipeline(time-based)
|
||||
|
||||
| Processor | 輸入 | 輸出 | 瓶頸資源 | 產線 |
|
||||
|-----------|------|------|---------|------|
|
||||
| ASR | audio stream | asr.json | GPU/CPU | Time |
|
||||
| ASRX | audio stream + ASR result | asrx.json | CPU | Time |
|
||||
|
||||
#### 合流點
|
||||
|
||||
| 項目 | 需要 | 產出 |
|
||||
|------|------|------|
|
||||
| Lip Sync | Face + ASR + ASRX | lip.json (who speaks when) |
|
||||
| Rule 1 Chunking | ASR + ASRX | sentence chunks |
|
||||
| Rule 3 Chunking | CUT + ASR | scene chunks |
|
||||
| TKG Build | 所有 processor | tkg_nodes / tkg_edges |
|
||||
| 5W1H Agent | CUT + ASR | story summary |
|
||||
| Identity Agent | Face + ASRX | identity bindings |
|
||||
|
||||
---
|
||||
|
||||
## 3. MarkbaseFMS 底層設計
|
||||
|
||||
### 3.1 三層架構
|
||||
|
||||
```
|
||||
Application
|
||||
YOLO / Face / OCR / Pose / CUT
|
||||
讀取 frame buffer → 直接做 inference
|
||||
│
|
||||
│ frame-aligned access
|
||||
▼
|
||||
┌───────────────────────────────────┐
|
||||
│ FMS Filesystem Layer │
|
||||
│ Layout: frame data 連續存放 │
|
||||
│ 不跨 frame split │
|
||||
│ 格式: opaque raw buffer │
|
||||
│ metadata: 獨立區域 │
|
||||
│ read-ahead: 預取下一個 Block │
|
||||
│ alignment: 每 frame page-aligned │
|
||||
└──────────────────┬────────────────┘
|
||||
│ page-aligned (4096)
|
||||
┌──────────────────▼────────────────┐
|
||||
│ FMS Cache Layer │
|
||||
│ unit: FrameBlock (64 frames) │
|
||||
│ alignment: page boundary/frame │
|
||||
│ eviction: LRU on Block │
|
||||
│ pin: 使用中的 frame 不 evict │
|
||||
│ prefetch: 預拉下一個 Block │
|
||||
│ Direct I/O bypass OS cache │
|
||||
└──────────────────┬────────────────┘
|
||||
│ block-aligned (4K / 64K)
|
||||
┌──────────────────▼────────────────┐
|
||||
│ Block Device / Storage │
|
||||
│ sector alignment 4K │
|
||||
│ FrameBlock = N 個連續 sectors │
|
||||
│ 無跨 sector split │
|
||||
│ atomic write per block │
|
||||
│ O_DIRECT 直接 IO │
|
||||
└───────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 對齊原則
|
||||
|
||||
#### 各層對齊要求
|
||||
|
||||
| 層級 | 對齊單位 | 原因 |
|
||||
|------|---------|------|
|
||||
| Block Device | 4K sector | 現代 SSD 原生 sector,避免 RMW |
|
||||
| Cache | 4096 page | `mmap` + `madvise` 大頁面,減少 TLB miss |
|
||||
| Filesystem | frame block (64 frames) | 連續 layout,預測性 read-ahead |
|
||||
| Frame Buffer | 16 bytes stride | NEON SIMD,MPS/ANE texture 要求 |
|
||||
| Block Index | power of 2 | index 用 bit shift + mask,無需除法 |
|
||||
|
||||
#### Frame Buffer Layout
|
||||
|
||||
```
|
||||
每個 frame 的 raw buffer:
|
||||
|
||||
┌──────────────┐
|
||||
│ Y Plane │ ← 16-byte aligned stride, page-aligned offset
|
||||
│ (width×h) │
|
||||
├──────────────┤
|
||||
│ UV Plane │ ← 16-byte aligned stride
|
||||
│ (w/2×h/2×2) │ (NV12 interleaved)
|
||||
└──────────────┘
|
||||
|
||||
frame buffer offset: page-aligned (4096)
|
||||
row stride: align(width * pixel_size, 16)
|
||||
frame size: align(total_bytes, page_size)
|
||||
```
|
||||
|
||||
### 3.3 Block 排列方式
|
||||
|
||||
```
|
||||
每個 Block 包含連續 64 frames(configurable):
|
||||
|
||||
Block index ≤— bit shift (frame_num / 64)
|
||||
Frame offset = base + (frame_num & 63) * frame_size ← bit mask
|
||||
|
||||
Block Dispatching Strategy:
|
||||
|
||||
Worker 要求 "batch 0, format=RGB, width=640"
|
||||
FMS:
|
||||
1. Check Block Cache (RAM):
|
||||
Block 0 frames 0-63 是否已 decode?
|
||||
✅ hit → 直接回傳
|
||||
❌ miss → decode 64 frames → 存入 cache → 回傳
|
||||
2. Format Conversion (on-the-fly):
|
||||
原始 NV12 → per request:
|
||||
YOLO → RGB (float32 normalized)
|
||||
Face → RGB (uint8)
|
||||
OCR → Gray (uint8)
|
||||
Pose → RGB (uint8)
|
||||
CUT → RGB (降解析, uint8)
|
||||
```
|
||||
|
||||
### 3.4 儲存 Layout
|
||||
|
||||
```
|
||||
Disk Layout (per file_uuid):
|
||||
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ Metadata Region │
|
||||
│ - file_uuid (32 bytes) │
|
||||
│ - total_frames (u32) │
|
||||
│ - width, height (u32 × 2) │
|
||||
│ - fps (f64) │
|
||||
│ - pixel_format (u8 : 0=NV12) │
|
||||
│ - block_capacity (u32, default 64) │
|
||||
│ - block_count (u32) │
|
||||
│ - frame_size (u32, bytes per raw frame)│
|
||||
│ - block_offsets [u64 × block_count] │
|
||||
│ Padding to 4096 │
|
||||
├──────────────────────────────────────────────┤
|
||||
│ Data Region │
|
||||
│ Block 0: frames [0, 63] │
|
||||
│ frame_0: [frame_size bytes] ← page align│
|
||||
│ frame_1: [frame_size bytes] │
|
||||
│ ... │
|
||||
│ frame_63: [frame_size bytes] │
|
||||
│ Block 1: frames [64, 127] │
|
||||
│ ... │
|
||||
│ Block N: frames [N*64, ...] │
|
||||
└──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.5 記憶體管理
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
│ FMS Cache │
|
||||
├──────────┬──────────┬──────────┬───────────┤
|
||||
│ Block 0 │ Block 1 │ Block 2 │ ... │ ← mmap'd
|
||||
│ (64 fr) │ (64 fr) │ (64 fr) │ │ or anonymous
|
||||
├──────────┴──────────┴──────────┴───────────┤
|
||||
│ LRU eviction policy │
|
||||
│ max_memory = configurable │
|
||||
│ (default: 256 frames ~1.5GB @1080p NV12) │
|
||||
│ pin_count: 正在被 processor 存取的 frame │
|
||||
│ pinned frame 不參與 LRU eviction │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Worker 調度器修改
|
||||
|
||||
### 4.1 Processor 產線標記
|
||||
|
||||
```rust
|
||||
enum PipelineType {
|
||||
Frame, // frame-based
|
||||
Time, // time-based
|
||||
Cross, // needs both frame + time
|
||||
}
|
||||
|
||||
impl ProcessorType {
|
||||
fn pipeline(&self) -> PipelineType {
|
||||
match self {
|
||||
Self::Cut
|
||||
| Self::Yolo
|
||||
| Self::Face
|
||||
| Self::Ocr
|
||||
| Self::Pose => PipelineType::Frame,
|
||||
|
||||
Self::Asr | Self::Asrx => PipelineType::Time,
|
||||
|
||||
Self::Story
|
||||
| Self::Tkg
|
||||
| Self::Identity
|
||||
| Self::FiveW1h
|
||||
| Self::Caption => PipelineType::Cross,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 資源配額
|
||||
|
||||
| 產線 | Max Concurrent | 策略 |
|
||||
|------|---------------|------|
|
||||
| Frame | 2 | 最多同時 2 個 frame processor |
|
||||
| Time | 1 | 一次只跑 1 個 audio processor |
|
||||
| Cross | 1 | Frame + Time 都完成後才允許 |
|
||||
|
||||
Frame pipeline 內部建議順序:
|
||||
|
||||
```
|
||||
CUT (先確定場景邊界)
|
||||
→ YOLO / Face 可同時 (GPU-bound)
|
||||
→ OCR / Pose 可同時 (CPU/GPU mixed)
|
||||
```
|
||||
|
||||
### 4.3 產出驗證加強
|
||||
|
||||
```rust
|
||||
// 目前 (job_worker.rs:346):
|
||||
if output_path.exists() {
|
||||
mark_completed();
|
||||
}
|
||||
|
||||
// 改為:
|
||||
if output_path.exists() {
|
||||
match validate_processor_output(&output_path, processor_type) {
|
||||
Ok(true) => mark_completed(),
|
||||
Ok(false) => retry_or_fail(),
|
||||
Err(e) => mark_failed(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_processor_output(path: &Path, pt: ProcessorType) -> Result<bool> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let json: serde_json::Value = serde_json::from_str(&content)?;
|
||||
// 至少要有基本欄位
|
||||
match pt {
|
||||
ProcessorType::Asr => json.get("segments").is_some(),
|
||||
ProcessorType::Yolo => json.get("frames").is_some(),
|
||||
ProcessorType::Face => json.get("frames").is_some(),
|
||||
// ...
|
||||
};
|
||||
Ok(true) // or false
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 啟動順序
|
||||
|
||||
```
|
||||
1. Probe → 決定 frame 數、audio 格式
|
||||
2. CUT (Frame Pipeline 第一階段—決定場景邊界)
|
||||
3. Frame Pipeline 平行: YOLO / Face / OCR / Pose
|
||||
Time Pipeline 平行: ASR
|
||||
4. ASRX (依賴 ASR 結果)
|
||||
5. Lip Sync (等待 Face + ASR + ASRX)
|
||||
6. 所有 processor 完成 → 合流:
|
||||
- Rule 1 / Rule 3 Chunking
|
||||
- Face Trace / TKG
|
||||
- 5W1H / Identity Agent
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Processor 串接 FMS
|
||||
|
||||
### 5.1 FMS API
|
||||
|
||||
```rust
|
||||
// Frame access API (async)
|
||||
impl FmsClient {
|
||||
/// 取得單一 frame buffer
|
||||
async fn get_frame(
|
||||
&self,
|
||||
file_uuid: &str,
|
||||
frame_num: u32,
|
||||
format: PixelFormat,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<RawFrame>;
|
||||
|
||||
/// 取得一個 batch frames (block-aligned)
|
||||
async fn get_block(
|
||||
&self,
|
||||
file_uuid: &str,
|
||||
block_idx: u32,
|
||||
format: PixelFormat,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<FrameBlock>;
|
||||
|
||||
/// 串流 frames (lazy batch iteration)
|
||||
fn stream_frames(
|
||||
&self,
|
||||
file_uuid: &str,
|
||||
range: Range<u32>,
|
||||
format: PixelFormat,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> FrameStream;
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 YOLO 為例:processor 修改
|
||||
|
||||
```
|
||||
目前:
|
||||
processor::process_yolo(video_path, output_path, uuid)
|
||||
→ 自己開 ffmpeg decode
|
||||
→ 逐 frame 處理
|
||||
→ 寫入 yolo.json
|
||||
|
||||
改為 FMS-based:
|
||||
processor::process_yolo_with_fms(fms, uuid, output_path)
|
||||
→ let results = Vec::new()
|
||||
→ for batch in fms.stream_frames(uuid, 0..total, RGB, 640, 640):
|
||||
→ let detections = yolo_model.infer(batch)
|
||||
→ results.push(detections)
|
||||
→ write partial → yolo.partial.{batch_idx}.json
|
||||
→ merge_partials() → yolo.json
|
||||
```
|
||||
|
||||
### 5.3 Partial Result Merge
|
||||
|
||||
```
|
||||
Frame Pipeline 產出多個 partial JSON:
|
||||
|
||||
yolo.0000.json (frames 0-63)
|
||||
yolo.0001.json (frames 64-127)
|
||||
...
|
||||
yolo.0063.json (frames 4032-4095)
|
||||
|
||||
Worker 合併為單一 yolo.json:
|
||||
|
||||
```rust
|
||||
async fn merge_partials(
|
||||
uuid: &str,
|
||||
processor: &str,
|
||||
partial_dir: &Path,
|
||||
output_path: &Path,
|
||||
) -> Result<()> {
|
||||
let partials = read_sorted_partials(uuid, processor, partial_dir);
|
||||
let merged = merge_detections(partials);
|
||||
write_json(output_path, merged)
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 實作優先序
|
||||
|
||||
| 優先 | 項目 | 說明 |
|
||||
|------|------|------|
|
||||
| P0 | Worker 產線分流 | ProcessorType::pipeline() + frame_slots / time_slots 分開計算 |
|
||||
| P0 | 產出驗證加強 | `output_path.exists()` + JSON validity + schema check |
|
||||
| P1 | FMS FrameBlock 資料結構 | 含 16-byte stride / 4096 page alignment |
|
||||
| P1 | mmap-based frame cache | page-aligned frame buffers, LRU eviction |
|
||||
| P2 | FMS API 實作 | get_frame / get_block / stream_frames |
|
||||
| P2 | YOLO processor 串接 FMS | 改為 stream_frames 方式 |
|
||||
| P2 | 其他 processor 串接 FMS | Face / OCR / Pose / CUT |
|
||||
| P3 | FMS Direct I/O + sector alignment | O_DIRECT bypass OS page cache |
|
||||
| P3 | Prefetch / readahead | 預測下一個 block 並提前載入 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 注意事項
|
||||
|
||||
| # | 項目 |
|
||||
|---|------|
|
||||
| 1 | raw buffer 格式依 processor 需求轉換,FMS 負責 NV12 → RGB / Gray |
|
||||
| 2 | Time pipeline 的 ASR/ASRX **不經過 FMS**,直接處理 audio stream |
|
||||
| 3 | macOS 的 `mmap` 支援 page-aligned,但 `O_DIRECT` 需確認 compat |
|
||||
| 4 | FrameBlock size (64) 可配置,但需維持 power-of-2 |
|
||||
| 5 | FMS 只管理 frame lifecycle,不處理 processor-specific 邏輯 |
|
||||
| 6 | 多 processor 共享 frame 時,FMS 保證只 decode 一次 |
|
||||
|
||||
---
|
||||
|
||||
## 版本歷史
|
||||
|
||||
| Version | Date | Author | Changes |
|
||||
|---------|------|--------|---------|
|
||||
| 1.0 | 2026-05-23 | M5 | 初版 |
|
||||
@@ -0,0 +1,66 @@
|
||||
# Library Page: Flash & Filter Fix
|
||||
|
||||
- **Date**: 2026-05-29
|
||||
- **Author**: OpenCode
|
||||
- **Status**: Completed
|
||||
|
||||
## Summary
|
||||
|
||||
Fixed three interconnected issues on the library page (`/library/`) where video cards would flash 3 times on load, and the enhanced filter panel (size slider, duration, registered/unregistered) stopped working after flash fixes.
|
||||
|
||||
## Root Causes & Fixes
|
||||
|
||||
### Issue 1: 3x Flash on Load
|
||||
|
||||
**Root Cause**: Multiple redundant render cycles triggered by:
|
||||
|
||||
1. **`delayedPeopleFilesLoader`** (snippet 55) schedules **6x** `setTimeout(startPeopleFilesLoader, ...)` — 3 from `DOMContentLoaded`, 3 from `window 'load'`. Each creates a `setInterval` that retries `initPeopleFilesMediaLoader` every 200ms.
|
||||
|
||||
2. **`loadMediaItems`** (snippet 55) resets `root.dataset.mediaLoaded = ''` after successful load, allowing the next pending `setTimeout(startPeopleFilesLoader, 500/1200)` to trigger a second/third `loadMediaItems` call → each calls `renderItems()` → re-renders all cards.
|
||||
|
||||
3. **`bootFilterOnly()`** (snippet 58) has no guard, runs 5+ times from multiple `setTimeout(start, 300/1000/2000)` and event listeners.
|
||||
|
||||
4. **`loadMediaMeta()`** (snippet 58) had no guard, ran on every `bootFilterOnly()` call → `debouncedApply()` → `applyEnhancedFilters()` reordered cards via DOM appendChild after async completion.
|
||||
|
||||
**Fix**:
|
||||
- Snippet 55: Removed `root.dataset.mediaLoaded = ''` reset in `loadMediaItems` success path. `mediaLoaded` stays `'1'` after first successful load, preventing re-triggers.
|
||||
- Snippet 58: Removed `debouncedApply()` from `loadMediaMeta()`.
|
||||
- Snippet 58: `setGridView()` already had a class-duplicate guard.
|
||||
- Snippet 58: `renderFinderRows()` already had a skip guard.
|
||||
|
||||
### Issue 2: Filter Not Working
|
||||
|
||||
**Root Cause**: `debouncedApply()` (which calls `applyEnhancedFilters()`) was only triggered automatically from `loadMediaMeta()`. After removing it (fix #1), the filter state was never applied to cards.
|
||||
|
||||
**Fix** (snippet 58):
|
||||
- Added `applyEnhancedFilters()` to the `ltPeopleFilesFiltered` event handler (after `renderFinderRows()`).
|
||||
- Removed the `setTimeout(0)` re-dispatch loop inside `applyEnhancedFilters` that would cause infinite event chaining. Replaced with simple `isApplyingFilter = false`.
|
||||
|
||||
### Issue 3: Infinite Event Loop
|
||||
|
||||
**Root Cause**: `applyEnhancedFilters()` used `setTimeout(0)` to set `isApplyingFilter = false` and re-dispatch `ltPeopleFilesFiltered`, which would call back into the handler → `applyEnhancedFilters()` → re-dispatch → loop.
|
||||
|
||||
**Fix**: Directly set `isApplyingFilter = false` at the end of `applyEnhancedFilters()`.
|
||||
|
||||
## Files Modified
|
||||
|
||||
| Snippet | ID | Changes |
|
||||
|---------|-----|---------|
|
||||
| LT-檔案管理-註冊 | 55 | Removed `mediaLoaded = ''` reset in `loadMediaItems` success |
|
||||
| LT-檔案管理-篩選功能 | 58 | Added `applyEnhancedFilters()` to `ltPeopleFilesFiltered` handler; removed `debouncedApply()` from `loadMediaMeta`; removed re-dispatch loop in `applyEnhancedFilters` |
|
||||
|
||||
## Verification
|
||||
|
||||
- ✅ No flashes on page load (single paint)
|
||||
- ✅ Filter panel works (registered/unregistered, search, sort, sliders)
|
||||
- ✅ Video streaming works (snippet 61, curl-based proxy)
|
||||
- ✅ `cargo clippy --lib` — N/A (WordPress PHP)
|
||||
- ✅ `cargo test --lib` — N/A
|
||||
|
||||
## Context Saved At
|
||||
|
||||
- User confirmed "沒有閃了" (no more flashes) and filter working
|
||||
- AGENTS.md development boundary: WordPress snippets #55, #58, #61 (Code Snippets plugin)
|
||||
- All edits done via direct MySQL UPDATE on `wp_snippets` table
|
||||
- Working directory: `/Users/accusys/momentry_core`
|
||||
- Latest context: user asked to save handoff before changing topic
|
||||
156
docs_v1.0/M4_workspace/2026-05-29_wp_api_url_update.md
Normal file
156
docs_v1.0/M4_workspace/2026-05-29_wp_api_url_update.md
Normal file
@@ -0,0 +1,156 @@
|
||||
---
|
||||
title: WordPress API URL Update - 2026-05-29
|
||||
version: "1.0"
|
||||
date: 2026-05-29
|
||||
author: OpenCode
|
||||
status: in_progress
|
||||
---
|
||||
|
||||
# WordPress API URL Update Session
|
||||
|
||||
## Scope
|
||||
|
||||
Update WordPress Code Snippets to point momentry_core API from `m5api.momentry.ddns.net` / `api.momentry.ddns.net` to `192.168.110.201:3002` (M5Max48 LAN IP).
|
||||
|
||||
## Summary
|
||||
|
||||
| Item | Status |
|
||||
|------|--------|
|
||||
| URL update | ✅ Done |
|
||||
| `/scan` route | ✅ Working (122 files) |
|
||||
| `/search-proxy?mode=people` | ✅ Working (3788 results) |
|
||||
| `/search-proxy?mode=semantic` | ❌ Returns 0 results (direct API works with 20 results) |
|
||||
| `/search-proxy?mode=keyword` | ❌ Returns 0 results (direct API works with 21 results) |
|
||||
| Snippet #66 PHP syntax fix | ✅ Fixed (removed `.` before array keys) |
|
||||
| Added `limit/page/page_size` | ✅ Added to search bodies |
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. URL Updates
|
||||
|
||||
Changed in multiple snippets:
|
||||
|
||||
| Old URL | New URL |
|
||||
|---------|---------|
|
||||
| `https://m5api.momentry.ddns.net` | `http://192.168.110.201:3002` |
|
||||
| `https://api.momentry.ddns.net` | `http://192.168.110.201:3002` |
|
||||
| `localhost:3002` | `192.168.110.201:3002` |
|
||||
|
||||
Affected snippets: #37, #43, #44, #48, #55, #59, #60, #61, #62, #63, #64, #66, #67
|
||||
|
||||
### 2. Snippet #66 Fixes
|
||||
|
||||
**Before (syntax error)**:
|
||||
```php
|
||||
$body = [
|
||||
. 'query' => $query, // ❌ Invalid PHP syntax
|
||||
. 'limit' => 20,
|
||||
];
|
||||
```
|
||||
|
||||
**After (fixed)**:
|
||||
```php
|
||||
// Semantic search body
|
||||
$body = [
|
||||
'query' => $query,
|
||||
'limit' => 20,
|
||||
'page' => 1,
|
||||
'page_size' => 20,
|
||||
];
|
||||
|
||||
// Universal search body
|
||||
$body = [
|
||||
'query' => $query,
|
||||
'limit' => 20,
|
||||
'page' => 1,
|
||||
'page_size' => 20,
|
||||
];
|
||||
```
|
||||
|
||||
Note: `file_uuid` was NOT added per user request.
|
||||
|
||||
## Backup Location
|
||||
|
||||
```
|
||||
/Users/accusys/momentry_core/backups/wp_snippets_20260529_181847/
|
||||
```
|
||||
|
||||
Contains:
|
||||
- `wp_snippets_full.sql` - Full backup before any changes
|
||||
- `snippets_with_old_url.sql` - Snippets containing old URLs
|
||||
- `snippets_43_44_48_54_before_api_fix.sql`
|
||||
- `snippet_66_before_syntax_fix.sql`
|
||||
|
||||
## Restore Command
|
||||
|
||||
```bash
|
||||
mysql -u wp_user -p'wp_password_123' wordpress < /Users/accusys/momentry_core/backups/wp_snippets_20260529_181847/wp_snippets_full.sql
|
||||
```
|
||||
|
||||
## Pending Issue: Semantic/Keyword Search Returns Empty
|
||||
|
||||
### Symptoms
|
||||
|
||||
- Direct API call to momentry_core: Returns results
|
||||
- WP proxy call: Returns `{"results": [], "total": 0}`
|
||||
|
||||
### Direct API Test (Works)
|
||||
|
||||
```bash
|
||||
curl -s http://192.168.110.201:3002/api/v1/search/smart \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69' \
|
||||
-d '{"query":"love","limit":20,"page":1,"page_size":20}'
|
||||
# Returns 20 results
|
||||
```
|
||||
|
||||
### WP Proxy Test (Empty)
|
||||
|
||||
```bash
|
||||
curl -sk 'https://m5wp.momentry.ddns.net/wp-json/momentry/v1/search-proxy?mode=semantic&query=love'
|
||||
# Returns {"query":"love","results":[],"page":1,"page_size":20,"strategy":"semantic_vector_search"}
|
||||
```
|
||||
|
||||
### Hypothesis
|
||||
|
||||
1. WordPress `wp_remote_request` may encode JSON differently
|
||||
2. Header mismatch between WordPress and curl
|
||||
3. PHP `$body` array construction issue
|
||||
|
||||
### Debug Steps Needed
|
||||
|
||||
1. Add debug output to snippet to return the exact `$body` JSON being sent
|
||||
2. Check WordPress HTTP request logs
|
||||
3. Compare raw request payload from WordPress vs curl
|
||||
|
||||
### Temporary Workaround
|
||||
|
||||
Use people search (works) or call momentry_core directly from frontend bypassing WP proxy.
|
||||
|
||||
## Environment Context
|
||||
|
||||
| Server | IP | Port | Role |
|
||||
|--------|-----|------|------|
|
||||
| M5Max48 | 192.168.110.201 | 3002 | momentry_core production |
|
||||
| M5Max48 | 192.168.110.201 | 3003 | momentry_core playground (dev) |
|
||||
| M4mini | 192.168.110.210 | 443 | Caddy reverse proxy for WordPress |
|
||||
| WordPress | - | - | MariaDB, PHP-FPM 8.5, Code Snippets plugin |
|
||||
|
||||
## API Key
|
||||
|
||||
```
|
||||
muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69
|
||||
```
|
||||
|
||||
## Database State
|
||||
|
||||
- PostgreSQL: `momentry` database
|
||||
- `public.chunk`: 294,531 rows (has embeddings)
|
||||
- `public.videos`: 4 registered files including Charade_YouTube_24fps.mp4
|
||||
- Qdrant: `momentry_rule1` collection with embeddings
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Author | Change |
|
||||
|---------|------|--------|--------|
|
||||
| 1.0 | 2026-05-29 | OpenCode | Initial session record |
|
||||
@@ -1,7 +1,7 @@
|
||||
# Release Notes — v1.0.0 (Production 3002)
|
||||
# Release Notes — v1.0.1 (Production 3002)
|
||||
|
||||
**Date**: 2026-05-13
|
||||
**Build**: `301da08`
|
||||
**Date**: 2026-05-25
|
||||
**Build**: `de88fd4e`
|
||||
**Deployed by**: M4
|
||||
|
||||
---
|
||||
@@ -101,6 +101,36 @@
|
||||
|
||||
---
|
||||
|
||||
## Changes Since v1.0.0 (301da08 → de88fd4e)
|
||||
|
||||
### Added
|
||||
- POST `/api/v1/agents/search` — Gemma4 function calling agent
|
||||
- POST `/api/v1/identity/:uuid/bind/trace` — trace-level identity binding
|
||||
- GET `/api/v1/file/:uuid/identities/:a/co-occur-with/:b` — co-occurrence
|
||||
- GET `/api/v1/file/:uuid/trace/:tid/thumbnail` — trace thumbnail
|
||||
- GET `/api/v1/file/:uuid/trace/:tid/representative-face` — representative face
|
||||
- PATCH `/api/v1/identity/:identity_uuid` — identity update + alias system
|
||||
- TKG extension: pose data + mutual gaze detection
|
||||
- `/health/consistency` — data consistency check
|
||||
- Config endpoints: cache toggle, auto-pipeline, watcher-auto-register
|
||||
- Representative frame auto-detection
|
||||
|
||||
### Fixed
|
||||
- System consistency: store_vector, search, worker trigger reliability
|
||||
- trigger_processing: remove fake QUEUED state, create monitor_job if missing
|
||||
- stranger_id set to NULL on bind/merge operations
|
||||
- frame_number type: i32→i64 to match BIGINT schema
|
||||
- Compilation errors: restored PipelineType enum, pipeline() method, constants
|
||||
|
||||
### Changed
|
||||
- Unified LLM config: CHAT_URL/VISION_URL/SUMMARY_URL with env var overrides
|
||||
- Port config centralized (8082 conflict resolved)
|
||||
- Resources API returns data (config+metadata)
|
||||
- server.rs split into modular route files
|
||||
- API Reference: 55 endpoints, removed phantom routes, fixed methods, renamed endpoints
|
||||
|
||||
---
|
||||
|
||||
## Known Notes
|
||||
|
||||
| Item | Note |
|
||||
|
||||
@@ -6,13 +6,135 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- Compilation errors: restore PipelineType enum, pipeline() method, OLLAMA_URL/EMBED_URL/LLM_HEALTH_URL constants
|
||||
- frame_number type: changed i32→i64 to match BIGINT schema
|
||||
|
||||
## [1.0.1] - 2026-05-25
|
||||
|
||||
### Fixed
|
||||
- System consistency: store_vector, search, worker trigger reliability
|
||||
- trigger_processing: remove fake QUEUED state, create monitor_job if missing
|
||||
- stranger_id set to NULL on bind/merge operations
|
||||
- resource path cleanup
|
||||
|
||||
### Added
|
||||
- Gitea API token integration
|
||||
- n8n API key integration
|
||||
- API key caching with Moka
|
||||
- Rate limiting for API key validation
|
||||
- Constant-time hash comparison
|
||||
- OpenAPI documentation with utoipa
|
||||
- Frame/time pipeline split with output validation
|
||||
- `/api/v1/agents/search` — Gemma4 function calling agent
|
||||
- `/api/v1/identity/:uuid/bind/trace` — trace-level identity binding
|
||||
- `/api/v1/file/:uuid/identities/:a/co-occur-with/:b` — co-occurrence endpoint
|
||||
- `/api/v1/file/:uuid/trace/:tid/thumbnail` — trace thumbnail
|
||||
- `/api/v1/file/:uuid/trace/:tid/representative-face` — representative face
|
||||
- identity PATCH update, alias system, name UNIQUE removal
|
||||
- TKG extension: pose data + mutual gaze detection
|
||||
|
||||
### Changed
|
||||
- Unified LLM config: CHAT_URL/VISION_URL/SUMMARY_URL with env var overrides
|
||||
- Refactored server.rs into modular route files
|
||||
- Port config centralized (8082 conflict resolved)
|
||||
- Resources API returns data (config+metadata); register source code resource
|
||||
|
||||
## [1.0.0] - 2026-05-14
|
||||
|
||||
### Added
|
||||
- Release version v1.0.0 tag
|
||||
- Production binary at port 3002
|
||||
- Playground binary at port 3003
|
||||
- Dedicated Gitea sync pipeline
|
||||
|
||||
### Changed
|
||||
- Full V4.0 API reference published (55 endpoints)
|
||||
- Health APIs include build_timestamp + resources + pipeline
|
||||
- Qdrant refactored to use i64 for frame_number
|
||||
- Rust edition 2021, max_width=100, tab_spaces=4
|
||||
|
||||
## [0.6.0] - 2026-05-01
|
||||
|
||||
### Added
|
||||
- `/api/v1/agents/translate` — AI text translation
|
||||
- `/api/v1/agents/5w1h/batch` — batch analysis
|
||||
- `/api/v1/agents/identity/match-from-photo` — face matching from photo
|
||||
- `/api/v1/agents/identity/match-from-trace` — face matching from trace
|
||||
- `/api/v1/agents/suggest/merge` — merge suggestions
|
||||
- `/api/v1/agents/suggest/clustering` — re-clustering suggestions
|
||||
- `/api/v1/search/visual` endpoints (class, density, combination, stats)
|
||||
- `/api/v1/search/frames` — frame-level search
|
||||
- `/api/v1/faces/candidates` — unbound face gallery
|
||||
- `/api/v1/identities/search` — identity name search
|
||||
- `/api/v1/search/identity_text` — full-text identity-bound chunk search
|
||||
- `/health/consistency` — data consistency check
|
||||
- Config endpoints: auto-pipeline toggle, watcher-auto-register toggle
|
||||
- Cache toggle via `/api/v1/config/cache`
|
||||
|
||||
### Fixed
|
||||
- Various type mismatches in DB layer (i64/i32 alignment)
|
||||
- Trace debug mode bbox rendering
|
||||
- Interpolation for sparse face detections
|
||||
|
||||
## [0.5.0] - 2026-04-15
|
||||
|
||||
### Added
|
||||
- `/api/v1/file/:file_uuid/traces` — trace listing (replaces face_trace/sortby)
|
||||
- `/api/v1/file/:file_uuid/trace/:trace_id/faces` — trace detections with interpolation
|
||||
- `/api/v1/file/:file_uuid/video/bbox` — bbox overlay video
|
||||
- `/api/v1/file/:file_uuid/trace/:trace_id/video` — trace clip (normal/debug mode)
|
||||
- File probe endpoint (`/api/v1/file/:file_uuid/probe`)
|
||||
- Jobs monitoring (`/api/v1/jobs`)
|
||||
- Processing progress (`/api/v1/progress/:file_uuid`)
|
||||
- Chunk detail (`/api/v1/file/:file_uuid/chunk/:chunk_id`)
|
||||
- `/api/v1/unregister` — file unregistration by uuid or pattern
|
||||
|
||||
### Changed
|
||||
- face_trace/sortby renamed to `/api/v1/file/:file_uuid/traces`
|
||||
- Chunk/progress/jobs endpoints changed from GET to POST
|
||||
- Qdrant MongoDB dependency replaced with Rust-native driver
|
||||
- Redis upgraded to 1.0.x for performance
|
||||
|
||||
## [0.4.0] - 2026-04-01
|
||||
|
||||
### Added
|
||||
- Identity management: register, detail, delete, files, chunks
|
||||
- Identity binding: bind/unbind face → identity
|
||||
- Identity merging: mergeinto endpoint
|
||||
- Resource management: register, heartbeat, list
|
||||
- Docker health check integration
|
||||
- SFTPGo status endpoint
|
||||
- Pagination support for list endpoints
|
||||
|
||||
### Changed
|
||||
- Database schema: `person_identities` table removed
|
||||
- N:N relationship via `file_identities` table
|
||||
- Architecture shift: Face → Identity (two-layer direct binding)
|
||||
- Terminology: `video_uuid` → `file_uuid` across all APIs
|
||||
|
||||
## [0.3.0] - 2026-03-25
|
||||
|
||||
### Added
|
||||
- `/api/v1/search/universal` — BM25 keyword search
|
||||
- `/api/v1/search/smart` — semantic search (pgvector)
|
||||
- `/api/v1/files/register` — file registration
|
||||
- `/api/v1/files/lookup` — name conflict check
|
||||
- `/api/v1/files/scan` — directory scan
|
||||
- File listing and detail endpoints
|
||||
- POST `/api/v1/file/:file_uuid/process` — processing pipeline trigger
|
||||
- `/api/v1/auth/login` and `/api/v1/auth/logout`
|
||||
- `/health/detailed` — per-service health check
|
||||
|
||||
### Changed
|
||||
- Video processing pipeline refactored (ASR, OCR, YOLO, Face, Pose)
|
||||
- Centralized Python executor with timeout support
|
||||
- Logging unified under `logs/` directory
|
||||
- Startup scripts auto-build and kill old processes
|
||||
|
||||
## [0.2.0] - 2026-03-22
|
||||
|
||||
### Added
|
||||
- `/health` endpoint with status/build info
|
||||
- Video stream endpoint (`/api/v1/file/:file_uuid/video`)
|
||||
- Thumbnail extraction endpoint
|
||||
- FaceNet CoreML model integration
|
||||
- Basic API key authentication
|
||||
- PostgreSQL, MongoDB, Redis, Qdrant service initialization
|
||||
|
||||
## [0.1.0] - 2026-03-21
|
||||
|
||||
@@ -140,4 +262,11 @@ N8N_URL=https://n8n.momentry.ddns.net
|
||||
|
||||
| Version | Date | Description |
|
||||
|---------|------|-------------|
|
||||
| 1.0.1 | 2026-05-25 | Bug fixes, agent endpoints, identity alias system, TKG extension |
|
||||
| 1.0.0 | 2026-05-14 | Release v1.0.0, V4.0 API reference, production binary |
|
||||
| 0.6.0 | 2026-05-01 | Agent endpoints (translate, 5w1h, identity match), search visual endpoints |
|
||||
| 0.5.0 | 2026-04-15 | Traces API, media endpoints, jobs/progress/chunk, rename face_trace→traces |
|
||||
| 0.4.0 | 2026-04-01 | Identity management, resource API, video_uuid→file_uuid rename |
|
||||
| 0.3.0 | 2026-03-25 | Search (BM25/semantic), file registration, health check, auth |
|
||||
| 0.2.0 | 2026-03-22 | Health endpoint, video stream, thumbnail, FaceNet model, DB init |
|
||||
| 0.1.0 | 2026-03-21 | Initial release with API Key Management |
|
||||
|
||||
@@ -234,6 +234,11 @@ Bind a face detection to an identity. Associates the face trace with the identit
|
||||
| `file_uuid` | string | Yes | File where face is detected |
|
||||
| `face_id` | string | Yes | Face ID (format: `{frame}_{idx}`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 清除該 face detection row 的 `stranger_id`(設為 NULL)
|
||||
- 不影響 `identities` 表中原有的 stranger auto-identity 記錄
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
@@ -259,6 +264,11 @@ Bind all face detections of a trace to an identity. Updates all rows in `face_de
|
||||
| `file_uuid` | string | Yes | File where trace exists |
|
||||
| `trace_id` | integer | Yes | Trace ID (from `face_detections.trace_id`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 清除該 trace 所有 face detection rows 的 `stranger_id`(設為 NULL)
|
||||
- 不影響 `identities` 表中原有的 stranger auto-identity 記錄
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
@@ -287,6 +297,78 @@ curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/bind/trace" \
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/identity/:identity_uuid/traces`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: identity-level
|
||||
|
||||
Get paginated face traces (continuous tracking segments) associated with this identity across all files.
|
||||
|
||||
#### Query Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `page` | integer | No | `1` | Page number |
|
||||
| `page_size` | integer | No | `20` | Items per page |
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s "$API/api/v1/identity/$IDENTITY_UUID/traces?page=1&page_size=3" \
|
||||
-H "X-API-Key: $KEY" | jq '{total, total_faces, traces}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"identity_uuid": "a9a901056d6b46ff92da0c3c1a57dff4",
|
||||
"name": "Cary Grant",
|
||||
"total": 18,
|
||||
"page": 1,
|
||||
"page_size": 3,
|
||||
"total_faces": 542,
|
||||
"traces": [
|
||||
{
|
||||
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692",
|
||||
"trace_id": 906,
|
||||
"frame_count": 52,
|
||||
"first_frame": 37974,
|
||||
"last_frame": 38127,
|
||||
"first_sec": 1519.0,
|
||||
"last_sec": 1525.1,
|
||||
"avg_confidence": 0.8254
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `success` | bool | Always `true` |
|
||||
| `identity_uuid` | string | Identity UUID |
|
||||
| `name` | string | Identity display name |
|
||||
| `total` | integer | Total number of traces (across all pages) |
|
||||
| `total_faces` | integer | Sum of all face detections in returned traces |
|
||||
| `traces[].file_uuid` | string | File where trace exists |
|
||||
| `traces[].trace_id` | integer | Trace tracking ID |
|
||||
| `traces[].frame_count` | integer | Number of frames in this trace |
|
||||
| `traces[].first_frame` | integer | Start frame number |
|
||||
| `traces[].last_frame` | integer | End frame number |
|
||||
| `traces[].first_sec` | float | Start time in seconds |
|
||||
| `traces[].last_sec` | float | End time in seconds |
|
||||
| `traces[].avg_confidence` | float | Average detection confidence (0.0–1.0) |
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `404` | Identity not found |
|
||||
| `500` | Database error |
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/identity/:identity_uuid/unbind`
|
||||
|
||||
**Auth**: Required
|
||||
@@ -294,6 +376,61 @@ curl -s -X POST "$API/api/v1/identity/$IDENTITY_UUID/bind/trace" \
|
||||
|
||||
Unbind a face detection from an identity. Removes the identity association from the face record.
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 只清除 `identity_id`(設為 NULL),**不會恢復 `stranger_id`**
|
||||
- 被 unbind 的 face 不會自動成為 stranger
|
||||
- 要重新標記為 stranger 需重新跑 Agent API(`identity/analyze`)
|
||||
|
||||
---
|
||||
|
||||
### `POST /api/v1/identity/:identity_uuid/mergeinto`
|
||||
|
||||
**Auth**: Required
|
||||
**Scope**: identity-level
|
||||
|
||||
Transfer all face bindings from this identity to another identity, then optionally delete or mark the source as merged.
|
||||
|
||||
#### Request Parameters
|
||||
|
||||
| Field | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `into_uuid` | string | Yes | — | Target identity UUID to merge into |
|
||||
| `keep_history` | bool | No | `true` | Keep source identity record with `status='merged'` (`true`) or delete it (`false`) |
|
||||
|
||||
#### Side Effects
|
||||
|
||||
- 轉移所有 `face_detections.identity_id` 到目標 identity
|
||||
- 同時清除所有被轉移 rows 的 `stranger_id`
|
||||
- `keep_history: true`(預設):source identity 設為 `status='merged'`,保留記錄
|
||||
- `keep_history: false`:**刪除** source identity 及其 identity JSON 檔案
|
||||
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$API/api/v1/identity/$SOURCE_UUID/mergeinto" \
|
||||
-H "X-API-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"into_uuid": "'"$TARGET_UUID"'", "keep_history": false}'
|
||||
```
|
||||
|
||||
#### Response (200)
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Merged 'stranger_13894' into 'Louis Viret' (52 faces transferred, source deleted)",
|
||||
"data": { "faces_transferred": 52 }
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Responses
|
||||
|
||||
| HTTP | When |
|
||||
|------|------|
|
||||
| `404` | Source or target identity not found |
|
||||
| `500` | Database error |
|
||||
|
||||
---
|
||||
|
||||
### `GET /api/v1/identities/search`
|
||||
@@ -491,4 +628,4 @@ PATCH /api/v1/identity/:identity_uuid
|
||||
This **replaces** the entire `aliases` array. To add to existing aliases, include all existing entries in the request.
|
||||
|
||||
---
|
||||
*Updated: 2026-05-22
|
||||
*Updated: 2026-05-25
|
||||
|
||||
34
momentry_runtime/plist/com.momentry.playground.plist
Normal file
34
momentry_runtime/plist/com.momentry.playground.plist
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.momentry.playground</string>
|
||||
|
||||
<key>UserName</key>
|
||||
<string>accusys</string>
|
||||
|
||||
<key>GroupName</key>
|
||||
<string>staff</string>
|
||||
|
||||
<key>WorkingDirectory</key>
|
||||
<string>/Users/accusys/momentry_core</string>
|
||||
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Users/accusys/momentry_core/scripts/wrapper_playground.sh</string>
|
||||
</array>
|
||||
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
|
||||
<key>StandardOutPath</key>
|
||||
<string>/Users/accusys/momentry/log/playground.log</string>
|
||||
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/Users/accusys/momentry/log/playground.error.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -174,8 +174,8 @@ class SelfASRXFixed:
|
||||
wav = np.mean(wav, axis=1) # 轉 mono
|
||||
print(f" Audio loaded: {len(wav)/sample_rate:.2f}s, {sample_rate}Hz")
|
||||
|
||||
# 使用 ASR segments 取代 VAD
|
||||
speech_segments = [(s["start"], s["end"]) for s in asr_segments]
|
||||
# 使用 ASR segments 取代 VAD (audio处理用time)
|
||||
speech_segments = [(s["start_time"], s["end_time"]) for s in asr_segments]
|
||||
print(f" Speech segments from ASR: {len(speech_segments)}")
|
||||
|
||||
if len(speech_segments) == 0:
|
||||
|
||||
@@ -86,7 +86,9 @@ class RedisPublisher:
|
||||
|
||||
try:
|
||||
client: redis.Redis = self._client
|
||||
client.publish(self.channel, json.dumps(asdict(msg)))
|
||||
json_data = json.dumps(asdict(msg))
|
||||
client.publish(self.channel, json_data)
|
||||
client.hset(self.channel, msg.processor, json_data)
|
||||
return True
|
||||
except Exception as e:
|
||||
import sys
|
||||
|
||||
@@ -25,7 +25,7 @@ echo ""
|
||||
LOG_DIR="/Users/accusys/momentry/logs"
|
||||
|
||||
# ── 1. PostgreSQL ──
|
||||
echo -e "${YELLOW}[1/7] PostgreSQL${NC}"
|
||||
echo -e "${YELLOW}[1/8] PostgreSQL${NC}"
|
||||
PG_DATA="/Users/accusys/pgsql/data"
|
||||
PG_BIN="/Users/accusys/pgsql/18.3/bin"
|
||||
if $PG_BIN/pg_isready -q 2>/dev/null; then
|
||||
@@ -37,7 +37,7 @@ else
|
||||
fi
|
||||
|
||||
# ── 2. Redis ──
|
||||
echo -e "${YELLOW}[2/7] Redis${NC}"
|
||||
echo -e "${YELLOW}[2/8] Redis${NC}"
|
||||
if redis-cli ping 2>/dev/null | grep -q PONG; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
@@ -47,14 +47,14 @@ else
|
||||
fi
|
||||
|
||||
# ── 3. Qdrant ──
|
||||
echo -e "${YELLOW}[3/7] Qdrant${NC}"
|
||||
QDRANT_BIN="${PROJECT_DIR}/services/qdrant/target/release/qdrant"
|
||||
echo -e "${YELLOW}[3/8] Qdrant${NC}"
|
||||
QDRANT_BIN="/Users/accusys/momentry_resources/bin/qdrant"
|
||||
QDRANT_STORAGE="/Users/accusys/momentry/qdrant_storage"
|
||||
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 http://localhost:6333/healthz 2>/dev/null | grep -q 200; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
mkdir -p "$QDRANT_STORAGE"
|
||||
nohup "$QDRANT_BIN" > "$LOG_DIR/qdrant.log" 2>&1 &
|
||||
"$QDRANT_BIN" > "$LOG_DIR/qdrant.log" 2>&1 &
|
||||
for i in $(seq 1 15); do
|
||||
sleep 2
|
||||
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 http://localhost:6333/healthz 2>/dev/null | grep -q 200; then
|
||||
@@ -65,7 +65,7 @@ else
|
||||
fi
|
||||
|
||||
# ── 4. Qdrant Collection ──
|
||||
echo -e "${YELLOW}[4/7] Qdrant Collection${NC}"
|
||||
echo -e "${YELLOW}[4/8] Qdrant Collection${NC}"
|
||||
source "$ENV_FILE" 2>/dev/null || true
|
||||
COLLECTION="${QDRANT_COLLECTION:-momentry_dev_rule1_v2}"
|
||||
EXISTS=$(curl -s "http://localhost:6333/collections/$COLLECTION" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result',{}).get('status','not_found'))" 2>/dev/null)
|
||||
@@ -79,13 +79,13 @@ curl -s "http://localhost:6333/collections/$COLLECTION" 2>/dev/null | python3 -c
|
||||
check "collection '$COLLECTION' ready"
|
||||
|
||||
# ── 5. LLM (Gemma4 / llama.cpp) ──
|
||||
echo -e "${YELLOW}[5/7] LLM Server (Gemma4)${NC}"
|
||||
echo -e "${YELLOW}[5/8] LLM Server (Gemma4)${NC}"
|
||||
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://localhost:8082/health 2>/dev/null | grep -q 200; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
LLM_BIN="/Users/accusys/llama/bin/llama-server"
|
||||
LLM_MODEL="/Users/accusys/models/google_gemma-4-26B-A4B-it-Q5_K_M.gguf"
|
||||
nohup "$LLM_BIN" -m "$LLM_MODEL" --host 0.0.0.0 --port 8082 -ngl 99 -c 16384 --temp 0.1 --mlock --reasoning off > "$LOG_DIR/llama_server.log" 2>&1 &
|
||||
LLM_BIN="/Users/accusys/momentry_resources/llama/bin/llama-server"
|
||||
LLM_MODEL="/Users/accusys/momentry/models/llm/google_gemma-4-26B-A4B-it-Q5_K_M.gguf"
|
||||
"$LLM_BIN" -m "$LLM_MODEL" --host 0.0.0.0 --port 8082 -ngl 99 -c 16384 --temp 0.1 --mlock --reasoning off > "$LOG_DIR/llama_server.log" 2>&1 &
|
||||
echo -e " ${YELLOW}⏳ loading model (~30s)...${NC}"
|
||||
for i in $(seq 1 30); do
|
||||
sleep 2
|
||||
@@ -97,7 +97,7 @@ else
|
||||
fi
|
||||
|
||||
# ── 6. Embedding Server ──
|
||||
echo -e "${YELLOW}[6/7] EmbeddingGemma${NC}"
|
||||
echo -e "${YELLOW}[6/8] EmbeddingGemma${NC}"
|
||||
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://localhost:11436/health 2>/dev/null | grep -q 200; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
@@ -107,22 +107,37 @@ else
|
||||
VENV_PYTHON="/opt/homebrew/bin/python3.11"
|
||||
pip install flask 2>/dev/null || true
|
||||
fi
|
||||
nohup "$VENV_PYTHON" "$EMBED_SCRIPT" --port 11436 > "$LOG_DIR/embed.log" 2>&1 &
|
||||
"$VENV_PYTHON" "$EMBED_SCRIPT" --port 11436 > "$LOG_DIR/embed.log" 2>&1 &
|
||||
sleep 5
|
||||
curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://localhost:11436/health 2>/dev/null | grep -q 200; check "started"
|
||||
fi
|
||||
|
||||
# ── 7. Playground Server ──
|
||||
echo -e "${YELLOW}[7/7] Playground API Server${NC}"
|
||||
echo -e "${YELLOW}[7/8] Playground API Server${NC}"
|
||||
if curl -s -o /dev/null -w "%{http_code}" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" --connect-timeout 5 http://127.0.0.1:3003/api/v1/agents/5w1h/status 2>/dev/null | grep -q 200; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
cd "$PROJECT_DIR"
|
||||
nohup target/debug/momentry_playground server > "$LOG_DIR/playground.log" 2>&1 &
|
||||
target/debug/momentry_playground server > "$LOG_DIR/playground.log" 2>&1 &
|
||||
sleep 4
|
||||
curl -s -o /dev/null -w "%{http_code}" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" --connect-timeout 5 http://127.0.0.1:3003/api/v1/agents/5w1h/status 2>/dev/null | grep -q 200; check "started"
|
||||
fi
|
||||
|
||||
# ── 8. Ollama (Gemma4 E4B) ──
|
||||
echo -e "${YELLOW}[8/8] Ollama (Gemma4 E4B)${NC}"
|
||||
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://localhost:11434/api/tags 2>/dev/null | grep -q 200; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
OLLAMA_BIN="/Users/accusys/momentry_resources/bin/ollama"
|
||||
if [ ! -f "$OLLAMA_BIN" ]; then
|
||||
echo -e " ${YELLOW}⚠ ollama binary not found, skipping${NC}"
|
||||
else
|
||||
"$OLLAMA_BIN" serve > "$LOG_DIR/ollama.log" 2>&1 &
|
||||
sleep 3
|
||||
curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://localhost:11434/api/tags 2>/dev/null | grep -q 200; check "started"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ ${#FAILURES[@]} -eq 0 ]; then
|
||||
echo -e "${GREEN}====================================${NC}"
|
||||
@@ -138,6 +153,7 @@ echo ""
|
||||
echo " Playground: http://127.0.0.1:3003"
|
||||
echo " LLM: http://127.0.0.1:8082"
|
||||
echo " Embedding: http://127.0.0.1:11436"
|
||||
echo " Ollama: http://localhost:11434"
|
||||
echo " Qdrant: http://localhost:6333"
|
||||
echo " PostgreSQL: localhost:5432"
|
||||
echo " Redis: localhost:6379"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
17505
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user