feat: backup architecture docs, source code, and scripts

This commit is contained in:
Warren
2026-04-25 17:15:45 +08:00
parent 59809dae1f
commit 1f84e5469f
368 changed files with 146329 additions and 261 deletions

View File

@@ -0,0 +1,198 @@
# 3002 vs 3003 API 比較報告
> **日期**: 2026-04-14 | **目的**: 分析正式版與開發版 API 差異
---
## 1. 版本差異
| 項目 | 3002 (正式版) | 3003 (開發版) |
|------|:---:|:---:|
| **Build 日期** | 2026-04-13 00:29:25 | 2026-04-14 23:01:47 |
| **程式碼狀態** | ❌ 舊版,缺少新 API | ✅ 最新版,包含所有新功能 |
| **需要重新編譯** | ✅ 是 | ❌ 否 |
---
## 2. API 端點可用性比較
### 2.1 完全相同的端點JSON 結構一致)
| 端點 | 3002 | 3003 | JSON 結構 |
|------|:---:|:---:|:---:|
| `GET /health` | ✅ 200 | ✅ 200 | ✅ 一致 |
| `GET /api/v1/person/:id` | ✅ 200 | ✅ 200 | ✅ 一致 |
| `GET /api/v1/chunks/:id/persons` | ✅ 200 | ✅ 200 | ✅ 一致 |
| `GET /api/v1/face/list` | ✅ 200 | ✅ 200 | ✅ 一致 |
| `GET /api/v1/face/:id` | ✅ 200 | ✅ 200 | ✅ 一致 |
| `POST /api/v1/face/search` | ✅ 200 | ✅ 200 | ✅ 一致 |
### 2.2 3002 缺少的端點
| 端點 | 3002 | 3003 | 新增日期 |
|------|:---:|:---:|:---:|
| `GET /api/v1/person/list` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `POST /api/v1/person/auto-identify` | ❌ 405 | ✅ 200 | 2026-04-14 |
| `POST /api/v1/person/suggest` | ❌ 405 | ✅ 200 | 2026-04-14 |
| `POST /api/v1/person/merge` | ❌ 405 | ✅ 200/400 | 2026-04-14 |
| `POST /api/v1/person/merge/undo` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `GET /api/v1/person/merge/history` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `GET /api/v1/person/:id/similar` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `PATCH /api/v1/person/:id/confirm` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `POST /api/v1/person/:id/unbind-speaker` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `POST /api/v1/person/:id/reassign-speaker` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `POST /api/v1/person/:id/remove-appearance` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `POST /api/v1/person/:id/reassign-appearance` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `POST /api/v1/person/:id/split` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `POST /api/v1/search/universal` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `POST /api/v1/search/frames` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `GET /api/v1/search/persons` | ❌ 404 | ✅ 200 | 2026-04-14 |
| `GET /api/v1/person/:id/thumbnail` | ❌ 404 | ✅ 200 | 2026-04-14 |
---
## 3. JSON 回應結構比較
### 3.1 GET /api/v1/person/:id (兩者一致)
```json
{
"success": "bool",
"person_id": "str",
"name": "str",
"face_identity_id": "null",
"speaker_id": "str|null",
"confidence": "float",
"appearance_count": "int",
"total_appearance_duration": "float",
"first_appearance_time": "float",
"last_appearance_time": "float",
"is_confirmed": "bool",
"created_at": "str",
"updated_at": "str"
}
```
### 3.2 3003 獨有的 JSON 結構
#### POST /api/v1/person/auto-identify
```json
{
"success": "bool",
"message": "str",
"total_persons": "int",
"matched_speakers": "int",
"persons": [{
"person_id": "str",
"name": "str|null",
"speaker_id": "str|null",
"appearance_count": "int",
"total_appearance_duration": "float",
"first_appearance_time": "float|null",
"last_appearance_time": "float|null",
"is_confirmed": "bool",
"speaker_confidence": "float|null"
}]
}
```
#### POST /api/v1/person/suggest
```json
{
"success": "bool",
"naming_suggestions": [{
"person_id": "str",
"current_name": "str|null",
"suggested_name": "str",
"confidence": "float",
"sources": [{"type": "str", "detail": "str", "weight": "float"}],
"action": "str (auto_apply|needs_review)"
}],
"merge_suggestions": [{
"person_id": "str",
"merge_with": ["str"],
"confidence": "float",
"reasons": ["str"],
"action": "str"
}],
"total_naming": "int",
"total_merge": "int"
}
```
#### POST /api/v1/search/universal
```json
{
"query": "str",
"results": [{
"type": "str (chunk|frame|person)",
// chunk 類型
"chunk_id": "str",
"chunk_type": "str",
"start_time": "float",
"end_time": "float",
"score": "float",
"text": "str|null",
"speaker_id": "str|null",
"metadata": "object"
// 或 frame 類型
// 或 person 類型
}],
"total": "int",
"took_ms": "int"
}
```
---
## 4. 資料來源隔離
| 層級 | 3002 | 3003 |
|------|------|------|
| PostgreSQL Schema | `public` | `dev` |
| MongoDB Database | `momentry` | `momentry_dev` |
| Redis Prefix | `momentry:` | `momentry_dev:` |
| Qdrant Collection | `momentry_rule1` | `momentry_dev_rule1` |
| Output Dir | `/Users/accusys/momentry/output` | `/Users/accusys/momentry/output_dev` |
---
## 5. 部署建議
### 3002 需要更新
3002 運行的是 **2026-04-13** 的舊版程式碼,缺少 **17 個新 API 端點**
**更新步驟**:
```bash
# 1. 確認程式碼已是最新
git status
# 2. 重新編譯
cargo build --release
# 3. 停止舊版
kill $(lsof -ti:3002)
# 4. 啟動新版
nohup cargo run --release --bin momentry -- server > /tmp/momentry_prod.log 2>&1 &
# 5. 驗證
curl http://localhost:3002/test-schema
# 應回傳: SCHEMA=public
curl http://localhost:3002/api/v1/person/list?limit=1
# 應回傳: {"success":true,"persons":[...], "total":N}
```
---
## 6. 總結
| 類別 | 狀態 |
|------|------|
| **程式碼版本** | 3002 落後 1 天 (2026-04-13 vs 2026-04-14) |
| **API 端點** | 3002 缺少 17 個新端點 |
| **JSON 結構** | 相同端點的 JSON 結構完全一致 |
| **資料隔離** | ✅ 完全獨立 (public vs dev) |
| **需要行動** | ✅ 3002 需要重新編譯部署 |

View File

@@ -0,0 +1,230 @@
# Momentry Core API 存取指南
| 項目 | 內容 |
|------|------|
| 版本 | V1.3 |
| 日期 | 2026-03-25 |
| 用途 | API 存取方式、端點與整合指南 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.3 | 2026-03-25 | 更新: n8n 搜尋回傳 `file_path` 取代 `media_url`,新增 API Key 驗證說明 | OpenCode | deepseek-reasoner |
| V1.2 | 2026-03-24 | 更新網址與服務列表 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-23 | 初始版本 | Warren | OpenCode / MiniMax M2.5 |
---
## 基本網址
| 環境 | URL | 說明 |
|------|-----|------|
| **本地開發** | `http://localhost:3002` | 直接訪問 API繞過反向代理 |
| **外部訪問** | `https://api.momentry.ddns.net` | 通過 Caddy 反向代理訪問,需網路可達 |
### 何時使用哪個 URL
**使用 `localhost:3002`**
- 開發/測試環境
- 直接在伺服器上操作
- 當反向代理有問題時
**使用 `api.momentry.ddns.net`**
- n8n workflow 中呼叫 API
- 外部系統整合
- 生產環境
## 認證
所有 `/api/v1/*` 端點(除了健康檢查 `/health``/health/detailed`)都需要 API Key 認證。
請在請求標頭中加入:
```
X-API-Key: YOUR_API_KEY
```
**目前示範使用的 API Key**: `demo_api_key_12345`
> **注意**: 正式環境請使用安全的 API Key 管理機制,避免在客戶端暴露 API Key。
---
## 影片搜尋 API
### 語意搜尋
**端點:** `POST /api/v1/search`
**請求:**
```json
{
"query": "charade",
"limit": 5,
"uuid": "a1b10138a6bbb0cd"
}
```
| 欄位 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `query` | 字串 | 是 | 搜尋文字 |
| `limit` | 整數 | 否 | 最大回傳結果數(預設 10 |
| `uuid` | 字串 | 否 | 依影片 UUID 過濾 |
**回應:**
```json
{
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "sentence_0006",
"chunk_type": "sentence",
"start_time": 48.8,
"end_time": 55.44,
"text": "fun plot twists, Woody Dialog and charming performances...",
"score": 0.526
}
],
"query": "charade"
}
```
---
### n8n 整合搜尋
**端點:** `POST /api/v1/n8n/search`
**請求:**
```json
{
"query": "charade",
"limit": 5
}
```
**回應:**
```json
{
"query": "charade",
"count": 5,
"hits": [
{
"id": "sentence_0006",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0006",
"text": "fun plot twists...",
"score": 0.526,
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
]
}
```
> **注意**: API 現在返回 `file_path`(檔案系統路徑)而非 `media_url`(網頁 URL。如需在網頁中播放影片請將檔案路徑轉換為可訪問的 URL例如透過 SFTPGo 分享連結)。
---
## 影片管理 API
### 列出所有影片
**端點:** `GET /api/v1/videos`
### 查詢影片資訊
**端點:** `GET /api/v1/lookup?uuid={uuid}``GET /api/v1/lookup?path={path}`
### 取得處理進度
**端點:** `GET /api/v1/progress/{uuid}`
---
## 區塊資料結構
每個搜尋結果包含影片播放的時間資訊:
| 欄位 | 說明 |
|------|------|
| `uuid` | 影片識別碼 |
| `chunk_id` | 區塊唯一識別碼 |
| `chunk_type` | 類型:`sentence``cut``time_based` |
| `start_time` | 開始時間(秒) |
| `end_time` | 結束時間(秒) |
| `text` | 語音轉文字內容 |
| `score` | 相關性分數0-1 |
---
## 整合範例
### JavaScript/fetch
```javascript
const response = await fetch('http://localhost:3002/api/v1/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'YOUR_API_KEY' // 替換為實際的 API Key
},
body: JSON.stringify({ query: 'charade', limit: 5 })
});
const data = await response.json();
console.log(data.results);
```
### PHP/cURL
```php
$ch = curl_init('http://localhost:3002/api/v1/search');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'query' => 'charade',
'limit' => 5
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-API-Key: YOUR_API_KEY' // 替換為實際的 API Key
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
```
---
## 影片嵌入網址
> **重要**: API 現在返回 `file_path`(檔案系統路徑),而非直接可訪問的網址。您需要將檔案路徑轉換為 SFTPGo 分享連結才能嵌入影片。
**檔案路徑轉換為網址:**
- API 返回的 `file_path` 範例:`/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4`
- 對應的 SFTPGo 分享連結:`https://wp.momentry.ddns.net/demo/video.mp4`
- 轉換方式:移除 `/Users/accusys/momentry/var/sftpgo/data/` 前綴,將剩餘路徑附加到 `https://wp.momentry.ddns.net/`
**手動建立分享連結:**
1. 開啟 SFTPGo Web UI`http://localhost:8080`
2. 使用帳號 `demo` / 密碼 `demopassword123` 登入
3. 導航至 `Files` → 選擇影片檔案
4. 點擊 `Share``Create Link`
5. 複製產生的分享連結
使用搜尋結果中的 `start_time``end_time` 來嵌入影片片段。
---
## 服務列表
| 服務 | 網址 | 用途 |
|------|------|------|
| Momentry API | `http://localhost:3002` | 核心 API |
| SFTPGo | `http://localhost:8080` | 檔案儲存 |
| Qdrant | `http://localhost:6333` | 向量搜尋 |
| PostgreSQL | `localhost:5432` | 資料庫 |
---
## 示範影片
- **檔案:** `Old_Time_Movie_Show_-_Charade_1963.HD.mov`
- **UUID** `a1b10138a6bbb0cd`
- **長度:** 約 6879 秒(約 1.9 小時)
- **區塊數:** 3886 個(句子 + 場景 + 時間)

View File

@@ -0,0 +1,321 @@
# Momentry Core API 端點總覽
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
| 文件版本 | V1.3 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 |
|------|------|------|--------|
| V1.0 | 2026-03-18 | 創建文件 | OpenCode |
| V1.1 | 2026-03-23 | 更新端點與實際一致 | OpenCode |
| V1.2 | 2026-03-25 | 新增快取/刪除 API | OpenCode |
| V1.3 | 2026-03-26 | 更新API回應格式 (media_url→file_path) | OpenCode |
---
## Base URL
| 環境 | URL |
|------|-----|
| 本地 | `http://localhost:3002` |
| 外部 | `https://api.momentry.ddns.net` |
---
## 認證
除健康檢查端點外,所有 API 端點都需要 API Key。
### Header 方式
```bash
curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/videos
```
### 響應
- `401 Unauthorized` - 缺少或無效的 API Key
- `200 OK` - 認證成功
### 取得 API Key
使用 CLI 建立:
```bash
./target/release/momentry api-key create "My API Key" --key-type user
```
---
## 端點列表
### 健康檢查(公開)
| 方法 | 端點 | 說明 |
|------|------|------|
| GET | `/health` | 基本健康檢查 |
| GET | `/health/detailed` | 詳細健康檢查(含服務狀態) |
**範例**:
```bash
curl http://localhost:3002/health
# {"status":"ok","version":"0.1.0","uptime_ms":123456}
```
---
### 影片搜尋
| 方法 | 端點 | 說明 |
|------|------|------|
| POST | `/api/v1/search` | 語意搜尋(標準格式) |
| POST | `/api/v1/n8n/search` | 語意搜尋n8n 專用格式) |
| POST | `/api/v1/search/hybrid` | 混合搜尋 |
**標準搜尋** (`/api/v1/search`):
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"query": "test", "limit": 10}'
```
**n8n 格式搜尋** (`/api/v1/n8n/search`):
```bash
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"query": "test", "limit": 10}'
```
---
### 影片管理
| 方法 | 端點 | 說明 |
|------|------|------|
| POST | `/api/v1/register` | 註冊影片 |
| POST | `/api/v1/probe` | 探測影片資訊(不註冊) |
| GET | `/api/v1/videos` | 列出所有影片 |
| GET | `/api/v1/lookup` | 查詢影片資訊 |
| GET | `/api/v1/progress/:uuid` | 取得處理進度 |
**註冊影片**:
```bash
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"path": "/path/to/video.mp4"}'
```
**註冊回應範例**:
```json
{
"uuid": "a1b10138a6bbb0cd",
"video_id": 1,
"job_id": 10,
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080,
"already_exists": false
}
```
**探測影片** (不註冊,只取得影片資訊):
```bash
curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"path": "./demo/video.mp4"}'
```
**Probe 回應範例**:
```json
{
"uuid": "a1b10138a6bbb0cd",
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080,
"fps": 30.0,
"cached": false,
"format": {
"filename": "/path/to/video.mp4",
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"duration": "120.5",
"size": "12345678",
"bit_rate": "819200"
},
"streams": [
{
"index": 0,
"codec_name": "h264",
"codec_type": "video",
"width": 1920,
"height": 1080,
"r_frame_rate": "30/1",
"duration": "120.5"
}
]
}
```
**列出影片**:
```bash
curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/videos
```
**查詢影片**:
```bash
curl -H "X-API-Key: your-api-key" "http://localhost:3002/api/v1/lookup?uuid=5dea6618a606e7c7"
```
**處理進度**:
```bash
curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/progress/5dea6618a606e7c7
```
---
### 工作管理
| 方法 | 端點 | 說明 |
|------|------|------|
| GET | `/api/v1/jobs` | 列出所有工作 |
| GET | `/api/v1/jobs/:uuid` | 取得指定工作的詳細資訊 |
**列出工作**:
```bash
curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/jobs
```
**取得工作詳細資訊**:
```bash
curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/jobs/a03485a40b2df2d3
```
---
### 系統管理
| 方法 | 端點 | 說明 |
|------|------|------|
| POST | `/api/v1/config/cache` | 切換快取功能(管理員) |
| POST | `/api/v1/unregister` | 刪除影片及其所有資料(管理員) |
**快取設定**:
```bash
curl -X POST http://localhost:3002/api/v1/config/cache \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"enabled": true}'
```
**刪除影片**:
```bash
curl -X POST http://localhost:3002/api/v1/unregister \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{"uuid": "5dea6618a606e7c7"}'
```
---
## 端點對照表
| 功能 | n8n 使用 | WordPress 使用 | curl 測試 |
|------|-----------|----------------|------------|
| 健康檢查 | ✓ | ✓ | ✓ |
| 語意搜尋 | ✓ (n8n格式) | ✓ (標準格式) | ✓ |
| 影片探測 | ✓ | ✓ | ✓ |
| 註冊影片 | ✓ | ✓ | ✓ |
| 列出影片 | ✓ | ✓ | ✓ |
| 查詢影片 | ✓ | ✓ | ✓ |
| 處理進度 | ✓ | ✓ | ✓ |
| 工作管理 | ✓ | ✓ | ✓ |
| 快取設定 | ✓ (管理員) | ✓ (管理員) | ✓ (管理員) |
| 刪除影片 | ✓ (管理員) | ✓ (管理員) | ✓ (管理員) |
---
## 回應格式
### n8n 格式 (`/api/v1/n8n/search`)
```json
{
"query": "charade",
"count": 10,
"hits": [
{
"id": "sentence_0001",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0001",
"text": "...",
"score": 0.92,
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
```
### 標準格式 (`/api/v1/search`)
```json
{
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"start_time": 48.8,
"end_time": 55.44,
"text": "...",
"score": 0.92
}
],
"query": "charade"
}
```
---
## HTTP 狀態碼
| 狀態 | 說明 |
|------|------|
| 200 | 成功 |
| 400 | 請求格式錯誤 |
| 404 | 端點或資源不存在 |
| 500 | 伺服器內部錯誤 |
| 502 | API 服務未啟動 |
---
## 錯誤處理
### 502 Bad Gateway
**原因**: Momentry API 服務未啟動
**解決**:
```bash
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
```
---
## 相關文件
- [API_INDEX.md](./API_INDEX.md) - 文件總覽(起點)
- [API_EXAMPLES.md](./API_EXAMPLES.md) - **完整範例總覽curl / n8n / WordPress**
- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 詳細指南
- [API_WORDPRESS_GUIDE.md](./API_WORDPRESS_GUIDE.md) - WordPress 詳細指南
- [API_CURL_EXAMPLES.md](./API_CURL_EXAMPLES.md) - curl 範例

View File

@@ -0,0 +1,106 @@
# API Error Codes (API 標準錯誤碼)
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-04-25 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-04-25 | 定義全局標準錯誤碼與 Response 格式 | OpenCode | OpenCode |
---
## 1. 錯誤 Response 格式
所有 API 錯誤回應必須遵循以下 JSON 結構:
```json
{
"success": false,
"error": {
"code": "E001_NOT_FOUND",
"message": "找不到指定的資源",
"details": {
"resource": "file_uuid",
"value": "abc-123"
}
}
}
```
---
## 2. 錯誤碼列表
### 2.1 通用錯誤 (E0xx)
| 錯誤碼 | HTTP 狀態 | 說明 |
|--------|-----------|------|
| `E001_NOT_FOUND` | 404 | 找不到資源 (File, Identity, Chunk...) |
| `E002_DUPLICATE` | 409 | 資源已存在 (例如:重複註冊 File UUID) |
| `E003_VALIDATION` | 400 | 請求參數驗證失敗 (缺欄位、格式錯誤) |
| `E004_UNAUTHORIZED` | 401 | 無效的 API Key 或 Token |
| `E005_INTERNAL` | 500 | 系統內部錯誤 (資料庫連線失敗等) |
### 2.2 處理器相關 (E1xx)
| 錯誤碼 | HTTP 狀態 | 說明 |
|--------|-----------|------|
| `E101_PROCESSOR_FAIL` | 500 | Python 腳本執行失敗 (返回非 0 狀態碼) |
| `E102_TIMEOUT` | 504 | 處理超時 (例如:長影片 ASR 處理過久) |
| `E103_RESUME_FAIL` | 500 | 續傳失敗 (找不到 Checkpoint 檔案) |
| `E104_NO_VIDEO` | 400 | 找不到影片路徑 |
### 2.3 身份與 Face (E2xx)
| 錯誤碼 | HTTP 狀態 | 說明 |
|--------|-----------|------|
| `E201_FACE_NOT_FOUND` | 404 | 找不到指定的 Face Pre-chunk |
| `E202_MERGE_CONFLICT` | 409 | Identity 合併衝突 |
| `E203_CANDIDATE_EMPTY` | 404 | 沒有待確認的 Candidates |
---
## 3. 實作建議 (Rust Axum)
`src/api/server.rs` 中,建議使用自訂錯誤型別來統一處理:
```rust
#[derive(Debug)]
pub enum AppError {
NotFound(String),
Validation(String),
Internal(anyhow::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (code, message, status) = match self {
AppError::NotFound(msg) => ("E001_NOT_FOUND", msg, StatusCode::NOT_FOUND),
AppError::Validation(msg) => ("E003_VALIDATION", msg, StatusCode::BAD_REQUEST),
AppError::Internal(e) => ("E005_INTERNAL", e.to_string(), StatusCode::INTERNAL_SERVER_ERROR),
};
(status, Json(serde_json::json!({
"success": false,
"error": {
"code": code,
"message": message
}
}))).into_response()
}
}
```
---
## 版本資訊
- 版本: V1.0
- 建立日期: 2026-04-25

View File

@@ -0,0 +1,129 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core API 文件總覽"
date: "2026-04-23"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "文件總覽"
- "core"
ai_query_hints:
- "查詢 Momentry Core API 文件總覽 的內容"
- "Momentry Core API 文件總覽 的主要目的是什麼?"
- "如何操作或實施 Momentry Core API 文件總覽?"
---
# Momentry Core API 文件總覽
> **Version**: 3.0 | **Updated**: 2026-04-23
> **Source**: Generated from actual Rust code (`src/api/`)
---
## 📁 文件結構
```
docs_v1.0/
├── REFERENCE/
│ ├── API_REFERENCE.md ← 主要 API 參考文件71 個端點)
│ ├── API_KEY_DESIGN.md ← API Key 系統設計文件
│ └── API_TRAINING_MARCOM.md ← marcom 團隊教育訓練手冊
├── IMPLEMENTATION/
│ ├── API_EXAMPLES.md ← 完整範例curl / n8n / WordPress
│ ├── API_CURL_EXAMPLES.md ← curl 快速範例
│ ├── API_WORDPRESS_GUIDE.md ← WordPress 整合指南
│ └── API_N8N_GUIDE.md ← n8n 整合指南
└── ARCHITECTURE/
└── API_KEY_ARCHITECTURE.md ← API Key 架構圖
```
---
## 快速選擇指南
| 需求 | 閱讀文件 |
|------|----------|
| **我要查看所有 API 端點** | [API_REFERENCE.md](./API_REFERENCE.md) |
| **我要 curl 範例** | [API_EXAMPLES.md](../IMPLEMENTATION/API_EXAMPLES.md) |
| **我是 marcom 團隊** | [API_TRAINING_MARCOM.md](./API_TRAINING_MARCOM.md) |
| **我要整合 n8n** | [API_N8N_GUIDE.md](../IMPLEMENTATION/API_N8N_GUIDE.md) |
| **我要整合 WordPress** | [API_WORDPRESS_GUIDE.md](../IMPLEMENTATION/API_WORDPRESS_GUIDE.md) |
| **我要了解 API Key 設計** | [API_KEY_DESIGN.md](./API_KEY_DESIGN.md) |
---
## 認證
### 使用方式
```bash
export API_KEY="your_api_key_here"
curl -H "X-API-Key: $API_KEY" http://localhost:3002/api/v1/videos
```
### 環境
| 環境 | URL | 使用時機 |
|------|-----|----------|
| **本地開發** | `http://localhost:3002` | 開發/測試 |
| **Playground** | `http://localhost:3003` | 開發測試dev 模式) |
| **外部訪問** | `https://api.momentry.ddns.net` | n8n、WordPress、遠端 |
---
## API 端點總覽
| 類別 | 端點數 | 說明 |
|------|--------|------|
| Health & Stats | 5 | 健康檢查與統計(公開) |
| Core Asset | 6 | 影片註冊、查詢、進度 |
| Processing | 7 | 探針、處理、任務 |
| Search | 7 | 向量、BM25、混合搜索 |
| Visual Chunk | 5 | 視覺分片搜索 |
| Face Recognition | 7 | 人臉識別 |
| Person Identity | 21 | 人物身份管理 |
| Global Identities | 6 | 全局身份 |
| Identity Binding | 6 | 身份綁定 |
| Configuration | 1 | 緩存配置 |
| **Total** | **71** | **可達端點** |
### ⚠️ 未掛載端點
以下端點已定義但**未在 router 中掛載**
| 端點 | 定義位置 |
|------|----------|
| `/api/v1/search/universal` | `universal_search.rs` |
| `/api/v1/search/frames` | `universal_search.rs` |
| `/api/v1/search/persons` | `universal_search.rs` |
| `/api/v1/who` | `who.rs` |
| `/api/v1/who/candidates` | `who.rs` |
---
## 常見問題
### Q: API 返回 401 錯誤?
API Key 無效或過期。請檢查 `X-API-Key` header。
### Q: API 返回 502 錯誤?
```bash
# 檢查服務狀態
launchctl list | grep momentry.api
# 重啟服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
```
---
## 相關文件
- [API_REFERENCE.md](./API_REFERENCE.md) - 完整 API 參考
- [INSTALL_MOMENTRY_API.md](../IMPLEMENTATION/INSTALL_MOMENTRY_API.md) - 安裝指南
- [API_KEY_DESIGN.md](./API_KEY_DESIGN.md) - API Key 設計

View File

@@ -0,0 +1,731 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry API Key 管理系統設計"
date: "2026-03-21"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "管理系統設計"
ai_query_hints:
- "查詢 Momentry API Key 管理系統設計 的內容"
- "Momentry API Key 管理系統設計 的主要目的是什麼?"
- "如何操作或實施 Momentry API Key 管理系統設計?"
---
# Momentry API Key 管理系統設計
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-21 |
| 文件版本 | V1.2 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-20 | 新增 Key 類型與管理流程 | Warren | OpenCode |
| V1.2 | 2026-03-21 | 更新 API Key 格式與驗證流程 | Warren | OpenCode |
---
**狀態**: 開發中
---
## 1. 概述
### 1.1 目標
建立安全的 API Key 管理機制,支援:
- 多類型 API Key系統、用戶、服務
- 自動過期與輪換
- 異常使用偵測
- 強制更新機制
- 完整審計日誌
- Gitea Token 整合
- n8n API Key 整合
### 1.2 設計原則
| 原則 | 說明 |
|------|------|
| 最小權限 | 每個 Key 僅授予必要權限 |
| 定期輪換 | 自動過期強制更新 |
| 追蹤可審 | 所有操作都有日誌 |
| 分離儲存 | Key 與使用者資料分離 |
---
## 2. API Key 類型
### 2.1 Key 類型矩陣
| 類型 | 前綴 | 用途 | 預設有效期 | 輪換方式 |
|------|------|------|------------|----------|
| `system` | `msys_` | 系統內部服務 | 365 天 | 手動 |
| `user` | `muser_` | 個人用戶 | 90 天 | 自動 |
| `service` | `msvc_` | 服務間通訊 | 180 天 | 自動 |
| `integration` | `mint_` | 第三方整合 | 30 天 | 強制更新 |
| `emergency` | `memg_` | 緊急存取 | 24 小時 | 一次性 |
### 2.2 Key 格式
```
{prefix}{uuid_v4}_{timestamp}_{checksum}
```
**範例:**
```
msys_a1b2c3d4-e5f6-7890-abcd-ef1234567890_1710998400_sha256
```
---
## 3. 資料庫 Schema
### 3.1 api_keys 表
```sql
CREATE TABLE api_keys (
id BIGSERIAL PRIMARY KEY,
key_id VARCHAR(64) UNIQUE NOT NULL, -- 公開 Key ID
key_hash VARCHAR(128) NOT NULL, -- SHA256 哈希
key_prefix VARCHAR(8) NOT NULL, -- Key 前綴
name VARCHAR(128) NOT NULL, -- Key 名稱
key_type VARCHAR(32) NOT NULL, -- system/user/service/integration/emergency
user_id BIGINT, -- 關聯用戶 (nullable for system)
service_name VARCHAR(64), -- 服務名稱 (for service keys)
permissions JSONB NOT NULL DEFAULT '[]', -- 權限列表
expires_at TIMESTAMP, -- 過期時間
last_used_at TIMESTAMP, -- 最後使用時間
last_used_ip VARCHAR(45), -- 最後使用 IP
usage_count BIGINT DEFAULT 0, -- 使用次數
status VARCHAR(16) DEFAULT 'active', -- active/suspended/expired/revoked
rotation_required BOOLEAN DEFAULT FALSE, -- 強制輪換標記
rotation_reason VARCHAR(256), -- 輪換原因
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_api_keys_key_id ON api_keys(key_id);
CREATE INDEX idx_api_keys_user_id ON api_keys(user_id);
CREATE INDEX idx_api_keys_type ON api_keys(key_type);
CREATE INDEX idx_api_keys_status ON api_keys(status);
CREATE INDEX idx_api_keys_expires ON api_keys(expires_at);
```
### 3.2 api_key_audit_log 表
```sql
CREATE TABLE api_key_audit_log (
id BIGSERIAL PRIMARY KEY,
key_id VARCHAR(64) NOT NULL,
action VARCHAR(32) NOT NULL, -- created/used/rotated/revoked/expired/suspended
actor VARCHAR(64), -- 操作者 (user_id or 'system')
ip_address VARCHAR(45),
user_agent VARCHAR(512),
request_path VARCHAR(256),
response_code INTEGER,
details JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_audit_key_id ON api_key_audit_log(key_id);
CREATE INDEX idx_audit_action ON api_key_audit_log(action);
CREATE INDEX idx_audit_created ON api_key_audit_log(created_at);
```
### 3.3 api_key_rotation_log 表
```sql
CREATE TABLE api_key_rotation_log (
id BIGSERIAL PRIMARY KEY,
key_id VARCHAR(64) NOT NULL,
old_key_id VARCHAR(64),
new_key_id VARCHAR(64),
rotation_type VARCHAR(32) NOT NULL, -- scheduled/manual/forced/emergency
reason VARCHAR(256),
triggered_by VARCHAR(64), -- system/user/scheduler
grace_period_end TIMESTAMP, -- 寬限期結束時間
created_at TIMESTAMP DEFAULT NOW()
);
```
---
## 4. API Key 狀態機
```
┌──────────────┐
│ created │
└──────┬───────┘
┌────────────────────┐
│ active │◄─────────────┐
└─────────┬──────────┘ │
│ │
┌─────────────┼─────────────┐ │
│ │ │ │
▼ ▼ ▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ suspended │ │ expired │ │ revoked │─────┘
└──────────┘ └──────────┘ └──────────┘
```
### 狀態轉換規則
| 從 | 到 | 觸發條件 |
|----|----|----------|
| created | active | 啟用 Key |
| active | suspended | 異常使用偵測 |
| active | expired | 達到過期時間 |
| active | revoked | 手動撤銷 |
| suspended | active | 解除鎖定 |
| suspended | revoked | 確認異常 |
| expired | active | 重新啟用 |
---
## 5. 異常偵測機制
### 5.1 異常指標
| 指標 | 閾值 | 處置 |
|------|------|------|
| 每分鐘請求數 | > 1000 | 警告 |
| 每小時請求數 | > 10000 | 鎖定 |
| 錯誤率 | > 50% | 警告 |
| 不同 IP 數 | > 5/小時 | 警告 |
| 非工作時間使用 | 深夜請求 | 警告 |
| 異常模式 | 暴力破解 | 鎖定 |
### 5.2 異常處理流程
```
異常偵測
┌─────────┐
│ 分析 │──→ 排除正常流量
└────┬────┘
┌─────────┐
│ 評估 │──→ 輕微 → 警告
└────┬────┘
┌─────────┐
│ 處置 │──→ 嚴重 → 鎖定 + 輪換
└─────────┘
```
---
## 6. 強制更新機制
### 6.1 觸發條件
| 條件 | 嚴重性 | 動作 |
|------|--------|------|
| 疑似洩露 | 高 | 立即停用 + 強制輪換 |
| 異常使用 | 中 | 警告 + 建議輪換 |
| 計劃性維護 | 低 | 通知 + 排程輪換 |
| 政策要求 | 高 | 強制輪換 |
| 過期 | 低 | 停用 + 通知 |
### 6.2 強制輪換流程
```
1. 系統偵測到需要強制更新
2. 建立新 Key保留舊 Key 在寬限期內)
3. 發送通知Email/Slack/Redis PubSub
4. 寬限期開始(預設 24 小時)
├── 在寬限期內更新 → 完成輪換
└── 寬限期結束 → 舊 Key 停用
```
### 6.3 寬限期配置
| Key 類型 | 寬限期 |
|----------|--------|
| system | 72 小時 |
| user | 24 小時 |
| service | 48 小時 |
| integration | 24 小時 |
| emergency | 0 小時 |
---
## 7. CLI 管理命令
### 7.1 命令列表
```bash
# Key 管理
momentry api-key create --name "My Key" --type user --permissions read,write
momentry api-key list --type user
momentry api-key info <key_id>
momentry api-key revoke <key_id> --reason "安全原因"
# 輪換管理
momentry api-key rotate <key_id> # 正常輪換
momentry api-key force-rotate <key_id> # 強制輪換
momentry api-key rotation-status <key_id> # 查看輪換狀態
# 異常管理
momentry api-key suspend <key_id> --reason "異常使用"
momentry api-key unsuspend <key_id>
momentry api-key blacklist <key_id> # 列入黑名單
# 審計
momentry api-key audit <key_id> --since 7d
momentry api-key stats --type service --period 30d
```
### 7.2 輸出範例
```bash
$ momentry api-key list --type service
┌────────────────────────────────────┬─────────┬──────────────┬────────────────┐
│ Key ID │ Name │ Status │ Expires │
├────────────────────────────────────┼─────────┼──────────────┼────────────────┤
│ msvc_a1b2c3d4_1710998400_sha256 │ N8N │ active │ 2026-09-21 │
│ msvc_e5f6g7h8_1713600000_sha256 │ OpenCode│ rotation_req │ 2026-09-21 │
└────────────────────────────────────┴─────────┴──────────────┴────────────────┘
⚠️ 1 個 Key 需要輪換
```
---
## 8. 實現計畫
### Phase 1: 核心功能
- [ ] 資料庫 Schema
- [ ] Key 生成與哈希
- [ ] 基本 CRUD API
- [ ] 過期檢查
### Phase 2: 安全機制
- [ ] 異常偵測
- [ ] 自動鎖定
- [ ] 強制輪換
- [ ] 寬限期管理
### Phase 3: 管理工具
- [ ] CLI 命令
- [ ] 審計日誌
- [ ] 統計報表
- [ ] 通知系統
### Phase 4: 自動化
- [ ] 定時輪換排程
- [ ] Prometheus 指標
- [ ] Alertmanager 整合
- [ ] 自動化回應
---
## 9. 安全考量
### 9.1 Key 儲存
- 明文 Key 只顯示一次(創建時)
- 儲存時使用 SHA256 哈希
- 使用 Fernet 對稱加密敏感配置
### 9.2 傳輸安全
- 所有 API 必須使用 HTTPS
- Key 在 Header 中傳輸X-API-Key
- 避免 Key 在 URL 中
### 9.3 存取控制
- 只有管理員可創建/撤銷 Key
- 用戶只能管理自己的 Key
- 系統 Key 需要特殊權限
---
## 10. 環境變數配置
```bash
# API Key 管理
MOMENTRY_API_KEY_GRACE_PERIOD=86400 # 寬限期(秒)
MOMENTRY_API_KEY_MAX_PER_USER=5 # 每用戶最大 Key 數
MOMENTRY_API_KEY_ROTATION_DAYS=90 # 自動輪換天數
# 異常偵測
MOMENTRY_API_KEY_RATE_LIMIT=1000 # 每分鐘限制
MOMENTRY_API_KEY_ERROR_THRESHOLD=0.5 # 錯誤率閾值
MOMENTRY_API_KEY_IP_LIMIT=5 # 每小時 IP 限制
# 通知
MOMENTRY_API_KEY_ALERT_WEBHOOK= # 異常通知 webhook
```
---
## 11. Gitea API Token 整合
### 11.1 概述
支援透過 API Key 管理系統建立和管理 Gitea Personal Access Tokens採用「建立時納管」模式。
### 11.2 納管模式
```
使用者提供帳號密碼 → 呼叫 Gitea API 建立 Token → 明文只顯示一次 → 同步儲存至管理系統
```
**特點:**
- Token 明文僅在建立時取得
- 管理系統記錄 Token 元數據(不含明文)
- 支援本地查詢和刪除
### 11.3 資料庫結構
```sql
CREATE TABLE gitea_tokens (
id SERIAL PRIMARY KEY,
gitea_token_id BIGINT NOT NULL, -- Gitea 內部 Token ID
gitea_user VARCHAR(128) NOT NULL, -- Gitea 用戶名
token_name VARCHAR(128) NOT NULL, -- Token 名稱
token_last_eight VARCHAR(8) NOT NULL, -- SHA1 最後 8 碼(顯示用)
scopes JSONB DEFAULT '[]', -- 權限範圍
api_key_id VARCHAR(48), -- 關聯的 API Key ID可選
last_verified TIMESTAMP, -- 最後驗證時間
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(gitea_user, token_name)
);
```
### 11.4 Token 權限範圍
| 範圍 | 說明 |
|------|------|
| `read:repository` | 讀取倉庫 |
| `write:repository` | 寫入倉庫 |
| `read:issue` | 讀取議題 |
| `write:issue` | 寫入議題 |
| `read:user` | 讀取用戶資訊 |
| `write:write` | 修改用戶資訊 |
| `read:organization` | 讀取組織 |
| `write:organization` | 修改組織 |
| `read:package` | 讀取套件 |
| `write:package` | 發布套件 |
| `read:notification` | 讀取通知 |
| `write:notification` | 修改通知 |
| `read:admin` | 管理員讀取 |
| `write:admin` | 管理員寫入 |
### 11.5 CLI 命令
#### 建立 Token
```bash
# 基本用法
momentry gitea create \
--username <gitea_user> \
--password <gitea_password> \
--token-name <token_name> \
--scopes "read:repository,write:repository"
# 範例:建立整合用 Token
momentry gitea create \
--username admin \
--password "MyPassword123" \
--token-name "ci-pipeline" \
--scopes "read:repository,write:repository,read:issue,write:issue"
```
**輸出範例:**
```
✅ Gitea Token created successfully!
┌─────────────────────────────────────────────────────────────────────────────┐
│ ⚠️ IMPORTANT: Save this token now - it will not be shown again! │
└─────────────────────────────────────────────────────────────────────────────┘
Token ID: 9
Token Name: ci-pipeline
SHA1: 9a4f282e9ba817b430082e6bff2c18e2ae38e480
Last 8: ae38e480
Authorization Header:
Authorization: token 9a4f282e9ba817b430082e6bff2c18e2ae38e480
```
#### 列出 Token
```bash
# 列出用戶的所有 Token
momentry gitea list \
--username <gitea_user> \
--password <gitea_password>
```
**輸出範例:**
```
📋 Gitea Tokens for user: admin
┌────────────────────────────────────────────────────────────────────────────┐
│ ID │ Name │ Last 8 │ Registered │
├────────────────────────────────────────────────────────────────────────────┤
│ 9 │ ci-pipeline │ ae38e480 │ ✓ │
│ 8 │ dev-token │ 1234abcd │ - │
└────────────────────────────────────────────────────────────────────────────┘
Total: 2 token(s)
```
#### 刪除 Token
```bash
# 刪除指定 Token
momentry gitea delete \
--username <gitea_user> \
--password <gitea_password> \
--token-name <token_name>
```
#### 查詢本地記錄
```bash
# 查詢已納管的 Token 記錄
momentry gitea verify --token-name <token_name>
```
**輸出範例:**
```
📋 Gitea Token: ci-pipeline
User: admin
Token ID: 9
Last 8: ae38e480
Scopes: ["read:repository","write:repository"]
Created: 2026-03-21 06:44:55.577586 UTC
Last Verified: never
```
### 11.6 使用範圍
#### 適用場景
| 場景 | 說明 |
|------|------|
| CI/CD 整合 | 建立專用 Token 用於自動化流程 |
| 服務間通訊 | 建立 Token 供其他服務存取 Gitea API |
| 開發環境 | 為開發者建立短期 Token |
| 監控整合 | 建立只讀 Token 用於監控和報告 |
#### 限制
| 限制 | 說明 |
|------|------|
| 明文 Token | 僅在建立時取得,無法再次查詢 |
| 管理 API | 需要帳號密碼BasicAuth |
| Token 驗證 | 只能透過 API 呼叫驗證有效性 |
| 同步刪除 | 本地刪除不會自動同步到 Gitea |
### 11.7 環境變數
```bash
# Gitea 連線設定
GITEA_URL=http://localhost:3000 # Gitea API URL
```
### 11.8 安全考量
| 項目 | 措施 |
|------|------|
| 密碼傳輸 | 僅在 CLI 命令中使用,不儲存 |
| Token 儲存 | 本地僅存元數據,不含明文 |
| 權限最小化 | 建議僅授予必要權限 |
| 定期輪換 | 建議定期更新 Token |
---
## 12. n8n API Key 整合
### 12.1 概述
支援透過 API Key 管理系統建立和管理 n8n API Keys採用「建立時納管」模式。
### 12.2 納管模式
```
使用者提供現有 n8n API Key → 呼叫 n8n API 建立新 Key → 明文只顯示一次 → 同步儲存至管理系統
```
**特點:**
- 需要一個現有的 n8n API Key 作為管理憑證
- API Key 明文僅在建立時取得
- 管理系統記錄 Key 元數據(不含明文)
- 支援本地查詢和刪除
### 12.3 資料庫結構
```sql
CREATE TABLE n8n_api_keys (
id SERIAL PRIMARY KEY,
n8n_key_id VARCHAR(64) UNIQUE NOT NULL, -- n8n 內部 Key ID
label VARCHAR(100) NOT NULL, -- Key 標籤
api_key_last_eight VARCHAR(8) NOT NULL, -- API Key 最後 8 碼(顯示用)
momentry_api_key_id VARCHAR(48), -- 關聯的 API Key ID可選
expires_at TIMESTAMP WITH TIME ZONE, -- 過期時間
last_verified TIMESTAMP WITH TIME ZONE, -- 最後驗證時間
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
```
### 12.4 認證方式
n8n 使用 JWT-based API Key透過 `X-N8N-API-KEY` Header 認證:
```bash
curl -H "X-N8N-API-KEY: <your-api-key>" https://n8n.example.com/api/v1/workflows
```
### 12.5 CLI 命令
#### 建立 API Key
```bash
# 基本用法
momentry n8n create \
--api-key <existing_n8n_api_key> \
--label <key_label> \
--expires-in-days <days>
# 範例:建立 CI/CD 用 Key
momentry n8n create \
--api_key "n8n_api_xxxxxxxxxxxx" \
--label "ci-pipeline" \
--expires-in-days 90
```
**輸出範例:**
```
✅ n8n API Key created successfully!
┌─────────────────────────────────────────────────────────────────────────────┐
│ ⚠️ IMPORTANT: Save this API key now - it will not be shown again! │
└─────────────────────────────────────────────────────────────────────────────┘
Key ID: abc123-def456
Label: ci-pipeline
API Key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Usage:
curl -H 'X-N8N-API-KEY: eyJhbGciOiJIUz...' https://n8n.momentry.ddns.net/api/v1/workflows
```
#### 列出 API Keys
```bash
# 列出所有 API Keys
momentry n8n list --api-key <existing_n8n_api_key>
```
**輸出範例:**
```
📋 n8n API Keys
┌────────────────────────────────────────────────────────────────────────────┐
│ Label │ ID │
├────────────────────────────────────────────────────────────────────────────┤
│ ci-pipeline │ abc123-def456-789 │
│ monitoring │ xyz789-abc123-456 │
└────────────────────────────────────────────────────────────────────────────┘
Total: 2 key(s)
```
#### 刪除 API Key
```bash
# 刪除指定 API Key
momentry n8n delete \
--api-key <existing_n8n_api_key> \
--label <key_label>
```
#### 查詢本地記錄
```bash
# 查詢已納管的 API Key 記錄
momentry n8n verify --label <key_label>
```
**輸出範例:**
```
📋 n8n API Key: ci-pipeline
Key ID: abc123-def456
Last 8: ...JVCJ9
Created: 2026-03-21 06:44:55.577586 UTC
Expires: 2026-06-19 06:44:55.577586 UTC
Last Verified: never
```
### 12.6 使用範圍
#### 適用場景
| 場景 | 說明 |
|------|------|
| CI/CD 整合 | 建立專用 Key 用於自動化流程 |
| 監控整合 | 建立只讀 Key 用於監控工作流狀態 |
| 服務間通訊 | 建立 Key 供其他服務呼叫 n8n API |
| 開發環境 | 為開發者建立短期 Key |
#### 限制
| 限制 | 說明 |
|------|------|
| 明文 API Key | 僅在建立時取得,無法再次查詢 |
| 管理憑證 | 需要一個現有的 n8n API Key |
| 本地刪除 | 不會自動同步到 n8n |
| 權限範圍 | 非 Enterprise 版無細粒度權限 |
### 12.7 環境變數
```bash
# n8n 連線設定
N8N_URL=https://n8n.momentry.ddns.net # n8n API URL
```
### 12.8 安全考量
| 項目 | 措施 |
|------|------|
| 管理 Key | 需妥善保管,作為管理其他 Key 的憑證 |
| API Key 儲存 | 本地僅存元數據,不含明文 |
| 過期機制 | 建議設定過期時間 |
| 定期輪換 | 建議定期更新 Key |
---
## 13. 參考文檔
- PostgreSQL Schema
- Redis Key 設計( MOMENTRY_CORE_REDIS_KEYS.md
- 監控系統MOMENTRY_CORE_MONITORING.md
- Gitea 安裝指南INSTALL_GITEA.md
- n8n API 文件https://docs.n8n.io/api/authentication/

View File

@@ -0,0 +1,532 @@
# Momentry Core API 快速查詢表
| 版本 | 日期 | 建立者 |
|------|------|--------|
| V1.0 | 2026-03-26 | OpenCode |
---
## 📋 快速導覽
| 類別 | 端點數量 | 說明 |
|------|----------|------|
| 健康檢查 | 2 | 系統狀態監控 |
| 影片管理 | 5 | 影片註冊、查詢、刪除 |
| 搜尋功能 | 3 | 語意搜尋、混合搜尋 |
| 任務管理 | 2 | 處理任務狀態查詢 |
| 系統管理 | 2 | 快取設定、進度查詢 |
---
## 🔐 認證
所有 `/api/v1/*` 端點需要 `X-API-Key` header
```bash
curl -H "X-API-Key: YOUR_API_KEY" ...
```
**公開端點(無需認證):**
- `GET /health`
- `GET /health/detailed`
---
## 📊 端點總表
### 健康檢查
| 方法 | 端點 | 認證 | 描述 |
|------|------|------|------|
| GET | `/health` | 公開 | 基本健康檢查 |
| GET | `/health/detailed` | 公開 | 詳細健康檢查(包含所有服務狀態) |
### 影片管理
| 方法 | 端點 | 認證 | 描述 |
|------|------|------|------|
| POST | `/api/v1/register` | 需要 | 註冊影片並開始處理 |
| POST | `/api/v1/unregister` | 需要 | 刪除影片及其所有資料 |
| POST | `/api/v1/probe` | 需要 | 探測影片資訊(不註冊) |
| GET | `/api/v1/videos` | 需要 | 列出所有已註冊影片 |
| GET | `/api/v1/lookup` | 需要 | 查詢影片資訊 |
### 搜尋功能
| 方法 | 端點 | 認證 | 描述 |
|------|------|------|------|
| POST | `/api/v1/search` | 需要 | 語意搜尋(標準格式) |
| POST | `/api/v1/n8n/search` | 需要 | 語意搜尋n8n 格式) |
| POST | `/api/v1/search/hybrid` | 需要 | 混合搜尋(向量 + 關鍵字) |
### 任務管理
| 方法 | 端點 | 認證 | 描述 |
|------|------|------|------|
| GET | `/api/v1/jobs` | 需要 | 列出所有處理任務 |
| GET | `/api/v1/jobs/:uuid` | 需要 | 取得特定任務詳情 |
### 系統管理
| 方法 | 端點 | 認證 | 描述 |
|------|------|------|------|
| GET | `/api/v1/progress/:uuid` | 需要 | 取得影片處理進度 |
| POST | `/api/v1/config/cache` | 需要 | 切換快取功能 |
---
## 🔧 詳細端點說明
### 1. 健康檢查
#### GET /health
**基本健康檢查**
```bash
curl http://localhost:3002/health
```
**回應:**
```json
{
"status": "ok",
"version": "0.1.0",
"uptime_ms": 14426558
}
```
#### GET /health/detailed
**詳細健康檢查**
```bash
curl http://localhost:3002/health/detailed
```
**回應:**
```json
{
"status": "ok",
"version": "0.1.0",
"uptime_ms": 14441362,
"services": {
"postgres": {"status": "ok", "latency_ms": 50, "error": null},
"redis": {"status": "ok", "latency_ms": 0, "error": null},
"qdrant": {"status": "ok", "latency_ms": 2, "error": null},
"mongodb": {"status": "ok", "latency_ms": 2, "error": null}
}
}
```
### 2. 影片管理
#### POST /api/v1/register
**註冊影片並開始處理**
```bash
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "/path/to/video.mp4"}'
```
**請求:**
```json
{
"path": "/path/to/video.mp4"
}
```
**回應:**
```json
{
"uuid": "5dea6618a606e7c7",
"video_id": 10,
"job_id": 1,
"file_name": "video.mp4",
"duration": 596.458333,
"width": 320,
"height": 180,
"already_exists": false
}
```
#### POST /api/v1/unregister
**刪除影片及其所有資料**
```bash
curl -X POST http://localhost:3002/api/v1/unregister \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"uuid": "5dea6618a606e7c7"}'
```
**請求:**
```json
{
"uuid": "5dea6618a606e7c7"
}
```
**回應:**
```json
{
"success": true,
"uuid": "5dea6618a606e7c7",
"message": "Video unregistered successfully"
}
```
#### POST /api/v1/probe
**探測影片資訊(不註冊)**
```bash
curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "/path/to/video.mp4"}'
```
**請求:**
```json
{
"path": "/path/to/video.mp4"
}
```
**回應:**
```json
{
"uuid": "5dea6618a606e7c7",
"file_name": "video.mp4",
"duration": 596.458333,
"width": 320,
"height": 180,
"fps": 24.0,
"cached": true,
"format": {...},
"streams": [...]
}
```
#### GET /api/v1/videos
**列出所有已註冊影片**
```bash
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
```
**回應:**
```json
{
"videos": [
{
"uuid": "a03485a40b2df2d3",
"file_path": "/path/to/video.mp4",
"file_name": "video.mp4",
"duration": 596.458333,
"width": 320,
"height": 180
}
]
}
```
#### GET /api/v1/lookup
**查詢影片資訊**
```bash
# 依 UUID 查詢
curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?uuid=a03485a40b2df2d3"
# 依路徑查詢
curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
```
**回應:**
```json
{
"uuid": "a03485a40b2df2d3",
"file_path": "/path/to/video.mp4",
"file_name": "video.mp4",
"duration": 596.458333
}
```
### 3. 搜尋功能
#### POST /api/v1/search
**語意搜尋(標準格式)**
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "search term", "limit": 5}'
```
**請求:**
```json
{
"query": "search term",
"limit": 5
}
```
**回應:**
```json
{
"results": [
{
"uuid": "a1b10138a6bbb0cd",
"chunk_id": "sentence_0001",
"chunk_type": "sentence",
"start_time": 10.5,
"end_time": 15.2,
"text": "Found text matching query",
"score": 0.85
}
],
"query": "search term"
}
```
#### POST /api/v1/n8n/search
**語意搜尋n8n 格式)**
```bash
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "search term", "limit": 5}'
```
**回應:**
```json
{
"query": "search term",
"count": 1,
"hits": [
{
"id": "sentence_0001",
"vid": "a1b10138a6bbb0cd",
"start_time": 10.5,
"end_time": 15.2,
"title": "Chunk sentence_0001",
"text": "Found text matching query",
"score": 0.85,
"file_path": "/path/to/video.mp4"
}
]
}
```
#### POST /api/v1/search/hybrid
**混合搜尋(向量 + 關鍵字)**
```bash
curl -X POST http://localhost:3002/api/v1/search/hybrid \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "search term", "limit": 5}'
```
**請求:**
```json
{
"query": "search term",
"limit": 5
}
```
**回應:**`/api/v1/search` 相同格式
### 4. 任務管理
#### GET /api/v1/jobs
**列出所有處理任務**
```bash
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/jobs
```
**回應:**
```json
{
"jobs": [
{
"id": 10,
"uuid": "a03485a40b2df2d3",
"status": "running",
"current_processor": null,
"progress_current": 0,
"progress_total": 0,
"created_at": "2026-03-26 13:39:37.830465",
"started_at": null
}
]
}
```
#### GET /api/v1/jobs/:uuid
**取得特定任務詳情**
```bash
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/jobs/a03485a40b2df2d3
```
**回應:**
```json
{
"id": 10,
"uuid": "a03485a40b2df2d3",
"status": "running",
"current_processor": null,
"progress_current": 0,
"progress_total": 0,
"processors": [
{
"processor_type": "asr",
"status": "completed",
"started_at": "2026-03-26 05:39:40.275468",
"completed_at": "2026-03-26 07:19:43.166613",
"duration_secs": 6002.891145,
"error_message": null
},
// ... 其他處理器
],
"created_at": "2026-03-26 13:39:37.830465",
"started_at": null,
"updated_at": "2026-03-26 07:19:16.338406"
}
```
### 5. 系統管理
#### GET /api/v1/progress/:uuid
**取得影片處理進度**
```bash
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/progress/a03485a40b2df2d3
```
**回應:**
```json
{
"uuid": "a03485a40b2df2d3",
"user": null,
"group": null,
"file_name": "video.mp4",
"duration": 596.458333,
"overall_progress": 0,
"cpu_percent": 0.2,
"gpu_percent": null,
"memory_percent": 0.1,
"memory_mb": 16720,
"processors": [
{
"name": "asr",
"status": "pending",
"current": 0,
"total": 0,
"progress": 0,
"message": ""
},
// ... 其他處理器
]
}
```
#### POST /api/v1/config/cache
**切換快取功能**
```bash
curl -X POST http://localhost:3002/api/v1/config/cache \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"enabled": true}'
```
**請求:**
```json
{
"enabled": true
}
```
**回應:**
```json
{
"success": true,
"cache_enabled": true,
"message": "Cache enabled"
}
```
---
## 🚀 快速工作流程
### 1. 註冊並處理影片
```bash
# 1. 註冊影片
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "/path/to/video.mp4"}'
# 回應包含 UUID: 5dea6618a606e7c7
# 2. 監控進度
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/progress/5dea6618a606e7c7
# 3. 查看任務狀態
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/jobs/5dea6618a606e7c7
```
### 2. 搜尋影片內容
```bash
# 1. 列出所有影片
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
# 2. 搜尋內容
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade scene", "limit": 10}'
```
### 3. 系統管理
```bash
# 1. 檢查系統健康
curl http://localhost:3002/health/detailed
# 2. 管理快取
curl -X POST http://localhost:3002/api/v1/config/cache \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"enabled": false}'
# 3. 刪除影片(需要時)
curl -X POST http://localhost:3002/api/v1/unregister \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"uuid": "5dea6618a606e7c7"}'
```
---
## 📝 注意事項
1. **API Key 格式:**
- 使用完整 API Key`muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69`
- 系統存儲的是 SHA256 哈希值
2. **路徑格式:**
- 絕對路徑:`/Users/accusys/test_video/video.mp4`
- 相對路徑:`./demo/video.mp4`(相對於 SFTPGo 資料目錄)
3. **回應時間:**
- 健康檢查:< 100ms
- 搜尋:取決於查詢複雜度,通常 100-500ms
- 影片註冊:立即返回,背景處理可能需要數分鐘到數小時
4. **錯誤處理:**
- 401: 認證失敗
- 404: 資源不存在
- 500: 伺服器內部錯誤
---
## 🔗 相關文件
- [API 參考指南](./API_REFERENCE.md) - 詳細 API 說明
- [API 範例總覽](./API_EXAMPLES.md) - 完整使用範例
- [API 端點列表](./API_ENDPOINTS.md) - 端點簡介
- [Curl 範例指南](./API_CURL_EXAMPLES.md) - curl 命令範例
- [n8n 整合指南](./API_N8N_GUIDE.md) - n8n 工作流程整合

View File

@@ -0,0 +1,310 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core API Reference"
date: "2026-04-25"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "reference"
- "momentry"
- "core"
ai_query_hints:
- "查詢 Momentry Core API Reference 的內容"
- "Momentry Core API Reference 的主要目的是什麼?"
- "如何操作或實施 Momentry Core API Reference"
---
# Momentry Core API Reference
> **Version**: 1.0 | **Source**: Generated from actual Rust code (`src/api/`)
> **Server**: Port 3002 (production) | 3003 (playground)
> **Auth**: Bearer token via `X-API-Key` header (required for most endpoints)
---
## 📋 Table of Contents
1. [Health & Stats (Public)](#health--stats)
2. [Core Asset Management](#core-asset-management)
3. [Processing Pipeline](#processing-pipeline)
4. [Search APIs](#search-apis)
5. [Visual Chunk Search](#visual-chunk-search)
6. [Face Recognition](#face-recognition)
7. [Person Identity](#person-identity)
8. [Global Identities](#global-identities)
9. [Identity Binding](#identity-binding)
10. [Configuration](#configuration)
---
## Health & Stats
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| GET | `/health` | No | Basic health check |
| GET | `/health/detailed` | No | Detailed health (all services) |
| GET | `/api/v1/stats/ingest` | No | Ingest statistics |
| GET | `/api/v1/stats/sftpgo` | No | SFTPGo service status |
| GET | `/api/v1/stats/inference` | No | Inference service health |
### Example
```bash
curl http://localhost:3002/health
# Response: {"status": "ok"}
```
---
## Core Asset Management
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/v1/register` | Yes | Register a new video |
| POST | `/api/v1/unregister` | Yes | Delete a video and all data |
| GET | `/api/v1/videos` | Yes | List all videos |
| GET | `/api/v1/videos/:uuid/details` | Yes | Get video details with chunks |
| GET | `/api/v1/lookup` | Yes | Lookup video by path or UUID |
| GET | `/api/v1/progress/:uuid` | Yes | Get processing progress |
### Register Video
```bash
curl -X POST http://localhost:3002/api/v1/register \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"path": "./demo/video.mp4"}'
```
### Video Details
```bash
curl http://localhost:3002/api/v1/videos/$UUID/details \
-H "X-API-Key: $API_KEY"
```
---
## Processing Pipeline
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/v1/probe` | Yes | Probe video metadata |
| GET | `/api/v1/assets/:uuid/probe` | Yes | Get probe result by UUID |
| POST | `/api/v1/assets/:uuid/process` | Yes | Trigger processing pipeline |
| GET | `/api/v1/assets/:uuid/status` | Yes | Get asset processing status |
| GET | `/api/v1/jobs/:job_id` | Yes | Get job status by ID |
| GET | `/api/v1/jobs` | Yes | List all jobs |
| GET | `/api/v1/rules/:rule/status` | Yes | Get rule processing status |
### Trigger Processing
```bash
curl -X POST http://localhost:3002/api/v1/assets/$UUID/process \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"rules": ["rule1"], "processors": ["asr", "yolo", "face"]}'
```
---
## Search APIs
### Vector Search
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/v1/search` | Yes | Vector/semantic search |
| POST | `/api/v1/search/hybrid` | Yes | Hybrid search (vector + BM25) |
| POST | `/api/v1/search/bm25` | Yes | BM25 text search |
### N8N Search (Library Functions)
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/v1/n8n/search` | Yes | N8N vector search |
| POST | `/api/v1/n8n/search/bm25` | Yes | N8N BM25 search |
| POST | `/api/v1/n8n/search/hybrid` | Yes | N8N hybrid search |
| POST | `/api/v1/n8n/search/smart` | Yes | N8N smart search |
### Search Request Body
```json
{
"query": "男女主角見面的場景",
"uuid": "optional-video-uuid",
"types": ["chunk", "frame", "person"],
"time_range": [0.0, 60.0],
"filters": {
"min_confidence": 0.8,
"required_object_classes": ["person"],
"speaker_id": "speaker_1"
},
"limit": 20,
"offset": 0
}
```
---
## Visual Chunk Search
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/v1/search/visual` | Yes | Visual chunk search |
| POST | `/api/v1/search/visual/class` | Yes | Search by object class |
| POST | `/api/v1/search/visual/density` | Yes | Search by spatial density |
| POST | `/api/v1/search/visual/stats` | Yes | Get visual statistics |
| POST | `/api/v1/search/visual/combination` | Yes | Search by object combination |
### Visual Search Request
```bash
curl -X POST http://localhost:3002/api/v1/search/visual \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"uuid": "abc123",
"criteria": {
"min_avg_confidence": 0.8,
"required_classes": ["person", "car"],
"min_spatial_density": 0.5
}
}'
```
### Search by Class
```bash
curl -X POST http://localhost:3002/api/v1/search/visual/class \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"uuid": "abc123",
"object_class": "person",
"min_count": 5,
"max_count": 20
}'
```
---
## Face Recognition
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/v1/face/recognize` | Yes | Recognize faces in video |
| POST | `/api/v1/face/register` | Yes | Register a face |
| POST | `/api/v1/face/search` | Yes | Search similar faces |
| GET | `/api/v1/face/list` | Yes | List all faces |
| GET | `/api/v1/face/:face_id` | Yes | Get face details |
| DELETE | `/api/v1/face/:face_id` | Yes | Delete a face |
| GET | `/api/v1/face/results/:video_uuid` | Yes | Get recognition results |
---
## Person Identity
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/v1/person/identify` | Yes | Identify persons in video |
| POST | `/api/v1/person/auto-identify` | Yes | Auto-identify persons |
| POST | `/api/v1/person/suggest` | Yes | Get person suggestions |
| GET | `/api/v1/person/list` | Yes | List all persons |
| GET | `/api/v1/person/:person_id` | Yes | Get person details |
| PATCH | `/api/v1/person/:person_id` | Yes | Update person identity |
| GET | `/api/v1/person/:person_id/timeline` | Yes | Get person timeline |
| GET | `/api/v1/person/:person_id/appearances` | Yes | Get person appearances |
| GET | `/api/v1/person/:person_id/thumbnail` | Yes | Get person thumbnail |
| POST | `/api/v1/person/merge` | Yes | Merge two persons |
| POST | `/api/v1/person/merge/undo` | Yes | Undo merge |
| GET | `/api/v1/person/merge/history` | Yes | Get merge history |
| POST | `/api/v1/person/:person_id/split` | Yes | Split a person |
| GET | `/api/v1/person/:person_id/similar` | Yes | Get similar persons |
| PATCH | `/api/v1/person/:person_id/confirm` | Yes | Confirm suggestion |
| POST | `/api/v1/person/:person_id/unbind-speaker` | Yes | Unbind speaker |
| POST | `/api/v1/person/:person_id/reassign-speaker` | Yes | Reassign speaker |
| POST | `/api/v1/person/:person_id/remove-appearance` | Yes | Remove appearance |
| POST | `/api/v1/person/:person_id/reassign-appearance` | Yes | Reassign appearance |
| POST | `/api/v1/person/:person_id/register` | Yes | Register identity |
| GET | `/api/v1/chunks/:chunk_id/persons` | Yes | Get chunk persons |
---
## Global Identities
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/v1/identities/from-person` | Yes | Register from person |
| GET | `/api/v1/identities` | Yes | List all identities |
| GET | `/api/v1/identities/:identity_id/videos` | Yes | Get identity videos |
| GET | `/api/v1/identities/:identity_id/faces` | Yes | Get identity faces |
| POST | `/api/v1/identities/search` | Yes | Search identities |
| GET | `/api/v1/videos/:uuid/faces` | Yes | Get video faces |
---
## Identity Binding
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/v1/identities/bind` | Yes | Bind identity |
| POST | `/api/v1/identities/unbind` | Yes | Unbind identity |
| GET | `/api/v1/identity/:binding_type/:binding_value` | Yes | Get identity info |
| GET | `/api/v1/signals/unbound` | Yes | List unbound signals |
| GET | `/api/v1/signals/:uuid/:binding_type/:binding_value/timeline` | Yes | Get signal timeline |
| POST | `/api/v1/identities/suggest-av` | Yes | Suggest AV bindings |
---
## Configuration
| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/api/v1/config/cache` | Yes | Toggle cache |
### Toggle Cache
```bash
curl -X POST http://localhost:3002/api/v1/config/cache \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
```
---
## Authentication
All endpoints except `/health` and `/health/detailed` require an API key:
```bash
export API_KEY="muser_xxx"
curl -H "X-API-Key: $API_KEY" http://localhost:3002/api/v1/videos
```
---
## ⚠️ Notable Notes
1. **Universal Search routes** (`/api/v1/search/universal`, `/api/v1/search/frames`, `/api/v1/search/persons`) are defined in `universal_search.rs` but **NOT MOUNTED** in `server.rs`.
2. **Who routes** (`/api/v1/who`, `/api/v1/who/candidates`) are defined in `who.rs` but **NOT MOUNTED** in `server.rs`.
3. **Total endpoints**: 71 reachable + 6 unreachable = 77 defined.
---
## 📁 Related Documents
| Document | Location |
|----------|----------|
| API Examples | `IMPLEMENTATION/API_EXAMPLES.md` |
| cURL Examples | `IMPLEMENTATION/API_CURL_EXAMPLES.md` |
| WordPress Guide | `IMPLEMENTATION/API_WORDPRESS_GUIDE.md` |
| n8n Guide | `IMPLEMENTATION/API_N8N_GUIDE.md` |
| API Key Design | `REFERENCE/API_KEY_DESIGN.md` |
| API Key Architecture | `ARCHITECTURE/API_KEY_ARCHITECTURE.md` |

View File

@@ -0,0 +1,407 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core API 教育訓練手冊"
date: "2026-03-25"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "core"
- "教育訓練手冊"
ai_query_hints:
- "查詢 Momentry Core API 教育訓練手冊 的內容"
- "Momentry Core API 教育訓練手冊 的主要目的是什麼?"
- "如何操作或實施 Momentry Core API 教育訓練手冊?"
---
# Momentry Core API 教育訓練手冊
> **對象**: marcom 團隊
> **版本**: V1.4 | **日期**: 2026-03-25
---
## 1. 快速開始
### 基本資訊
| 項目 | 值 |
|------|-----|
| API 網址 | `https://api.momentry.ddns.net` |
| 認證方式 | Header `X-API-Key` |
| 格式 | JSON |
### Demo 測試帳號
#### API Key用於 API 認證)
```
X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69
```
#### SFTPGo用於影片上傳
| 項目 | 值 |
|------|-----|
| SFTP 主機 | `sftpgo.momentry.ddns.net` |
| SFTP 連接埠 | `2022` |
| 用戶名 | `demo` |
| 密碼 | `demopassword123` |
| Web 管理介面 | `https://sftpgo.momentry.ddns.net` |
**使用方式**:透過 SFTP 上傳影片,系統會自動註冊並處理。
---
## 2. 快速範例
### 查詢所有影片
```bash
curl -s -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
"https://api.momentry.ddns.net/api/v1/videos"
```
### 查詢單一影片
```bash
curl -s -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
"https://api.momentry.ddns.net/api/v1/videos/{uuid}/details"
```
### 查詢處理任務狀態
```bash
curl -s -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
"https://api.momentry.ddns.net/api/v1/jobs/{job_id}"
```
---
## 3. API 端點說明
### 3.1 影片相關
#### GET /api/v1/videos
取得所有影片列表
**回應範例**:
```json
{
"videos": [
{
"uuid": "5dea6618a606e7c7",
"filename": "demo_video.mp4",
"duration": 123.45,
"status": "ready",
"created_at": "2026-03-25T10:00:00Z"
}
]
}
```
#### GET /api/v1/videos/:uuid/details
取得單一影片詳情(包含 chunks、processing status 等)
### 3.2 搜尋與分段查詢
#### POST /api/v1/search
向量搜尋查詢分段Chunk詳情
**請求參數**:
| 參數 | 類型 | 必填 | 說明 |
|------|------|------|------|
| `query` | string | 是 | 搜尋關鍵字 |
| `limit` | number | 否 | 回傳數量(預設 10 |
| `uuid` | string | 否 | 只搜尋指定影片 |
**請求範例**:
```json
{
"query": "天氣",
"limit": 10,
"uuid": "5dea6618a606e7c7"
}
```
**回應範例**:
```json
{
"results": [
{
"uuid": "39567a0eb16f39fd",
"chunk_id": "sentence_1471",
"chunk_type": "sentence",
"start_time": 5309.08,
"end_time": 5311.08,
"text": "influenced by a vital way,",
"score": 0.68
}
],
"query": "天氣"
}
```
**Chunk 欄位說明**:
| 欄位 | 說明 | 範例 |
|------|------|------|
| `uuid` | 影片唯一識別碼 | `39567a0eb16f39fd` |
| `chunk_id` | 分段識別碼 | `sentence_1471` |
| `chunk_type` | 分段類型 | `sentence` / `cut` / `time` / `trace` / `story` |
| `start_time` | 開始時間(秒) | `5309.08` |
| `end_time` | 結束時間(秒) | `5311.08` |
| `text` | 內容文字 | `influenced by a vital way` |
| `score` | 相似度分數0-1 | `0.68` |
**Chunk 類型說明**:
| 類型 | 說明 | 來源 |
|------|------|------|
| `sentence` | 語音轉文字片段 | ASR 處理 |
| `cut` | 場景變化片段 | CUT 處理 |
| `time` | 固定時間分段 | 系統自動切割 |
| `trace` | 軌跡追蹤片段 | YOLO 追蹤 |
| `story` | 故事線片段(父子關係) | Story 分析 |
#### POST /api/v1/n8n/search
n8n 專用搜尋(包含完整影片檔案路徑 file_path
**請求參數**: 與 `/search` 相同
**回應範例**:
```json
{
"query": "天氣",
"count": 2,
"hits": [
{
"id": "sentence_1471",
"vid": "39567a0eb16f39fd",
"start": 5309.08,
"end": 5311.08,
"title": "Chunk sentence_1471",
"text": "influenced by a vital way,",
"score": 0.68,
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
```
**與 /search 的差異**:
| 欄位 | `/search` | `/n8n/search` |
|------|-----------|----------------|
| 影片 UUID | `uuid` | `vid` |
| Chunk ID | `chunk_id` | `id` |
| 開始時間 | `start_time` | `start` |
| 結束時間 | `end_time` | `end` |
| 相似度分數 | `score` | `score` |
| **檔案路徑** | ❌ | ✅ `file_path` |
> **注意**: `file_path` 是影片的實際路徑,可用於本地播放。
### 3.3 任務相關
### 3.4 任務相關
#### GET /api/v1/jobs/:uuid
查詢處理任務狀態
**回應範例**:
```json
{
"uuid": "9760d0820f0cf9a7",
"video_uuid": "5dea6618a606e7c7",
"status": "completed",
"progress": 100,
"created_at": "2026-03-25T10:00:00Z",
"completed_at": "2026-03-25T10:05:00Z"
}
```
#### GET /api/v1/jobs
查詢所有任務
**查詢參數**:
| 參數 | 說明 | 範例 |
|------|------|------|
| `status` | 篩選狀態 | `pending`, `processing`, `completed`, `failed` |
| `limit` | 回傳數量 | `10` |
**範例**:
```bash
curl -s -H "X-API-Key: ..." \
"https://api.momentry.ddns.net/api/v1/jobs?status=completed&limit=5"
```
### 3.5 系統管理
#### POST /api/v1/config/cache
切換快取功能(管理員專用)
**請求範例**:
```json
{
"enabled": true
}
```
**回應範例**:
```json
{
"cache_enabled": true,
"message": "Cache toggled successfully"
}
```
#### POST /api/v1/unregister
刪除影片及其所有關聯資料(管理員專用)
**請求範例**:
```json
{
"uuid": "5dea6618a606e7c7"
}
```
**回應範例**:
```json
{
"success": true,
"message": "Video unregistered successfully",
"uuid": "5dea6618a606e7c7"
}
```
**注意**: 此操作會刪除影片及其所有分段、處理結果、縮圖等關聯資料,**無法復原**。
### 3.6 健康檢查
#### GET /health
服務健康狀態(**無需認證**
**回應**:
```json
{
"status": "ok",
"version": "0.9.20260325_144654"
}
```
---
## 4. n8n Workflow 範例
### 4.1 基本設定
在 n8n workflow 中使用 HTTP Request 節點:
```
┌─────────────────┐
│ HTTP Request │
├─────────────────┤
│ Method: GET │
│ URL: https://api.momentry.ddns.net/api/v1/videos
│ Headers: │
│ X-API-Key: │
│ [YOUR_KEY] │
└────────┬────────┘
┌─────────────────┐
│ 處理回應資料 │
└─────────────────┘
```
### 4.2 範例:檢查任務狀態
```javascript
// n8n Function Node 範例
const jobUuid = $input.item.json.uuid;
return [{
json: {
method: "GET",
url: `https://api.momentry.ddns.net/api/v1/jobs/${jobUuid}`,
headers: {
"X-API-Key": "YOUR_API_KEY"
}
}
}];
```
---
## 5. 常見問題
### Q: 返回 401 錯誤怎麼辦?
確認 Header 中有正確的 `X-API-Key`
### Q: 如何確認影片處理完成?
```
GET /api/v1/jobs/{uuid}
```
檢查 `status` 是否為 `completed`
### Q: 查不到資料?
確認 UUID 格式正確16碼 hex 字串)
---
## 6. 快速參考卡
```
┌─────────────────────────────────────────────────────────────┐
│ Momentry API 速查 │
├─────────────────────────────────────────────────────────────┤
│ 查詢所有影片 GET /api/v1/videos │
│ 查詢單一影片 GET /api/v1/videos/:uuid │
│ 向量搜尋 POST /api/v1/search │
│ n8n 搜尋 POST /api/v1/n8n/search │
│ 查詢任務狀態 GET /api/v1/jobs/:uuid │
│ 查詢所有任務 GET /api/v1/jobs │
│ 快取設定 POST /api/v1/config/cache (管理員) │
│ 刪除影片 POST /api/v1/unregister (管理員) │
│ 健康檢查 GET /health (免認證) │
├─────────────────────────────────────────────────────────────┤
│ Header: X-API-Key: [YOUR_KEY] │
│ URL: https://api.momentry.ddns.net │
└─────────────────────────────────────────────────────────────┘
```
---
## 附錄:回應狀態說明
### 任務狀態 (status)
| 狀態 | 說明 |
|------|------|
| `pending` | 等待處理 |
| `processing` | 處理中 |
| `completed` | 已完成 |
| `failed` | 處理失敗 |
### 影片狀態 (status)
| 狀態 | 說明 |
|------|------|
| `uploading` | 上傳中 |
| `pending` | 等待處理 |
| `processing` | 處理中 |
| `ready` | 已就緒 |
| `error` | 錯誤 |
---
## 附錄:版本歷史
| 版本 | 日期 | 內容 | 操作人 |
|------|------|------|--------|
| V1.0 | 2026-03-25 | 初版建立 | OpenCode |
| V1.1 | 2026-03-25 | 新增快取/刪除 API、搜尋端點文件 | OpenCode |
| V1.2 | 2026-03-25 | 新增 Chunk 欄位說明、類型、播放方式 | OpenCode |
| V1.3 | 2026-03-25 | 新增 Demo 測試帳號SFTPGo| OpenCode |
| V1.4 | 2026-03-25 | 更新 n8n 搜尋回傳欄位說明 (media_url→file_path) | OpenCode |

View File

@@ -0,0 +1,559 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core 開發日誌"
date: "2026-03-18"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "開發日誌"
- "momentry"
- "core"
ai_query_hints:
- "查詢 Momentry Core 開發日誌 的內容"
- "Momentry Core 開發日誌 的主要目的是什麼?"
- "如何操作或實施 Momentry Core 開發日誌?"
---
# Momentry Core 開發日誌
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
> **文檔維護開始**2026-03-18
> **⚠️ 補充說明**事後補記2026-03-18 以前),僅供參考。未來紀錄將即時記錄,參考價值較高。
---
## 開發工具
### Coding LLM 模型
| 階段 | 工具 | 模型 | ID | 說明 |
|------|------|------|-----|------|
| **初期** | Claude CLI | - | - | 初始專案架構建立 |
| **中期** | OpenCode | big-pickle | opencode/big-pickle | 主要開發協作者 |
**切換記錄**
- 初期使用 Claude CLI 建立專案基本架構
- 中期切換至 OpenCode (big-pickle) 進行主要功能開發
---
## 2026-03-17
### ML 模型選用
| Processor | 模型 | 版本/大小 | 說明 |
|----------|------|-----------|------|
| **ASR** | WhisperX (faster-whisper) | base, int8 | 語音識別 + 對話分段 |
| **CUT** | PySceneDetect | 0.6.7.1 | ContentDetector 場景檢測 |
| **YOLO** | YOLOv8n | yolov8n.pt (6.2MB) | 物體檢測nano 版本最快) |
| **OCR** | EasyOCR | 1.7.2 | 文字識別 |
| **Face** | OpenCV Haar Cascade | built-in | 人臉檢測(無需額外下載) |
| **Pose** | YOLOv8n-Pose | yolov8n-pose.pt (6.5MB) | 姿態估計nano 版本) |
**模型下載**
- YOLOv8n: `yolov8n.pt` (6.2MB)
- YOLOv8n-Pose: `yolov8n-pose.pt` (6.5MB)
**Python 依賴**
```
torch==2.8.0
whisperx==3.8.2
ultralytics==8.4.23
scenedetect==0.6.7.1
easyocr==1.7.2
opencv-python==4.13.0.92
```
---
### ASR 實作完成
- 完成 Python ML processor scripts使用本地模型
- `asrx_processor.py` - whisperx for speaker diarization
- `cut_processor.py` - PySceneDetect for scene detection
- `yolo_processor.py` - YOLOv8 for object detection
- `ocr_processor.py` - EasyOCR for text recognition
- `face_processor.py` - OpenCV Haar Cascade for face detection
- `pose_processor.py` - YOLOv8 Pose for pose estimation
- 更新 `requirements.txt` with all dependencies
- 安裝完成torch 2.8.0, whisperx 3.8.2, ultralytics 8.4.23, scenedetect 0.6.7.1, easyocr 1.7.2, opencv-python 4.13.0.92
- 下載模型YOLOv8n.pt (6.2MB), YOLOv8n-Pose.pt (6.5MB)
### Async Streaming 實作
- 更新 Rust processor modules 使用 async streaming 進行 real-time progress
- `src/core/processor/asr.rs`
- `src/core/processor/cut.rs`
- `src/core/processor/yolo.rs`
- `src/core/processor/ocr.rs`
- `src/core/processor/face.rs`
- `src/core/processor/pose.rs`
### 測試結果
- 測試影片BigBuckBunny_320x180.mp4
- ASR: 4 segments
- CUT: 134 scenes
- YOLO: 14315 frames每幀處理耗時
- OCR: 40 frames with text
- Face: 44 frames with faces
- Pose: Timeout
---
### Warning 清理
修復 clippy warnings
- 移除未使用的 imports (HashMap in mongodb_db.rs, postgres_db.rs)
- 新增 `#[allow(dead_code)]` 標註未使用變數
- 新增 `Default` implementation for MongoDb, QdrantDb
-`probe` module 重新命名為 `ffprobe`
- 新增 `player` feature in Cargo.toml
- 修復 `format_in_format_args` 警告
---
### TUI Progress Window 實作
建立新的 UI module
- 建立 `src/ui/mod.rs`
- 建立 `src/ui/progress/mod.rs`
實作功能:
- ProcessorProgress 結構(追蹤每個 processor 狀態)
- ProgressState 結構(管理所有 processors
- ProgressUi 結構ratatui TUI 渲染)
- 整合到 `src/main.rs` 的 process 命令
TUI 顯示:
```
┌ Processing: BigBuckBunny_320x180.mp4 ────────────────────────────────────────┐
│ ASR [████████████] 100% (4 segs) │
│ CUT [████████████] 100% (134 scenes) │
│ ASRX [████████████] 100% (0 segs) │
│ YOLO [██░░░░░░░░░░░] 30% (4200/14315) ETA 2:30 │
│ OCR [---------] 0% │
│ Face [---------] 0% │
│ Pose [---------] 0% │
└──────────────────────────────────────────────────────────────────────────────┘
```
---
### 輸出位置討論
討論 stdout vs stderr vs TUI 的輸出配置:
- 最終結果 → stdout
- Python progress → 需改用 Redis Pub/Sub
- TUI Progress → stderr (ratatui)
---
## 2026-03-18
### Redis Message Bus 設計
討論使用 Redis 作為消息總線,分離 Python 輸出與 Rust TUI 顯示。
設計重點:
1. 頻道命名:`momentry:progress:{uuid}`
2. 本地 Redis`localhost:6379`
3. 失敗策略:完全失效(因 stdout 問題未解決)
### UUID 使用時機分析
分析 Redis Key 上使用 UUID 的時機:
**全局 Keys無 UUID**
- health, stats, jobs 管理
**Per-Video KeysUUID 必要)**
- job:{uuid}, progress:{uuid}, metrics:{uuid}
**Per-Processor KeysUUID + Processor 必要)**
- job:{uuid}:processor:{name}
### 備份系統整合
參考 `docs_v1.0/IMPLEMENTATION/SERVICE_ADDITION_GUIDE.md` 設計規範,規劃 OutputDir 模組:
1. **環境變數**
- `MOMENTRY_OUTPUT_DIR` - JSON 輸出目錄
- `MOMENTRY_BACKUP_DIR` - 備份目錄(預設:`/Users/accusys/momentry/backup/momentry`
- `MOMENTRY_BACKUP_ENABLED` - 啟用備份
2. **命名格式**
- 備份格式:`momentry_data_{YYYYMMDD}_{HHMMSS}_{uuid}.{ext}`
- 校驗和:`{filename}.sha256`
3. **CLI 命令**
- `cargo run -- backup list` - 列出備份
- `cargo run -- backup cleanup` - 清理舊備份
- `cargo run -- backup verify` - 驗證備份
---
### 監控系統整合
討論將 momentry_core 納入監控系統:
1. **Layer 2: Service 監控**
- 新增 momentry_core CLI 檢查
2. **Layer 7: Backup 監控**
- 新增 momentry 備份配置
3. **Redis 監控**
- 健康檢查
- Job 狀態監控
- 即時進度監控
---
## 實作完成項目
### 程式碼變更
| 日期 | 檔案 | 變更 |
|------|------|------|
| 2026-03-17 | `src/core/processor/*.rs` | Async streaming 更新 |
| 2026-03-17 | `src/ui/mod.rs` | 新增 UI module |
| 2026-03-17 | `src/ui/progress/mod.rs` | 新增 Progress TUI |
| 2026-03-17 | `src/main.rs` | 整合 Progress UI |
| 2026-03-18 | `src/core/storage/output_dir.rs` | 新增 OutputDir 模組 |
| 2026-03-18 | `src/core/storage/mod.rs` | 新增 output_dir export |
| 2026-03-18 | `src/core/db/redis_client.rs` | 新增 Redis 客戶端Hash + Pub/Sub |
| 2026-03-18 | `src/core/db/mod.rs` | 新增 redis_client export |
### 新增檔案
| 日期 | 檔案 | 說明 |
|------|------|------|
| 2026-03-18 | `docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md` | Redis Key 設計規範 |
| 2026-03-18 | `docs_v1.0/OPERATIONS/MOMENTRY_CORE_MONITORING.md` | 監控規範(暫定) |
| 2026-03-18 | `scripts/redis_publisher.py` | Redis 訊息發布模組 |
### 更新檔案
| 日期 | 檔案 | 說明 |
|------|------|------|
| 2026-03-17 | `Cargo.toml` | 新增 player feature |
| 2026-03-17 | `src/lib.rs` | 新增 ui module exports |
| 2026-03-18 | `docs_v1.0/REFERENCE/PENDING_ISSUES.md` | 新增問題 #2, #3 |
| 2026-03-18 | `src/core/storage/output_dir.rs` | 預設改為 `./output` |
| 2026-03-18 | `scripts/yolo_processor.py` | 新增 --uuid 參數 + Redis |
| 2026-03-18 | `scripts/cut_processor.py` | 新增 Redis |
| 2026-03-18 | `scripts/ocr_processor.py` | 新增 Redis |
| 2026-03-18 | `scripts/face_processor.py` | 新增 --uuid 參數 + Redis |
| 2026-03-18 | `scripts/pose_processor.py` | 新增 --uuid 參數 + Redis |
| 2026-03-18 | `scripts/asr_processor.py` | 新增 --uuid 參數 + Redis |
| 2026-03-18 | `scripts/asrx_processor.py` | 新增 --uuid 參數 + Redis |
| 2026-03-18 | `requirements.txt` | 新增 redis>=5.0.0 |
| 2026-03-18 | `src/core/processor/yolo.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/cut.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/ocr.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/face.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/pose.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/asr.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/core/processor/asrx.rs` | 新增 uuid 參數 |
| 2026-03-18 | `src/main.rs` | 更新所有 processor 調用傳入 uuid |
| 2026-03-18 | `Cargo.toml` | 新增 futures-util 依賴 |
| 2026-03-18 | `src/core/db/redis_client.rs` | 新增 subscribe_and_callback 方法,密碼認證 |
| 2026-03-18 | `src/ui/progress/mod.rs` | 新增 update_from_redis 方法 |
| 2026-03-18 | `scripts/redis_publisher.py` | 新增密碼認證支援 |
| 2026-03-18 | 測試 | Redis Pub/Sub 成功運作 |
---
## 待解決問題
### 問題 #1: sqlx async INSERT 不會實際寫入數據庫
- 狀態:待解決
- 影響:`store_vector` 函數PVector 存儲
### 問題 #2: TUI 與 stdout 輸出混合
- 狀態:已解決
- 解決方案:使用 Redis Message Bus
- 進度:
- ✅ Redis 客戶端 (`src/core/db/redis_client.rs`)
- ✅ Python redis_publisher.py
- ✅ 所有 Python processors 更新完成
- ✅ 所有 Rust processor 函數更新完成
- ✅ main.rs 調用更新完成
- ✅ Rust TUI Redis 訂閱已完成
### 問題 #3: Redis Message Bus 尚未實作
- 狀態:已解決
- 詳細設計:參考 `docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md`
- 進度Python 端 + Rust 端均已完成
---
## 環境變數
```bash
# 輸出目錄
MOMENTRY_OUTPUT_DIR=./output # 預設
# 備份
MOMENTRY_BACKUP_ENABLED=false # 預設
MOMENTRY_BACKUP_DIR=/Users/accusys/momentry/backup/momentry
# Redis未來實作
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=accusys
```
---
## 數據庫
- PostgreSQL: `postgres://accusys@localhost:5432/momentry`
- Redis: `localhost:6379`(待實作)
- Qdrant: `localhost:6333`
---
## 指令範例
```bash
# 註冊視頻
cargo run -- register /path/to/video.mp4
# 處理視頻
cargo run -- process <uuid>
# 列出備份
cargo run -- backup list
# 清理備份
cargo run -- backup cleanup
# 驗證備份
cargo run -- backup verify
# 查看狀態
cargo run -- status
# API Server
cargo run -- server --host 0.0.0.0 --port 3000
```
---
## 2026-03-18 (進行中)
### Redis Message Bus 實作
**問題**TUI 與 Python stdout 輸出混合,導致 TUI 顯示混亂
**解決方案**:使用 Redis Pub/Sub 作為訊息匯流排
**實作內容**
| 元件 | 檔案 | 狀態 |
|------|------|------|
| Redis 客戶端 | `src/core/db/redis_client.rs` | ✅ |
| Progress 訂閱 | `src/main.rs` | ✅ |
| UI 更新 | `src/ui/progress/mod.rs` | ✅ |
| Python Publisher | `scripts/redis_publisher.py` | ✅ |
| Python Processors | 7 個 `scripts/*_processor.py` | ✅ |
| Rust 函數 | `src/core/processor/*.rs` | ✅ |
**流程**
```
Python Processor ──(Redis Pub)──> Redis ──(Subscribe)──> Rust TUI
```
**測試結果**
- Redis 連線 ✅
- 密碼認證 ✅
- 即時進度發布 ✅
- TUI 即時更新 ✅
**新增依賴**
- `futures-util = "0.3"` (Cargo.toml)
- `redis >= 5.0.0` (requirements.txt)
---
## 2026-03-18 (HTTP API)
### HTTP API 實作
**問題**TUI 運作正常但使用者偏好 HTTP API 來查詢進度
**解決方案**:建立 HTTP 端點 + Redis Hash 儲存
**實作內容**
| 元件 | 檔案 | 變更 |
|------|------|------|
| HTTP 端點 | `src/api/server.rs` | 新增 `/api/v1/progress/:uuid` |
| Redis Hash 查詢 | `src/core/db/redis_client.rs` | 新增 `get_processor_status` 方法 |
| Progress 儲存 | `src/main.rs` | 新增 Redis HSET 儲存進度 |
**API 端點**
```
GET /api/v1/progress/:uuid
Response:
{
"uuid": "5dea6618a606e7c7",
"processors": [
{"name": "asr", "status": "complete", "current": 0, "total": 0, "message": "7 segments"},
{"name": "cut", "status": "complete", "current": 134, "total": 134, "message": "134 scenes"},
{"name": "yolo", "status": "complete", "current": 14300, "total": 14315, "message": "..."},
...
]
}
```
**流程**
```
Python Processor ──(Redis Pub)──> Redis ──(Subscribe)──> Rust TUI
└──(HSET)──> Redis Hash
HTTP Client ──(GET /progress/:uuid)──> Rust API ─(HGETALL)──> Redis Hash
```
**測試結果**
- ✅ 編譯成功
- ✅ API 伺服器啟動 (port 3002)
- ✅ 即時進度查詢
- ✅ 完整流程測試 (BigBuckBunny_320x180.mp4)
**除錯記錄**
1. 語法錯誤main.rs 有重複程式碼區塊 (lines 297-322),已移除
2. DB 連線池:從 5 增加到 10 個連線
3. PostgreSQL 狀態:處理 shutdown 狀態,殺掉 stale 連線
**新增變更**
- `src/api/server.rs` - 新增進度端點
- `src/core/db/redis_client.rs` - 新增 `get_processor_status` 方法
- `src/core/db/postgres_db.rs` - 連線池 5→10
- `src/main.rs` - Redis Hash 儲存 + 語法修復
**使用方式**
```bash
# 啟動 API 伺服器
cargo run --bin momentry -- server --host 127.0.0.1 --port 3002
# 註冊影片
cargo run --bin momentry -- register ~/test_video/BigBuckBunny_320x180.mp4
# 處理影片
cargo run --bin momentry -- process <uuid>
# 查詢進度
curl http://127.0.0.1:3002/api/v1/progress/<uuid>
```
---
## 2026-03-18 (Dashboard)
### Web Dashboard 實作
**目標**:建立 Web 介面監控 momentry_core 處理進度
**技術選擇**Static HTML + JavaScript (非 WASM)
**實作內容**
| 元件 | 檔案 | 說明 |
|------|------|------|
| Dashboard | `momentry_dashboard/dist/index.html` | 靜態 HTML 頁面 |
| API 代理 | Caddyfile port 3200 | 反向代理到 API server |
**功能**
- 影片列表顯示
- 即時進度條 (每 5 秒自動刷新)
- 搜尋功能
- 處理器狀態 (ASR/CUT/YOLO/OCR/Face/Pose)
**訪問**
- Dashboard: http://localhost:3200
- API: http://localhost:3200/api/v1/*
---
## 發生問題記錄
### HTTP API 問題
1. **語法錯誤** (main.rs)
- 位置lines 297-322
- 原因:重複的程式碼區塊
- 解決:移除重複區塊
2. **DB 連線池耗盡**
- 原因:預設 5 個連線不足
- 解決:增加到 10 個連線
3. **PostgreSQL shutdown 狀態**
- 原因:共享記憶體未釋放
- 解決:殺掉 stale 連線
### WASM Dashboard 問題
1. **Yew 版本問題**
- 嘗試yew 0.21 → 0.23
- 問題feature 名稱變更 (`web-sys``web_sys``csr`)
- 解決:放棄 WASM改用靜態 HTML
2. **編譯錯誤**
- `wasm32-unknown-unknown` target 未安裝
- 解決:`rustup target add wasm32-unknown-unknown`
3. **Yew 0.23 API 變更**
- Properties 需要 PartialEq derive
- 多處 API 語法變更
- 放棄 WASM 方案
### Gitea Push 問題
1. **Remote URL 錯誤**
- 原因:使用 localhost:3000 而非 gitea.momentry.ddns.net
- 解決:建立新 repo `momentry_core_0_1`
2. **認證問題**
- SSH key 未授權
- 密碼認證成功推送
### Caddy 設定問題
1. **API 代理順序**
- 問題try_files 在 reverse_proxy 之前導致 API 回傳 HTML
- 解決:使用 `handle` 區塊明確定義順序
```caddyfile
:3200 {
handle /api/* {
reverse_proxy localhost:3002
}
handle {
root * /Users/accusys/momentry_dashboard/dist
try_files {path} /index.html
file_server
}
}
```
---
## 未來工作
- [ ] 修復 WASM Dashboard (Yew 0.23 相容性)
- [ ] 新增影片播放器整合
- [ ] WebSocket 實時推送
- [ ] 移動端響應式設計

View File

@@ -0,0 +1,187 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Document Embedding Strategy - Parent-Child Chunks"
date: "2026-03-23"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "embedding"
- "chunks"
- "strategy"
- "document"
ai_query_hints:
- "查詢 Document Embedding Strategy - Parent-Child Chunks 的內容"
- "Document Embedding Strategy - Parent-Child Chunks 的主要目的是什麼?"
- "如何操作或實施 Document Embedding Strategy - Parent-Child Chunks"
---
# Document Embedding Strategy - Parent-Child Chunks
| Item | Content |
|------|---------|
| Author | Warren |
| Created | 2026-03-23 |
| Document Version | V1.0 |
---
## Version History
| Version | Date | Purpose | Operator | Tool/Model |
|---------|------|---------|----------|------------|
| V1.0 | 2026-03-23 | Create document embedding strategy | Warren | OpenCode |
---
## Overview
Momentry uses a **parent-child chunk hierarchy** for improved RAG retrieval. This document describes the embedding strategy for this hierarchy.
## Chunk Structure
### Parent Chunk
- **Purpose**: Summarize multiple child chunks with narrative description
- **Content**: High-level description of multiple scenes/segments
- **Example**:
```json
{
"chunk_id": "story_asr_0000",
"chunk_type": "story",
"text_content": "[0s-125s] A man enters a building. He walks down a hallway.",
"child_chunk_ids": ["asr_0001", "asr_0002", "asr_0003", "asr_0004", "asr_0005"]
}
```
### Child Chunk
- **Purpose**: Individual segments from ASR, scenes from CUT, etc.
- **Content**: Raw transcription or detection results
- **Example**:
```json
{
"chunk_id": "asr_0001",
"chunk_type": "sentence",
"text_content": "Hello world",
"parent_chunk_id": "story_asr_0000"
}
```
## Embedding Strategy
### For Vector Search
When embedding chunks for vector search, we combine **parent description + child content** to provide both context and detail.
#### Parent Chunk Embedding
```
embedding_text = f"Summary: {parent.text_content}
Children: {child_text_1}. {child_text_2}. {child_text_3}..."
```
**Prefix**: `search_document:` (for documents in Qdrant)
**Example**:
```
search_document: Summary: A man enters a building. He walks down a hallway.
Children: Hello, how are you? I'm fine thank you. The weather is nice today.
```
#### Child Chunk Embedding
```
embedding_text = f"[{child.chunk_type}] {child.text_content}
Parent: {parent.description}"
```
**Prefix**: `search_document:`
**Example**:
```
search_document: [sentence] Hello, how are you?
Parent: A man enters a building. He walks down a hallway.
```
### For BM25 Text Search
BM25 operates on raw text with PostgreSQL full-text search.
- **Index**: `search_vector` (TSVECTOR) on `chunks.text_content`
- **Search**: Uses `ts_rank_cd()` for ranking
## Hybrid Search Ranking
Combined score = `(vector_score * 0.7) + (bm25_score * 0.3)`
### Why 0.7/0.3?
| Weight | Vector | BM25 |
|--------|--------|------|
| Pros | Semantic similarity | Exact keyword match |
| Cons | May miss specific terms | No semantic understanding |
| Best for | Thematic queries | Fact lookup |
## Query Patterns
### Thematic Query ("What are the main themes?")
- Use higher `vector_weight` (0.8-0.9)
- Vector search finds semantically similar content
### Fact Lookup ("Who said X?")
- Use higher `bm25_weight` (0.5-0.7)
- BM25 finds exact matches
### Balanced ("Tell me about scene 5")
- Use default 0.7/0.3
## Implementation
### Embedding Generation
```rust
fn build_embedding_text(chunk: &Chunk, parent_text: Option<&str>) -> String {
match chunk.chunk_type {
ChunkType::Story => {
format!(
"Summary: {}\nChildren: {}",
chunk.text_content,
get_children_text(chunk)
)
}
_ => {
format!(
"[{}] {}\nParent: {}",
chunk.chunk_type.as_str(),
chunk.text_content,
parent_text.unwrap_or("N/A")
)
}
}
}
```
### Storage
- Parent chunks stored with their `child_chunk_ids`
- Child chunks reference `parent_chunk_id`
- Both stored in PostgreSQL with full-text index
- Vectors stored in Qdrant
## Example Flow
1. **Story Processing** generates parent-child hierarchy
2. **Embedding** creates vector for each chunk
3. **Storage** saves to PostgreSQL + Qdrant
4. **Search** retrieves using hybrid search
5. **Results** include both parent context and child details
## Best Practices
1. **Chunk Size**: 5 child chunks per parent (configurable)
2. **Text Length**: Keep embeddings under 512 tokens
3. **Parent Description**: Include temporal markers (timestamps)
4. **Child Content**: Preserve original transcription
## Future Enhancements
- [ ] GraphRAG integration for relationship traversal
- [ ] Cross-chunk entity linking
- [ ] Temporal graph building

View File

@@ -0,0 +1,538 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry JSON 輸出檔案規範"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "json"
- "輸出檔案規範"
ai_query_hints:
- "查詢 Momentry JSON 輸出檔案規範 的內容"
- "Momentry JSON 輸出檔案規範 的主要目的是什麼?"
- "如何操作或實施 Momentry JSON 輸出檔案規範?"
---
# Momentry JSON 輸出檔案規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
本文檔定義 Momentry Core 系統中所有 JSON 輸出檔案的結構、命名規範與儲存位置。
---
## 1. 輸出檔案總覽
### 1.1 檔案類型
| 類型 | 前綴 | 說明 | 狀態 |
|------|------|------|------|
| **Probe** | `{uuid}.probe.json` | 影片元數據 | ✅ 已實作 |
| **ASR** | `{uuid}.asr.json` | 語音識別結果 | ✅ 已實作 |
| **ASRx** | `{uuid}.asrx.json` | 說話者分離 | 🔜 規劃中 |
| **OCR** | `{uuid}.ocr.json` | 文字辨識結果 | 🔜 規劃中 |
| **YOLO** | `{uuid}.yolo.json` | 物件偵測結果 | 🔜 規劃中 |
| **Face** | `{uuid}.face.json` | 人臉偵測結果 | 🔜 規劃中 |
| **Pose** | `{uuid}.pose.json` | 姿態估計結果 | 🔜 規劃中 |
| **Thumbnail** | `{uuid}/thumb_XXX.jpg` | 縮圖檔案 | ✅ 已實作 |
### 1.2 命名規範
```
{UUID}.{類型}.json
範例:
1636719dc31f78ac.probe.json - 影片探測結果
1636719dc31f78ac.asr.json - 語音識別結果
1636719dc31f78ac.ocr.json - 文字辨識結果
```
- **UUID**: 16 字元,基於檔案路徑計算
- **類型**: 小寫 snake_case
- **副檔名**: `.json`
---
## 2. 輸出目錄結構
### 2.1 預設輸出位置
```
momentry_core_0.1/
├── {uuid}.probe.json # 影片探測
├── {uuid}.asr.json # 語音識別
├── {uuid}.asrx.json # 說話者分離
├── {uuid}.ocr.json # 文字辨識
├── {uuid}.yolo.json # 物件偵測
├── {uuid}.face.json # 人臉偵測
├── {uuid}.pose.json # 姿態估計
└── thumbnails/
└── {uuid}/
├── thumb_000.jpg
├── thumb_001.jpg
└── ...
```
### 2.2 儲存策略
| 資料類型 | 儲存位置 | 說明 |
|----------|----------|------|
| JSON 檔案 | 專案根目錄 | 方便快速存取 |
| 縮圖 | thumbnails/{uuid}/ | 分離儲存 |
| 資料庫 | PostgreSQL | 長期儲存 |
---
## 3. JSON 結構定義
### 3.1 Probe (影片探測)
**檔案**: `{uuid}.probe.json`
```json
{
"streams": [
{
"index": 0,
"codec_name": "h264",
"codec_type": "video",
"width": 1920,
"height": 1080,
"r_frame_rate": "60000/1001",
"duration": "6879.329524",
"sample_rate": null,
"channels": null
},
{
"index": 1,
"codec_name": "aac",
"codec_type": "audio",
"width": null,
"height": null,
"r_frame_rate": "0/0",
"duration": "6879.245333",
"sample_rate": "48000",
"channels": 2
}
],
"format": {
"filename": "/path/to/video.mov",
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"duration": "6879.329524",
"size": "2361629896",
"bit_rate": "2748000"
}
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `streams` | Array | 媒體串流陣列 |
| `streams[].index` | Integer | 串流索引 |
| `streams[].codec_name` | String | 編碼名稱 |
| `streams[].codec_type` | String | 串流類型 (video/audio) |
| `streams[].width` | Integer | 寬度 (video) |
| `streams[].height` | Integer | 高度 (video) |
| `streams[].r_frame_rate` | String | 幀率 |
| `streams[].duration` | String | 持續時間 (秒) |
| `streams[].sample_rate` | String | 採樣率 (audio) |
| `streams[].channels` | Integer | 聲道數 (audio) |
| `format` | Object | 檔案格式資訊 |
| `format.filename` | String | 原始檔案路徑 |
| `format.format_name` | String | 格式名稱 |
| `format.duration` | String | 總時長 (秒) |
| `format.size` | String | 檔案大小 (bytes) |
| `format.bit_rate` | String | 位元率 |
---
### 3.2 ASR (語音識別)
**檔案**: `{uuid}.asr.json`
```json
{
"language": "en",
"language_probability": 0.9945855736732483,
"segments": [
{
"start": 0.0,
"end": 19.04,
"text": "Hello and welcome to the old-time movie show."
},
{
"start": 19.04,
"end": 25.44,
"text": "Today we are featuring the 1963 comedy mystery film Charade."
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `language` | String | 偵測語言代碼 (ISO 639-1) |
| `language_probability` | Float | 語言偵測機率 (0-1) |
| `segments` | Array | 語音分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].text` | String | 識別文字 |
---
### 3.3 ASRx (說話者分離)
**檔案**: `{uuid}.asrx.json`
```json
{
"language": "en",
"language_probability": 0.95,
"segments": [
{
"start": 0.0,
"end": 19.04,
"text": "Hello and welcome to the old-time movie show.",
"speaker_id": "SPEAKER_00",
"speaker_embedding": [0.123, -0.456, ...]
},
{
"start": 19.04,
"end": 25.44,
"text": "Today we are featuring the 1963 comedy mystery film Charade.",
"speaker_id": "SPEAKER_01",
"speaker_embedding": [0.789, -0.123, ...]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `language` | String | 偵測語言代碼 |
| `language_probability` | Float | 語言偵測機率 |
| `segments` | Array | 語音分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].text` | String | 識別文字 |
| `segments[].speaker_id` | String | 說話者 ID |
| `segments[].speaker_embedding` | Array | 說話者嵌入向量 (可選) |
---
### 3.4 OCR (文字辨識)
**檔案**: `{uuid}.ocr.json`
```json
{
"segments": [
{
"start": 10.5,
"end": 12.3,
"text": "EXAMPLE TEXT",
"boxes": [
{
"x1": 100,
"y1": 50,
"x2": 400,
"y2": 100
}
],
"confidence": 0.95
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | OCR 分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].text` | String | 辨識文字 |
| `segments[].boxes` | Array | 文字邊界框陣列 |
| `segments[].boxes[].x1` | Integer | 左上 X 座標 |
| `segments[].boxes[].y1` | Integer | 左上 Y 座標 |
| `segments[].boxes[].x2` | Integer | 右下 X 座標 |
| `segments[].boxes[].y2` | Integer | 右下 Y 座標 |
| `segments[].confidence` | Float | 辨識信心度 |
---
### 3.5 YOLO (物件偵測)
**檔案**: `{uuid}.yolo.json`
```json
{
"segments": [
{
"start": 0.0,
"end": 1.0,
"objects": [
{
"class": "person",
"confidence": 0.92,
"box": {
"x1": 150,
"y1": 200,
"x2": 400,
"y2": 800
}
},
{
"class": "car",
"confidence": 0.87,
"box": {
"x1": 800,
"y1": 400,
"x2": 1200,
"y2": 700
}
}
]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | 時間分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].objects` | Array | 偵測物件陣列 |
| `segments[].objects[].class` | String | 物件類別 |
| `segments[].objects[].confidence` | Float | 偵測信心度 |
| `segments[].objects[].box` | Object | 邊界框 |
---
### 3.6 Face (人臉偵測)
**檔案**: `{uuid}.face.json`
```json
{
"segments": [
{
"start": 0.0,
"end": 1.0,
"faces": [
{
"face_id": "face_001",
"box": {
"x1": 100,
"y1": 50,
"x2": 300,
"y2": 350
},
"embedding": [0.123, -0.456, ...],
"emotion": "happy",
"age": 35,
"gender": "female"
}
]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | 時間分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].faces` | Array | 人臉陣列 |
| `segments[].faces[].face_id` | String | 人臉 ID |
| `segments[].faces[].box` | Object | 邊界框 |
| `segments[].faces[].embedding` | Array | 人臉嵌入向量 |
| `segments[].faces[].emotion` | String | 情緒分類 (可選) |
| `segments[].faces[].age` | Integer | 年齡估計 (可選) |
| `segments[].faces[].gender` | String | 性別估計 (可選) |
---
### 3.7 Pose (姿態估計)
**檔案**: `{uuid}.pose.json`
```json
{
"segments": [
{
"start": 0.0,
"end": 1.0,
"poses": [
{
"person_id": "person_001",
"keypoints": {
"nose": {"x": 320, "y": 120, "confidence": 0.98},
"left_eye": {"x": 335, "y": 110, "confidence": 0.95},
"right_eye": {"x": 305, "y": 110, "confidence": 0.93},
"left_shoulder": {"x": 280, "y": 180, "confidence": 0.91},
"right_shoulder": {"x": 360, "y": 180, "confidence": 0.89}
},
"confidence": 0.92
}
]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | 時間分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].poses` | Array | 姿態陣列 |
| `segments[].poses[].person_id` | String | 人員 ID |
| `segments[].poses[].keypoints` | Object | 關鍵點 |
| `segments[].poses[].keypoints.{name}` | Object | 各關鍵點 |
| `segments[].poses[].keypoints.{name}.x` | Integer | X 座標 |
| `segments[].poses[].keypoints.{name}.y` | Integer | Y 座標 |
| `segments[].poses[].keypoints.{name}.confidence` | Float | 信心度 |
| `segments[].poses[].confidence` | Float | 整體信心度 |
---
## 4. 處理流程
### 4.1 處理管線
```
影片檔案
┌─────────────────┐
│ 1. Register │ 建立 UUID註冊影片
└────────┬────────┘
┌────▼────┐
│ 2. Probe │ ffprobe 擷取元數據
└────┬────┘
│ {uuid}.probe.json
┌────▼─────┐
│ 3. ASR │ faster-whisper 語音識別
└────┬─────┘
│ {uuid}.asr.json
┌────▼──────┐
│ 4. ASRx │ 說話者分離 (pyannote)
└────┬──────┘
│ {uuid}.asrx.json
┌────▼────┐
│ 5. OCR │ Tesseract 文字辨識
└────┬────┘
│ {uuid}.ocr.json
┌────▼────┐
│ 6. YOLO │ 物件偵測
└────┬────┘
│ {uuid}.yolo.json
┌────▼────┐
│ 7. Face │ 人臉偵測
└────┬────┘
│ {uuid}.face.json
┌────▼────┐
│ 8. Pose │ 姿態估計
└────┬────┘
│ {uuid}.pose.json
┌────▼──────┐
│ 9. Chunk │ 轉換為資料庫 chunks
└───────────┘
```
### 4.2 失敗處理
| 階段 | 失敗時 | 處理 |
|------|--------|------|
| Probe | 無法讀取影片 | 終止流程,輸出錯誤 |
| ASR | 無音軌 | 產生空 segments繼續流程 |
| OCR/YOLO/Face/Pose | 處理失敗 | 跳過該階段,記錄日誌 |
---
## 5. 資料庫儲存
### 5.1 Chunk 結構
```sql
CREATE TABLE chunks (
id BIGSERIAL PRIMARY KEY,
uuid VARCHAR(16) NOT NULL,
chunk_id VARCHAR(64) NOT NULL,
chunk_index INTEGER NOT NULL,
chunk_type VARCHAR(32) NOT NULL,
start_time DOUBLE PRECISION NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
content JSONB NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(uuid, chunk_id)
);
```
### 5.2 轉換範例
```rust
// ASR → Chunk (Sentence)
for (i, seg) in asr_result.segments.iter().enumerate() {
let chunk = Chunk::new(
uuid.clone(),
i as u32,
ChunkType::Sentence,
seg.start,
seg.end,
serde_json::json!({"text": seg.text}),
);
db.store_chunk(&chunk).await?;
}
```
---
## 6. 版本歷史
| 版本 | 日期 | 變更 |
|------|------|------|
| 1.0.0 | 2026-03-16 | 初始版本 |
---
## 7. 相關文件
- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範
- [AGENTS.md](../AGENTS.md) - 開發規範
- [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置

View File

@@ -0,0 +1,647 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "處理器模組標準化規範"
date: "2026-04-25"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "處理器模組標準化規範"
ai_query_hints:
- "查詢 處理器模組標準化規範 的內容"
- "處理器模組標準化規範 的主要目的是什麼?"
- "如何操作或實施 處理器模組標準化規範?"
---
# 處理器模組標準化規範
## 概述
本規範定義 Momentry Core 中處理器模組的標準化架構、接口和實現模式。目標是確保所有處理器模組ASR、OCR、YOLO、Face、Pose、CUT、ASRX、Caption、Story遵循一致的設計原則提高代碼可維護性、可測試性和可擴展性。
## 架構原則
### 1. 分層架構
```
┌─────────────────────────────────────────┐
│ Rust API 層 │
│ (src/core/processor/*.rs) │
├─────────────────────────────────────────┤
│ Python 執行層 │
│ (scripts/*_processor.py) │
├─────────────────────────────────────────┤
│ AI 模型層 │
│ (Whisper, YOLO, EasyOCR, etc.) │
└─────────────────────────────────────────┘
```
### 2. 職責分離
- **Rust 層**: 接口定義、錯誤處理、配置管理、結果解析
- **Python 層**: AI 模型調用、數據處理、中間文件管理
- **模型層**: 特定 AI 任務執行
## Rust 模組規範
### 文件結構
```
src/core/processor/
├── mod.rs # 模組導出
├── executor.rs # Python 執行器(共享)
├── asr.rs # ASR 處理器
├── ocr.rs # OCR 處理器
├── yolo.rs # YOLO 處理器
├── face.rs # 人臉檢測處理器
├── pose.rs # 姿態檢測處理器
├── cut.rs # 場景切割處理器
├── asrx.rs # ASRX 處理器
├── caption.rs # 字幕生成處理器
└── story.rs # 故事分析處理器
```
### 模組模板
#### 1. 結果結構定義
```rust
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use super::executor::PythonExecutor;
use crate::core::config::processor;
// 主要結果結構
#[derive(Debug, Serialize, Deserialize)]
pub struct ModuleResult {
// 通用字段
pub processing_time: Option<f64>,
pub metadata: Option<serde_json::Value>,
// 模組特定字段
// ...
}
// 數據單元結構
#[derive(Debug, Serialize, Deserialize)]
pub struct DataUnit {
// 時間或幀相關字段
pub start: f64,
pub end: f64,
pub frame: u64,
// 數據內容
// ...
}
```
#### 2. 處理函數模板
```rust
pub async fn process_module(
video_path: &str,
output_path: &str,
uuid: Option<&str>,
) -> Result<ModuleResult> {
// 1. 初始化執行器
let executor = PythonExecutor::new()?;
let script_path = executor.script_path("module_processor.py");
// 2. 記錄日誌
tracing::info!("[MODULE] Starting processing: {}", video_path);
// 3. 執行 Python 腳本
executor
.run(
"module_processor.py",
&[video_path, output_path],
uuid,
"MODULE",
Some(Duration::from_secs(*processor::MODULE_TIMEOUT_SECS)),
)
.await
.with_context(|| format!("Failed to run {:?}", script_path))?;
// 4. 讀取並解析結果
let json_str = std::fs::read_to_string(output_path)
.context("Failed to read module output")?;
let result: ModuleResult = serde_json::from_str(&json_str)
.context("Failed to parse module output")?;
// 5. 記錄結果摘要
tracing::info!(
"[MODULE] Result: processed {} units",
result.data_units.len()
);
Ok(result)
}
```
#### 3. 配置管理
```rust
// 在 src/core/config.rs 中添加
pub mod processor {
use super::*;
pub static MODULE_TIMEOUT_SECS: Lazy<u64> = Lazy::new(|| {
env::var("MOMENTRY_MODULE_TIMEOUT")
.unwrap_or_else(|_| "3600".to_string())
.parse()
.unwrap_or(3600)
});
pub static MODULE_CHUNK_SIZE: Lazy<u64> = Lazy::new(|| {
env::var("MOMENTRY_MODULE_CHUNK_SIZE")
.unwrap_or_else(|_| "300".to_string())
.parse()
.unwrap_or(300)
});
}
```
#### 4. 測試規範
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_result_serialization() {
// 測試序列化/反序列化
}
#[test]
fn test_empty_result() {
// 測試邊界條件
}
#[tokio::test]
async fn test_integration() {
// 集成測試(可選)
}
}
```
## Python 腳本規範
### 文件命名
```
scripts/
├── module_processor.py # 主要處理腳本
├── module_utils.py # 工具函數(可選)
└── module_debug.py # 調試腳本(可選)
```
### 腳本模板
```python
#!/opt/homebrew/bin/python3.11
"""
模組處理器 - 標準化模板
功能:執行 [模組名稱] 處理
輸入:視頻文件路徑,輸出文件路徑
輸出JSON 格式的處理結果
"""
import sys
import json
import os
import argparse
import signal
import tempfile
import time
from pathlib import Path
from typing import Dict, Any, List, Optional
# 環境檢查
def check_environment() -> bool:
"""檢查必要的環境和依賴"""
try:
# 檢查必要庫
import required_library
return True
except ImportError as e:
print(f"ERROR: Missing dependency: {e}", file=sys.stderr)
return False
# 信號處理
def signal_handler(signum, frame):
"""處理中斷信號"""
print(f"[MODULE] Received signal {signum}, cleaning up...")
sys.exit(1)
# 主要處理類
class ModuleProcessor:
def __init__(self, video_path: str, output_path: str):
self.video_path = video_path
self.output_path = output_path
self.start_time = time.time()
def validate_input(self) -> bool:
"""驗證輸入文件"""
if not os.path.exists(self.video_path):
print(f"ERROR: Video file not found: {self.video_path}", file=sys.stderr)
return False
return True
def process(self) -> Dict[str, Any]:
"""執行處理邏輯"""
try:
# 1. 準備工作目錄
work_dir = tempfile.mkdtemp(prefix="module_")
# 2. 執行核心處理邏輯
result = self._core_processing(work_dir)
# 3. 添加元數據
result["metadata"] = {
"processing_time": time.time() - self.start_time,
"video_path": self.video_path,
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
"module_version": "1.0.0"
}
return result
except Exception as e:
print(f"ERROR: Processing failed: {e}", file=sys.stderr)
raise
def _core_processing(self, work_dir: str) -> Dict[str, Any]:
"""核心處理邏輯(模組特定)"""
# 模組特定實現
return {
"data_units": [],
"summary": {}
}
def save_result(self, result: Dict[str, Any]):
"""保存結果到文件"""
with open(self.output_path, 'w', encoding='utf-8') as f:
json.dump(result, f, ensure_ascii=False, indent=2)
print(f"[MODULE] Result saved to: {self.output_path}")
# 命令行接口
def main():
parser = argparse.ArgumentParser(description="模組處理器")
parser.add_argument("video_path", help="輸入視頻文件路徑")
parser.add_argument("output_path", help="輸出 JSON 文件路徑")
args = parser.parse_args()
# 設置信號處理
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# 環境檢查
if not check_environment():
sys.exit(1)
# 執行處理
processor = ModuleProcessor(args.video_path, args.output_path)
if not processor.validate_input():
sys.exit(1)
try:
result = processor.process()
processor.save_result(result)
print(f"[MODULE] Processing completed successfully")
except Exception as e:
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
```
### 輸出格式規範
```json
{
"data_units": [
{
"id": "unit_1",
"start": 0.0,
"end": 2.5,
"frame": 0,
"data": {},
"confidence": 0.95
}
],
"summary": {
"total_units": 1,
"processing_time": 4.7,
"average_confidence": 0.95
},
"metadata": {
"video_path": "/path/to/video.mp4",
"module": "module_name",
"version": "1.0.0",
"timestamp": "2026-03-27 10:30:00"
}
}
```
## 配置標準化
### 環境變量
```
# 超時設置
MOMENTRY_ASR_TIMEOUT=3600
MOMENTRY_OCR_TIMEOUT=7200
MOMENTRY_YOLO_TIMEOUT=7200
MOMENTRY_FACE_TIMEOUT=3600
MOMENTRY_POSE_TIMEOUT=3600
MOMENTRY_CUT_TIMEOUT=3600
MOMENTRY_ASRX_TIMEOUT=3600
MOMENTRY_CAPTION_TIMEOUT=1800
MOMENTRY_STORY_TIMEOUT=1800
# 性能設置
MOMENTRY_MODULE_CHUNK_SIZE=300
MOMENTRY_MODULE_BATCH_SIZE=32
MOMENTRY_MODULE_CACHE_ENABLED=true
# 模型設置
MOMENTRY_MODULE_MODEL=base
MOMENTRY_MODULE_DEVICE=cpu
```
### 配置優先級
1. 命令行參數(最高優先級)
2. 環境變量
3. 配置文件
4. 默認值(最低優先級)
## 錯誤處理規範
### Rust 錯誤處理
```rust
use anyhow::{Context, Result};
pub async fn process_module(...) -> Result<ModuleResult> {
// 使用 .context() 添加上下文
executor.run(...)
.await
.with_context(|| format!("Failed to run module script"))?;
// 使用 anyhow::bail! 進行錯誤返回
if !condition {
anyhow::bail!("Condition not met: {}", reason);
}
}
```
### Python 錯誤處理
```python
def process(self) -> Dict[str, Any]:
try:
# 主要邏輯
result = self._core_processing()
return result
except FileNotFoundError as e:
print(f"ERROR: File not found: {e}", file=sys.stderr)
raise
except RuntimeError as e:
print(f"ERROR: Runtime error: {e}", file=sys.stderr)
raise
except Exception as e:
print(f"ERROR: Unexpected error: {e}", file=sys.stderr)
raise
```
### 錯誤分類
1. **輸入錯誤**: 文件不存在、格式不支持、權限問題
2. **配置錯誤**: 缺少依賴、環境變量錯誤、模型文件缺失
3. **運行時錯誤**: 內存不足、超時、模型推理錯誤
4. **輸出錯誤**: 結果解析失敗、文件寫入失敗
## 日誌規範
### Rust 日誌
```rust
tracing::info!("[MODULE] Starting processing: {}", video_path);
tracing::debug!("[MODULE] Processing details: {:?}", details);
tracing::warn!("[MODULE] Warning: {}", warning_message);
tracing::error!("[MODULE] Error: {}", error_message);
```
### Python 日誌
```python
import sys
def log_info(message: str):
print(f"[MODULE] INFO: {message}", file=sys.stderr)
def log_debug(message: str):
if os.environ.get("MODULE_DEBUG") == "1":
print(f"[MODULE] DEBUG: {message}", file=sys.stderr)
def log_error(message: str):
print(f"[MODULE] ERROR: {message}", file=sys.stderr)
```
## 性能監控
### 指標收集
```rust
pub struct ProcessingMetrics {
pub start_time: std::time::Instant,
pub end_time: Option<std::time::Instant>,
pub memory_usage_mb: f64,
pub cpu_usage_percent: f64,
pub items_processed: u64,
pub items_per_second: f64,
}
impl ProcessingMetrics {
pub fn new() -> Self {
Self {
start_time: std::time::Instant::now(),
end_time: None,
memory_usage_mb: 0.0,
cpu_usage_percent: 0.0,
items_processed: 0,
items_per_second: 0.0,
}
}
pub fn record_completion(&mut self, items_processed: u64) {
self.end_time = Some(std::time::Instant::now());
self.items_processed = items_processed;
let duration = self.end_time.unwrap().duration_since(self.start_time);
self.items_per_second = items_processed as f64 / duration.as_secs_f64();
}
}
```
### 性能報告
```json
{
"performance": {
"processing_time_seconds": 4.7,
"memory_usage_mb": 512.5,
"cpu_usage_percent": 45.2,
"items_processed": 8,
"items_per_second": 1.7,
"throughput_mb_per_second": 10.5
}
}
```
## 測試規範
### 單元測試
```rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_result_structure() {
// 測試數據結構
}
#[test]
fn test_serialization() {
// 測試序列化
}
#[test]
fn test_edge_cases() {
// 測試邊界條件
}
}
```
### 集成測試
```rust
#[tokio::test]
async fn test_module_integration() {
// 使用測試文件進行集成測試
let test_video = "test_data/sample.mp4";
let output_file = tempfile::NamedTempFile::new().unwrap();
let result = process_module(test_video, output_file.path().to_str().unwrap(), None)
.await
.expect("Processing should succeed");
assert!(!result.data_units.is_empty());
}
```
### Python 測試
```python
def test_module_processor():
"""測試 Python 處理器"""
processor = ModuleProcessor("test.mp4", "output.json")
# 測試輸入驗證
assert not processor.validate_input() # 文件不存在
# 測試處理邏輯
with tempfile.NamedTemporaryFile() as tmp:
processor = ModuleProcessor("real_test.mp4", tmp.name)
result = processor.process()
assert "data_units" in result
assert "metadata" in result
```
## 文檔規範
### Rust 文檔
```rust
/// ASR 處理器模組
///
/// 提供自動語音識別功能,支持多種語言和大文件處理。
///
/// # 示例
/// ```
/// use momentry_core::processor::asr;
///
/// let result = asr::process_asr("video.mp4", "output.json", None).await?;
/// println!("識別到 {} 個語音片段", result.segments.len());
/// ```
pub mod asr {
// ...
}
```
### Python 文檔
```python
"""
模組處理器
提供 [功能描述] 功能。
使用示例:
python module_processor.py input.mp4 output.json
參數:
video_path: 輸入視頻文件路徑
output_path: 輸出 JSON 文件路徑
輸出格式:
詳見輸出格式規範部分。
"""
```
## 遷移指南
### 現有模組標準化步驟
1. **分析現有代碼**: 識別不符合規範的部分
2. **創建備份**: 備份原始文件
3. **重構 Rust 模組**: 按照模板重構
4. **重構 Python 腳本**: 按照模板重構
5. **更新配置**: 統一配置管理
6. **添加測試**: 補充單元和集成測試
7. **更新文檔**: 更新 API 文檔和使用說明
8. **驗證功能**: 確保功能正常
### 兼容性保證
- 保持現有 API 不變
- 逐步遷移,不中斷現有功能
- 提供遷移工具和文檔
## 附錄
### A. 模組分類
| 模組 | 功能 | 主要技術 | 輸出類型 |
|------|------|----------|----------|
| ASR | 語音識別 | Whisper | 時間段文本 |
| OCR | 文字識別 | EasyOCR | 幀級文字 |
| YOLO | 物體檢測 | YOLOv8 | 幀級物體 |
| Face | 人臉檢測 | OpenCV | 幀級人臉 |
| Pose | 姿態檢測 | OpenPose | 幀級姿態 |
| CUT | 場景切割 | PySceneDetect | 場景邊界 |
| ASRX | 語音增強 | WhisperX | 說話人分離 |
| Caption | 字幕生成 | BLIP | 幀級描述 |
| Story | 故事分析 | 自定義 | 故事結構 |
### B. 性能基準
| 模組 | 平均處理時間 | 內存使用 | CPU 使用 |
|------|--------------|----------|----------|
| ASR | 4.7s (小文件) | 1.2GB | 45% |
| OCR | 12.3s (小文件) | 800MB | 35% |
| YOLO | 8.5s (小文件) | 1.5GB | 60% |
| Face | 3.2s (小文件) | 500MB | 25% |
### C. 常見問題
1. **依賴問題**: 確保 Python 環境正確設置
2. **內存不足**: 調整 chunk_size 參數
3. **超時錯誤**: 增加 timeout 設置或優化算法
4. **模型加載慢**: 啟用模型緩存
---
*版本: 1.0.0*
*更新日期: 2026-03-27*
*負責人: Warren (Technical Lead)*
*狀態: 草案*

View File

@@ -0,0 +1,303 @@
---
document_type: "reference_doc"
service: "REDIS"
title: "Momentry Core Redis Key 設計規範"
date: "2026-03-17"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "core"
- "redis"
- "設計規範"
ai_query_hints:
- "查詢 Momentry Core Redis Key 設計規範 的內容"
- "Momentry Core Redis Key 設計規範 的主要目的是什麼?"
- "如何操作或實施 Momentry Core Redis Key 設計規範?"
---
# Momentry Core Redis Key 設計規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-17 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-17 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-25 | 新增可配置 Redis Key Prefix | Warren | OpenCode / GLM-5 |
---
## 1. 概述
本文檔說明 momentry_core 如何使用 Redis 作為監控和狀態管理系統。
## 2. 可配置 Redis Key Prefix
### 2.1 環境變數
從 V1.1 開始,所有 Redis Keys 都支援自定義前綴:
```bash
MOMENTRY_REDIS_PREFIX=momentry:
```
此設定允許多個 momentry 實例共用同一個 Redis 伺服器,例如:
- **生產環境**: `MOMENTRY_REDIS_PREFIX=momentry:`
- **開發環境**: `MOMENTRY_REDIS_PREFIX=momentry_dev:`
### 2.2 Key 格式
所有 Key 都遵循以下格式:
```
{prefix}{key_type}:{uuid}
```
範例 (生產環境):
```
momentry:job:5dea6618a606e7c7
momentry:jobs:active
momentry:health:current
```
範例 (開發環境):
```
momentry_dev:job:5dea6618a606e7c7
momentry_dev:jobs:active
momentry_dev:health:current
```
### 2.3 預設值
| Binary | 預設 Port | 預設 Redis Prefix |
|--------|-----------|-------------------|
| `momentry` (生產) | 3002 | `momentry:` |
| `momentry_playground` (開發) | 3003 | `momentry_dev:` |
## 3. UUID 使用時機
### 3.1 全局 Keys無 UUID
- 單一實例全局狀態
- 聚合統計數據
### 3.2 Per-Video KeysUUID 必要)
- 每個視頻獨立處理狀態
- 即時進度追蹤
### 3.3 Per-Processor KeysUUID + Processor 必要)
- 每個 processor 獨立狀態
## 4. Key 命名空間
```
momentry
├── health: # 健康檢查
│ ├── current # 當前狀態 (TTL: 60s)
│ └── services # 依賴服務狀態
├── config: # 配置
├── stats: # 聚合統計
│ ├── total_jobs # 總 Jobs 數
│ ├── processed_today # 今日處理數
│ ├── cpu:current # 當前 CPU 使用
│ └── memory:current # 當前 Memory 使用
├── jobs: # Jobs 管理
│ ├── active # Set: 運行中 UUIDs
│ ├── completed # Set: 完成 UUIDs
│ └── failed # Set: 失敗 UUIDs
├── job:{uuid} # Per-Video Job 狀態 (TTL: 24h)
│ ├── status # 狀態 String
│ ├── video_path # 視頻路徑
│ ├── current_processor # 當前 processor
│ ├── progress_total # 總進度
│ ├── progress_current # 當前進度
│ ├── started_at # 開始時間
│ ├── updated_at # 最後更新
│ └── processor:{name} # Per-Processor 狀態
│ ├── status
│ ├── progress
│ ├── current
│ ├── total
│ └── started_at
├── progress:{uuid} # Pub/Sub 頻道 (即時進度)
├── result:{uuid} # 處理結果 Hash
├── output:{uuid} # 輸出路徑
├── metrics:{uuid} # Per-Video 指標
│ ├── cpu # CPU 歷史 List (100條, TTL: 1h)
│ ├── memory # Memory 歷史 List (100條, TTL: 1h)
│ └── duration # 處理時長
└── log:{uuid} # 處理日誌 String
```
## 5. Key 詳細說明
### 全局 Keys
| Key | Type | TTL | 說明 |
|-----|------|-----|------|
| `momentry:health:current` | String | 60s | 當前健康狀態 |
| `momentry:health:services` | Hash | 60s | 依賴服務健康狀態 |
| `momentry:stats:total_jobs` | String | - | 總 Jobs 數 |
| `momentry:stats:processed_today` | String | 86400s | 今日處理數 |
| `momentry:stats:cpu:current` | String | 10s | 當前 CPU 使用 |
| `momentry:stats:memory:current` | String | 10s | 當前 Memory 使用 |
| `momentry:jobs:active` | Set | - | 運行中 Job UUIDs |
| `momentry:jobs:completed` | Set | - | 完成 Job UUIDs |
| `momentry:jobs:failed` | Set | - | 失敗 Job UUIDs |
### Per-Video Keys
| Key | Type | TTL | 說明 |
|-----|------|-----|------|
| `momentry:job:{uuid}` | Hash | 24h | Job 完整狀態 |
| `momentry:job:{uuid}:status` | String | 24h | Job 狀態 |
| `momentry:progress:{uuid}` | Pub/Sub | - | 即時進度頻道 |
| `momentry:result:{uuid}` | Hash | 24h | 處理結果 |
| `momentry:output:{uuid}` | String | 24h | 輸出路徑 |
| `momentry:metrics:{uuid}:cpu` | List | 1h | CPU 歷史 (100條) |
| `momentry:metrics:{uuid}:memory` | List | 1h | Memory 歷史 (100條) |
| `momentry:metrics:{uuid}:duration` | String | 24h | 處理時長 |
| `momentry:log:{uuid}` | String | 24h | 處理日誌 |
### Per-Processor Keys
| Key | Type | TTL | 說明 |
|-----|------|-----|------|
| `momentry:job:{uuid}:processor:{name}` | Hash | 24h | Processor 狀態 |
| `momentry:job:{uuid}:processor:{name}:status` | String | 24h | 狀態 |
| `momentry:job:{uuid}:processor:{name}:progress` | String | 24h | 進度百分比 |
| `momentry:job:{uuid}:processor:{name}:current` | String | 24h | 當前項目 |
| `momentry:job:{uuid}:processor:{name}:total` | String | 24h | 總項目數 |
| `momentry:job:{uuid}:processor:{name}:started_at` | String | 24h | 開始時間 |
## 6. TTL 策略
| Key 類型 | TTL | 原因 |
|----------|-----|------|
| Health | 60s | 需要定期更新 |
| Job | 24h | 處理完成後保留一天 |
| Processor | 24h | 處理完成後保留一天 |
| Metrics | 1h | 只保留近期歷史 |
| Progress Pub/Sub | - | 不持久,僅即時訊息 |
| Stats | 無 | 持久統計 |
## 7. 訊息格式
### Pub/Sub 訊息 (progress:{uuid})
```json
{
"type": "info | progress | complete | error",
"processor": "yolo | ocr | face | pose | cut | asr | asrx",
"timestamp": 1700000000,
"data": {
"message": "Processing frame 5000",
"current": 5000,
"total": 14315
}
}
```
### Job 狀態 Hash
```json
{
"uuid": "5dea6618a606e7c7",
"video_path": "/path/to/video.mp4",
"status": "running",
"current_processor": "yolo",
"progress_total": 70,
"progress_current": 50,
"started_at": 1700000000,
"updated_at": 1700000100,
"error_count": 0,
"last_error": ""
}
```
### Processor 狀態 Hash
```json
{
"name": "yolo",
"status": "running",
"progress": 70,
"current_frame": 10000,
"total_frames": 14315,
"started_at": 1700000000,
"updated_at": 1700000100
}
```
## 8. 實作函數 (Rust)
所有 Redis Key 生成函數使用 `REDIS_KEY_PREFIX` 靜態變數:
```rust
use crate::core::config::REDIS_KEY_PREFIX;
fn global_key(key: &str) -> String {
format!("{}{}", REDIS_KEY_PREFIX, key)
}
fn job_key(uuid: &str) -> String {
format!("{}job:{}", REDIS_KEY_PREFIX, uuid)
}
fn processor_key(uuid: &str, processor: &str) -> String {
format!("{}job:{}:processor:{}", REDIS_KEY_PREFIX, uuid, processor)
}
fn progress_channel(uuid: &str) -> String {
format!("{}progress:{}", REDIS_KEY_PREFIX, uuid)
}
fn metrics_key(uuid: &str, metric: &str) -> String {
format!("{}metrics:{}:{}", REDIS_KEY_PREFIX, uuid, metric)
}
fn jobs_set_key(status: &str) -> String {
format!("{}jobs:{}", REDIS_KEY_PREFIX, status)
}
```
**注意**: `REDIS_KEY_PREFIX` 定義於 `src/core/config.rs`,由環境變數 `MOMENTRY_REDIS_PREFIX` 控制。
## 9. 環境變數
```bash
# Redis 連接
REDIS_URL=redis://localhost:6379
REDIS_PASSWORD=accusys
REDIS_DB=0
# Redis Key Prefix (可選,預設: momentry:)
MOMENTRY_REDIS_PREFIX=momentry:
# 生產環境範例 (.env)
MOMENTRY_SERVER_PORT=3002
MOMENTRY_REDIS_PREFIX=momentry:
# 開發環境範例 (.env.development)
MOMENTRY_SERVER_PORT=3003
MOMENTRY_REDIS_PREFIX=momentry_dev:
```
## 11. 監控腳本
使用 Redis 進行監控的腳本應參考:
- `monitor/service/momentry_redis_monitor.sh` - Redis 健康檢查
- `monitor/service/momentry_job_monitor.sh` - Job 狀態監控

View File

@@ -0,0 +1,353 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core 影片 RAG 系統說明稿"
date: "2026-03-22"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "core"
- "系統說明稿"
ai_query_hints:
- "查詢 Momentry Core 影片 RAG 系統說明稿 的內容"
- "Momentry Core 影片 RAG 系統說明稿 的主要目的是什麼?"
- "如何操作或實施 Momentry Core 影片 RAG 系統說明稿?"
---
# Momentry Core 影片 RAG 系統說明稿
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-22 |
| 文件版本 | V1.1 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-22 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-25 | 更新API回應格式 (media_url→file_path) 與認證標頭 | OpenCode | deepseek-reasoner |
---
## 系統架構
```
┌─────────────────────────────────────────────────────────────┐
│ 使用者 │
│ (marcom 團隊) │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ WordPress 入口 │
│ (wp.momentry.ddns.net) │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ n8n 自動化 │
│ (localhost:5678) │
│ │
│ [Webhook] → [HTTP Request] → [處理結果] → [回覆用戶] │
└─────────────────┬───────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Momentry Core API │
│ (localhost:3002) │
│ │
│ POST /api/v1/search → 語意搜尋 │
│ POST /api/v1/n8n/search → n8n 專用格式 │
│ GET /api/v1/videos → 影片列表 │
└─────────────────┬───────────────────────────────────────────┘
┌─────────┴──────────┐
▼ ▼
┌───────────────┐ ┌───────────────┐
│ PostgreSQL │ │ Qdrant │
│ (chunks) │ │ (vectors) │
└───────────────┘ └───────────────┘
```
---
## 資料流程
```
1. 上傳影片 → SFTPGo
2. 影片註冊 → PostgreSQL
3. ASR 處理 → 產生字幕區塊
4. 儲存 chunks → PostgreSQL
5. 向量化 → Qdrant
6. 搜尋查詢 → API
7. 回傳結果 → n8n → 用戶
```
---
## 示範影片
| 項目 | 內容 |
|------|------|
| 檔案名稱 | Old_Time_Movie_Show_-_Charade_1963.HD.mov |
| UUID | a1b10138a6bbb0cd |
| 時長 | 6879 秒(約 1.9 小時) |
| 區塊數 | 3,886 個 |
| 向量數 | 3,688 個 |
---
## API 端點
### 1. 語意搜尋
```
POST http://localhost:3002/api/v1/search
```
**請求:**
```json
{
"query": "charade",
"limit": 5,
"uuid": "a1b10138a6bbb0cd"
}
```
> **注意**:
> 1. **API 認證**: 所有 `/api/v1/*` 端點需要 `X-API-Key` 標頭
> 2. **檔案路徑轉換**: API 現在返回 `file_path`(檔案系統路徑),需要轉換為可訪問的 URL例如透過 SFTPGo 分享連結)
---
### 2. n8n 專用格式
```
POST http://localhost:3002/api/v1/n8n/search
```
**請求:**
```json
{
"query": "charade",
"limit": 5
}
```
**回應:**
```json
{
"query": "charade",
"count": 5,
"hits": [
{
"id": "sentence_0006",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0006",
"text": "fun plot twists...",
"score": 0.526,
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
```
---
## 實作範例
### n8n Workflow 設計
```
┌─────────────┐
│ Webhook │ ← 接收用戶搜尋請求
└──────┬──────┘
┌─────────────┐
│ HTTP Request│ → POST /api/v1/n8n/search
└──────┬──────┘
┌─────────────┐
│ Code │ → 處理回傳結果
└──────┬──────┘
┌─────────────┐
│ Telegram │ → 回覆給用戶
│ (或 LINE) │
└─────────────┘
```
---
## Step-by-Step n8n Workflow
### Step 1: 建立 Webhook
1. n8n 開新 Workflow
2. 新增 node: **Webhook**
3. 設定 path: `video-search`
4. 複製 Webhook URL
---
### Step 2: 設定 HTTP Request
1. 新增 node: **HTTP Request**
2. 設定:
```
Method: POST
URL: http://localhost:3002/api/v1/n8n/search
Body Content Type: JSON
Headers: X-API-Key (需設定)
```
3. Body:
```json
{
"query": "={{ $json.body }}",
"limit": 5
}
```
---
### Step 3: 處理結果 (Code)
```javascript
const hits = $input.first().json.hits;
if (!hits || hits.length === 0) {
return {
json: { message: "找不到相關結果" }
};
}
const results = hits.map((hit, index) => ({
number: index + 1,
text: hit.text,
time: `${hit.start}s - ${hit.end}s`,
score: Math.round(hit.score * 100) + "%",
// 注意: API 現在返回 file_path檔案系統路徑需要轉換為可訪問的 URL
url: hit.file_path + "#t=" + hit.start + "," + hit.end // 需實作檔案路徑轉換為 URL
}));
return { json: { results } };
```
> **注意**:
> 1. **API 認證**: 所有 `/api/v1/*` 端點需要 `X-API-Key` 標頭
> 2. **檔案路徑轉換**: API 現在返回 `file_path`(檔案系統路徑),需要轉換為可訪問的 URL例如透過 SFTPGo 分享連結)
---
### Step 4: 格式化輸出
**Telegram 格式:**
```
🎬 搜尋結果: "{{ $json.query }}"
1⃣ "fun plot twists, Woody Dialog and charming performances..."
⏱ 48.8s - 55.4s
📊 相關度: 53%
2⃣ "Don't you like me to say that a pretty girl..."
⏱ 4745.6s - 4748.6s
📊 相關度: 52%
```
---
## 測試指令
### curl 測試
```bash
# 語意搜尋
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade", "limit": 3}'
# n8n 格式
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "charade", "limit": 3}'
# 影片列表
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
# 特定影片區塊
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos/a1b10138a6bbb0cd/chunks
```
---
## 實際搜尋範例
| 搜尋詞 | 結果摘要 |
|--------|----------|
| `charade` | "fun plot twists, Woody Dialog and charming performances..." |
| `woody` | "Well, you thick skull hair, brain half-witted..." |
| `classic movie` | "Hello and welcome to the old-time movie show..." |
| `charming` | "fun plot twists, Woody Dialog and charming performances..." |
---
## 資料庫狀態
| 資料庫 | 資料筆數 | 狀態 |
|--------|----------|------|
| PostgreSQL (videos) | 4 | ✅ |
| PostgreSQL (chunks) | 3,950 | ✅ |
| PostgreSQL (vectors) | 1,870 | ✅ |
| Qdrant (vectors) | 3,688 | ✅ |
| Redis (job cache) | 4 keys | ✅ |
---
## 下一步
1. **建立 SFTPGo 分享連結**
- 開啟 http://localhost:8080
- 登入 demo / demopassword123
- 建立影片分享連結
2. **測試 n8n Workflow**
- 匯入 Postman Collection
- 建立 Webhook
- 測試搜尋
3. **整合到 WordPress**
- 建立表單接收用戶輸入
- 呼叫 n8n Webhook
- 顯示搜尋結果
---
## 快速開始
```bash
# 1. 測試搜尋 API
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-d '{"query": "charade", "limit": 3}'
# 2. 查看影片列表
curl http://localhost:3002/api/v1/videos
# 3. 查看 n8n 是否運行
curl http://localhost:5678
```

View File

@@ -0,0 +1,127 @@
{
"info": {
"name": "Momentry Core API",
"description": "Video RAG API for Momentry Core - Video search and management",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
{
"key": "baseUrl",
"value": "http://localhost:3002/api/v1"
}
],
"item": [
{
"name": "Search",
"item": [
{
"name": "Semantic Search",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"charade\",\n \"limit\": 5\n}"
},
"url": {
"raw": "{{baseUrl}}/search",
"host": ["{{baseUrl}}"],
"path": ["search"]
},
"description": "Semantic search across video chunks using vector embeddings"
}
},
{
"name": "Search with UUID Filter",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"charade\",\n \"limit\": 5,\n \"uuid\": \"a1b10138a6bbb0cd\"\n}"
},
"url": {
"raw": "{{baseUrl}}/search",
"host": ["{{baseUrl}}"],
"path": ["search"]
},
"description": "Search within a specific video by UUID"
}
},
{
"name": "n8n Integration Search",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"query\": \"charade\",\n \"limit\": 5\n}"
},
"url": {
"raw": "{{baseUrl}}/n8n/search",
"host": ["{{baseUrl}}"],
"path": ["n8n", "search"]
},
"description": "Search formatted for n8n workflow integration"
}
}
]
},
{
"name": "Videos",
"item": [
{
"name": "List All Videos",
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/videos",
"host": ["{{baseUrl}}"],
"path": ["videos"]
},
"description": "Get list of all registered videos"
}
},
{
"name": "Get Video by UUID",
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/videos/a1b10138a6bbb0cd",
"host": ["{{baseUrl}}"],
"path": ["videos", "a1b10138a6bbb0cd"]
},
"description": "Get details for a specific video"
}
},
{
"name": "Get Video Chunks",
"request": {
"method": "GET",
"url": {
"raw": "{{baseUrl}}/videos/a1b10138a6bbb0cd/chunks",
"host": ["{{baseUrl}}"],
"path": ["videos", "a1b10138a6bbb0cd", "chunks"]
},
"description": "Get all chunks for a video"
}
}
]
}
]
}

View File

@@ -0,0 +1,106 @@
# n8n REST API Fix Summary
## Issue
n8n REST API was returning 404 errors for all endpoints (`/api/v1/workflows`, `/rest/workflows`, etc.)
## Root Cause
Port 5678 was occupied by the **n8n worker** process, preventing the main n8n instance from starting properly.
## Solution
### 1. Identified Port Conflict
- Worker process was listening on port 5678 (same as main instance)
- Main n8n couldn't start because port was in use
### 2. Fixed Worker Configuration
Updated `/Library/LaunchDaemons/com.momentry.n8n.worker.plist`:
- Added `N8N_PORT=5680` to worker environment variables
- Workers shouldn't need HTTP ports, but this prevents port conflict
### 3. Restarted Services
```bash
# Kill all n8n processes
sudo pkill -9 -f n8n
# Start main n8n (now successfully binds to port 5678)
sudo launchctl enable system/com.momentry.n8n.main
sudo launchctl bootstrap system /Library/LaunchDaemons/com.momentry.n8n.main.plist
```
## Current Status
### n8n Instance
- **URL**: http://localhost:5678
- **Version**: 2.3.5
- **Status**: Running ✅
- **API Enabled**: Yes ✅
### API Key
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJlNjdiY2UzOS1iY2RkLTRjMjEtYmMwYy0yODNhYmI3ZjVjMjMiLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzc0MTk4NzgwfQ.zke_Qc-saILl_tcwXm2K3J4slCmaXnzCfxVbdVPPvCE
```
### MCP Configuration
File: `~/.config/opencode/opencode.json`
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"gitea": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/gitea-mcp-server",
"-token", "<GITEA_TOKEN>",
"-host", "http://localhost:3000"
]
},
"n8n": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-n8n"],
"environment": {
"N8N_BASE_URL": "http://localhost:5678",
"N8N_API_KEY": "<N8N_API_KEY>"
}
}
}
}
```
## Verified Endpoints
### List Workflows
```bash
curl -H "X-N8N-API-KEY: <API_KEY>" http://localhost:5678/api/v1/workflows
```
**Result**: ✅ 30 workflows returned
### List Executions
```bash
curl -H "X-N8N-API-KEY: <API_KEY>" http://localhost:5678/api/v1/executions
```
**Result**: ✅ 100 executions returned
## Next Steps
1. **Start n8n Worker** (optional for MCP):
Workers handle job processing but aren't required for API access.
2. **Test MCP Integration**:
Restart OpenCode to load the MCP configuration and test n8n integration.
3. **Verify Workflow Management**:
- Create workflow via API
- Execute workflow
- Monitor execution status
## Files Modified
- `/Library/LaunchDaemons/com.momentry.n8n.worker.plist` - Added N8N_PORT=5680
## API Documentation
- Base URL: `http://localhost:5678/api/v1`
- Authentication: Header `X-N8N-API-KEY: <token>`
- Available endpoints: workflows, executions, credentials, users, etc.
See full API reference: https://docs.n8n.io/api/

View File

@@ -0,0 +1,321 @@
---
document_type: "reference_doc"
service: "N8N"
title: "n8n Video Search 工作流程 - 成功設定指南"
date: "2026-03-22"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "成功設定指南"
- "video"
- "工作流程"
- "search"
ai_query_hints:
- "查詢 n8n Video Search 工作流程 - 成功設定指南 的內容"
- "n8n Video Search 工作流程 - 成功設定指南 的主要目的是什麼?"
- "如何操作或實施 n8n Video Search 工作流程 - 成功設定指南?"
---
# n8n Video Search 工作流程 - 成功設定指南
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-22 |
| 文件版本 | V1.1 |
| 適用版本 | n8n 2.3.5 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-22 | 創建文件 | Warren | OpenCode |
| V1.1 | 2026-03-26 | 更新 API 範例,新增 X-API-Key 驗證標頭 | OpenCode | deepseek-reasoner |
---
## ✅ 成功案例
| 項目 | 內容 |
|------|------|
| **工作流程名稱** | Video Search - Working v3 |
| **ID** | 4vQo8I4SXEaR5E1A |
| **狀態** | ✅ 成功運作 |
| **測試結果** | 成功搜尋 "charade",返回 3 個結果 |
---
## 正確的 HTTP Request Node 設定
### 成功的設定方式
```json
{
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"query\":\"charade\",\"limit\":3}",
"options": {
"headers": {
"X-API-Key": "muser_68600856036340bcafc01930eb4bd839"
}
}
}
```
### 關鍵設定說明
| 設定項目 | 正確值 | 錯誤值 | 說明 |
|---------|--------|--------|------|
| **specifyBody** | `"json"` | `"body"` | 必須選擇 `"json"` |
| **jsonBody** | 字串 `"{...}"` | 物件 `{}` | 必須是 JSON 字串格式 |
| **轉義** | `\"query\"` | `"query"` | 引號需要轉義 |
---
## 工作流程架構
```
┌─────────────────────────┐
│ Manual Trigger │ ← 點擊 "Execute Workflow"
└───────────┬─────────────┘
┌─────────────────────────┐
│ HTTP Request Node │ ← 呼叫 Momentry API
│ - specifyBody: "json" │
│ - jsonBody: "{...}" │
└───────────┬─────────────┘
┌─────────────────────────┐
│ Code Node │ ← 格式化輸出
│ - console.log() │
│ - return json │
└─────────────────────────┘
```
---
## 完整的 Workflow JSON
```json
{
"name": "Video Search - Working v3",
"nodes": [
{
"parameters": {},
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [250, 300]
},
{
"parameters": {
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"query\":\"charade\",\"limit\":3}",
"options": {
"headers": {
"X-API-Key": "muser_68600856036340bcafc01930eb4bd839"
}
}
},
"name": "Search API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [450, 300]
},
{
"parameters": {
"jsCode": "const data = $input.first().json;\nconsole.log('Response:', JSON.stringify(data, null, 2));\nreturn [{ json: data }];"
},
"name": "Show Result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [650, 300]
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [[{"node": "Search API", "type": "main", "index": 0}]]
},
"Search API": {
"main": [[{"node": "Show Result", "type": "main", "index": 0}]]
}
},
"settings": {"executionOrder": "v1"},
"staticData": null
}
```
---
## 使用步驟
### 步驟 1: 導入工作流程
1. 開啟 n8n UI: `https://n8n.momentry.ddns.net`
2. 點擊 **Add Workflow** (+)
3. 點擊 **Import from File**
4. 選擇上面的 JSON 檔案
### 步驟 2: 執行測試
1. 點擊 **"Execute Workflow"** 按鈕 (▶️)
2. 等待執行完成
3. 點擊 **"Show Result"** 節點
4. 查看右側 **JSON** 面板
### 步驟 3: 修改搜尋關鍵字
1. 點擊 **"Search API"** 節點
2. 修改 `jsonBody`:
```json
"{\"query\":\"您的關鍵字\",\"limit\":5}"
```
3. 點擊 **Save** (Ctrl+S)
4. 重新執行
---
## 成功的回應範例
```json
{
"query": "charade",
"count": 3,
"hits": [
{
"id": "sentence_0006",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0006",
"text": "fun plot twists, Woody Dialog and charming performances...",
"score": 0.526,
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
]
}
```
---
## 常見錯誤與解決
### ❌ 錯誤 1: "Your request is invalid"
**原因**: `specifyBody` 設為 `"body"` 而不是 `"json"`
**解決**:
```json
✅ "specifyBody": "json"
❌ "specifyBody": "body"
```
### ❌ 錯誤 2: "$httpRequest is not defined"
**原因**: Code Node 中使用 `$httpRequest`,但您的 n8n 版本不支援
**解決**: 使用 **HTTP Request Node** 代替 Code Node
### ❌ 錯誤 3: Body 格式錯誤
**原因**: `body` 使用物件格式 `{query: "..."}`
**解決**: 使用 `jsonBody` 字串格式 `{"query":"..."}`
### ❌ 錯誤 4: JSON 引號未轉義
**原因**: `"{query: "charade"}"` - 引號衝突
**解決**: `\"` 轉義 `"{\"query\":\"charade\"}"`
---
## 測試指令
### 直接測試 API
```bash
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
-H "X-API-Key: muser_68600856036340bcafc01930eb4bd839" \
-d '{"query":"charade","limit":3}'
```
### 驗證服務狀態
```bash
# 檢查 Momentry Core
curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839" https://api.momentry.ddns.net/api/v1/videos
# 檢查 n8n
curl http://localhost:5678/api/v1/workflows \
-H "X-N8N-API-KEY: <your_api_key>"
```
---
## 服務資訊
| 服務 | URL | 說明 |
|------|-----|------|
| **n8n UI** | https://n8n.momentry.ddns.net | 工作流程管理 |
| **Momentry API** | https://api.momentry.ddns.net | 影片搜尋 API |
| **工作流程** | https://n8n.momentry.ddns.net/workflow/4vQo8I4SXEaR5E1A | 成功案例 |
---
## 進階使用
### 添加 Webhook 觸發器
如果你想從外部呼叫這個工作流程:
1. 在第一個節點前添加 **Webhook** Node
2. 設定:
```
Method: POST
Path: video-search
Response Mode: Last Node
```
3. 將 Webhook 連接到 Search API
4. 儲存並執行
5. 使用生成的 Webhook URL 呼叫:
```bash
curl -X POST <webhook_url> \
-d '{"query":"charade","limit":3}'
```
### 使用動態變數
修改 jsonBody 使用表達式:
```json
"{\"query\":\"={{ $json.query }}\",\"limit\":{{ $json.limit }}}"
```
然後在前面添加 Set Node 設定變數。
---
## 相關文件
- `docs_v1.0/IMPLEMENTATION/N8N_SETUP_COMPLETE.md` - 完整設定總結
- `docs_v1.0/IMPLEMENTATION/N8N_HTTP_REQUEST_GUIDE.md` - HTTP Request 詳細指南
- `docs/API_URL_EXAMPLES.md` - API URL 範例
---
## 完成!🎉
您現在擁有一個可以成功搜尋影片的 n8n 工作流程!
**關鍵成功要素**:
1. ✅ 使用 `specifyBody: "json"`
2. ✅ 使用 `jsonBody` 字串格式
3. ✅ 正確轉義 JSON 引號
4. ✅ 使用外部 API URL (`https://api.momentry.ddns.net`)

View File

@@ -0,0 +1,467 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Node.js 開發指南"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "開發指南"
ai_query_hints:
- "查詢 Node.js 開發指南 的內容"
- "Node.js 開發指南 的主要目的是什麼?"
- "如何操作或實施 Node.js 開發指南?"
---
# Node.js 開發指南
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明 Momentry 專案中 Node.js 環境的配置、管理與監控。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| 系統預設 Node.js | v25.8.1 |
| 鎖定 Node.js (n8n) | v22.22.1 |
| n8n 版本 | v2.3.5 |
| npm 路徑 (預設) | /opt/homebrew/bin/npm |
| node 路徑 (預設) | /opt/homebrew/bin/node |
| node@22 路徑 | /opt/homebrew/opt/node@22/bin/node |
---
## 版本策略
### 為什麼需要版本鎖定?
n8n 要求 Node.js 版本為 22.x LTS。系統預設的 Node.js 25.x 會導致:
- n8n 啟動警告
- Task-runner 執行錯誤
- 相容性問題
### 版本對照表
| 用途 | Node.js 版本 | 路徑 |
|------|-------------|------|
| 系統預設 | 25.8.1 | /opt/homebrew/bin/node |
| n8n 專用 | 22.22.1 | /opt/homebrew/opt/node@22/bin/node |
| 開發測試 | 22.x | /opt/homebrew/opt/node@22/bin/node |
---
## 安裝步驟
### 安裝特定版本 Node.js
```bash
# 安裝 Node.js 22.x (LTS)
brew install node@22
# 驗證安裝
/opt/homebrew/opt/node@22/bin/node --version
# v22.22.1
```
### 安裝全局工具
```bash
# 使用 node@22 安裝全局工具
/opt/homebrew/opt/node@22/bin/npm install -g <package-name>
# 例如安裝 n8n
/opt/homebrew/opt/node@22/bin/npm install -g n8n
```
---
## 使用方式
### 方式 1直接使用完整路徑
```bash
# 使用 node@22
/opt/homebrew/opt/node@22/bin/node script.js
# 使用 npm@22
/opt/homebrew/opt/node@22/bin/npm install
```
### 方式 2修改 PATH 環境變數
在 shell 配置檔案 (~/.zshrc) 中加入:
```bash
# 優先使用 node@22
export PATH="/opt/homebrew/opt/node@22/bin:$PATH"
# 重新載入
source ~/.zshrc
# 驗證
node --version
# v22.22.1
```
### 方式 3使用 nvm (推薦)
```bash
# 安裝 nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# 安裝 Node.js 22.x
nvm install 22
nvm use 22
# 設定預設版本
nvm alias default 22
```
---
## n8n 配置
### 環境變數設定
在 plist 或啟動腳本中設定:
```xml
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/node@22/bin/node</string>
<string>/opt/homebrew/lib/node_modules/n8n/bin/n8n</string>
<string>start</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
```
### Task Runner 配置
n8n Task Runner 用於執行 Code node 中的 JavaScript 代碼。
確保 PATH 中 node@22 在前面,這樣 task-runner 子進程會使用正確版本。
---
## 監控配置
### 健康檢查
`monitor/service/health_check.sh` 已包含 Node.js 版本檢查:
```bash
# 檢查 n8n 進程是否使用正確的 Node.js 版本
check_node() {
local LOCKED_NODE_VERSION="22"
# ...
}
```
### 執行監控
```bash
# 單獨檢查 Node.js
bash /Users/accusys/momentry_core_0.1/monitor/control/monitor_control.sh check node
# 檢查 Python
bash /Users/accusys/momentry_core_0.1/monitor/control/monitor_control.sh check python
```
### 查看版本狀態
```bash
# 查詢資料庫中的版本記錄
psql -U accusys -h localhost -d momentry -c "SELECT * FROM node_version_baseline;"
```
---
## 應用Registry
記錄系統中所有使用 Node.js 的應用程式。
| 應用 | Node.js 版本 | 執行路徑 | Port | 狀態 | 說明 |
|------|-------------|----------|------|------|------|
| n8n | 22.22.1 | /opt/homebrew/opt/node@22/bin/node | 5678/5679 | ✅ 執行中 | 工作流自動化平台 |
| markdownlint-cli | 25.x | /opt/homebrew/bin/npm | - | ✅ 已安裝 | Markdown lint 工具 |
| - | - | - | - | - | 新增應用請填入此表 |
---
## 新增 Node.js 應用決策
### 決策樹
```
新應用需要 Node.js?
├─ 支援 Node.js 22.x ────→ 使用現有 node@22
│ (路徑: /opt/homebrew/opt/node@22/bin/node)
├─ 需要特定版本 (如 18.x, 20.x) ────→ 安裝新版本 node@XX
│ │
│ ├─ 建立獨立目錄: /opt/homebrew/opt/node@XX/
│ ├─ 更新本文檔 Registry
│ └─ 建立獨立 plist 使用完整路徑
└─ 需要最新版本 ────→ 使用系統預設 node@25
(路徑: /opt/homebrew/bin/node)
```
### 步驟清單
1. **確認版本需求**
```bash
# 查看應用支援的 Node.js 版本
# 通常在 package.json 或官方文件中說明
```
2. **選擇現有版本或安裝新版本**
- 若支援 22.x → 使用 node@22
- 若需要特定版本 → 使用完整路徑隔離
3. **安裝新版本 (如需要)**
```bash
# 安裝特定版本
brew install node@20
# 或
brew install node@18
# 驗證安裝
/opt/homebrew/opt/node@20/bin/node --version
```
4. **建立隔離的服務配置**
- 使用完整路徑,不要依賴 PATH
- plist 中明确指定 node 路徑
- 設定獨立的環境變數
5. **更新文件**
- 更新本文檔 Registry 表格
- 新增應用的安裝文檔
- 更新監控配置
### 隔離原則
| 原則 | 說明 |
|------|------|
| **完整路徑** | 使用 `/opt/homebrew/opt/node@XX/bin/node` 而非 `node` |
| **獨立 PATH** | plist 中設定隔離的 PATH 環境變數 |
| **獨立目錄** | 不同版本的 node 安裝在各自目錄 |
| **獨立全局套件** | 每個版本有自己的 node_modules |
---
## 版本衝突處理
### 常見衝突場景
| 場景 | 原因 | 解決方案 |
|------|------|----------|
| n8n 顯示版本警告 | 使用 Node.js 25.x 啟動 | 確認 plist 使用 node@22 路徑 |
| task-runner 執行錯誤 | 子進程繼承錯誤版本 | 在 plist 中設定正確的 PATH |
| 全域套件找不到 | 跨版本安裝 | 使用正確版本的 npm |
| Port 被佔用 | 多個應用使用相同 Port | 分配獨立 Port |
### 診斷命令
```bash
# 1. 查看程序使用的 node 版本
ps aux | grep node
# 2. 查看程序 PID 使用的執行檔
lsof -p <PID> | grep bin/node
# 3. 確認路徑優先順序
echo $PATH
# 4. 查看 launchctl 服務的環境變數
sudo launchctl list | grep <service-name>
# 5. 檢查 Port 佔用
lsof -i :<port-number>
```
### 預防措施
1. **每次新增服務都更新 Registry**
2. **使用完整路徑而非 PATH**
3. **建立服務時指定版本**
4. **定期檢查執行中的程序**
---
## 故障排除
### 問題n8n 顯示 Node.js 版本警告
**原因**n8n 檢測到 Node.js 版本不是 22.x
**解決方案**
1. 確認 plist 中 ProgramArguments 使用正確路徑
2. 確認 PATH 環境變數中 node@22 在前面
3. 重載服務:
```bash
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.worker.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.worker.plist
```
### 問題Task Runner 使用錯誤版本
**原因**PATH 環境變數設定不正確
**診斷**
```bash
# 查看 task-runner 進程使用的 node
ps aux | grep "task-runner"
lsof -p <PID> | grep "txt" | grep node
```
**解決方案**
更新 plist 中的 PATH確保 `/opt/homebrew/opt/node@22/bin` 在最前面。
### 問題npm 全域套件找不到
**原因**:使用錯誤的 node 版本安裝
**解決方案**
```bash
# 確認使用的是哪個 node
which node
node --version
# 使用正確版本重新安裝
/opt/homebrew/opt/node@22/bin/npm install -g <package>
```
---
## 版本切換腳本
建立快速切換腳本 `~/bin/switch-node.sh`
```bash
#!/bin/bash
VERSION=$1
case $VERSION in
22)
export PATH="/opt/homebrew/opt/node@22/bin:$PATH"
echo "切換到 Node.js 22.x"
;;
system|25)
export PATH="/opt/homebrew/bin:$PATH"
echo "切換到系統 Node.js 25.x"
;;
*)
echo "用法: $0 {22|system}"
echo " 22 - Node.js 22.x (n8n)"
echo " system - Node.js 25.x (系統預設)"
exit 1
;;
esac
node --version
```
賦予執行權限:
```bash
chmod +x ~/bin/switch-node.sh
```
使用方式:
```bash
~/bin/switch-node.sh 22 # 切換到 22.x
~/bin/switch-node.sh system # 切換到系統預設
```
---
## 常用指令
```bash
# 查看 node 版本
node --version
# 查看 npm 版本
npm --version
# 查看 node 安裝位置
which node
# 查看 node@22 版本
/opt/homebrew/opt/node@22/bin/node --version
# 安裝全域套件 (使用 node@22)
/opt/homebrew/opt/node@22/bin/npm install -g n8n
# 檢查 n8n 進程
ps aux | grep n8n | grep -v grep
# 檢查 task-runner
ps aux | grep task-runner | grep -v grep
# 查看 task-runner 使用的 node 版本
lsof -p <PID> | grep "txt" | grep node
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| node (系統) | /opt/homebrew/bin/node | 預設 node |
| node@22 | /opt/homebrew/opt/node@22/bin/node | n8n 專用 |
| npm (系統) | /opt/homebrew/bin/npm | 預設 npm |
| npm@22 | /opt/homebrew/opt/node@22/bin/npm | n8n 專用 |
| n8n 安裝 | /opt/homebrew/lib/node_modules/n8n/ | n8n 目錄 |
| n8n main plist | /Library/LaunchDaemons/com.momentry.n8n.main.plist | 開機啟動 |
| n8n worker plist | /Library/LaunchDaemons/com.momentry.n8n.worker.plist | 開機啟動 |
---
## 版本速查
| 版本 | 用途 | 路徑 |
|------|------|------|
| 22.22.1 | n8n 專用 | /opt/homebrew/opt/node@22/bin/node |
| 25.x | 系統預設 | /opt/homebrew/bin/node |
---
## 相關文件
- [INSTALL_N8N.md](./INSTALL_N8N.md) - n8n 安裝指南 (包含 Node.js 配置範例)
- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範
- [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置
- [node_monitor.sh](../monitor/service/node_monitor.sh) - Node.js 監控腳本

View File

@@ -0,0 +1,830 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "待解決問題追蹤"
date: "2026-03-17"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "待解決問題追蹤"
ai_query_hints:
- "查詢 待解決問題追蹤 的內容"
- "待解決問題追蹤 的主要目的是什麼?"
- "如何操作或實施 待解決問題追蹤?"
---
# 待解決問題追蹤
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-17 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-17 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-21 | 更新問題狀態 | OpenCode | - |
| V1.2 | 2026-03-21 | 添加備份機制優化待辦 | OpenCode | - |
| V1.3 | 2026-03-21 | 完成清理硬編碼密碼 | OpenCode | - |
| V1.4 | 2026-03-21 | 完成 OpenCode n8n MCP 整合 | OpenCode | - |
| V1.5 | 2026-03-21 | 完成 API Key Management 核心模組 | OpenCode | - |
| V1.6 | 2026-03-23 | 添加 Momentry Core API launchd 待辦 | OpenCode | - |
| V1.7 | 2026-03-23 | 完成 Momentry Core API launchd 設定 | OpenCode | - |
| V1.8 | 2026-03-24 | 完成服務統一遷移,所有服務使用自定義 plist | OpenCode | big-pickle |
| V1.9 | 2026-03-24 | 建立統一會員系統實作計畫 | OpenCode | big-pickle |
| V2.0 | 2026-03-24 | 建立 Job Worker 實作計畫 | OpenCode | big-pickle |
---
## 問題 #1: sqlx async INSERT 不會實際寫入數據庫
### 問題描述
使用 sqlx async 執行 INSERT 時報告成功rows_affected=1但數據沒有實際寫入數據庫。
### 嘗試過的解決方案
| # | 嘗試方法 | 結果 |
|---|---------|------|
| 1 | 使用 `execute()` | 報告成功但未寫入 |
| 2 | 使用 `fetch()` | 同樣問題 |
| 3 | 使用交易 | 同樣問題 |
| 4 | 使用連接池 `acquire()` | 同樣問題 |
| 5 | 每個操作創建新連接池 | 同樣問題 |
| 6 | 使用 `std::process::Command` 同步調用 psql | 同樣問題 |
| 7 | 使用 `tokio::task::spawn_blocking` | 同樣問題 |
### 觀察到的現象
- 直接用 psql 命令行可以成功寫入
- 用另一個 PostgreSQL client 可以成功寫入
- sqlx 查詢 COUNT(*) 可以正確讀取數據
- 但 sqlx INSERT 報告成功卻不寫入
### 懷疑方向
- sqlx 連接池與 PostgreSQL 的某種交互問題
- Rust async runtime 與 PostgreSQL client 的問題
- postgresql.conf 配置問題
### 臨時解決方案
- Qdrant 向量存儲正常工作(不受影響)
- 存儲狀態追蹤功能正常運作
### 負責人
-
### 建立日期
2026-03-17
### 備註
影響 `store_vector` 函數PVector 存儲無法正常工作,但 QVector 正常運作
### 2026-03-21 調查結果
#### 測試結果
- 直接 psql INSERT: ✅ 成功
- 資料寫入驗證: ✅ 成功
#### 發現的問題
`store_vector` 函數 (`postgres_db.rs:819-860`) 存在以下問題:
```rust
// 問題 1: 錯誤被靜默忽略
match join_result {
Ok((cid, Ok(output))) => {
if !output.status.success() {
let err = String::from_utf8_lossy(&output.stderr);
tracing::error!("psql error for {}: {}", cid, err);
// 沒有返回錯誤!只是記錄日誌
}
}
// ...
}
Ok(()) // 即使失敗也返回 Ok
```
#### 建議修復
1.`store_vector` 返回實際結果
2. 在失敗時返回 `Err`
3. 添加單元測試驗證
#### 下一步
- [x] 修復 `store_vector` 錯誤處理
- [x] 添加單元測試
- [ ] 重現並確認問題根因
### 2026-03-21 修復完成
已修復 `store_vector` 函數的錯誤處理:
```rust
// 修復前:錯誤被靜默忽略
match join_result {
Ok((cid, Ok(output))) => {
if !output.status.success() {
tracing::error!("..."); // 只記錄,不返回錯誤
}
}
}
Ok(()) // 即使失敗也返回 Ok
// 修復後:正確傳播錯誤
let (cid, output) = result;
let output = output.map_err(|e| anyhow::anyhow!(...))?;
if !output.status.success() {
anyhow::bail!("psql INSERT failed...");
}
```
---
## 問題 #2: TUI 與 stdout 輸出混合
### 問題描述
Python processors 的 progress 輸出蓋過 TUI導致使用者無法清楚看到處理進度。
### 解決方案
~~使用 TUI 渲染到 stderr~~ → 使用 Redis Pub/Sub 作為消息總線
### 當前狀態
| 子項目 | 狀態 |
|--------|------|
| RedisPublisher Python 端 | ✅ 已實作 |
| Rust Redis 客戶端 | ✅ 已實作 (`redis_client.rs`) |
| Rust 訂閱更新 TUI | ✅ 已實作 (main.rs) |
| 中斷後繼續/重做 | 🔜 待實作 |
### 負責人
-
### 建立日期
2026-03-17
### 更新日期
2026-03-21
### 參考文檔
- `docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md`
- `scripts/redis_publisher.py`
- `src/core/db/redis_client.rs`
---
## 問題 #3: Redis Message Bus 尚未實作
### 問題描述
根據設計規範,需要使用 Redis 作為監控和狀態管理系統。
### 當前狀態
| 實作項目 | 狀態 | 說明 |
|---------|------|------|
| Redis 客戶端 (Hash) | ✅ | `redis_client.rs` |
| Redis 客戶端 (Pub/Sub) | ✅ | `redis_client.rs::subscribe_progress()` |
| Python RedisPublisher | ✅ | `scripts/redis_publisher.py` |
| Rust 訂閱頻道 | ✅ | `main.rs` 中的 redis 訂閱邏輯 |
| 監控腳本 | ✅ | `monitor/service/health_check.sh` |
### 負責人
-
### 建立日期
2026-03-17
### 更新日期
2026-03-21
### 優先級
~~高~~ → 中 - 核心功能已完成
### 參考文檔
- `docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md`
- `docs_v1.0/OPERATIONS/MOMENTRY_CORE_MONITORING.md`
---
## 架構優化待評估
詳細內容請參考 [ARCHITECTURE_EVALUATION.md](./ARCHITECTURE_EVALUATION.md)
| 項目 | 複雜度 | 優先級 |
|------|--------|--------|
| PostgreSQL → Redis 故障轉移 | 中 | 待評估 |
| 連接池監控 | 低 | 待評估 |
| Processor 重試機制 | 中 | 待評估 |
| PyO3 整合 | 高 | 低 |
| HTTP 健康端點 | 低 | ✅ 已完成 |
| Commit Message Lint | 低 | ✅ 已完成 |
---
## 備份機制優化 (2026-03-21)
### 問題分析
| 問題 | 嚴重性 | 說明 |
|------|--------|------|
| 溫冷分層未啟用 | 中 | weekly/monthly/archive 目錄為空 |
| 清理策略未執行 | 高 | 每日備份保留過多,空間浪費 |
| 無異地備份 | 高 | 無遠程備份(雲端/另一設備) |
| 備份驗證未自動化 | 中 | 只檢查文件存在,沒驗證可恢復性 |
| Gitea 大文件 | 中 | 每次備份 1GB頻率過高 |
| 密碼硬編碼 | 高 | 腳本中有多處明文密碼 |
### 待辦事項
| # | 任務 | 優先級 | 狀態 |
|---|------|--------|------|
| 1 | 啟用溫冷分層 (`backup_monitor.sh tier`) | 高 | 待辦 |
| 2 | 啟用清理策略 (`backup_monitor.sh cleanup`) | 高 | 待辦 |
| 3 | 添加備份驗證腳本 | 高 | 待辦 |
| 4 | 優化 Gitea 備份頻率 (改為週備份) | 中 | 待辦 |
| 5 | 創建外部備份腳本 (rsync/雲端) | 高 | 待辦 |
| 6 | 清理腳本中的硬編碼密碼 | 高 | ✅ 已完成 |
### 推薦備份策略
| 層級 | 保留時間 | 頻率 | 目標 |
|------|----------|------|------|
| Hot | 7 天 | 每日 | 本地 SSD |
| Warm | 30 天 | 每週 | 本地 HDD |
| Cold | 90 天 | 每月 | 外部存儲 |
| Archive | 1 年 | 每季 | 離線/雲端 |
### 建議的 Crontab
```bash
# 每日備份 (排除 Gitea)
0 3 * * 1-6 /Users/accusys/momentry/scripts/backup_all.sh all_except_gitea
# 每週完整備份 (含 Gitea)
0 3 * * 0 /Users/accusys/momentry/scripts/backup_all.sh all
# 每週溫冷分層
0 4 * * 0 /Users/accusys/momentry_core_0.1/monitor/storage/backup_monitor.sh tier
# 每週清理
0 5 * * 0 /Users/accusys/momentry_core_0.1/monitor/storage/backup_monitor.sh cleanup
# 每月驗證
0 6 1 * * /Users/accusys/momentry/scripts/verify_backup.sh
```
### 負責人
-
### 參考文件
- `/Users/accusys/momentry/scripts/backup_all.sh`
- `/Users/accusys/momentry_core_0.1/monitor/storage/backup_monitor.sh`
- `/Users/accusys/momentry/backup/`
---
## OpenCode n8n MCP 整合 (2026-03-21)
### 完成狀態
| 項目 | 狀態 | 說明 |
|------|------|------|
| n8n REST API 啟用 | ✅ | `N8N_PUBLIC_API_ENABLED=true` |
| API Key 生成 | ✅ | Settings → API |
| MCP Server 安裝 | ✅ | `@nextoolsolutions/mcp-n8n` |
| OpenCode 設定 | ✅ | `~/.config/opencode/opencode.json` |
| 43 工具可用 | ✅ | workflows, executions, datatables, tags 等 |
### 設定檔案
`~/.config/opencode/opencode.json`:
```json
{
"mcp": {
"gitea": {
"type": "local",
"enabled": true,
"command": [
"/opt/homebrew/bin/gitea-mcp-server",
"-token", "<GITEA_TOKEN>",
"-host", "http://localhost:3000"
]
},
"n8n": {
"type": "local",
"enabled": true,
"command": ["/opt/homebrew/bin/mcp-n8n"],
"environment": {
"N8N_BASE_URL": "http://localhost:5678",
"N8N_API_KEY": "<N8N_API_KEY>"
}
}
}
}
```
### 重要提醒
1. **API Key 安全**: 避免提交到 Git
2. **n8n 需通過反向代理**: localhost:5678 無法直接訪問 API需通過 Caddy
3. **重啟生效**: 修改 `opencode.json` 後需重啟 OpenCode
### 參考文件
- `docs_v1.0/IMPLEMENTATION/OPENCODE_GUIDE.md` - MCP 設定章節
- `docs_v1.0/IMPLEMENTATION/INSTALL_N8N.md` - n8n 安裝指南
---
## n8n API 備份腳本 (2026-03-21)
### 腳本位置
| 腳本 | 說明 | 依賴 |
|------|------|------|
| `monitor/workflow/backup_n8n_api.py` | REST API 備份 | requests |
| `monitor/workflow/backup_n8n_mcp.py` | MCP 備份(開發中) | mcp SDK |
### 使用方式
```bash
# 備份所有 workflows
N8N_API_KEY="..." python3.11 backup_n8n_api.py
# 只顯示變更(不備份)
N8N_API_KEY="..." python3.11 backup_n8n_api.py --diff
# 差異備份(只備份變更的 workflows
N8N_API_KEY="..." python3.11 backup_n8n_api.py --incremental
# 列出可用備份
python3.11 backup_n8n_api.py --list
# 驗證最新備份
python3.11 backup_n8n_api.py --verify
# 顯示備份統計
python3.11 backup_n8n_api.py --stats
# 只備份啟用的 workflows
N8N_API_KEY="..." python3.11 backup_n8n_api.py --active-only
# 備份失敗的執行記錄
N8N_API_KEY="..." python3.11 backup_n8n_api.py --failed-only
```
### 功能
- [x] REST API 備份
- [x] 變更偵測
- [x] SHA256 校驗
- [x] 備份版本化
- [x] 差異備份(`--incremental`
- [x] 備份驗證(`--verify`
- [x] 備份統計(`--stats`
- [x] 失敗執行記錄備份(`--failed-only`
- [ ] 選擇性備份(按 Tags
### 備份位置
```
/Users/accusys/momentry/backup/n8n_workflows/api/
├── 20260321_122059/
│ ├── workflows.json # 21 workflows
│ ├── workflows.json.sha256 # SHA256 校驗
│ ├── tags.json # Tags若有
│ └── manifest.json # 元數據
└── ...
```
### Crontab 建議
```bash
# 每日備份(下午 3 點)
0 15 * * * N8N_API_KEY="..." /opt/homebrew/bin/python3.11 /Users/accusys/momentry_core_0.1/monitor/workflow/backup_n8n_api.py >> /Users/accusys/momentry/log/monitor/workflow_backup_api.log 2>&1
```
---
## API Key Management System (2026-03-21)
### 設計目標
為 Momentry Core 實現完整的 API Key 管理系統,包括:
- API Key 生成(安全隨機)
- Key 哈希SHA256
- 異常檢測
- 強制輪換機制
- 審計日誌
### 模組架構
```
src/core/api_key/
├── mod.rs # 模組導出
├── models.rs # 數據模型和類型
├── service.rs # 核心服務邏輯
├── anomaly.rs # 異常檢測
└── rotation.rs # 輪換管理
```
### Key 類型
| 類型 | 前綴 | 默認 TTL | 寬限期 |
|------|------|----------|--------|
| System | `msys_` | 365 天 | 72 小時 |
| User | `muser_` | 90 天 | 24 小時 |
| Service | `msvc_` | 180 天 | 48 小時 |
| Integration | `mint_` | 30 天 | 24 小時 |
| Emergency | `memg_` | 1 天 | 0 小時 |
### Key 格式
```
{prefix}{uuid}_{timestamp}_{random}
例如: muser_a1b2c3d4e5f6_1710998400_abc12345
```
### 異常檢測閾值
| 指標 | 閾值 |
|------|------|
| 每分鐘請求數 | 1000 |
| 每小時請求數 | 10000 |
| 錯誤率 | 50% |
| 每小時唯一 IP 數 | 5 |
| 鎖定閾值 | 3 次觸發 |
### 實現狀態
| 組件 | 狀態 | 說明 |
|------|------|------|
| 數據模型 | ✅ 完成 | `models.rs` |
| Key 生成/哈希 | ✅ 完成 | `service.rs` |
| 異常檢測 | ✅ 完成 | `anomaly.rs` |
| 輪換機制 | ✅ 完成 | `rotation.rs` |
| CLI 命令 | ✅ 完成 | `main.rs` |
| 數據庫集成 | ✅ 完成 | `postgres_db.rs` |
| Redis 告警 | ✅ 完成 | `redis_client.rs` |
| 數據庫遷移 | ✅ 完成 | `migrations/001_api_key_management.sql` |
| 單元測試 | ✅ 完成 | 55 個測試通過 |
### CLI 命令
```bash
# 創建 API Key
momentry api-key create <name> --key-type service --ttl 90
# 列出所有 Keys
momentry api-key list
# 驗證 Key
momentry api-key validate --key <key>
# 撤銷 Key
momentry api-key revoke --key <key>
# 請求輪換
momentry api-key rotate --key <key>
# 顯示統計
momentry api-key stats
```
### 參考文件
- `src/core/api_key/` - API Key 模組
- `docs_v1.0/REFERENCE/API_KEY_MANAGEMENT.md` - 設計文檔
- `migrations/001_api_key_management.sql` - 數據庫遷移
---
## 問題 #5: Redis 用戶名問題 (2026-03-21)
### 問題描述
Redis 僅有 `default` 用戶,無 `accusys` 用戶。`.env` 檔案使用 `redis://accusys:accusys@localhost:6379` 格式會導致認證失敗。
### 測試結果
| URL 格式 | 結果 |
|----------|------|
| `redis://accusys:accusys@localhost:6379` | ❌ AUTH failed |
| `redis://:accusys@localhost:6379` | ✅ PONG |
### Redis ACL 狀態
```
user default on sanitize-payload #1bd51c... ~* &* +@all
requirepass: accusys
```
### 根本原因
1. Redis 啟動時僅設定 `--requirepass accusys`
2. 未建立自訂用戶 `accusys`
3. ACL 變更不會持久化(無 config file
### 已執行修復
| 項目 | 修改 |
|------|------|
| `.env` | `redis://accusys:accusys@localhost:6379``redis://:accusys@localhost:6379` |
### 待解決問題
1. **ACL 持久化**Redis 啟動後手動建立的用戶不會保留(重啟後消失)
2. **需配置 ACL 文件**:建議建立 `users.acl` 並在 plist 中指定
### 建議解決方案
#### 方案 A使用默認用戶現行
```bash
# .env
REDIS_URL=redis://:accusys@localhost:6379
```
**優點**:簡單,無需修改 Redis 配置
**缺點**:所有應用共享默認用戶
#### 方案 B建立 ACL 配置文件
```bash
# 1. 創建 ACL 文件
cat > /Users/accusys/momentry/etc/redis/users.acl << 'EOF'
user default on sanitize-payload ~* &* +@all >accusys
user accusys on sanitize-payload ~* &* +@all >accusys
EOF
# 2. 修改 plist 添加 --aclfile 參數
--aclfile /Users/accusys/momentry/etc/redis/users.acl
# 3. 重啟 Redis
sudo launchctl unload /Library/LaunchDaemons/com.momentry.redis.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
```
**優點**支持多用戶ACL 持久化
**缺點**:需修改 plist 並重啟
### 影響範圍
- `src/core/config.rs` - REDIS_URL 讀取
- `src/core/db/redis_client.rs` - Redis 連線
- `momentry api-key` 命令 - 異常告警
### 狀態
- [x] 已確認問題存在
- [x] 已修改 `.env` 使用默認用戶
- [ ] 待決定是否實施 ACL 方案
---
## 問題 #6: Momentry Core API 未開機自動啟動 (2026-03-23)
### 問題描述
Momentry Core API 服務 (`momentry server --port 3002`) 未設定 launchd導致
1. 系統重啟後 API 服務不會自動啟動
2. `api.momentry.ddns.net` 返回 502 Bad Gateway
3. n8n workflow 呼叫 API 時失敗
### 發現過程
1. n8n workflow 呼叫 `https://api.momentry.ddns.net/api/v1/n8n/search` 返回 502
2. 檢查發現 port 3002 無服務運行
3. Caddy 配置正向確,但後端服務未啟動
4. 手動啟動服務後 API 正常運作
### 配置需求
| 項目 | 值 |
|------|-----|
| 服務名稱 | `com.momentry.api` |
| 二進位檔 | `/Users/accusys/momentry_core_0.1/target/release/momentry` |
| 命令 | `server --port 3002` |
| Port | 3002 |
| 環境變數 | `DATABASE_URL`, `REDIS_URL` 等 |
### 待辦事項
- [x] 建立 `docs_v1.0/IMPLEMENTATION/INSTALL_MOMENTRY_API.md` 安裝文件
- [x] 建立 `/Library/LaunchDaemons/com.momentry.api.plist`
- [x] 設定環境變數 (`DATABASE_URL`, `REDIS_URL` 等)
- [x] 測試 launchctl load/unload
- [x] 驗證開機自動啟動 (launchd 載入成功)
### 完成日期
2026-03-23
### 參考文件
- `/Library/LaunchDaemons/com.momentry.n8n.main.plist` - n8n plist 範例
- `docs_v1.0/IMPLEMENTATION/INSTALL_N8N.md` - plist 配置說明
---
## 服務統一遷移 (2026-03-24)
### 問題描述
Reboot 後發現 n8n workflow 數量從 42 變成 41確認是 PostgreSQL 資料庫問題。經過調查發現:
1. **兩組不同的 PostgreSQL 資料目錄**
- Homebrew plist: `/opt/homebrew/var/postgresql@18` (有最新資料)
- Custom plist: `/Users/accusys/momentry/var/postgresql` (可能是舊資料)
2. **Reboot 時 custom plist 搶先啟動**,使用了錯誤的資料目錄
### 解決方案
1. **統一使用 custom plist**
- 刪除 homebrew plist (`~/Library/LaunchAgents/homebrew.mxcl.postgresql@18.plist`)
- Custom plist 使用 `/Users/accusys/momentry/var/postgresql` 作為資料目錄
- 將所有 14 個服務的 plist 註冊到 launchd
2. **所有已遷移的服務**
| 服務 | Plist | 資料目錄 |
|------|-------|----------|
| PostgreSQL | ✅ | `/Users/accusys/momentry/var/postgresql` |
| MariaDB | ✅ | `/Users/accusys/momentry/var/mariadb` |
| MongoDB | ✅ | `/opt/homebrew/var/mongodb` |
| Redis | ✅ | - |
| Ollama | ✅ | - |
| Qdrant | ✅ | - |
| n8n Main | ✅ | - |
| n8n Worker | ✅ | - |
| Caddy | ✅ | - |
| SFTPGo | ✅ | - |
| Gitea | ✅ | - |
| Gitea MCP | ✅ | - |
| PHP | ✅ | - |
| Momentry API | ✅ | - |
| RustDesk HBBR | ✅ | - |
| RustDesk HBBS | ✅ | - |
### 還發現的 Homebrew 服務 (未遷移)
| 服務 | 建議 |
|------|------|
| homebrew.mxcl.grafana | ⚠️ 考慮遷移 |
| homebrew.mxcl.prometheus | ⚠️ 考慮遷移 |
| homebrew.mxcl.openwebui | ⚠️ 考慮遷移 |
| homebrew.mxcl.kafka | ⚠️ 考慮遷移 |
| homebrew.mxcl.seaweedfs | ⚠️ 考慮遷移 |
| homebrew.mxcl.netdata | ⚠️ 考慮遷移 |
| homebrew.mxcl.ddclient | ⚠️ 動態 DNS |
| homebrew.mxcl.shadowsocks-rust | ⚠️ VPN |
### 預防措施
1. **確保統一資料目錄**:所有服務只使用一個資料目錄
2. **Reboot 測試**:遷移完成後需進行 Reboot 測試
3. **文件同步**plist 檔案同步到 repo
### 完成日期
2026-03-24
### 參考文件
- `docs_v1.0/REFERENCE/SERVICES.md` - 服務管理文檔
- `docs_v1.0/IMPLEMENTATION/SERVICE_ADDITION_GUIDE.md` - 服務添加規範
- `momentry_runtime/plist/` - plist 檔案存放位置
---
## Job Worker 實作 (2026-03-24)
### 目標
實作輪詢式 Job Worker實現檔案註冊後自動觸發處理
1. **輪詢機制**Worker 定期輪詢 jobs 佇列
2. **並行處理**:最多 2 個 processor 同時執行
3. **失敗容忍**:任一模組獨立,失敗可接續
### 設計決策
| 項目 | 決策 | 理由 |
|------|------|------|
| 觸發方式 | 輪詢Job Worker | 暫無可靠 API 觸發 |
| 並行處理 | 最多 2 個 | 可根據 CPU/GPU 調整 |
| 失敗處理 | 獨立模組,部分完成可接續 | 任何模組失敗都產出狀態 |
| Worker 啟動 | 獨立進程 | 隔離、易管理 |
| 並行上限 | 環境變數 + 預設值 | 靈活調整 |
| 狀態同步 | PostgreSQL + Redis | 可靠 + 即時 |
### 環境變數
| 變數 | 預設值 | 說明 |
|------|--------|------|
| `MOMENTRY_MAX_CONCURRENT` | 2 | 最大並行 processor 數 |
| `MOMENTRY_POLL_INTERVAL` | 5 | 輪詢間隔(秒) |
| `MOMENTRY_WORKER_ENABLED` | true | 是否啟用 worker |
### 實作計畫
詳細內容請參考 [JOB_WORKER_IMPLEMENTATION_PLAN.md](./JOB_WORKER_IMPLEMENTATION_PLAN.md)
### Phase 規劃
| Phase | 任務 | 預估工時 |
|-------|------|----------|
| 1 | 資料庫遷移 | 2h |
| 2 | Worker 框架 | 4h |
| 3 | Register API 整合 | 2h |
| 4 | Processor 執行 | 4h |
| 5 | 進度追蹤 | 2h |
| 6 | API 端點 | 3h |
| 7 | CLI 命令 | 2h |
| 8 | 測試 | 4h |
**總預估**: ~23h
### 實作結構
```
src/
├── worker/
│ ├── mod.rs # Worker 模組導出
│ ├── config.rs # Worker 配置
│ ├── worker.rs # Worker 主邏輯
│ ├── processor.rs # Processor 執行器
│ ├── queue.rs # Job 佇列管理
│ └── progress.rs # 進度追蹤
├── api/
│ └── server.rs # 更新 Register API
└── main.rs # 新增 worker 命令
```
### 狀態
- [x] 系統分析完成
- [x] 實作計畫文件建立
- [ ] Phase 1: 資料庫遷移
- [ ] Phase 2: Worker 框架
- [ ] Phase 3: Register API 整合
- [ ] Phase 4: Processor 執行
- [ ] Phase 5-8: 依序實作
### 參考文件
- `docs_v1.0/ARCHITECTURE/JOB_WORKER_IMPLEMENTATION_PLAN.md` - 完整實作計畫
- `docs_v1.0/ARCHITECTURE/PROCESSING_PIPELINE.md` - 處理流程
- `docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md` - Redis Key 設計
---
## 統一會員系統 + 影片歸屬追蹤 (2026-03-24)
### 目標
建立統一的會員系統:
1. WordPress 作為唯一登入入口
2. 每個影片關聯到 user_id追蹤歸屬
3. Per-user 配額管理
4. API 端點啟用認證
### 實作計畫
詳細內容請參考 [USER_MANAGEMENT_PLAN.md](./USER_MANAGEMENT_PLAN.md)
### Phase 規劃
| Phase | 任務 | 複雜度 | 優先級 | 預估工時 |
|-------|------|--------|--------|----------|
| 1 | WordPress Application Passwords 測試 | 低 | P0 | 1.5h |
| 2 | 資料庫遷移 (users 表) | 中 | P0 | 3h |
| 3 | API auth middleware | 中 | P0 | 4h |
| 4 | Register API 更新 | 低 | P0 | 2h |
| 5 | Admin users API | 中 | P1 | 4h |
| 6 | n8n workflow | 中 | P1 | 6h |
| 7 | 配額管理 | 中 | P2 | 4h |
| 8 | 測試驗證 | 中 | P2 | 4h |
**總預估**: ~28.5h
### 待確認事項
- [ ] WordPress 用戶建立方式(手動/Elementor表單
- [ ] API Key 格式確認
- [ ] SFTPGo 整合方式
- [ ] 配額管理策略
- [ ] 用戶刪除同步流程
### 狀態
- [x] 系統分析完成
- [x] 實作計畫文件建立
- [ ] Phase 1: WordPress 認證測試
- [ ] Phase 2: 資料庫遷移
- [ ] Phase 3-8: 依序實作
### 參考文件
- `docs_v1.0/ARCHITECTURE/USER_MANAGEMENT_PLAN.md` - 完整實作計畫
- `docs_v1.0/REFERENCE/API_KEY_MANAGEMENT.md` - API Key 管理
- `docs_v1.0/IMPLEMENTATION/SFTPGO_DEMO_USER.md` - SFTPGo 用戶設定

View File

@@ -0,0 +1,412 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Playground Binary Implementation Plan"
date: "2026-03-23"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "binary"
- "plan"
- "implementation"
- "playground"
ai_query_hints:
- "查詢 Playground Binary Implementation Plan 的內容"
- "Playground Binary Implementation Plan 的主要目的是什麼?"
- "如何操作或實施 Playground Binary Implementation Plan"
---
# Playground Binary Implementation Plan
| Item | Content |
|------|---------|
| Author | Warren |
| Created | 2026-03-23 |
| Document Version | V1.0 |
---
## Version History
| Version | Date | Purpose | Operator | Tool/Model |
|---------|------|---------|----------|------------|
| V1.0 | 2026-03-23 | Create implementation plan | Warren | OpenCode |
---
## Overview
Create separate `momentry_playground` binary with distinct configuration from `momentry` (production).
| Aspect | Production (`momentry`) | Development (`momentry_playground`) |
|--------|------------------------|-------------------------------------|
| **Port** | 3002 | 3003 |
| **Redis Prefix** | `momentry:` | `momentry_dev:` |
| **Worker** | Enabled | Disabled |
| **Purpose** | Production deployment | Testing/Development |
---
## Files to Modify
```
Files Changed: 6 files (+1 new)
├── src/core/config.rs ← Add server_port(), redis_key_prefix()
├── src/core/db/redis_client.rs ← Replace hardcoded prefixes
├── src/core/cache/redis_cache.rs ← Use configurable prefix
├── src/main.rs ← Update CLI defaults
├── src/playground.rs ← NEW: Development binary
├── Cargo.toml ← Add new binary
└── .env.development ← NEW: Dev environment config
```
---
## Implementation Steps
### Step 1: Update `src/core/config.rs`
Add after line 51 (after `MEDIA_BASE_URL`):
```rust
pub static SERVER_PORT: Lazy<u16> = Lazy::new(|| {
env::var("MOMENTRY_SERVER_PORT")
.unwrap_or_else(|_| "3002".to_string())
.parse()
.unwrap_or(3002)
});
pub static REDIS_KEY_PREFIX: Lazy<String> = Lazy::new(|| {
env::var("MOMENTRY_REDIS_PREFIX")
.unwrap_or_else(|_| "momentry:".to_string())
});
```
---
### Step 2: Update `src/core/db/redis_client.rs`
Replace all hardcoded `momentry:` prefixes with configurable prefix.
**Import at top:**
```rust
use crate::core::config::REDIS_KEY_PREFIX;
```
**Pattern for each method:**
```rust
let prefix = REDIS_KEY_PREFIX.as_str();
let key = format!("{}job:{}", prefix, uuid);
```
**Affected lines:**
| Line | Key Pattern |
|------|-------------|
| 47 | `job:{uuid}` |
| 81, 109 | `job:{uuid}:processor:{processor}` |
| 136, 146 | `progress:{uuid}` |
| 172 | `jobs:active` |
| 179 | `jobs:active``jobs:completed` |
| 187 | `jobs:active``jobs:failed` |
| 194 | `jobs:active` |
| 201, 208 | `health:momentry_core` |
| 214 | `monitor:job:{uuid}` |
| 242, 300 | `errors:{uuid}` |
| 258, 281 | `anomaly:alerts`, `anomaly:key:{key_id}` |
| 317, 346, 364, 392, 397 | `worker:job:{uuid}...` |
| 406, 410 | `worker:job:*` |
---
### Step 3: Update `src/core/cache/redis_cache.rs`
**Import:**
```rust
use crate::core::config::REDIS_KEY_PREFIX;
```
**Replace line 10:**
```rust
// Remove: const KEY_PREFIX: &str = "momentry:cache:";
```
**Update `prefixed_key` method (line 24):**
```rust
fn prefixed_key(&self, key: &str) -> String {
format!("{}cache:{}", REDIS_KEY_PREFIX.as_str(), key)
}
```
**Update tests (lines 161-162):**
```rust
#[test]
fn test_prefixed_key() {
// Note: This test will use the configured prefix
let cache = RedisCache::new().unwrap();
// With default prefix "momentry:"
assert_eq!(cache.prefixed_key("test"), "momentry:cache:test");
assert_eq!(cache.prefixed_key("video:abc"), "momentry:cache:video:abc");
}
```
---
### Step 4: Update `src/main.rs`
**Change CLI defaults (Lines 691-695):**
```rust
// Before:
#[arg(long, default_value = "3000")]
port: u16,
// After:
#[arg(long)]
port: Option<u16>,
```
**Update Server match arm (around line 2398):**
```rust
Commands::Server { host, port } => {
let port = port.unwrap_or_else(|| *crate::core::config::SERVER_PORT);
momentry_core::api::start_server(&host, port).await?;
Ok(())
}
```
**Update Redis key usage (Line 1098):**
```rust
// Before:
let key = format!("momentry:job:{}:processor:{}", uuid, processor);
// After:
let key = format!(
"{}job:{}:processor:{}",
crate::core::config::REDIS_KEY_PREFIX.as_str(),
uuid,
processor
);
```
---
### Step 5: Create `src/playground.rs`
```rust
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
// ... same imports as main.rs ...
fn main() -> Result<()> {
// Load development environment first
dotenv::from_filename(".env.development").ok();
tracing_subscriber::fmt::init();
tracing::info!("Starting momentry_playground (development binary)");
tracing::info!("Port: {}", *momentry_core::core::config::SERVER_PORT);
tracing::info!("Redis prefix: {}", *momentry_core::core::config::REDIS_KEY_PREFIX);
let cli = Cli::parse();
// ... rest identical to main.rs ...
}
```
---
### Step 6: Update `Cargo.toml`
**Add after line 90:**
```toml
[[bin]]
name = "momentry_playground"
path = "src/playground.rs"
```
**Add dependency (if not present):**
```toml
dotenv = "0.15"
```
---
### Step 7: Create `.env.development`
```bash
# Development Environment Configuration
# Used by: momentry_playground binary
# Server Configuration
MOMENTRY_SERVER_PORT=3003
MOMENTRY_REDIS_PREFIX=momentry_dev:
# Worker Configuration (disabled for development)
MOMENTRY_WORKER_ENABLED=false
MOMENTRY_MAX_CONCURRENT=1
MOMENTRY_POLL_INTERVAL=10
# Database (can use separate dev database)
DATABASE_URL=postgres://accusys@localhost:5432/momentry
MONGODB_URL=mongodb://accusys:Test3200Test3200@localhost:27017/admin
# Redis
REDIS_URL=redis://:accusys@localhost:6379
```
---
### Step 8: Update `.env` (Production)
Add these lines:
```bash
# Production Environment Configuration
# Used by: momentry binary
# Server Configuration
MOMENTRY_SERVER_PORT=3002
MOMENTRY_REDIS_PREFIX=momentry:
# Worker Configuration
MOMENTRY_WORKER_ENABLED=true
MOMENTRY_MAX_CONCURRENT=2
MOMENTRY_POLL_INTERVAL=5
```
---
## Testing Checklist
### 1. Build and Run Production Binary
```bash
cargo build --release --bin momentry
cargo run --bin momentry -- server
# Expected: Listening on http://127.0.0.1:3002
cargo run --bin momentry -- worker
# Expected: Worker started with momentry: prefix
```
### 2. Build and Run Development Binary
```bash
cargo build --bin momentry_playground
cargo run --bin momentry_playground -- server
# Expected: Listening on http://127.0.0.1:3003
```
### 3. Verify Redis Key Isolation
```bash
# Production data
redis-cli KEYS "momentry:*"
# Development data
redis-cli KEYS "momentry_dev:*"
# Should be separate
```
### 4. Run Both Simultaneously
```bash
# Terminal 1: Production
cargo run --bin momentry -- server
# Terminal 2: Development
cargo run --bin momentry_playground -- server
# Both should run without port conflicts
```
### 5. Unit Tests
```bash
cargo test --lib
# All tests should pass
```
---
## Redis Key Structure
### Production (`momentry:`)
```
momentry:job:{uuid} # Job status
momentry:job:{uuid}:processor:{name} # Processor progress
momentry:progress:{uuid} # Progress pub/sub
momentry:jobs:active # Active job set
momentry:jobs:completed # Completed job set
momentry:jobs:failed # Failed job set
momentry:health:momentry_core # Health status
momentry:cache:{key} # Cache entries
momentry:worker:job:{uuid} # Worker job
momentry:worker:job:{uuid}:processor:{name}
```
### Development (`momentry_dev:`)
```
momentry_dev:job:{uuid}
momentry_dev:job:{uuid}:processor:{name}
momentry_dev:progress:{uuid}
momentry_dev:jobs:active
momentry_dev:jobs:completed
momentry_dev:jobs:failed
momentry_dev:health:momentry_core
momentry_dev:cache:{key}
momentry_dev:worker:job:{uuid}
momentry_dev:worker:job:{uuid}:processor:{name}
```
---
## Potential Issues & Solutions
| Issue | Solution |
|-------|----------|
| `dotenv` crate not in dependencies | Add to Cargo.toml |
| Tests use hardcoded prefix | Update tests to use config, or use `#[cfg(test)]` defaults |
| Worker starts in playground | Check `MOMENTRY_WORKER_ENABLED=false` in `.env.development` |
| Port already in use | Graceful error message with suggestion to use `--port` flag |
| Mixed data in Redis | Ensure prefix is loaded before any Redis operations |
---
## Files Summary
| File | Lines Changed | Purpose |
|------|---------------|---------|
| `src/core/config.rs` | +15 | Add SERVER_PORT and REDIS_KEY_PREFIX |
| `src/core/db/redis_client.rs` | ~50 | Replace hardcoded prefixes |
| `src/core/cache/redis_cache.rs` | ~10 | Use configurable prefix |
| `src/main.rs` | ~15 | Update CLI defaults, Redis key usage |
| `src/playground.rs` | NEW (~2800) | Development binary |
| `Cargo.toml` | +4 | Add binary definition |
| `.env.development` | NEW (~20) | Development environment |
**Total**: ~60 lines modified + ~2800 lines new file
---
## Reference Documents
| Document | Purpose |
|----------|---------|
| `docs_v1.0/REFERENCE/SERVICES.md` | Port allocations |
| `docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md` | Redis key design |
| `AGENTS.md` | Code style and conventions |
---
## Version History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0 | 2025-03-25 | OpenCode | Initial implementation plan |

View File

@@ -0,0 +1,122 @@
# Portal 開發計畫
> 建立時間: 2026-04-25
> 狀態: 進行中
---
## 一、已完成功能
### 認證系統
- ✅ Login API (`/api/v1/auth/login`) - demo/demo
- ✅ Logout API (`/api/v1/auth/logout`)
- ✅ Session 管理 (localStorage)
- ✅ CORS 配置 (AllowOrigin::any)
### 納管檔案 (/files)
- ✅ 影片列表(分頁)
- ✅ 狀態過濾:未處理 / 已處理 / 全選
- ✅ 檔名搜尋 + 高亮匹配文字
- ✅ 影片詳情頁 (probe_json)
- ✅ 臉部群組顯示
- ✅ Tauri VideosResponse 修復 (count 欄位)
- ✅ API 狀態過濾修復 (pending/ready)
- ✅ 檔名搜尋功能修復
---
## 二、檔案管理增強
### 2.1 批量操作
- [ ] 全選/多選影片
- [ ] 批量納管Register
- [ ] 批量取消納管Unregister
- [ ] 批量觸發處理Process
- [ ] 批量刪除
### 2.2 進階搜尋過濾
- [ ] 日期範圍過濾
- [ ] 解析度過濾
- [ ] 檔案大小過濾
- [ ] 處理狀態過濾
### 2.3 處理控制
- [ ] 處理進度顯示Progress bar
- [ ] 處理日誌查看
- [ ] 錯誤訊息顯示
### 2.4 操作功能
- [ ] 取消納管Unregister
- [ ] 重新處理Reprocess
- [ ] 檔案下載
---
## 三、人物管理
### 3.1 人物列表 (/persons)
- [ ] 搜尋人物名稱
- [ ] 出現次數/時間排序
- [ ] 人物照片縮圖顯示
- [ ] 分頁
### 3.2 人物詳情 (/identity/:id)
- [ ] 所屬影片列表
- [ ] 時間軸顯示
- [ ] 截圖展示
- [ ] 出現片段時間
### 3.3 身份管理 (/identities)
- [ ] 已註冊身份列表
- [ ] 註冊新人物(從圖片)
- [ ] 人物更名
- [ ] 合併人物
- [ ] 刪除人物
### 3.4 臉部群組管理
- [ ] 群組審核/確認
- [ ] 未註冊群組列表
- [ ] 點擊註冊身份
- [ ] 群組截圖預覽
---
## 四、整體架構
```
首頁 (/home)
├── 搜尋 (/search)
├── 人物管理 (/persons)
│ ├── 已註冊人物列表
│ ├── 未註冊群組
│ └── 人物詳情 (/identity/:id)
├── 納管檔案 (/files)
│ ├── 影片列表
│ ├── 狀態過濾
│ ├── 檔名搜尋(高亮)
│ └── 影片詳情 (/videos/:uuid)
├── 設定 (/settings)
└── Console 面板
```
---
## 五、技術債務
### 待修復
- [ ] `hash_password` 函式未使用server.rs:35
- [ ] CORS 改為 AllowOrigin::any安全考量
- [ ] MongoCache 未考慮 status/query 過濾
### API 不一致
- [ ] API 返回 `count`,前端原使用 `total`
- [ ] Tauri 與 HTTP 模式返回格式差異
---
## 六、待確認問題
1. Identities 頁面是否要合併到 Persons
2. 是否需要「影片處理批次」功能?
3. 人物搜尋是否需要中文拼音/相似度?
4. 是否需要匯出報表功能?

View File

@@ -0,0 +1,586 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Python 開發規範"
date: "2026-03-16"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "開發規範"
- "python"
ai_query_hints:
- "查詢 Python 開發規範 的內容"
- "Python 開發規範 的主要目的是什麼?"
- "如何操作或實施 Python 開發規範?"
---
# Python 開發規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-21 | 新增 RedisPublisher API 文檔 | OpenCode | - |
---
## 概述
本文檔定義 Momentry 專案中 Python 程式碼的開發標準與最佳實踐。
---
## 版本管理
### 鎖定版本
| 版本 | 用途 | 路徑 |
|------|------|------|
| Python 3.11.14 | Momentry venv | /Users/accusys/momentry_core_0.1/venv/bin/python |
| Python 3.11.14 | 系統安裝 | /opt/homebrew/bin/python3.11 |
| Python 3.14.3 | 系統預設 | /opt/homebrew/bin/python3 |
| Python 3.9.6 | 系統預設 (備用) | /usr/bin/python3 |
### 版本選擇原則
- **Momentry 專案**:使用 venv 中的 Python 3.11.14
- **新專案**:建議使用 venv 管理
- **系統工具**:可使用系統預設版本
---
## 腳本規範
### Shebang 宣告
所有 Momentry Python 腳本必須在第一行宣告明確的 Python 路徑:
```python
#!/opt/homebrew/bin/python3.11
```
**錯誤範例**
```python
#!/usr/bin/env python3 # 會解析到系統預設 (3.14.3)
#!/usr/bin/python3 # 會使用系統 Python (3.9.6)
```
**正確範例**
```python
#!/opt/homebrew/bin/python3.11
import sys
...
```
### 檔案結構
```
scripts/
├── asr_processor.py # ASR 處理腳本
├── thumbnail_extractor.py # 縮圖提取腳本
└── new_script.py # 新腳本模板
```
### 腳本模板
```python
#!/opt/homebrew/bin/python3.11
"""
腳本名稱
簡短描述腳本功能
用法:
python3.11 script.py <args>
作者: Momentry Team
版本: 1.0.0
"""
import argparse
import json
import logging
import sys
from pathlib import Path
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
stream=sys.stderr,
)
logger = logging.getLogger(__name__)
def main():
parser = argparse.ArgumentParser(description="腳本功能描述")
parser.add_argument("input", help="輸入檔案或參數")
parser.add_argument("-o", "--output", default="output.json", help="輸出檔案")
parser.add_argument("-v", "--verbose", action="store_true", help="詳細輸出")
parser.add_argument("-c", "--count", type=int, default=10, help="數量")
args = parser.parse_args()
if args.verbose:
logger.setLevel(logging.DEBUG)
# 業務邏輯
result = process_data(args.input, args.count)
# 輸出 JSON結果
print(json.dumps(result))
def process_data(input_path: str, count: int) -> dict:
"""處理資料並返回結果"""
logger.info(f"Processing: {input_path}")
# TODO: 實作業務邏輯
return {
"status": "success",
"input": input_path,
"count": count,
}
if __name__ == "__main__":
main()
```
---
## 與 Rust 整合
### 使用 venv (目前採用)
Momentry 使用 venv 管理 Python 環境,避免與系統其他程式衝突。
#### 建立 venv
```bash
# 建立虛擬環境
cd /Users/accusys/momentry_core_0.1
/opt/homebrew/bin/python3.11 -m venv venv
# 啟用虛擬環境
source venv/bin/activate
# 安裝依賴
pip install -r requirements.txt
```
#### 從 Rust 呼叫 venv Python
```rust
use std::path::Path;
let script_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("scripts")
.join("asr_processor.py");
// 使用 venv 中的 Python
let venv_python = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("venv")
.join("bin")
.join("python");
let output = Command::new(venv_python)
.arg(script_path)
.arg(video_path)
.output()
.context("Failed to run processor")?;
```
**優點**
- 專案依賴隔離
- 不同專案可使用不同 Python 版本
- 易於重現環境
- 不影響系統其他程式
---
## 依賴管理
### venv 目錄結構
```
momentry_core_0.1/
├── venv/ # 虛擬環境
│ ├── bin/
│ │ ├── python # Python 3.11.14
│ │ ├── pip
│ │ └── ...
│ └── lib/python3.11/ # 安裝的套件
├── requirements.txt # 依賴列表
├── scripts/ # Python 腳本
│ ├── asr_processor.py
│ └── thumbnail_extractor.py
└── src/ # Rust 程式碼
```
### 使用虛擬環境
```bash
# 啟用虛擬環境
source venv/bin/activate
# 安裝依賴
pip install faster-whisper
# 退出虛擬環境
deactivate
```
# 退出虛擬環境
deactivate
```
### 依賴列表格式
建立 `requirements.txt`
```
faster-whisper>=1.0.0
ffmpeg-python>=0.2.0
Pillow>=10.0.0
```
### 安裝專案依賴
```bash
# 使用 python3.11 安裝
/opt/homebrew/bin/python3.11 -m pip install -r requirements.txt
```
---
## RedisPublisher 進度發布
### 概述
`redis_publisher.py` 提供統一的進度發布介面,用於 Python 處理器向 Rust 端的 TUI 即時回報進度。
### 基本用法
```python
#!/opt/homebrew/bin/python3.11
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from redis_publisher import RedisPublisher
def process_video(video_path: str, uuid: str):
pub = RedisPublisher(uuid)
pub.info("asr", "Starting ASR processing")
pub.progress("asr", current=50, total=100, message="Processing segment")
pub.complete("asr", "Transcription complete")
```
### API 參考
| 方法 | 說明 | 範例 |
|------|------|------|
| `info(proc, msg)` | 發布資訊訊息 | `pub.info("asr", "Model loaded")` |
| `progress(proc, cur, tot, msg)` | 發布進度 | `pub.progress("asr", 50, 100, "...")` |
| `complete(proc, msg)` | 發布完成 | `pub.complete("asr", "Done")` |
| `error(proc, msg)` | 發布錯誤 | `pub.error("asr", "Failed")` |
| `warning(proc, msg)` | 發布警告 | `pub.warning("asr", "Retry...")` |
| `percentage(proc, pct, msg)` | 發布百分比 | `pub.percentage("asr", 50.5, "50%")` |
### 結構化訊息格式
```python
from redis_publisher import MessageType, ProgressContext
# 使用 Context Manager
with ProgressContext(pub, "asr"):
# 自動發布開始/完成/錯誤
run_asr()
# 帶 extra 資料
pub.progress("asr", current=50, total=100, message="...",
extra={"fps": 30.5, "model": "tiny"})
```
### 環境變數
| 變數 | 預設值 | 說明 |
|------|--------|------|
| `REDIS_URL` | `redis://:accusys@localhost:6379` | Redis 連線 URL |
| `REDIS_PASSWORD` | `accusys` | Redis 密碼 |
---
## 程式碼規範
### Import 排序
```python
# 1. 標準庫
import sys
import os
import json
import logging
from pathlib import Path
from typing import Optional
# 2. 第三方庫
import numpy as np
import pandas as pd
from faster_whisper import WhisperModel
# 3. 本地模組
from . import local_module
from ..package import module
```
### 命名規範
| 類型 | 規範 | 範例 |
|------|------|------|
| 模組/檔案 | snake_case | `asr_processor.py` |
| 類別 | PascalCase | `class VideoProcessor` |
| 函數/變數 | snake_case | `def process_video()` |
| 常量 | UPPER_SNAKE_CASE | `MAX_WORKERS = 4` |
| 私有成員 | _leading_underscore | `_private_method()` |
### 類型提示
```python
from typing import Optional, List, Dict
def process_video(
video_path: str,
options: Optional[Dict[str, int]] = None,
) -> List[Dict[str, float]]:
"""處理影片並返回結果"""
...
```
### 錯誤處理
```python
import logging
from pathlib import Path
logger = logging.getLogger(__name__)
def process_video(video_path: str) -> dict:
path = Path(video_path)
if not path.exists():
logger.error(f"Video file not found: {video_path}")
raise FileNotFoundError(f"Video not found: {video_path}")
try:
result = _do_process(path)
logger.info(f"Processed successfully: {path}")
return result
except Exception as e:
logger.exception(f"Processing failed: {e}")
raise
```
### 日誌規範
```python
import logging
import sys
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
stream=sys.stderr,
)
logger = logging.getLogger(__name__)
# 使用說明
logger.info("Starting process...")
logger.debug(f"Input: {input_path}")
logger.warning(f"Using fallback: {reason}")
logger.error(f"Failed: {error}")
```
---
## 測試規範
### 測試結構
```
tests/
├── __init__.py
├── test_asr_processor.py
└── test_thumbnail_extractor.py
```
### 測試範例
```python
import pytest
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from asr_processor import run_asr
def test_asr_processor_with_valid_video(tmp_path):
video_path = tmp_path / "test.mp4"
output_path = tmp_path / "output.json"
# 建立測試影片
video_path.write_text("dummy")
# 執行
result = run_asr(str(video_path), str(output_path))
# 斷言
assert output_path.exists()
assert result["segments"]
def test_asr_processor_with_invalid_video():
with pytest.raises(FileNotFoundError):
run_asr("/nonexistent/video.mp4", "/tmp/output.json")
```
### 執行測試
```bash
# 使用 python3.11 執行測試
/opt/homebrew/bin/python3.11 -m pytest tests/ -v
# 包含覆蓋率
/opt/homebrew/bin/python3.11 -m pytest tests/ --cov=scripts
```
---
## 監控配置
### 監控腳本
`monitor/config/monitor_config.yaml` 中配置:
```yaml
service:
- name: "python"
type: "process"
process_name: "python3"
enabled: true
check_interval: 60
version_lock: "3.11.14"
scripts:
- "/Users/accusys/momentry_core_0.1/scripts/asr_processor.py"
- "/Users/accusys/momentry_core_0.1/scripts/thumbnail_extractor.py"
```
### 檢查版本
```bash
# 執行 Python 監控
bash /Users/accusys/momentry_core_0.1/monitor/control/monitor_control.sh check python
# 查看資料庫記錄
psql -U accusys -h localhost -d momentry -c "SELECT * FROM python_version_baseline;"
```
---
## CI/CD 考量
### GitHub Actions 範例
```yaml
name: Python Tests
on: [push, pull_request]
jobs:
test:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Run tests
run: |
python -m pytest tests/ -v
```
---
## 常見問題
### Q: 為什麼腳本要用 `#!/opt/homebrew/bin/python3.11` 而不是 `#!/usr/bin/env python3`
A: `#!/usr/bin/env python3` 會解析 PATH 中的第一個 `python3`,在 macOS 上可能是:
- `/opt/homebrew/bin/python3` (3.14.3)
- `/usr/bin/python3` (3.9.6)
明確指定路徑可確保使用正確版本。
### Q: Rust 呼叫 Python 腳本時如何確保版本正確?
A: 有三種方式:
1. Rust 程式碼中使用明確路徑:`Command::new("/opt/homebrew/bin/python3.11")`
2. 設定環境變數 PATH
3. 建立系統別名(不推薦,影響其他程式)
### Q: 如何管理多個 Python 版本?
A: 建議使用:
- **pyenv**:管理多個 Python 版本
- **venv**:隔離專案依賴
- **Docker**:容器化環境
---
## 檢查清單
新增 Python 腳本時確認:
- [ ] 使用 `#!/opt/homebrew/bin/python3.11` shebang
- [ ] 包含 docstring 說明功能
- [ ] 使用 argparse 處理命令行參數
- [ ] 使用 logging 進行日誌輸出
- [ ] 錯誤處理適當
- [ ] 類型提示完整
- [ ] 更新監控配置
- [ ] 建立測試案例
---
## 版本速查
| 版本 | 用途 | 路徑 |
|------|------|------|
| 3.11.14 | Momentry venv | /Users/accusys/momentry_core_0.1/venv/bin/python |
| 3.11.14 | 系統安裝 | /opt/homebrew/bin/python3.11 |
| 3.14.3 | 系統預設 | /opt/homebrew/bin/python3 |
---
## 相關文件
- [NODEJS.md](./NODEJS.md) - Node.js 開發指南
- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範
- [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置
- [python_monitor.sh](../monitor/service/python_monitor.sh) - Python 監控腳本

View File

@@ -0,0 +1,119 @@
# Momentry Core API 文件總覽
| 項目 | 內容 |
|------|------|
| 建立者 | OpenCode |
| 建立時間 | 2026-03-25 |
| 文件版本 | V2.2 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V2.0 | 2026-03-22 | 創建 API 文件總覽 | Warren | OpenCode |
| V2.1 | 2026-03-24 | 新增文件分類與快速選擇指南 | OpenCode | deepseek-reasoner |
| V2.2 | 2026-03-25 | 更新 API Key 驗證說明與文件連結 | OpenCode | deepseek-reasoner |
---
## 文件架構
```
docs/
├── API_INDEX.md ← 本文件:總覽與入口
├── API_ENDPOINTS.md ← API 端點完整說明
├── API_EXAMPLES.md ← 完整範例總覽curl / n8n / WordPress
├── API_REFERENCE.md ← 詳細技術參考
├── DEMO_MANUAL.md ← ⭐ 示範手冊(含 Demo API Key
├── API_N8N_GUIDE.md ← n8n 詳細指南
├── API_WORDPRESS_GUIDE.md ← WordPress 詳細指南
├── API_CURL_EXAMPLES.md ← curl 快速範例
├── API_TRAINING_MARCOM.md ← ⭐ marcom 團隊教育訓練手冊
├── API_WORKFLOW_WORDPRESS_N8N.md ← WordPress/n8n 完整工作流程
└── CHUNK_DATA_STRUCTURE.md ← Chunk 資料結構說明
```
---
## 快速選擇指南
| 需求 | 閱讀文件 |
|------|----------|
| **我要快速開始測試** | ⭐ [DEMO_MANUAL.md](./DEMO_MANUAL.md) |
| **我要查看所有範例** | [API_EXAMPLES.md](./API_EXAMPLES.md) |
| **我是 marcom 團隊** | ⭐ [API_TRAINING_MARCOM.md](./API_TRAINING_MARCOM.md) |
| 我想了解有哪些 API 端點 | [API_ENDPOINTS.md](./API_ENDPOINTS.md) |
| 我要整合 WordPress/n8n | [API_WORKFLOW_WORDPRESS_N8N.md](./API_WORKFLOW_WORDPRESS_N8N.md) |
| 我要在 n8n workflow 中呼叫 API | [DEMO_MANUAL.md](./DEMO_MANUAL.md#2-n8n-範例) |
| 我要在 WordPress 中呼叫 API | [DEMO_MANUAL.md](./DEMO_MANUAL.md#3-wordpress-範例) |
| 我要用 curl 快速測試 | [DEMO_MANUAL.md](./DEMO_MANUAL.md#1-curl-範例) |
---
## 認證
### Demo API Key
```
API Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69
Key ID: muser_68600856036340bcafc01930eb4bd839
過期日: 2027-03-25
```
### 使用方式
```bash
curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
http://localhost:3002/api/v1/videos
```
---
## API URL 選擇
| 環境 | URL | 使用時機 |
|------|-----|----------|
| **本地開發** | `http://localhost:3002` | 開發/測試、繞過反向代理 |
| **外部訪問** | `https://api.momentry.ddns.net` | n8n、WordPress、遠端系統 |
### 何時用哪個
**使用 `localhost:3002`**
- 本地終端機測試
- 當反向代理有問題時
- 快速除錯
**使用 `api.momentry.ddns.net`**
- n8n workflow
- WordPress 網站
- 外部系統整合
---
## 常見問題
### Q: API 返回 401 錯誤?
API Key 無效或過期。請使用 Demo API Key 或建立新的 API Key。
### Q: API 返回 502 錯誤?
```bash
# 檢查服務狀態
launchctl list | grep momentry.api
# 如未啟動
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
```
### Q: 兩個 URL 功能相同嗎?
是的,所有端點完全相同,只是訪問路徑不同。
---
## 相關文件
- [DEMO_MANUAL.md](./DEMO_MANUAL.md) - ⭐ 示範手冊(推薦新手)
- [INSTALL_MOMENTRY_API.md](./INSTALL_MOMENTRY_API.md) - API 服務安裝指南
- [PENDING_ISSUES.md](./PENDING_ISSUES.md) - 待解決問題追蹤

View File

@@ -0,0 +1,528 @@
# Momentry Core API 安裝指南
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-18 |
| 文件版本 | V1.3 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
| V1.1 | 2026-03-23 | 更新端點與實際一致 | OpenCode | - |
| V1.2 | 2026-03-25 | 新增快取/刪除 API | OpenCode | - |
| V1.3 | 2026-03-26 | 修正認證聲明與API回應格式 | OpenCode | - |
---
## Base URL
| 環境 | URL | 說明 |
|------|-----|------|
| **本地開發** | `http://localhost:3002` | 直接訪問 API繞過反向代理 |
| **外部訪問** | `https://api.momentry.ddns.net` | 通過 Caddy 反向代理訪問,需網路可達 |
> **Note:** Port 3000 is used by Gitea. Momentry API server runs on **port 3002**.
### URL 使用時機
| 情境 | 建議 URL |
|------|----------|
| 本地開發/測試 | `http://localhost:3002` |
| n8n workflow | `https://api.momentry.ddns.net` |
| 外部系統整合 | `https://api.momentry.ddns.net` |
| 反向代理有問題時 | `http://localhost:3002` (繞過代理) |
## Authentication
**API Key 認證:**
所有 `/api/v1/*` 端點需要 `X-API-Key` header 進行認證。
**公開端點:**
- `GET /health` - 健康檢查
- `GET /health/detailed` - 詳細健康檢查
**認證格式:**
```bash
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
```
**API Key 管理:**
- 使用 `/api/v1/api-keys` 端點管理 API Keys
- 詳細說明請參考 [API Key Management Guide](../docs/API_KEY_MANAGEMENT.md)
---
## Endpoints
### 1. Register Video
Register a video file to the system.
**Endpoint:** `POST /api/v1/register`
**Request Body:**
```json
{
"path": "/path/to/video.mp4"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `path` | string | Yes | Absolute path to video file |
**Response (200):**
```json
{
"uuid": "5dea6618a606e7c7",
"video_id": 1,
"job_id": 10,
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080,
"already_exists": false
}
```
**Example:**
```bash
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "/Users/accusys/test_video/BigBuckBunny_320x180.mp4"}'
```
---
### 2. Process Video (CLI)
Process video to generate ASR, CUT, YOLO, OCR, Face, Pose data.
**Note:** This is a CLI command, not an HTTP endpoint.
```bash
# Process video by UUID
cargo run --bin momentry -- process 5dea6618a606e7c7
# Or process by file path
cargo run --bin momentry -- process /path/to/video.mp4
```
---
### 3. Get Progress
Get real-time processing progress via Redis.
**Endpoint:** `GET /api/v1/progress/:uuid`
| Parameter | Type | Description |
|-----------|------|-------------|
| `uuid` | path | Video UUID (16 characters) |
**Response (200):**
```json
{
"uuid": "5dea6618a606e7c7",
"processors": [
{
"name": "asr",
"status": "complete",
"current": 0,
"total": 0,
"message": "7 segments"
},
{
"name": "cut",
"status": "complete",
"current": 134,
"total": 134,
"message": "134 scenes"
},
{
"name": "yolo",
"status": "progress",
"current": 5000,
"total": 14315,
"message": "frame 5000"
},
{
"name": "ocr",
"status": "pending",
"current": 0,
"total": 0,
"message": ""
}
]
}
```
**Processor Status Values:**
- `pending` - Not started
- `info` - Starting/info message
- `progress` - In progress
- `complete` - Finished
- `error` - Failed
**Example:**
```bash
# Get progress for specific video
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/progress/5dea6618a606e7c7
```
---
### 4. Natural Language Search
Search video chunks using natural language queries (RAG).
**Endpoint:** `POST /api/v1/search`
**Request Body:**
```json
{
"query": "What is the person saying about machine learning?",
"limit": 10,
"uuid": "5dea6618a606e7c7"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `query` | string | Yes | Natural language search query |
| `limit` | integer | No | Max results (default: 10) |
| `uuid` | string | No | Filter by specific video UUID |
**Response (200):**
```json
{
"results": [
{
"uuid": "5dea6618a606e7c7",
"chunk_id": "0",
"chunk_type": "sentence",
"start_time": 5.5,
"end_time": 8.2,
"text": "Machine learning is a subset of artificial intelligence...",
"score": 0.85
}
],
"query": "What is the person saying about machine learning?"
}
```
**Example:**
```bash
curl -X POST http://localhost:3002/api/v1/search \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "machine learning", "limit": 5}'
```
---
### 4a. N8N Search (n8n Workflow Integration)
N8n-compatible search endpoint with standardized response format for direct workflow integration.
**Endpoint:** `POST /api/v1/n8n/search`
**Request Body:**
```json
{
"query": "sunset",
"limit": 10,
"uuid": "5dea6618a606e7c7"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `query` | string | Yes | Natural language search query |
| `limit` | integer | No | Max results (default: 10) |
| `uuid` | string | No | Filter by specific video UUID |
**Response (200):**
```json
{
"query": "sunset",
"count": 2,
"hits": [
{
"id": "c_001",
"vid": "5dea6618a606e7c7",
"start": 5.5,
"end": 8.2,
"title": "Sunset Scene",
"text": "The sun slowly sets over the ocean...",
"score": 0.92,
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4"
}
]
}
```
| Field | Type | Description |
|-------|------|-------------|
| `query` | string | Original search query |
| `count` | integer | Number of results |
| `hits[].id` | string | Chunk ID |
| `hits[].vid` | string | Video UUID |
| `hits[].start` | number | Start time in seconds |
| `hits[].end` | number | End time in seconds |
| `hits[].title` | string | Chunk title (from metadata or auto-generated) |
| `hits[].text` | string | Text content |
| `hits[].score` | number | Relevance score (0-1) |
| `hits[].file_path` | string | Full file path to video file |
**Example:**
```bash
curl -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"query": "sunset", "limit": 5}'
```
**Environment Variables:**
| Variable | Default | Description |
|----------|---------|-------------|
| `MOMENTRY_MEDIA_BASE_URL` | `https://wp.momentry.ddns.net` | Base URL for constructing media URLs |
---
### 5. Lookup Video
Lookup video UUID by path or get video details by UUID.
**Endpoint:** `GET /api/v1/lookup`
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `path` | query | No* | Video file path |
| `uuid` | query | No* | Video UUID |
*One of `path` or `uuid` is required.
**Response (200):**
```json
{
"uuid": "5dea6618a606e7c7",
"file_path": "/path/to/video.mp4",
"file_name": "video.mp4",
"duration": 120.5
}
```
**Example:**
```bash
# Lookup by path
curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4"
# Lookup by UUID
curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?uuid=5dea6618a606e7c7"
```
---
### 6. List Videos
List all registered videos.
**Endpoint:** `GET /api/v1/videos`
**Response (200):**
```json
{
"videos": [
{
"uuid": "5dea6618a606e7c7",
"file_path": "/path/to/video.mp4",
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080
}
]
}
```
**Example:**
```bash
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos
```
---
## Data Flow
```
┌─────────────────────────────────────────────────────────────────────┐
│ 完整工作流程 │
└─────────────────────────────────────────────────────────────────────┘
1. Register Video
POST /api/v1/register
└── UUID: 5dea6618a606e7c7
2. Process Video (CLI)
cargo run -- process 5dea6618a606e7c7
├── ASR (WhisperX) → 7 segments
├── CUT (PySceneDetect) → 134 scenes
├── YOLO (YOLOv8) → 10483 frames with objects
├── OCR (EasyOCR) → 40 frames with text
├── Face (OpenCV) → 44 frames with faces
└── Pose (YOLOv8-Pose) → 14315 frames
3. Monitor Progress (Real-time)
GET /api/v1/progress/:uuid
└── Redis Pub/Sub + Hash
4. Chunk (CLI)
cargo run -- chunk 5dea6618a606e7c7
└── Create chunks in database
5. Vectorize (CLI)
cargo run -- vectorize 5dea6618a606e7c7
└── Generate embeddings in Qdrant
6. Search (API)
POST /api/v1/search
└── Natural language query
```
---
## Processor Reference
| Processor | Model | Description |
|-----------|-------|-------------|
| **ASR** | WhisperX (faster-whisper) | Speech recognition + diarization |
| **CUT** | PySceneDetect | Scene detection/segmentation |
| **ASRX** | WhisperX | Speaker diarization |
| **YOLO** | YOLOv8n | Object detection |
| **OCR** | EasyOCR | Text recognition |
| **Face** | OpenCV Haar Cascade | Face detection |
| **Pose** | YOLOv8n-Pose | Pose estimation |
---
## Cache Toggle
Toggle caching at runtime.
**Endpoint:** `POST /api/v1/config/cache`
**Request Body:**
```json
{
"enabled": true
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `enabled` | boolean | Yes | Enable (true) or disable (false) cache |
**Response (200):**
```json
{
"cache_enabled": true,
"message": "Cache toggled successfully"
}
```
---
## Unregister Video
Delete a video and all associated data (chunks, processor results, thumbnails).
**Endpoint:** `POST /api/v1/unregister`
**Request Body:**
```json
{
"uuid": "5dea6618a606e7c7"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `uuid` | string | Yes | Video UUID (16 character hex) |
**Response (200):**
```json
{
"success": true,
"message": "Video unregistered successfully",
"uuid": "5dea6618a606e7c7"
}
```
**Warning:** This operation is irreversible and will delete all associated chunks, processor results, and thumbnails.
---
## Error Responses
**400 Bad Request**
```json
{
"error": "Invalid request body"
}
```
**404 Not Found**
```json
{
"error": "Resource not found"
}
```
**500 Internal Server Error**
```json
{
"error": "Internal server error"
}
```
---
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `DATABASE_URL` | `postgres://accusys@localhost:5432/momentry` | PostgreSQL connection |
| `REDIS_URL` | `redis://localhost:6379` | Redis connection |
| `REDIS_PASSWORD` | `accusys` | Redis password |
| `QDRANT_URL` | `http://localhost:6333` | Qdrant vector DB URL |
| `QDRANT_API_KEY` | - | Qdrant API key |
| `QDRANT_COLLECTION` | `chunks` | Qdrant collection name |
| `MOMENTRY_MEDIA_BASE_URL` | `https://wp.momentry.ddns.net` | Base URL for n8n search media URLs |
---
## Starting the Server
```bash
# Default (port 3002, since 3000 is Gitea)
cargo run --bin momentry -- server
# Custom host and port
cargo run --bin momentry -- server --host 127.0.0.1 --port 3002
```
---
## Quick Reference
| Task | Command |
|------|---------|
| Register video | `POST /api/v1/register` |
| Process video | `cargo run -- process <uuid>` |
| Check progress | `GET /api/v1/progress/<uuid>` |
| Search | `POST /api/v1/search` |
| List videos | `GET /api/v1/videos` |
| Lookup | `GET /api/v1/lookup?uuid=<uuid>` |
| Toggle cache | `POST /api/v1/config/cache` |
| Delete video | `POST /api/v1/unregister` |

View File

@@ -0,0 +1,519 @@
# Momentry JSON 輸出檔案規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
本文檔定義 Momentry Core 系統中所有 JSON 輸出檔案的結構、命名規範與儲存位置。
---
## 1. 輸出檔案總覽
### 1.1 檔案類型
| 類型 | 前綴 | 說明 | 狀態 |
|------|------|------|------|
| **Probe** | `{uuid}.probe.json` | 影片元數據 | ✅ 已實作 |
| **ASR** | `{uuid}.asr.json` | 語音識別結果 | ✅ 已實作 |
| **ASRx** | `{uuid}.asrx.json` | 說話者分離 | 🔜 規劃中 |
| **OCR** | `{uuid}.ocr.json` | 文字辨識結果 | 🔜 規劃中 |
| **YOLO** | `{uuid}.yolo.json` | 物件偵測結果 | 🔜 規劃中 |
| **Face** | `{uuid}.face.json` | 人臉偵測結果 | 🔜 規劃中 |
| **Pose** | `{uuid}.pose.json` | 姿態估計結果 | 🔜 規劃中 |
| **Thumbnail** | `{uuid}/thumb_XXX.jpg` | 縮圖檔案 | ✅ 已實作 |
### 1.2 命名規範
```
{UUID}.{類型}.json
範例:
1636719dc31f78ac.probe.json - 影片探測結果
1636719dc31f78ac.asr.json - 語音識別結果
1636719dc31f78ac.ocr.json - 文字辨識結果
```
- **UUID**: 16 字元,基於檔案路徑計算
- **類型**: 小寫 snake_case
- **副檔名**: `.json`
---
## 2. 輸出目錄結構
### 2.1 預設輸出位置
```
momentry_core_0.1/
├── {uuid}.probe.json # 影片探測
├── {uuid}.asr.json # 語音識別
├── {uuid}.asrx.json # 說話者分離
├── {uuid}.ocr.json # 文字辨識
├── {uuid}.yolo.json # 物件偵測
├── {uuid}.face.json # 人臉偵測
├── {uuid}.pose.json # 姿態估計
└── thumbnails/
└── {uuid}/
├── thumb_000.jpg
├── thumb_001.jpg
└── ...
```
### 2.2 儲存策略
| 資料類型 | 儲存位置 | 說明 |
|----------|----------|------|
| JSON 檔案 | 專案根目錄 | 方便快速存取 |
| 縮圖 | thumbnails/{uuid}/ | 分離儲存 |
| 資料庫 | PostgreSQL | 長期儲存 |
---
## 3. JSON 結構定義
### 3.1 Probe (影片探測)
**檔案**: `{uuid}.probe.json`
```json
{
"streams": [
{
"index": 0,
"codec_name": "h264",
"codec_type": "video",
"width": 1920,
"height": 1080,
"r_frame_rate": "60000/1001",
"duration": "6879.329524",
"sample_rate": null,
"channels": null
},
{
"index": 1,
"codec_name": "aac",
"codec_type": "audio",
"width": null,
"height": null,
"r_frame_rate": "0/0",
"duration": "6879.245333",
"sample_rate": "48000",
"channels": 2
}
],
"format": {
"filename": "/path/to/video.mov",
"format_name": "mov,mp4,m4a,3gp,3g2,mj2",
"duration": "6879.329524",
"size": "2361629896",
"bit_rate": "2748000"
}
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `streams` | Array | 媒體串流陣列 |
| `streams[].index` | Integer | 串流索引 |
| `streams[].codec_name` | String | 編碼名稱 |
| `streams[].codec_type` | String | 串流類型 (video/audio) |
| `streams[].width` | Integer | 寬度 (video) |
| `streams[].height` | Integer | 高度 (video) |
| `streams[].r_frame_rate` | String | 幀率 |
| `streams[].duration` | String | 持續時間 (秒) |
| `streams[].sample_rate` | String | 採樣率 (audio) |
| `streams[].channels` | Integer | 聲道數 (audio) |
| `format` | Object | 檔案格式資訊 |
| `format.filename` | String | 原始檔案路徑 |
| `format.format_name` | String | 格式名稱 |
| `format.duration` | String | 總時長 (秒) |
| `format.size` | String | 檔案大小 (bytes) |
| `format.bit_rate` | String | 位元率 |
---
### 3.2 ASR (語音識別)
**檔案**: `{uuid}.asr.json`
```json
{
"language": "en",
"language_probability": 0.9945855736732483,
"segments": [
{
"start": 0.0,
"end": 19.04,
"text": "Hello and welcome to the old-time movie show."
},
{
"start": 19.04,
"end": 25.44,
"text": "Today we are featuring the 1963 comedy mystery film Charade."
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `language` | String | 偵測語言代碼 (ISO 639-1) |
| `language_probability` | Float | 語言偵測機率 (0-1) |
| `segments` | Array | 語音分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].text` | String | 識別文字 |
---
### 3.3 ASRx (說話者分離)
**檔案**: `{uuid}.asrx.json`
```json
{
"language": "en",
"language_probability": 0.95,
"segments": [
{
"start": 0.0,
"end": 19.04,
"text": "Hello and welcome to the old-time movie show.",
"speaker_id": "SPEAKER_00",
"speaker_embedding": [0.123, -0.456, ...]
},
{
"start": 19.04,
"end": 25.44,
"text": "Today we are featuring the 1963 comedy mystery film Charade.",
"speaker_id": "SPEAKER_01",
"speaker_embedding": [0.789, -0.123, ...]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `language` | String | 偵測語言代碼 |
| `language_probability` | Float | 語言偵測機率 |
| `segments` | Array | 語音分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].text` | String | 識別文字 |
| `segments[].speaker_id` | String | 說話者 ID |
| `segments[].speaker_embedding` | Array | 說話者嵌入向量 (可選) |
---
### 3.4 OCR (文字辨識)
**檔案**: `{uuid}.ocr.json`
```json
{
"segments": [
{
"start": 10.5,
"end": 12.3,
"text": "EXAMPLE TEXT",
"boxes": [
{
"x1": 100,
"y1": 50,
"x2": 400,
"y2": 100
}
],
"confidence": 0.95
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | OCR 分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].text` | String | 辨識文字 |
| `segments[].boxes` | Array | 文字邊界框陣列 |
| `segments[].boxes[].x1` | Integer | 左上 X 座標 |
| `segments[].boxes[].y1` | Integer | 左上 Y 座標 |
| `segments[].boxes[].x2` | Integer | 右下 X 座標 |
| `segments[].boxes[].y2` | Integer | 右下 Y 座標 |
| `segments[].confidence` | Float | 辨識信心度 |
---
### 3.5 YOLO (物件偵測)
**檔案**: `{uuid}.yolo.json`
```json
{
"segments": [
{
"start": 0.0,
"end": 1.0,
"objects": [
{
"class": "person",
"confidence": 0.92,
"box": {
"x1": 150,
"y1": 200,
"x2": 400,
"y2": 800
}
},
{
"class": "car",
"confidence": 0.87,
"box": {
"x1": 800,
"y1": 400,
"x2": 1200,
"y2": 700
}
}
]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | 時間分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].objects` | Array | 偵測物件陣列 |
| `segments[].objects[].class` | String | 物件類別 |
| `segments[].objects[].confidence` | Float | 偵測信心度 |
| `segments[].objects[].box` | Object | 邊界框 |
---
### 3.6 Face (人臉偵測)
**檔案**: `{uuid}.face.json`
```json
{
"segments": [
{
"start": 0.0,
"end": 1.0,
"faces": [
{
"face_id": "face_001",
"box": {
"x1": 100,
"y1": 50,
"x2": 300,
"y2": 350
},
"embedding": [0.123, -0.456, ...],
"emotion": "happy",
"age": 35,
"gender": "female"
}
]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | 時間分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].faces` | Array | 人臉陣列 |
| `segments[].faces[].face_id` | String | 人臉 ID |
| `segments[].faces[].box` | Object | 邊界框 |
| `segments[].faces[].embedding` | Array | 人臉嵌入向量 |
| `segments[].faces[].emotion` | String | 情緒分類 (可選) |
| `segments[].faces[].age` | Integer | 年齡估計 (可選) |
| `segments[].faces[].gender` | String | 性別估計 (可選) |
---
### 3.7 Pose (姿態估計)
**檔案**: `{uuid}.pose.json`
```json
{
"segments": [
{
"start": 0.0,
"end": 1.0,
"poses": [
{
"person_id": "person_001",
"keypoints": {
"nose": {"x": 320, "y": 120, "confidence": 0.98},
"left_eye": {"x": 335, "y": 110, "confidence": 0.95},
"right_eye": {"x": 305, "y": 110, "confidence": 0.93},
"left_shoulder": {"x": 280, "y": 180, "confidence": 0.91},
"right_shoulder": {"x": 360, "y": 180, "confidence": 0.89}
},
"confidence": 0.92
}
]
}
]
}
```
**欄位說明**:
| 欄位 | 類型 | 說明 |
|------|------|------|
| `segments` | Array | 時間分段陣列 |
| `segments[].start` | Float | 開始時間 (秒) |
| `segments[].end` | Float | 結束時間 (秒) |
| `segments[].poses` | Array | 姿態陣列 |
| `segments[].poses[].person_id` | String | 人員 ID |
| `segments[].poses[].keypoints` | Object | 關鍵點 |
| `segments[].poses[].keypoints.{name}` | Object | 各關鍵點 |
| `segments[].poses[].keypoints.{name}.x` | Integer | X 座標 |
| `segments[].poses[].keypoints.{name}.y` | Integer | Y 座標 |
| `segments[].poses[].keypoints.{name}.confidence` | Float | 信心度 |
| `segments[].poses[].confidence` | Float | 整體信心度 |
---
## 4. 處理流程
### 4.1 處理管線
```
影片檔案
┌─────────────────┐
│ 1. Register │ 建立 UUID註冊影片
└────────┬────────┘
┌────▼────┐
│ 2. Probe │ ffprobe 擷取元數據
└────┬────┘
│ {uuid}.probe.json
┌────▼─────┐
│ 3. ASR │ faster-whisper 語音識別
└────┬─────┘
│ {uuid}.asr.json
┌────▼──────┐
│ 4. ASRx │ 說話者分離 (pyannote)
└────┬──────┘
│ {uuid}.asrx.json
┌────▼────┐
│ 5. OCR │ Tesseract 文字辨識
└────┬────┘
│ {uuid}.ocr.json
┌────▼────┐
│ 6. YOLO │ 物件偵測
└────┬────┘
│ {uuid}.yolo.json
┌────▼────┐
│ 7. Face │ 人臉偵測
└────┬────┘
│ {uuid}.face.json
┌────▼────┐
│ 8. Pose │ 姿態估計
└────┬────┘
│ {uuid}.pose.json
┌────▼──────┐
│ 9. Chunk │ 轉換為資料庫 chunks
└───────────┘
```
### 4.2 失敗處理
| 階段 | 失敗時 | 處理 |
|------|--------|------|
| Probe | 無法讀取影片 | 終止流程,輸出錯誤 |
| ASR | 無音軌 | 產生空 segments繼續流程 |
| OCR/YOLO/Face/Pose | 處理失敗 | 跳過該階段,記錄日誌 |
---
## 5. 資料庫儲存
### 5.1 Chunk 結構
```sql
CREATE TABLE chunks (
id BIGSERIAL PRIMARY KEY,
uuid VARCHAR(16) NOT NULL,
chunk_id VARCHAR(64) NOT NULL,
chunk_index INTEGER NOT NULL,
chunk_type VARCHAR(32) NOT NULL,
start_time DOUBLE PRECISION NOT NULL,
end_time DOUBLE PRECISION NOT NULL,
content JSONB NOT NULL,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(uuid, chunk_id)
);
```
### 5.2 轉換範例
```rust
// ASR → Chunk (Sentence)
for (i, seg) in asr_result.segments.iter().enumerate() {
let chunk = Chunk::new(
uuid.clone(),
i as u32,
ChunkType::Sentence,
seg.start,
seg.end,
serde_json::json!({"text": seg.text}),
);
db.store_chunk(&chunk).await?;
}
```
---
## 6. 版本歷史
| 版本 | 日期 | 變更 |
|------|------|------|
| 1.0.0 | 2026-03-16 | 初始版本 |
---
## 7. 相關文件
- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範
- [AGENTS.md](../AGENTS.md) - 開發規範
- [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置

View File

@@ -0,0 +1,301 @@
# n8n Video Search 工作流程 - 成功設定指南
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-22 |
| 文件版本 | V1.1 |
| 適用版本 | n8n 2.3.5 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-22 | 創建文件 | Warren | OpenCode |
| V1.1 | 2026-03-26 | 更新 API 範例,新增 X-API-Key 驗證標頭 | OpenCode | deepseek-reasoner |
---
## ✅ 成功案例
| 項目 | 內容 |
|------|------|
| **工作流程名稱** | Video Search - Working v3 |
| **ID** | 4vQo8I4SXEaR5E1A |
| **狀態** | ✅ 成功運作 |
| **測試結果** | 成功搜尋 "charade",返回 3 個結果 |
---
## 正確的 HTTP Request Node 設定
### 成功的設定方式
```json
{
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"query\":\"charade\",\"limit\":3}",
"options": {
"headers": {
"X-API-Key": "demo_api_key_12345"
}
}
}
```
### 關鍵設定說明
| 設定項目 | 正確值 | 錯誤值 | 說明 |
|---------|--------|--------|------|
| **specifyBody** | `"json"` | `"body"` | 必須選擇 `"json"` |
| **jsonBody** | 字串 `"{...}"` | 物件 `{}` | 必須是 JSON 字串格式 |
| **轉義** | `\"query\"` | `"query"` | 引號需要轉義 |
---
## 工作流程架構
```
┌─────────────────────────┐
│ Manual Trigger │ ← 點擊 "Execute Workflow"
└───────────┬─────────────┘
┌─────────────────────────┐
│ HTTP Request Node │ ← 呼叫 Momentry API
│ - specifyBody: "json" │
│ - jsonBody: "{...}" │
└───────────┬─────────────┘
┌─────────────────────────┐
│ Code Node │ ← 格式化輸出
│ - console.log() │
│ - return json │
└─────────────────────────┘
```
---
## 完整的 Workflow JSON
```json
{
"name": "Video Search - Working v3",
"nodes": [
{
"parameters": {},
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [250, 300]
},
{
"parameters": {
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "{\"query\":\"charade\",\"limit\":3}",
"options": {
"headers": {
"X-API-Key": "demo_api_key_12345"
}
}
},
"name": "Search API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [450, 300]
},
{
"parameters": {
"jsCode": "const data = $input.first().json;\nconsole.log('Response:', JSON.stringify(data, null, 2));\nreturn [{ json: data }];"
},
"name": "Show Result",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [650, 300]
}
],
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [[{"node": "Search API", "type": "main", "index": 0}]]
},
"Search API": {
"main": [[{"node": "Show Result", "type": "main", "index": 0}]]
}
},
"settings": {"executionOrder": "v1"},
"staticData": null
}
```
---
## 使用步驟
### 步驟 1: 導入工作流程
1. 開啟 n8n UI: `https://n8n.momentry.ddns.net`
2. 點擊 **Add Workflow** (+)
3. 點擊 **Import from File**
4. 選擇上面的 JSON 檔案
### 步驟 2: 執行測試
1. 點擊 **"Execute Workflow"** 按鈕 (▶️)
2. 等待執行完成
3. 點擊 **"Show Result"** 節點
4. 查看右側 **JSON** 面板
### 步驟 3: 修改搜尋關鍵字
1. 點擊 **"Search API"** 節點
2. 修改 `jsonBody`:
```json
"{\"query\":\"您的關鍵字\",\"limit\":5}"
```
3. 點擊 **Save** (Ctrl+S)
4. 重新執行
---
## 成功的回應範例
```json
{
"query": "charade",
"count": 3,
"hits": [
{
"id": "sentence_0006",
"vid": "a1b10138a6bbb0cd",
"start": 48.8,
"end": 55.44,
"title": "Chunk sentence_0006",
"text": "fun plot twists, Woody Dialog and charming performances...",
"score": 0.526,
"file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/Old_Time_Movie_Show_-_Charade_1963.HD.mov"
}
]
}
```
---
## 常見錯誤與解決
### ❌ 錯誤 1: "Your request is invalid"
**原因**: `specifyBody` 設為 `"body"` 而不是 `"json"`
**解決**:
```json
✅ "specifyBody": "json"
❌ "specifyBody": "body"
```
### ❌ 錯誤 2: "$httpRequest is not defined"
**原因**: Code Node 中使用 `$httpRequest`,但您的 n8n 版本不支援
**解決**: 使用 **HTTP Request Node** 代替 Code Node
### ❌ 錯誤 3: Body 格式錯誤
**原因**: `body` 使用物件格式 `{query: "..."}`
**解決**: 使用 `jsonBody` 字串格式 `{"query":"..."}`
### ❌ 錯誤 4: JSON 引號未轉義
**原因**: `"{query: "charade"}"` - 引號衝突
**解決**: `\"` 轉義 `"{\"query\":\"charade\"}"`
---
## 測試指令
### 直接測試 API
```bash
curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \
-H "Content-Type: application/json" \
-H "X-API-Key: demo_api_key_12345" \
-d '{"query":"charade","limit":3}'
```
### 驗證服務狀態
```bash
# 檢查 Momentry Core
curl -H "X-API-Key: demo_api_key_12345" https://api.momentry.ddns.net/api/v1/videos
# 檢查 n8n
curl http://localhost:5678/api/v1/workflows \
-H "X-N8N-API-KEY: <your_api_key>"
```
---
## 服務資訊
| 服務 | URL | 說明 |
|------|-----|------|
| **n8n UI** | https://n8n.momentry.ddns.net | 工作流程管理 |
| **Momentry API** | https://api.momentry.ddns.net | 影片搜尋 API |
| **工作流程** | https://n8n.momentry.ddns.net/workflow/4vQo8I4SXEaR5E1A | 成功案例 |
---
## 進階使用
### 添加 Webhook 觸發器
如果你想從外部呼叫這個工作流程:
1. 在第一個節點前添加 **Webhook** Node
2. 設定:
```
Method: POST
Path: video-search
Response Mode: Last Node
```
3. 將 Webhook 連接到 Search API
4. 儲存並執行
5. 使用生成的 Webhook URL 呼叫:
```bash
curl -X POST <webhook_url> \
-d '{"query":"charade","limit":3}'
```
### 使用動態變數
修改 jsonBody 使用表達式:
```json
"{\"query\":\"={{ $json.query }}\",\"limit\":{{ $json.limit }}}"
```
然後在前面添加 Set Node 設定變數。
---
## 相關文件
- `docs/N8N_SETUP_COMPLETE.md` - 完整設定總結
- `docs/N8N_HTTP_REQUEST_GUIDE.md` - HTTP Request 詳細指南
- `docs/API_URL_EXAMPLES.md` - API URL 範例
---
## 完成!🎉
您現在擁有一個可以成功搜尋影片的 n8n 工作流程!
**關鍵成功要素**:
1. ✅ 使用 `specifyBody: "json"`
2. ✅ 使用 `jsonBody` 字串格式
3. ✅ 正確轉義 JSON 引號
4. ✅ 使用外部 API URL (`https://api.momentry.ddns.net`)

View File

@@ -0,0 +1,450 @@
# Node.js 開發指南
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-16 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 |
---
## 概述
本文檔說明 Momentry 專案中 Node.js 環境的配置、管理與監控。
---
## 當前狀態
| 項目 | 狀態 |
|------|------|
| 系統預設 Node.js | v25.8.1 |
| 鎖定 Node.js (n8n) | v22.22.1 |
| n8n 版本 | v2.3.5 |
| npm 路徑 (預設) | /opt/homebrew/bin/npm |
| node 路徑 (預設) | /opt/homebrew/bin/node |
| node@22 路徑 | /opt/homebrew/opt/node@22/bin/node |
---
## 版本策略
### 為什麼需要版本鎖定?
n8n 要求 Node.js 版本為 22.x LTS。系統預設的 Node.js 25.x 會導致:
- n8n 啟動警告
- Task-runner 執行錯誤
- 相容性問題
### 版本對照表
| 用途 | Node.js 版本 | 路徑 |
|------|-------------|------|
| 系統預設 | 25.8.1 | /opt/homebrew/bin/node |
| n8n 專用 | 22.22.1 | /opt/homebrew/opt/node@22/bin/node |
| 開發測試 | 22.x | /opt/homebrew/opt/node@22/bin/node |
---
## 安裝步驟
### 安裝特定版本 Node.js
```bash
# 安裝 Node.js 22.x (LTS)
brew install node@22
# 驗證安裝
/opt/homebrew/opt/node@22/bin/node --version
# v22.22.1
```
### 安裝全局工具
```bash
# 使用 node@22 安裝全局工具
/opt/homebrew/opt/node@22/bin/npm install -g <package-name>
# 例如安裝 n8n
/opt/homebrew/opt/node@22/bin/npm install -g n8n
```
---
## 使用方式
### 方式 1直接使用完整路徑
```bash
# 使用 node@22
/opt/homebrew/opt/node@22/bin/node script.js
# 使用 npm@22
/opt/homebrew/opt/node@22/bin/npm install
```
### 方式 2修改 PATH 環境變數
在 shell 配置檔案 (~/.zshrc) 中加入:
```bash
# 優先使用 node@22
export PATH="/opt/homebrew/opt/node@22/bin:$PATH"
# 重新載入
source ~/.zshrc
# 驗證
node --version
# v22.22.1
```
### 方式 3使用 nvm (推薦)
```bash
# 安裝 nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# 安裝 Node.js 22.x
nvm install 22
nvm use 22
# 設定預設版本
nvm alias default 22
```
---
## n8n 配置
### 環境變數設定
在 plist 或啟動腳本中設定:
```xml
<key>ProgramArguments</key>
<array>
<string>/opt/homebrew/opt/node@22/bin/node</string>
<string>/opt/homebrew/lib/node_modules/n8n/bin/n8n</string>
<string>start</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
```
### Task Runner 配置
n8n Task Runner 用於執行 Code node 中的 JavaScript 代碼。
確保 PATH 中 node@22 在前面,這樣 task-runner 子進程會使用正確版本。
---
## 監控配置
### 健康檢查
`monitor/service/health_check.sh` 已包含 Node.js 版本檢查:
```bash
# 檢查 n8n 進程是否使用正確的 Node.js 版本
check_node() {
local LOCKED_NODE_VERSION="22"
# ...
}
```
### 執行監控
```bash
# 單獨檢查 Node.js
bash /Users/accusys/momentry_core_0.1/monitor/control/monitor_control.sh check node
# 檢查 Python
bash /Users/accusys/momentry_core_0.1/monitor/control/monitor_control.sh check python
```
### 查看版本狀態
```bash
# 查詢資料庫中的版本記錄
psql -U accusys -h localhost -d momentry -c "SELECT * FROM node_version_baseline;"
```
---
## 應用Registry
記錄系統中所有使用 Node.js 的應用程式。
| 應用 | Node.js 版本 | 執行路徑 | Port | 狀態 | 說明 |
|------|-------------|----------|------|------|------|
| n8n | 22.22.1 | /opt/homebrew/opt/node@22/bin/node | 5678/5679 | ✅ 執行中 | 工作流自動化平台 |
| markdownlint-cli | 25.x | /opt/homebrew/bin/npm | - | ✅ 已安裝 | Markdown lint 工具 |
| - | - | - | - | - | 新增應用請填入此表 |
---
## 新增 Node.js 應用決策
### 決策樹
```
新應用需要 Node.js?
├─ 支援 Node.js 22.x ────→ 使用現有 node@22
│ (路徑: /opt/homebrew/opt/node@22/bin/node)
├─ 需要特定版本 (如 18.x, 20.x) ────→ 安裝新版本 node@XX
│ │
│ ├─ 建立獨立目錄: /opt/homebrew/opt/node@XX/
│ ├─ 更新本文檔 Registry
│ └─ 建立獨立 plist 使用完整路徑
└─ 需要最新版本 ────→ 使用系統預設 node@25
(路徑: /opt/homebrew/bin/node)
```
### 步驟清單
1. **確認版本需求**
```bash
# 查看應用支援的 Node.js 版本
# 通常在 package.json 或官方文件中說明
```
2. **選擇現有版本或安裝新版本**
- 若支援 22.x → 使用 node@22
- 若需要特定版本 → 使用完整路徑隔離
3. **安裝新版本 (如需要)**
```bash
# 安裝特定版本
brew install node@20
# 或
brew install node@18
# 驗證安裝
/opt/homebrew/opt/node@20/bin/node --version
```
4. **建立隔離的服務配置**
- 使用完整路徑,不要依賴 PATH
- plist 中明确指定 node 路徑
- 設定獨立的環境變數
5. **更新文件**
- 更新本文檔 Registry 表格
- 新增應用的安裝文檔
- 更新監控配置
### 隔離原則
| 原則 | 說明 |
|------|------|
| **完整路徑** | 使用 `/opt/homebrew/opt/node@XX/bin/node` 而非 `node` |
| **獨立 PATH** | plist 中設定隔離的 PATH 環境變數 |
| **獨立目錄** | 不同版本的 node 安裝在各自目錄 |
| **獨立全局套件** | 每個版本有自己的 node_modules |
---
## 版本衝突處理
### 常見衝突場景
| 場景 | 原因 | 解決方案 |
|------|------|----------|
| n8n 顯示版本警告 | 使用 Node.js 25.x 啟動 | 確認 plist 使用 node@22 路徑 |
| task-runner 執行錯誤 | 子進程繼承錯誤版本 | 在 plist 中設定正確的 PATH |
| 全域套件找不到 | 跨版本安裝 | 使用正確版本的 npm |
| Port 被佔用 | 多個應用使用相同 Port | 分配獨立 Port |
### 診斷命令
```bash
# 1. 查看程序使用的 node 版本
ps aux | grep node
# 2. 查看程序 PID 使用的執行檔
lsof -p <PID> | grep bin/node
# 3. 確認路徑優先順序
echo $PATH
# 4. 查看 launchctl 服務的環境變數
sudo launchctl list | grep <service-name>
# 5. 檢查 Port 佔用
lsof -i :<port-number>
```
### 預防措施
1. **每次新增服務都更新 Registry**
2. **使用完整路徑而非 PATH**
3. **建立服務時指定版本**
4. **定期檢查執行中的程序**
---
## 故障排除
### 問題n8n 顯示 Node.js 版本警告
**原因**n8n 檢測到 Node.js 版本不是 22.x
**解決方案**
1. 確認 plist 中 ProgramArguments 使用正確路徑
2. 確認 PATH 環境變數中 node@22 在前面
3. 重載服務:
```bash
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl unload /Library/LaunchDaemons/com.momentry.n8n.worker.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.main.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.n8n.worker.plist
```
### 問題Task Runner 使用錯誤版本
**原因**PATH 環境變數設定不正確
**診斷**
```bash
# 查看 task-runner 進程使用的 node
ps aux | grep "task-runner"
lsof -p <PID> | grep "txt" | grep node
```
**解決方案**
更新 plist 中的 PATH確保 `/opt/homebrew/opt/node@22/bin` 在最前面。
### 問題npm 全域套件找不到
**原因**:使用錯誤的 node 版本安裝
**解決方案**
```bash
# 確認使用的是哪個 node
which node
node --version
# 使用正確版本重新安裝
/opt/homebrew/opt/node@22/bin/npm install -g <package>
```
---
## 版本切換腳本
建立快速切換腳本 `~/bin/switch-node.sh`
```bash
#!/bin/bash
VERSION=$1
case $VERSION in
22)
export PATH="/opt/homebrew/opt/node@22/bin:$PATH"
echo "切換到 Node.js 22.x"
;;
system|25)
export PATH="/opt/homebrew/bin:$PATH"
echo "切換到系統 Node.js 25.x"
;;
*)
echo "用法: $0 {22|system}"
echo " 22 - Node.js 22.x (n8n)"
echo " system - Node.js 25.x (系統預設)"
exit 1
;;
esac
node --version
```
賦予執行權限:
```bash
chmod +x ~/bin/switch-node.sh
```
使用方式:
```bash
~/bin/switch-node.sh 22 # 切換到 22.x
~/bin/switch-node.sh system # 切換到系統預設
```
---
## 常用指令
```bash
# 查看 node 版本
node --version
# 查看 npm 版本
npm --version
# 查看 node 安裝位置
which node
# 查看 node@22 版本
/opt/homebrew/opt/node@22/bin/node --version
# 安裝全域套件 (使用 node@22)
/opt/homebrew/opt/node@22/bin/npm install -g n8n
# 檢查 n8n 進程
ps aux | grep n8n | grep -v grep
# 檢查 task-runner
ps aux | grep task-runner | grep -v grep
# 查看 task-runner 使用的 node 版本
lsof -p <PID> | grep "txt" | grep node
```
---
## 檔案位置
| 類型 | 路徑 | 說明 |
|------|------|------|
| node (系統) | /opt/homebrew/bin/node | 預設 node |
| node@22 | /opt/homebrew/opt/node@22/bin/node | n8n 專用 |
| npm (系統) | /opt/homebrew/bin/npm | 預設 npm |
| npm@22 | /opt/homebrew/opt/node@22/bin/npm | n8n 專用 |
| n8n 安裝 | /opt/homebrew/lib/node_modules/n8n/ | n8n 目錄 |
| n8n main plist | /Library/LaunchDaemons/com.momentry.n8n.main.plist | 開機啟動 |
| n8n worker plist | /Library/LaunchDaemons/com.momentry.n8n.worker.plist | 開機啟動 |
---
## 版本速查
| 版本 | 用途 | 路徑 |
|------|------|------|
| 22.22.1 | n8n 專用 | /opt/homebrew/opt/node@22/bin/node |
| 25.x | 系統預設 | /opt/homebrew/bin/node |
---
## 相關文件
- [INSTALL_N8N.md](./INSTALL_N8N.md) - n8n 安裝指南 (包含 Node.js 配置範例)
- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範
- [monitor_config.yaml](../monitor/config/monitor_config.yaml) - 監控配置
- [node_monitor.sh](../monitor/service/node_monitor.sh) - Node.js 監控腳本

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,504 @@
# SFTPGo Demo 用戶指南
## Web 管理介面
**URL**: https://sftpgo.momentry.ddns.net
### 登入方式
| 角色 | 用戶名 | 密碼 |
|------|--------|------|
| **Demo 用戶** | `demo` | `demopassword123` |
### 可用功能
- 瀏覽個人目錄結構
- 上傳、下載檔案
- 查看上傳記錄
---
## 快速連線資訊
| 項目 | 值 |
|------|-----|
| **主機** | `sftpgo.momentry.ddns.net` |
| **SFTP 連接埠** | `2022` |
| **用戶名** | `demo` |
| **密碼** | `demopassword123` |
| **主目錄** | `/demo` |
---
## 連線方式
### 1. 命令列 SFTP
```bash
# 使用密碼連線
sshpass -p "demopassword123" sftp -P 2022 demo@sftpgo.momentry.ddns.net
# 使用金鑰連線 (需先設定)
sftp -P 2022 -i ~/.ssh/id_rsa demo@sftpgo.momentry.ddns.net
```
### 2. FileZilla
1. **主機**: `sftp://sftpgo.momentry.ddns.net`
2. **連接埠**: `2022`
3. **協定**: `SFTP`
4. **登入類型**: `一般`
5. **用戶名**: `demo`
6. **密碼**: `demopassword123`
### 3. Cyberduck (macOS)
1. 選擇 **連線 > 新連線**
2. 協定選擇 **SFTP (SSH File Transfer Protocol)**
3. 伺服器: `sftpgo.momentry.ddns.net`
4. 連接埠: `2022`
5. 使用者名稱: `demo`
6. 密碼: `demopassword123`
### 4. curl 上傳
```bash
curl -u demo:demopassword123 \
-T /path/to/video.mp4 \
sftp://sftpgo.momentry.ddns.net:2022/demo/
```
---
## SFTP 基本操作
### 連線後常用指令
```bash
# 進入互動式模式
sftp demo@sftpgo.momentry.ddns.net -P 2022
# 常用指令
sftp> pwd # 顯示目前目錄
sftp> ls # 列出檔案
sftp> ls -la # 詳細列表
sftp> cd uploads # 切換目錄
sftp> mkdir videos # 建立目錄
sftp> put local.mp4 # 上傳檔案
sftp> get remote.mp4 # 下載檔案
sftp> rm old.mp4 # 刪除檔案
sftp> exit # 斷線
```
### 批次上傳
```bash
# 上傳多個檔案
sshpass -p "demopassword123" sftp -P 2022 demo@sftpgo.momentry.ddns.net <<EOF
cd uploads
put video1.mp4
put video2.mp4
put video3.mp4
bye
EOF
# 使用 glob 上傳
sshpass -p "demopassword123" sftp -P 2022 demo@sftpgo.momentry.ddns.net <<EOF
mput /path/to/videos/*.mp4
bye
EOF
```
---
## 自動上傳腳本
### Bash 腳本
```bash
#!/bin/bash
# upload.sh - 上傳視頻到 Momentry
HOST="sftpgo.momentry.ddns.net"
PORT="2022"
USER="demo"
PASS="demopassword123"
REMOTE_DIR="/demo/uploads"
# 要上傳的檔案
FILE="$1"
if [ -z "$FILE" ]; then
echo "用法: $0 <檔案路徑>"
exit 1
fi
sshpass -p "$PASS" sftp -P $PORT $USER@$HOST <<EOF
mkdir $REMOTE_DIR
cd $REMOTE_DIR
put "$FILE"
bye
EOF
echo "上傳完成: $FILE"
```
使用方式:
```bash
chmod +x upload.sh
./upload.sh /path/to/video.mp4
```
### Python 腳本
```python
#!/usr/bin/env python3
"""上傳檔案到 Momentry SFTP"""
import paramiko
import sys
import os
def upload_file(local_path, remote_dir="/demo/uploads"):
host = "sftpgo.momentry.ddns.net"
port = 2022
username = "demo"
password = "demopassword123"
transport = paramiko.Transport((host, port))
transport.connect(username=username, password=password)
sftp = paramiko.SFTPClient.from_transport(transport)
filename = os.path.basename(local_path)
remote_path = f"{remote_dir}/{filename}"
sftp.put(local_path, remote_path)
print(f"已上傳: {filename} -> {remote_path}")
sftp.close()
transport.close()
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法: python upload_sftp.py <檔案路徑>")
sys.exit(1)
upload_file(sys.argv[1])
```
安裝依賴:
```bash
pip install paramiko
```
---
## WebDAV 替代方案
如果 SFTP 連線有問題,可使用 WebDAV:
| 項目 | 值 |
|------|-----|
| **URL** | `https://momentry.ddns.net/webdav/` |
| **用戶名** | `demo` |
| **密碼** | `demopassword123` |
### curl 使用 WebDAV
```bash
# 上傳
curl -u demo:demopassword123 \
-T video.mp4 \
"https://momentry.ddns.net/webdav/demo/uploads/"
# 下載
curl -u demo:demopassword123 \
-o video.mp4 \
"https://momentry.ddns.net/webdav/demo/uploads/video.mp4"
# 列出目錄
curl -u demo:demopassword123 \
-X PROPFIND \
"https://momentry.ddns.net/webdav/demo/" \
-H "Depth: 1"
```
---
## 故障排除
### 連線被拒絕
```bash
# 檢查 SFTPGo 是否運行
curl -s http://localhost:8080/api/v2/status | jq .status
# 檢查連接埠
nc -zv momentry.ddns.net 2022
```
### 認證失敗
確認密碼是否正確:
```bash
# 測試認證
curl -u demo:demopassword123 \
"https://momentry.ddns.net/webdav/" -I
```
### 權限不足
上傳目錄可能需要先建立:
```bash
sshpass -p "demopassword123" sftp -P 2022 demo@sftpgo.momentry.ddns.net <<EOF
mkdir uploads
mkdir videos
bye
EOF
```
---
## 檔案上傳後自動化
上傳後SFTPGo 會自動:
1. 觸發 Hook 腳本
2. 記錄上傳事件到 `/Users/accusys/sftpgo_test/hook.log`
3. 呼叫 Momentry Core API 註冊視頻
查看上傳日誌:
```bash
tail -f /Users/accusys/sftpgo_test/hook.log
```
---
## 管理手冊
### 管理員帳戶
| 角色 | 用戶名 | 密碼 | 說明 |
|------|--------|------|------|
| **WebAdmin** | `admin` | `Test3200Test3200` | SFTPGo 管理介面 |
**WebAdmin URL**: https://sftpgo.momentry.ddns.net
### Admin 創建方式
根據官方文檔SFTPGo 有兩種方式創建管理員:
#### 方式 1: Web UI (首次設定)
1. 訪問 `http://localhost:8080/web/admin`
2. 如果沒有管理員,會顯示設定畫面
3. 輸入用戶名和密碼創建第一個管理員
#### 方式 2: 自動創建 (推薦)
需要同時滿足以下條件:
1. 配置文件中設定 `"create_default_admin": true`
2. 設定環境變數 `SFTPGO_DEFAULT_ADMIN_USERNAME``SFTPGO_DEFAULT_ADMIN_PASSWORD`
### 設定步驟
#### Step 1: 確保配置文件正確
確認 `/Users/accusys/momentry/etc/sftpgo/sftpgo.json` 中有:
```json
{
"data_provider": {
"create_default_admin": true
},
"httpd": {
"setup": {
"installation_code": "Test3200Test3200"
}
}
}
```
#### Step 2: 更新 plist 加入環境變數
編輯 `/Library/LaunchDaemons/com.momentry.sftpgo.plist`,加入:
```xml
<key>EnvironmentVariables</key>
<dict>
<key>SFTPGO_DEFAULT_ADMIN_USERNAME</key>
<string>admin</string>
<key>SFTPGO_DEFAULT_ADMIN_PASSWORD</key>
<string>Test3200Test3200</string>
</dict>
```
#### Step 3: 重啟 SFTPGo
```bash
launchctl unload homebrew.mxcl.sftpgo
launchctl load homebrew.mxcl.sftpgo
```
#### Step 4: 驗證管理員
```bash
curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200"
```
成功回應:
```json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 1200
}
```
### REST API 認證流程
#### 1. 獲取 Token
```bash
curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200"
```
#### 2. 使用 Token 訪問 API
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
# 查看所有用戶
curl -s http://localhost:8080/api/v2/users \
-H "Authorization: Bearer $TOKEN"
# 查看系統狀態
curl -s http://localhost:8080/api/v2/status \
-H "Authorization: Bearer $TOKEN"
```
### 常用管理操作
#### 查看所有用戶
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s http://localhost:8080/api/v2/users \
-H "Authorization: Bearer $TOKEN" | jq .
```
#### 建立新用戶
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s -X POST http://localhost:8080/api/v2/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "newuser",
"password": "userpassword123",
"email": "user@example.com",
"status": 1,
"home_dir": "/Users/accusys/momentry/var/sftpgo/data/newuser",
"uid": 501,
"gid": 20,
"permissions": {
"/": ["*"]
}
}'
```
#### 建立用戶組
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s -X POST http://localhost:8080/api/v2/groups \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "editors",
"description": "Editor group with upload permissions"
}'
```
#### 刪除用戶
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s -X DELETE http://localhost:8080/api/v2/users/username \
-H "Authorization: Bearer $TOKEN"
```
#### 修改用戶密碼
```bash
TOKEN=$(curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq -r '.access_token')
curl -s -X PUT http://localhost:8080/api/v2/users/demo \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"password": "newpassword456"
}'
```
### 重要設定
| 設定項目 | 值 | 說明 |
|----------|-----|------|
| **SFTP 連接埠** | `2022` | SSH 檔案傳輸協定 |
| **HTTP/WebDAV 連接埠** | `8080` | 內部 HTTP 服務 |
| **WebAdmin 連接埠** | `8080` | 管理介面 (`/web/admin`) |
| **WebClient 連接埠** | `8080` | 用戶介面 (`/web/client`) |
| **資料庫** | PostgreSQL | 用戶和設定儲存 |
| **Hook 腳本** | `/Users/accusys/sftpgo_test/register_hook.sh` | 上傳後自動化處理 |
| **安裝碼** | `Test3200Test3200` | 首次設定管理員所需 |
| **create_default_admin** | `true` | 自動創建管理員 |
| **Token 有效期** | 1200 秒 (20分鐘) | JWT 過期時間 |
### 用戶目錄結構
所有 SFTPGo 用戶資料統一存放在 `/Users/accusys/momentry/var/sftpgo/data/` 目錄下:
| 用戶 | 資料夾路徑 | 密碼 | 說明 |
|------|------------|------|------|
| **demo** | `/Users/accusys/momentry/var/sftpgo/data/demo` | `demopassword123` | Demo 用戶上傳目錄 |
| **momentry** | `/Users/accusys/momentry/var/sftpgo/data/momentry` | `momentry123` | Momentry 系統用戶 |
| **warren** | `/Users/accusys/momentry/var/sftpgo/data/warren` | `warren123` | 其他用戶 |
### API Token 獲取方式
```bash
# 注意:使用 GET 而非 POST
curl -s -X GET "http://localhost:8080/api/v2/token" \
-u "admin:Test3200Test3200" | jq .access_token
```
### 故障排除
| 問題 | 解決方案 |
|------|----------|
| 無法獲取 Token | 確認環境變數已正確設定並重啟 SFTPGo |
| SFTP 連線被拒絕 | 檢查 SFTPGo 服務: `launchctl list \| grep sftpgo` |
| 無法登入 WebAdmin | 確認 admin 用戶存在: 檢查 plist 中環境變數是否正確 |
| 上傳失敗 | 檢查 Hook 腳本: `tail -f /Users/accusys/momentry/log/sftpgo.error.log` |
| 權限不足 | 檢查用戶權限或更新 `permissions` 設定 |
| API 返回 401 | Token 過期,需重新獲取: `curl -X POST .../token -u "admin:pass"` | |
---
## 安全注意事項
- **密碼保護**: `demopassword123` 為 demo 帳戶密碼
- **限制存取**: Demo 用戶只能訪問 `/demo` 目錄
- **監控**: 所有上傳都有日誌記錄
- **生產環境**: 正式環境應使用更強的密碼和金鑰認證

View File

@@ -0,0 +1,499 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry 使用手冊"
date: "2026-03-21"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "使用手冊"
ai_query_hints:
- "查詢 Momentry 使用手冊 的內容"
- "Momentry 使用手冊 的主要目的是什麼?"
- "如何操作或實施 Momentry 使用手冊?"
---
# Momentry 使用手冊
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-21 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-21 | 創建使用手冊 | Warren | OpenCode |
---
**目標讀者**: 系統管理員、開發者
---
## 目錄
1. [快速開始](#1-快速開始)
2. [安裝與設定](#2-安裝與設定)
3. [CLI 命令參考](#3-cli-命令參考)
4. [影片管理](#4-影片管理)
5. [API Key 管理](#5-api-key-管理)
6. [第三方整合](#6-第三方整合)
7. [監控與維護](#7-監控與維護)
8. [疑難排解](#8-疑難排解)
---
## 1. 快速開始
### 1.1 最小啟動流程
```bash
# 1. 啟動服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist
# 2. 設定環境變數
source .env
# 3. 啟動 API 伺服器
momentry server --host 127.0.0.1 --port 3000
# 4. 建立 API Key
momentry api-key create my-first-key --key-type user --ttl 90
```
### 1.2 驗證安裝
```bash
# 檢查系統狀態
curl http://localhost:3002/health
# 檢查版本
momentry --help
```
---
## 2. 安裝與設定
### 2.1 環境需求
| 項目 | 需求 |
|------|------|
| 作業系統 | macOS (Apple Silicon) |
| Rust | 1.70+ |
| PostgreSQL | 15+ |
| Redis | 7+ |
| Python | 3.11+ (用於 AI 處理) |
### 2.2 安裝步驟
```bash
# 1. 複製專案
git clone <repository-url>
cd momentry_core_0.1
# 2. 編譯
cargo build --release
# 3. 安裝到系統
cp target/release/momentry /usr/local/bin/
```
### 2.3 環境變數設定
建立 `.env` 檔案:
```bash
# Database
DATABASE_URL=postgres://accusys@localhost:5432/momentry
# Redis
REDIS_URL=redis://:accusys@localhost:6379
# Gitea (選用)
GITEA_URL=http://localhost:3000
# n8n (選用)
N8N_URL=https://n8n.momentry.ddns.net
# API Server
API_HOST=127.0.0.1
API_PORT=3000
# 監控目錄
WATCH_DIRECTORIES=~/Videos
```
---
## 3. CLI 命令參考
### 3.1 一般命令
| 命令 | 說明 |
|------|------|
| `momentry --help` | 顯示幫助 |
| `momentry server` | 啟動 API 伺服器 |
| `momentry register <path>` | 註冊影片 |
| `momentry process <uuid>` | 處理影片 |
| `momentry query <text>` | RAG 查詢 |
### 3.2 影片管理
```bash
# 註冊影片
momentry register /path/to/video.mp4
# 處理影片
momentry process <uuid>
# 生成縮圖
momentry thumbnails <uuid> --count 6
# 查看狀態
momentry status <uuid>
```
### 3.3 API Key 管理
```bash
# 建立 Key
momentry api-key create <name> --key-type <type> --ttl <days>
# 列出 Keys
momentry api-key list
# 驗證 Key
momentry api-key validate --key <key>
# 撤銷 Key
momentry api-key revoke --key <key>
# 請求輪換
momentry api-key rotate --key <key>
# 統計資訊
momentry api-key stats
```
### 3.4 Gitea 整合
```bash
# 建立 Token
momentry gitea create \
--username <user> \
--password <pwd> \
--token-name <name> \
--scopes "read:repository,write:repository"
# 列出 Tokens
momentry gitea list --username <user> --password <pwd>
# 刪除 Token
momentry gitea delete \
--username <user> \
--password <pwd> \
--token-name <name>
```
### 3.5 n8n 整合
```bash
# 建立 API Key
momentry n8n create \
--api-key <existing-key> \
--label <name> \
--expires-in-days 90
# 列出 Keys
momentry n8n list --api-key <key>
# 刪除 Key
momentry n8n delete --api-key <key> --label <name>
```
### 3.6 備份管理
```bash
# 列出備份
momentry backup list
# 清理舊備份
momentry backup cleanup --days 30
```
---
## 4. 影片管理
### 4.1 影片生命週期
```
上傳 → 註冊 → 處理 → 儲存 → 查詢
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
檔案 資料庫 AI分析 向量庫 RAG
```
### 4.2 註冊影片
```bash
# 自動偵測格式
momentry register ~/Videos/my-video.mp4
# 輸出:
# UUID: a1b2c3d4e5f6g7h8
# Duration: 120.5s
# Resolution: 1920x1080
```
### 4.3 處理流程
處理包含以下階段:
| 階段 | 說明 | 時間 (約) |
|------|------|-----------|
| Probe | 影片資訊分析 | 5s |
| ASR | 語音辨識 | 視長度 |
| OCR | 文字辨識 | 視長度 |
| YOLO | 物件偵測 | 視長度 |
| Cut | 場景切割 | 30s |
| Chunk | 內容分段 | 10s |
| Vector | 向量化 | 20s |
### 4.4 查詢影片
```bash
# RAG 查詢
momentry query "影片中有什麼內容?"
# 取得特定影片
momentry resolve <uuid>
```
---
## 5. API Key 管理
### 5.1 Key 類型
| 類型 | 前綴 | 用途 | 預設 TTL |
|------|------|------|----------|
| System | `msys_` | 系統內部 | 365 天 |
| User | `muser_` | 個人用戶 | 90 天 |
| Service | `msvc_` | 服務間通訊 | 180 天 |
| Integration | `mint_` | 第三方整合 | 30 天 |
| Emergency | `memg_` | 緊急存取 | 1 天 |
### 5.2 建立 Key
```bash
# 一般 Key
momentry api-key create my-service --key-type service --ttl 90
# 緊急 Key (24小時有效)
momentry api-key create emergency-access --key-type emergency
# 輸出:
# ✅ API Key created successfully!
# Key ID: msvc_xxxxxxxx
# API Key: msvc_xxxxxxxx_xxxxx_xxxxx
# Expires: 2026-06-19
```
### 5.3 Key 生命週期
```
建立 → 使用 → 過期/撤銷 → 清理
│ │ │ │
▼ ▼ ▼ ▼
資料庫 驗證 停用 定期刪除
```
### 5.4 安全建議
| 建議 | 說明 |
|------|------|
| 定期輪換 | 每 90 天更新 Key |
| 最小權限 | 只授予必要權限 |
| 監控使用 | 定期檢查使用統計 |
| 及時撤銷 | 異常時立即撤銷 |
---
## 6. 第三方整合
### 6.1 Gitea
```bash
# 建立 CI/CD 用 Token
momentry gitea create \
--username admin \
--password "your-password" \
--token-name "ci-pipeline" \
--scopes "read:repository,write:repository"
# 在 CI 中使用
export GITEA_TOKEN="token-sha1-value"
curl -H "Authorization: token $GITEA_TOKEN" \
http://localhost:3000/api/v1/user
```
### 6.2 n8n
```bash
# 建立工作流用 Key
momentry n8n create \
--api-key "existing-n8n-key" \
--label "workflow-key" \
--expires-in-days 90
# 在 n8n 中使用
# HTTP Request Header: X-N8N-API-Key: <key>
```
### 6.3 Webhook 通知
```bash
# 設定 Webhook
export WEBHOOK_URL="https://n8n.example.com/webhook/alerts"
export WEBHOOK_EVENTS="anomaly_detected,key_expired"
```
---
## 7. 監控與維護
### 7.1 系統監控
```bash
# 檢查服務狀態
ps aux | grep momentry
ps aux | grep postgres
redis-cli -a accusys ping
# 檢查日誌
tail -f /Users/accusys/momentry/log/momentry.log
tail -f /Users/accusys/momentry/log/redis.log
```
### 7.2 資料庫維護
```bash
# 檢查資料庫大小
psql -U accusys -d momentry -c "SELECT pg_size_pretty(pg_database_size('momentry'));"
# 清理過期記錄
momentry api-key stats # 檢查統計
# 定期清理由系統自動執行
```
### 7.3 備份
```bash
# 手動備份 PostgreSQL
pg_dump -U accusys momentry > backup_$(date +%Y%m%d).sql
# 恢復備份
psql -U accusys momentry < backup_20260321.sql
```
---
## 8. 疑難排解
### 8.1 常見問題
#### Q: 無法連接資料庫
```bash
# 檢查 PostgreSQL 狀態
pg_isready -h localhost -p 5432
# 檢查連線
psql -U accusys -d momentry -c "SELECT 1;"
```
#### Q: Redis 連線失敗
```bash
# 檢查 Redis 狀態
redis-cli -a accusys ping
# 檢查認證
redis-cli -a accusys INFO server | grep redis_version
```
#### Q: API Key 驗證失敗
```bash
# 檢查 Key 狀態
momentry api-key validate --key "your-key"
# 檢查是否過期
momentry api-key list
```
### 8.2 錯誤碼對照
| 錯誤碼 | 說明 | 解決方式 |
|--------|------|----------|
| `E001` | 資料庫連線失敗 | 檢查 PostgreSQL |
| `E002` | Redis 連線失敗 | 檢查 Redis |
| `E003` | API Key 無效 | 重新建立 Key |
| `E004` | 影片不存在 | 檢查 UUID |
| `E005` | 處理失敗 | 檢查日誌 |
### 8.3 日誌位置
| 日誌 | 路徑 |
|------|------|
| Momentry | `/Users/accusys/momentry/log/momentry.log` |
| PostgreSQL | `/Users/accusys/momentry/log/postgresql.log` |
| Redis | `/Users/accusys/momentry/log/redis.log` |
| Gitea | `/Users/accusys/momentry/log/gitea.log` |
---
## 附錄
### A. 完整命令列表
```bash
momentry --help
momentry register --help
momentry process --help
momentry api-key --help
momentry gitea --help
momentry n8n --help
momentry backup --help
```
### B. 環境變數總覽
| 變數 | 預設值 | 說明 |
|------|--------|------|
| `DATABASE_URL` | `postgres://accusys@localhost:5432/momentry` | PostgreSQL |
| `REDIS_URL` | `redis://:accusys@localhost:6379` | Redis |
| `GITEA_URL` | `http://localhost:3000` | Gitea |
| `N8N_URL` | `https://n8n.momentry.ddns.net` | n8n |
| `API_HOST` | `127.0.0.1` | API 主機 |
| `API_PORT` | `3000` | API 埠號 |
### C. 相關文件
| 文件 | 說明 |
|------|------|
| `docs_v1.0/IMPLEMENTATION/API_CURL_EXAMPLES.md` | API curl 範例 |
| `docs_v1.0/IMPLEMENTATION/N8N_INTEGRATION_GUIDE.md` | n8n 整合指南 |
| `docs_v1.0/REFERENCE/API_KEY_MANAGEMENT.md` | API Key 設計 |
| `CHANGELOG.md` | 版本記錄 |

View File

@@ -0,0 +1,280 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Momentry Core 版本管理規範"
date: "2026-03-23"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "momentry"
- "core"
- "版本管理規範"
ai_query_hints:
- "查詢 Momentry Core 版本管理規範 的內容"
- "Momentry Core 版本管理規範 的主要目的是什麼?"
- "如何操作或實施 Momentry Core 版本管理規範?"
---
# Momentry Core 版本管理規範
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-23 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-23 | 創建版本管理規範 | Warren | OpenCode |
---
## 1. 版本與通訊埠對照表
| 版本 | Binary | Port | Redis Prefix | 用途 |
|------|--------|------|--------------|------|
| **Production** | `momentry` | **3002** | `momentry:` | 正式環境 |
| **Development** | `momentry_playground` | **3003** | `momentry_dev:` | 開發測試 |
### 通訊埠嚴禁事項
- ❌ 開發版嚴禁使用 3002
- ❌ 任何 `cargo run` 直接啟動的 server 嚴禁綁定 3002
- ❌ Debug build 嚴禁部署到 3002
---
## 2. 開發環境隔離原則
### 2.1 開發流程
```bash
# 永遠在 3003 開發測試
cd /Users/accusys/momentry_core_0.1
# 開發版啟動 (3003)
cargo run --bin momentry_playground -- server
# 或
cargo run --bin momentry -- server --port 3003
```
### 2.2 測試完成後
1. 確認所有功能在 3003 正常運作
2. 進行 `cargo clippy --lib` 檢查
3. 進行 `cargo test --lib` 測試
4. 確認後才能進行 release
### 2.3 環境變數隔離
```bash
# Development
export MOMENTRY_SERVER_PORT=3003
export MOMENTRY_REDIS_PREFIX=momentry_dev:
# Production (launchd 管理,勿手動設定)
# MOMENTRY_SERVER_PORT=3002
# MOMENTRY_REDIS_PREFIX=momentry:
```
---
## 3. Release 版本管理
### 3.1 Release 前檢查清單
```
□ 開發版 (3003) 功能測試完成
□ cargo clippy --lib 通過
□ cargo test --lib 通過
□ cargo fmt -- --check 通過
□ 所有修改已 commit 到 Gitea
```
### 3.2 Release 流程
```bash
# 1. 確保目前是乾淨的工作目錄
git status
# 2. 備份當前 production binary
BACKUP_DIR="/Users/accusys/momentry/backup/bin"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
cp /Users/accusys/momentry/bin/momentry "${BACKUP_DIR}/momentry_${TIMESTAMP}"
# 3. 停止 production server
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
# 或
pkill -f "target/release/momentry server"
# 4. 編譯 release 版本
cargo build --release --bin momentry
# 5. 部署到正式位置
cp target/release/momentry /Users/accusys/momentry/bin/momentry
# 6. 啟動 production server
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 7. 驗證
curl http://localhost:3002/health
```
### 3.3 Backup 存放位置
```
/Users/accusys/momentry/backup/bin/
├── momentry_20260325_143000 (backup)
├── momentry_20260324_100000
├── momentry_20260323_090000
└── ...
```
---
## 4. Gitea 版本控制
### 4.1 Commit 規範
```
feat: 新功能
fix: 錯誤修復
refactor: 重構
docs: 文件更新
chore: 杂项
test: 测试
```
### 4.2 Release Tag 規範
```bash
# 建立 release tag
git tag -a v0.1.1 -m "Release v0.1.1 - API Key Authentication"
git push origin v0.1.1
```
### 4.3 版本號命名
```
v{major}.{minor}.{patch}
│ │ └── Patch version (bug fix)
│ └───────── Minor version (新功能)
└──────────────── Major version (破壞性變更)
```
### 4.4 Gitea Release 建立
1. 在 Gitea Repo > Releases > New Release
2. 選擇對應的 Tag
3. 填寫 Release Notes
4. 上傳 compiled binary如需要
---
## 5. 服務管理
### 5.1 Production Service (launchd)
```bash
# Plist 位置
/Library/LaunchDaemons/com.momentry.api.plist
# 管理指令
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist # 啟動
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist # 停止
sudo launchctl list | grep momentry # 狀態
```
### 5.2 緊急回滾
```bash
# 1. 停止當前服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
# 2. 恢復上一個 backup
BACKUP_FILE=$(ls -t /Users/accusys/momentry/backup/bin/ | head -1)
cp "/Users/accusys/momentry/backup/bin/${BACKUP_FILE}" /Users/accusys/momentry/bin/momentry
# 3. 重啟服務
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 4. 驗證
curl http://localhost:3002/health
```
---
## 6. 快速參考卡片
### Development
```bash
# 啟動開發版
cd /Users/accusys/momentry_core_0.1
cargo run --bin momentry_playground -- server
# 或手動指定 port
cargo run --bin momentry -- server --port 3003
# 測試端點
curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \
http://localhost:3003/api/v1/jobs
```
### Production
```bash
# 查看狀態
sudo launchctl list | grep momentry
# 重啟服務
sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist
sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist
# 查看日誌
tail -f /Users/accusys/momentry/log/momentry_release.log
```
### 常用指令
```bash
# 檢查 port 使用
lsof -i :3002 # Production
lsof -i :3003 # Development
# 檢查 process
ps aux | grep momentry | grep server
# 停止所有 momentry server
pkill -9 -f "momentry.*server"
```
---
## 7. 禁止事項
| 項目 | 說明 |
|------|------|
| ❌ 禁止在 3002 測試 | 3002 是 Production嚴禁用於測試 |
| ❌ 禁止覆蓋 production binary | 使用 backup + deploy 流程 |
| ❌ 禁止跳過測試直接 release | 必須完成檢查清單 |
| ❌ 禁止在未備份的情況下部署 | 每次部署前必須備份 |
---
## 8. 疑難排解
### Q: 3002 無法綁定怎麼辦?
```bash
# 檢查誰在使用
lsof -i :3002
# 停止舊的 server
pkill -9 -f "momentry.*server"
```
### Q: 如何確認使用的是哪個版本?
```bash
# 檢查 binary 位置和版本
file $(which momentry)
./target/release/momentry --version 2>/dev/null || echo "No version flag"
```
### Q: 如何確認有沒有 API Key 驗證?
```bash
# 沒有 API Key 應該返回 401
curl -s http://localhost:3002/api/v1/jobs
# HTTP/1.1 401 Unauthorized
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,264 @@
---
document_type: "reference_doc"
service: "MOMENTRY_CORE"
title: "Video Registration"
date: "2026-03-25"
version: "V1.0"
status: "active"
owner: "Warren"
created_by: "OpenCode"
tags:
- "video"
- "registration"
ai_query_hints:
- "查詢 Video Registration 的內容"
- "Video Registration 的主要目的是什麼?"
- "如何操作或實施 Video Registration"
---
# Video Registration
| 項目 | 內容 |
|------|------|
| 建立者 | Warren |
| 建立時間 | 2026-03-25 |
| 文件版本 | V1.1 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-03-25 | 創建文件 | Warren | OpenCode |
| V1.1 | 2026-03-26 | 修正 curl 範例,新增 API Key 驗證標頭 | OpenCode | deepseek-reasoner |
---
## 概述
影片註冊 API (`POST /api/v1/register`) 用於將影片加入 Momentry Core 系統進行處理。
## 路徑格式
### 支援的路徑格式
| 格式 | 範例 | 說明 |
|------|------|------|
| 相對路徑 | `./demo/video.mp4` | 推薦格式 |
| 相對路徑(無 ./ | `demo/video.mp4` | 自動加上 `./` |
| 絕對路徑 | `/Users/.../sftpgo/data/demo/video.mp4` | 支援但不推薦 |
### 路徑結構
```
./username/filepath
│ │ │
│ │ └── 檔案路徑(可以是多層目錄)
│ └── 使用者名稱SFTPgo 用戶目錄名稱)
└── 相對路徑前綴
```
**範例**
- `./demo/video.mp4` → username=`demo`, filepath=`video.mp4`
- `./demo/movies/2024/video.mp4` → username=`demo`, filepath=`movies/2024/video.mp4`
- `./warren/project1/interview.mp4` → username=`warren`, filepath=`project1/interview.mp4`
## UUID 計算
### 計算規則
```
UUID = SHA256(username/filepath)[0:16]
```
**範例**
```rust
// 路徑: ./demo/video.mp4
// username: "demo"
// filepath: "video.mp4"
// key: "demo/video.mp4"
// UUID: SHA256("demo/video.mp4")[0:16]
```
### 特性
| 特性 | 說明 |
|------|------|
| 用戶隔離 | 不同用戶的相同檔名會產生不同 UUID |
| 一致性 | 相同相對路徑一定產生相同 UUID |
| 遷移安全 | SFTPgo 資料路徑變更後 UUID 保持一致 |
### 範例
```rust
// 用戶 demo 的影片
compute_uuid_from_relative_path("./demo/video.mp4")
// → "9760d0820f0cf9a7"
// 用戶 warren 的相同檔名影片
compute_uuid_from_relative_path("./warren/video.mp4")
// → "a1b2c3d4e5f6g7h8" (不同的 UUID)
```
## 重複註冊檢查
### 行為
1. 系統檢查 UUID 是否已存在於資料庫
2. 如果存在,返回 `already_exists: true` 和現有影片資訊
3. 如果不存在,創建新的影片記錄
### API 回應
**新註冊**
```json
{
"uuid": "9760d0820f0cf9a7",
"video_id": 18,
"job_id": 2,
"file_name": "video.mp4",
"duration": 159.637188,
"width": 640,
"height": 360,
"already_exists": false
}
```
**重複註冊**
```json
{
"uuid": "9760d0820f0cf9a7",
"video_id": 18,
"job_id": 2,
"file_name": "video.mp4",
"duration": 159.637188,
"width": 640,
"height": 360,
"already_exists": true
}
```
## SFTPgo 整合
### 目錄結構
SFTPgo 的用戶目錄結構:
```
/Users/accusys/momentry/var/sftpgo/data/
├── demo/ ← 用戶目錄
│ ├── video.mp4
│ └── movies/
│ └── movie1.mp4
├── warren/ ← 用戶目錄
│ └── project1/
│ └── interview.mp4
└── momentry/ ← 用戶目錄
└── presentation.mp4
```
### 註冊流程
1. SFTPgo 用戶上傳檔案到各自的目錄
2. n8n 或其他服務調用註冊 API
3. 使用相對路徑格式:`./username/filepath`
4. 系統計算 UUID 並檢查重複
5. 創建處理任務
## 程式碼範例
### 註冊影片
```bash
# 使用相對路徑註冊
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/video.mp4"}'
# 或使用多層目錄
curl -X POST http://localhost:3002/api/v1/register \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/movies/2024/video.mp4"}'
```
### UUID 計算函數
```rust
// 使用相對路徑計算 UUID
pub fn compute_uuid_from_relative_path(relative_path: &str) -> String {
let (username, filepath) = extract_user_from_relative_path(relative_path);
compute_uuid(&username, &filepath)
}
// 從相對路徑提取用戶名和檔案路徑
pub fn extract_user_from_relative_path(relative_path: &str) -> (String, String) {
let path = relative_path.strip_prefix("./").unwrap_or(relative_path);
let path_buf = PathBuf::from(path);
let mut components = path_buf.components();
let username = components
.next()
.map(|c| c.as_os_str().to_string_lossy().to_string())
.unwrap_or_default();
let filepath: String = components
.map(|c| c.as_os_str().to_string_lossy().to_string())
.collect::<Vec<_>>()
.join("/");
(username, filepath)
}
```
## 相關 API
### Probe API僅探測不註冊
如果只需要取得影片資訊而不註冊,可以使用 Probe API
```bash
curl -X POST http://localhost:3002/api/v1/probe \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{"path": "./demo/video.mp4"}'
```
**回應範例**
```json
{
"uuid": "a1b10138a6bbb0cd",
"file_name": "video.mp4",
"duration": 120.5,
"width": 1920,
"height": 1080,
"fps": 30.0,
"cached": false,
"format": {...},
"streams": [...]
}
```
**與 Register API 的差異**
| 功能 | Probe API | Register API |
|------|-----------|---------------|
| 計算 UUID | ✓ | ✓ |
| 執行 ffprobe | ✓ | ✓ |
| 儲存 probe.json | ✓ | ✓ |
| 寫入 videos 表 | ✗ | ✓ |
| 建立 monitor_job | ✗ | ✓ |
| 返回 job_id | ✗ | ✓ |
| 適用場景 | 預覽影片資訊 | 註冊並處理影片 |
## 相關檔案
| 檔案 | 說明 |
|------|------|
| `src/core/storage/uuid.rs` | UUID 計算邏輯 |
| `src/api/server.rs` | 註冊與 Probe API 實現 |
| `src/core/probe/ffprobe.rs` | ffprobe 整合 |
| `docs_v1.0/IMPLEMENTATION/SFTPGO_DEMO_USER.md` | SFTPgo 用戶設置 |
| `docs_v1.0/REFERENCE/API_ENDPOINTS.md` | API 端點總覽 |

View File

@@ -0,0 +1,818 @@
{
"id": "o9MZ3XaJ5Vyf4kJ9",
"name": "Momentry Search API - Core v1.2",
"active": true,
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "search",
"responseMode": "responseNode",
"options": {}
},
"id": "b5f92603-1071-42e0-85b1-6c1655f9c992",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
-384,
1408
],
"webhookId": "79d6584e-c37c-49c4-9e83-c72896cef416"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"name": "query",
"value": "={{$json.body.query || $json.query}}",
"type": "string",
"id": "f9a4b19c-b478-4fd3-9969-cd78298d2138"
},
{
"name": "mode",
"value": "={{ $json.body?.mode || $json.mode || \"mock\" }}",
"type": "string",
"id": "59066dec-1e8e-4339-ab5e-ce538c0dc216"
},
{
"name": "request_source",
"value": "portal",
"type": "string",
"id": "1ff615c2-23b2-45af-a580-9d27a6a8d4bc"
},
{
"name": "limit",
"value": 10,
"type": "number",
"id": "16adb464-079b-4a91-bbb9-bd9b44db83ed"
},
{
"id": "d298ad4d-4fab-41f0-a11a-7d089cf78ae3",
"name": "query_normalized",
"value": "={{ ($json.body.query || $json.query || \"\").toLowerCase().trim() }}",
"type": "string"
}
]
},
"options": {}
},
"id": "fadff1c5-038b-4aa7-ad18-957440d0942c",
"name": "Build Search Request",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-160,
1408
]
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"leftValue": "={{$json.query_normalized}}",
"rightValue": "sun",
"operator": {
"type": "string",
"operation": "contains"
},
"id": "54556e33-0dff-4e3d-ada0-81ea89f422c2"
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"leftValue": "={{$json.query_normalized}}",
"rightValue": "morning",
"operator": {
"type": "string",
"operation": "contains"
},
"id": "cca4c190-d840-486c-937f-221d62fcf05f"
}
],
"combinator": "and"
}
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 1
},
"conditions": [
{
"id": "88239dfd-773c-4337-9f2e-521e6368305b",
"leftValue": "={{$json.query_normalized}}",
"rightValue": "error",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
}
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"id": "613caa5d-d3f7-4099-a5f3-5c642264d32c",
"name": "Search Adapter (Mock Router)",
"type": "n8n-nodes-base.switch",
"typeVersion": 3,
"position": [
512,
1664
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"name": "query",
"value": "={{$json.query}}",
"type": "string",
"id": "7af70495-2dbb-4e7b-aed3-79cd7e970183"
},
{
"name": "search_results",
"value": "={{[\n {\n \"moment_id\": \"m001\",\n \"video_id\": \"v001\",\n \"t_start\": 3,\n \"t_end\": 8,\n \"title\": \"Sunset moment\",\n \"snippet\": \"The sun slowly sets over the sea\",\n \"score\": 0.92,\n \"video_url\": \"https://wp.momentry.ddns.net/wp-content/uploads/2026/03/H_moment3.mp4\"\n },\n {\n \"moment_id\": \"m002\",\n \"video_id\": \"v002\",\n \"t_start\": 3,\n \"t_end\": 7,\n \"title\": \"Morning sunlight\",\n \"snippet\": \"Sunlight enters the room\",\n \"score\": 0.88,\n \"video_url\": \"https://wp.momentry.ddns.net/wp-content/uploads/2026/03/O_moment3.mp4\"\n }\n]}}",
"type": "array",
"id": "e504493f-0dec-4a80-b434-7586059159f0"
}
]
},
"options": {}
},
"id": "38b6c814-3e60-45e1-a31f-b93fcf9b2fd5",
"name": "Mock Sun Results",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
736,
1456
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"name": "query",
"value": "={{$json.query}}",
"type": "string",
"id": "137b4cd7-5ef8-41f2-94bc-921c9040e0e5"
},
{
"name": "search_results",
"value": "={{[\n {\n \"moment_id\": \"m002\",\n \"video_id\": \"v002\",\n \"t_start\": 3,\n \"t_end\": 7,\n \"title\": \"Morning sunlight\",\n \"snippet\": \"Sunlight enters the room\",\n \"score\": 0.88,\n \"video_url\": \"https://wp.momentry.ddns.net/wp-content/uploads/2026/03/H_moment3.mp4\"\n }\n]}}",
"type": "array",
"id": "181df2c1-9469-4d3d-9f23-2ca839091583"
}
]
},
"options": {}
},
"id": "e0bc4873-b4a7-4cdc-a4e8-01aef82a79b8",
"name": "Mock Morning Results",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
736,
1648
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"name": "query",
"value": "={{$json.query}}",
"type": "string",
"id": "be01ea1f-f92f-41c7-8dab-f998e7c07bec"
},
{
"name": "search_results",
"value": "={{ [] }}",
"type": "array",
"id": "21e46d9d-0a35-4641-b936-806713ff021c"
}
]
},
"options": {}
},
"id": "e7c3a7b0-6cee-4832-b6ad-9cdc30f59131",
"name": "Mock Empty Results",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
736,
2032
]
},
{
"parameters": {
"assignments": {
"assignments": [
{
"name": "query",
"value": "={{$json.query}}",
"type": "string"
},
{
"name": "total",
"value": "={{$json.search_results.length}}",
"type": "number"
},
{
"name": "results",
"value": "={{$json.search_results}}",
"type": "array"
}
]
},
"options": {}
},
"id": "e746705c-ddfc-4c6b-904b-a5a8ed5d7420",
"name": "Format Search Response",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
960,
1648
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { \"query\": $json.query, \"total\": $json.total, \"results\": $json.results } }}",
"options": {}
},
"id": "d7f9806c-ad9f-4e56-bbdc-a943f1d416fd",
"name": "Respond Search",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
1184,
1168
]
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "99d7d111-05ae-42aa-8b86-a5de7a7fdb52",
"leftValue": "={{ $json.mode }}",
"rightValue": "mock",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "mock"
},
{
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "d24d518d-bd6b-4b99-a4c9-cc98c49027ca",
"leftValue": "={{ $json.mode }}",
"rightValue": "core_stub",
"operator": {
"type": "string",
"operation": "equals",
"name": "filter.operator.equals"
}
}
],
"combinator": "and"
},
"renameOutput": true,
"outputKey": "core_stub"
}
]
},
"options": {
"fallbackOutput": "none"
}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.4,
"position": [
64,
1408
],
"id": "550273ba-8d33-4653-a26b-2b83218ce993",
"name": "Mode Router"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c2c253a2-689c-4d8f-92d1-017dacbf1eca",
"name": "error",
"value": true,
"type": "boolean"
},
{
"id": "523b04a9-9843-4542-846d-d027f697ceff",
"name": "message",
"value": "mock backend error",
"type": "string"
},
{
"id": "ef30be3a-cdf7-4461-90b9-6d3ee6a88ba3",
"name": "query",
"value": "={{$json.query}}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
736,
1840
],
"id": "be069c6a-da6b-4a1b-a930-b535818b9ae2",
"name": "Mock Error Response"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { \"error\": true, \"message\": $json.message, \"query\": $json.query } }}",
"options": {
"responseCode": 500
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
960,
1888
],
"id": "6777eded-6c8e-426d-82b2-550580f74591",
"name": "Respond Search Error"
},
{
"parameters": {
"jsCode": "const query = $json.query ?? \"\";\nconst hits = Array.isArray($json.hits) ? $json.hits : [];\n\nreturn [\n {\n json: {\n query,\n total: typeof $json.count === \"number\" ? $json.count : hits.length,\n results: hits.map(hit => ({\n moment_id: hit.id ?? \"\",\n video_id: hit.vid ?? \"\",\n t_start: Number(hit.start ?? 0),\n t_end: Number(hit.end ?? 0),\n title: hit.title ?? \"\",\n snippet: hit.text ?? \"\",\n score: Number(hit.score ?? 0),\n video_url: hit.media_url ?? \"\"\n }))\n }\n }\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
960,
1120
],
"id": "34662909-6e7a-4cf4-967b-1f474748777a",
"name": "Normalize Core Response"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "c51bf73f-e1e0-4811-985c-4de5ec70b9d8",
"leftValue": "={{ $json.error ? \"yes\" : \"no\" }}",
"rightValue": "=yes",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
736,
1168
],
"id": "cb6a5372-05a5-4f1b-ae5e-3b33bf009d4c",
"name": "Check Core Error"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "fad95504-6182-4d4c-bc95-8927c25c7f31",
"name": "ok",
"value": false,
"type": "boolean"
},
{
"id": "d267371c-90d8-401e-8180-a753e807441b",
"name": "error_code",
"value": "={{ $json.error?.code === \"ECONNABORTED\" ? \"SEARCH_TIMEOUT\" : \"SEARCH_BACKEND_ERROR\" }}",
"type": "string"
},
{
"id": "4992c4d9-e61d-453a-a826-8895374f56b8",
"name": "message",
"value": "={{ $json.error?.code === \"ECONNABORTED\" ? \"Search service timeout\" : \"Search service unavailable\" }}",
"type": "string"
},
{
"id": "56b9fbfa-4c98-4265-b992-498e3edf733b",
"name": "query",
"value": "={{ $node[\"Build Search Request\"].json.query || \"unknown\" }}",
"type": "string"
},
{
"id": "c50010c1-a0c8-47b9-a763-e7212a8db93c",
"name": "results",
"value": "={{ [] }}",
"type": "array"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
960,
1312
],
"id": "1c811ef9-91da-465c-9121-6247549d16fc",
"name": "Map Search Error"
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ {\n \"ok\": $json.ok,\n \"error_code\": $json.error_code,\n \"message\": $json.message,\n \"query\": $json.query,\n \"results\": $json.results\n} }}",
"options": {
"responseCode": 500
}
},
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
1184,
1360
],
"id": "797824a7-85ac-40d9-bd2b-42ea11dfd1a1",
"name": "Respond Search Error1"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "c08206fb-5d74-40f0-a682-f2116c290af1",
"name": "query",
"value": "={{ $json.query }}",
"type": "string"
},
{
"id": "85e83fbc-b4f0-4b22-8d37-516609387918",
"name": "limit",
"value": "={{ Number($json.limit || 10) }}",
"type": "number"
},
{
"id": "823706fe-288a-4a71-a498-0ad3daad5081",
"name": "mode",
"value": "={{ $json.mode }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
288,
1168
],
"id": "89366f65-c36a-4b19-9970-706a5d729594",
"name": "Prepare Core Request Context"
},
{
"parameters": {
"method": "POST",
"url": "https://api.momentry.ddns.net/api/v1/n8n/search",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ {\n \"query\": $json.query,\n \"limit\": $json.limit,\n \"request_query\": $json.query\n} }}",
"options": {
"timeout": 3000
}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.3,
"position": [
512,
1168
],
"id": "d5ea8f1b-5bd7-4c88-9c38-70e5b14ca1d8",
"name": "Call Momentry Core API",
"onError": "continueRegularOutput"
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Build Search Request",
"type": "main",
"index": 0
}
]
]
},
"Build Search Request": {
"main": [
[
{
"node": "Mode Router",
"type": "main",
"index": 0
}
]
]
},
"Search Adapter (Mock Router)": {
"main": [
[
{
"node": "Mock Sun Results",
"type": "main",
"index": 0
}
],
[
{
"node": "Mock Morning Results",
"type": "main",
"index": 0
}
],
[
{
"node": "Mock Error Response",
"type": "main",
"index": 0
}
],
[
{
"node": "Mock Empty Results",
"type": "main",
"index": 0
}
]
]
},
"Mock Sun Results": {
"main": [
[
{
"node": "Format Search Response",
"type": "main",
"index": 0
}
]
]
},
"Mock Morning Results": {
"main": [
[
{
"node": "Format Search Response",
"type": "main",
"index": 0
}
]
]
},
"Mock Empty Results": {
"main": [
[
{
"node": "Format Search Response",
"type": "main",
"index": 0
}
]
]
},
"Format Search Response": {
"main": [
[
{
"node": "Respond Search",
"type": "main",
"index": 0
}
]
]
},
"Mode Router": {
"main": [
[
{
"node": "Search Adapter (Mock Router)",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Core Request Context",
"type": "main",
"index": 0
}
]
]
},
"Mock Error Response": {
"main": [
[
{
"node": "Respond Search Error",
"type": "main",
"index": 0
}
]
]
},
"Normalize Core Response": {
"main": [
[
{
"node": "Respond Search",
"type": "main",
"index": 0
}
]
]
},
"Check Core Error": {
"main": [
[
{
"node": "Map Search Error",
"type": "main",
"index": 0
}
],
[
{
"node": "Normalize Core Response",
"type": "main",
"index": 0
}
]
]
},
"Map Search Error": {
"main": [
[
{
"node": "Respond Search Error1",
"type": "main",
"index": 0
}
]
]
},
"Prepare Core Request Context": {
"main": [
[
{
"node": "Call Momentry Core API",
"type": "main",
"index": 0
}
]
]
},
"Call Momentry Core API": {
"main": [
[
{
"node": "Check Core Error",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"availableInMCP": false,
"binaryMode": "separate"
},
"staticData": null,
"pinData": {
"Webhook": [
{
"json": {
"headers": {
"host": "n8n.momentry.ddns.net",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0",
"content-length": "15",
"accept": "*/*",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6,zh-HK;q=0.5",
"content-type": "application/json",
"origin": "https://wp.momentry.ddns.net",
"priority": "u=1, i",
"referer": "https://wp.momentry.ddns.net/",
"sec-ch-ua": "\"Not:A-Brand\";v=\"99\", \"Microsoft Edge\";v=\"145\", \"Chromium\";v=\"145\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"via": "3.0 Caddy",
"x-forwarded-for": "111.243.8.96",
"x-forwarded-host": "n8n.momentry.ddns.net",
"x-forwarded-proto": "https"
},
"params": {},
"query": {},
"body": {
"query": "sun"
},
"webhookUrl": "https://n8n.momentry.ddns.net/webhook/search",
"executionMode": "production"
},
"pairedItem": {
"item": 0
}
}
]
},
"triggerCount": 1,
"createdAt": "2026-03-23T19:06:43.592+08:00",
"updatedAt": "2026-03-23T20:06:51.588+08:00"
}

View File

@@ -0,0 +1,123 @@
{
"name": "Momentry Video Search - Simple",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "video-search-simple",
"responseMode": "lastNode",
"options": {}
},
"id": "webhook-simple",
"name": "Webhook (Simple)",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
250,
300
],
"webhookId": "video-search-simple"
},
{
"parameters": {
"url": "http://localhost:3002/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "query",
"value": "={{ $json.body }}"
},
{
"name": "limit",
"value": 5
}
]
},
"options": {
"headers": {
"Content-Type": "application/json",
"X-API-Key": "demo_api_key_12345"
}
}
},
"id": "http-search",
"name": "搜尋 Momentry",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
500,
300
]
},
{
"parameters": {
"jsCode": "// 處理 Momentry 搜尋結果\nconst data = $input.first().json;\nconst hits = data.hits;\n\nif (!hits || hits.length === 0) {\n return {\n json: {\n success: false,\n message: '找不到相關結果',\n query: data.query\n }\n };\n}\n\n// 格式化結果\nconst formattedResults = hits.map((hit, idx) => {\n return {\n index: idx + 1,\n id: hit.id,\n title: hit.title,\n text: hit.text,\n startTime: hit.start,\n endTime: hit.end,\n relevance: Math.round(hit.score * 100) + '%',\n file_path: hit.file_path\n };\n});\n\nreturn {\n json: {\n success: true,\n query: data.query,\n totalFound: data.count,\n results: formattedResults\n }\n};"
},
"id": "code-process-simple",
"name": "處理結果",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
750,
300
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ $json }}"
},
"id": "respond-webhook",
"name": "回傳結果",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
1000,
300
]
}
],
"connections": {
"Webhook (Simple)": {
"main": [
[
{
"node": "搜尋 Momentry",
"type": "main",
"index": 0
}
]
]
},
"搜尋 Momentry": {
"main": [
[
{
"node": "處理結果",
"type": "main",
"index": 0
}
]
]
},
"處理結果": {
"main": [
[
{
"node": "回傳結果",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"id": "momentry-video-search-simple",
"versionId": "1",
"createdAt": "2026-03-23T00:00:00.000Z",
"updatedAt": "2026-03-23T00:00:00.000Z"
}

View File

@@ -0,0 +1,89 @@
{
"name": "Momentry Video Search Test",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "video-search-test",
"responseMode": "lastNode",
"options": {}
},
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [250, 300]
},
{
"parameters": {
"url": "http://localhost:3002/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ query: $json.body.query || \"test\", limit: $json.body.limit || 5 }) }}",
"options": {}
},
"name": "HTTP Request",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [500, 300]
},
{
"parameters": {
"jsCode": "return $input.first().json;"
},
"name": "Code",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [750, 300]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}"
},
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [1000, 300]
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null
}

View File

@@ -0,0 +1,109 @@
{
"name": "Momentry Video RAG MCP",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "video-rag-mcp",
"responseMode": "lastNode",
"options": {}
},
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
250,
300
]
},
{
"parameters": {
"url": "http://localhost:3002/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"contentType": "json",
"body": "={{ JSON.stringify({query: $json.body.query || $json.body, limit: $json.body.limit || 5, uuid: $json.body.uuid}) }}",
"options": {
"timeout": 30000,
"headers": {
"X-API-Key": "demo_api_key_12345"
}
}
},
"name": "Search Momentry Core",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.1,
"position": [
500,
300
]
},
{
"parameters": {
"jsCode": "// Process Momentry Core search results\nconst data = $input.first().json;\nconst hits = data.hits || [];\n\nif (hits.length === 0) {\n return {\n json: {\n success: false,\n message: 'No relevant results found',\n query: data.query,\n results: []\n }\n };\n}\n\n// Format results for RAG\nconst formattedResults = hits.map((hit, idx) => {\n return {\n index: idx + 1,\n id: hit.id || hit.chunk_id,\n title: hit.title || 'Unknown Video',\n text: hit.text || hit.content || '',\n startTime: hit.start_time || hit.start || 0,\n endTime: hit.end_time || hit.end || 0,\n relevance: Math.round((hit.score || 0) * 100) + '%',\n videoUuid: hit.video_uuid || hit.uuid,\n file_path: hit.file_path || ''\n };\n});\n\n// Build context for RAG\nconst context = formattedResults\n .map(r => \\`[\\${r.index}] \\${r.text} (Video: \\${r.title}, Time: \\${r.startTime}s-\\${r.endTime}s)\\`)\n .join('\\n\\n');\n\nreturn {\n json: {\n success: true,\n query: data.query,\n totalFound: data.count || hits.length,\n context: context,\n results: formattedResults\n }\n};"
},
"name": "Process RAG Results",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
750,
300
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}",
"options": {
"statusCode": 200
}
},
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
1000,
300
]
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Search Momentry Core",
"type": "main",
"index": 0
}
]
]
},
"Search Momentry Core": {
"main": [
[
{
"node": "Process RAG Results",
"type": "main",
"index": 0
}
]
]
},
"Process RAG Results": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null
}

View File

@@ -0,0 +1,138 @@
{
"name": "Momentry Video Search",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "video-search",
"responseMode": "lastNode",
"options": {}
},
"id": "webhook-trigger",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
250,
300
],
"webhookId": "video-search"
},
{
"parameters": {
"url": "http://localhost:3002/api/v1/n8n/search",
"method": "POST",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "query",
"value": "={{ $json.body }}"
},
{
"name": "limit",
"value": 5
}
]
},
"options": {
"headers": {
"Content-Type": "application/json",
"X-API-Key": "demo_api_key_12345"
}
}
},
"id": "http-request-search",
"name": "搜尋 Momentry",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
500,
300
]
},
{
"parameters": {
"jsCode": "const hits = $input.first().json.hits;\n\nif (!hits || hits.length === 0) {\n return {\n json: {\n message: '找不到相關結果',\n query: $input.first().json.query\n }\n };\n}\n\nconst results = hits.map((hit, index) => {\n return {\n number: index + 1,\n text: hit.text,\n start: hit.start,\n end: hit.end,\n score: Math.round(hit.score * 100) + '%',\n video_title: hit.title,\n file_path: hit.file_path\n };\n});\n\nreturn {\n json: {\n query: $input.first().json.query,\n count: $input.first().json.count,\n results: results\n }\n};"
},
"id": "code-process",
"name": "處理結果",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
750,
300
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage",
"sendBody": true,
"bodyParameters": {
"parameters": [
{
"name": "chat_id",
"value": "={{ $json.chat_id }}"
},
{
"name": "text",
"value": "=🎬 搜尋結果: \"{{ $json.query }}\"\n\n{{ $json.results.map(r => r.number + '️⃣ \"' + r.text.substring(0, 50) + '...\"\n⏱ ' + r.start + 's - ' + r.end + 's\n📊 相關度: ' + r.score).join('\n\n') }}"
}
]
},
"options": {}
},
"id": "telegram-send",
"name": "Telegram 通知",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 3,
"position": [
1000,
300
],
"continueOnFail": true
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "搜尋 Momentry",
"type": "main",
"index": 0
}
]
]
},
"搜尋 Momentry": {
"main": [
[
{
"node": "處理結果",
"type": "main",
"index": 0
}
]
]
},
"處理結果": {
"main": [
[
{
"node": "Telegram 通知",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {},
"id": "momentry-video-search",
"versionId": "1",
"createdAt": "2026-03-23T00:00:00.000Z",
"updatedAt": "2026-03-23T00:00:00.000Z"
}

100
docs_v1.0/REFERENCE/test_all.sh Executable file
View File

@@ -0,0 +1,100 @@
#!/bin/bash
echo "=========================================="
echo "Momentry Core API 測試腳本"
echo "=========================================="
echo ""
# 顏色定義
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 測試 1: Momentry Core 搜尋
echo -e "${YELLOW}測試 1: Momentry Core 搜尋 API${NC}"
echo "URL: http://localhost:3002/api/v1/n8n/search"
echo ""
RESPONSE=$(curl -s -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":2}')
if echo "$RESPONSE" | grep -q '"hits"'; then
echo -e "${GREEN}✅ 成功!${NC}"
echo "$RESPONSE" | python3 -m json.tool | grep -E '"query"|"count"' | head -3
echo ""
echo "前 2 個結果:"
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
for i, hit in enumerate(data.get('hits', [])[:2]):
print(f\" [{i+1}] {hit.get('text', '')[:50]}...\")
print(f\" 時間: {hit.get('start')}s - {hit.get('end')}s\")
print(f\" 影片: {hit.get('title')}\")
"
else
echo -e "${RED}❌ 失敗${NC}"
echo "$RESPONSE"
fi
echo ""
# 測試 2: 列出影片
echo -e "${YELLOW}測試 2: 列出所有影片${NC}"
echo "URL: http://localhost:3002/api/v1/videos"
echo ""
RESPONSE=$(curl -s http://localhost:3002/api/v1/videos)
if echo "$RESPONSE" | grep -q '"videos"'; then
echo -e "${GREEN}✅ 成功!${NC}"
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(f\"找到 {len(data.get('videos', []))} 個影片:\")
for v in data.get('videos', []):
print(f\" - {v.get('file_name')} (UUID: {v.get('uuid')[:8]}...)\")
"
else
echo -e "${RED}❌ 失敗${NC}"
fi
echo ""
# 測試 3: n8n Webhook (Test Mode)
echo -e "${YELLOW}測試 3: n8n Webhook (Test Mode)${NC}"
echo "URL: http://localhost:5678/webhook-test/video-rag-mcp"
echo ""
echo "⚠️ 注意: 請先在 n8n UI 中點擊 'Execute workflow' 按鈕"
echo ""
RESPONSE=$(curl -s -X POST http://localhost:5678/webhook-test/video-rag-mcp \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":2}')
if echo "$RESPONSE" | grep -q '"success": true'; then
echo -e "${GREEN}✅ Webhook 測試成功!${NC}"
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(f\"查詢: {data.get('query')}\")
print(f\"找到: {data.get('totalFound')} 個結果\")
print(f\"Context 長度: {len(data.get('context', ''))} 字元\")
"
elif echo "$RESPONSE" | grep -q '404'; then
echo -e "${RED}❌ Webhook 未找到${NC}"
echo "請在 n8n UI 中:"
echo " 1. 開啟 'Momentry Video RAG MCP' 工作流程"
echo " 2. 點擊 'Execute workflow' 按鈕"
echo " 3. 30 秒內再次執行此腳本"
else
echo -e "${RED}❌ 錯誤${NC}"
echo "$RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE"
fi
echo ""
echo "=========================================="
echo "測試完成!"
echo "=========================================="
echo ""
echo "快速參考:"
echo " Momentry API: http://localhost:3002/api/v1"
echo " n8n UI: https://n8n.momentry.ddns.net"
echo " Webhook Test: http://localhost:5678/webhook-test/video-rag-mcp"

View File

@@ -0,0 +1,33 @@
#!/bin/bash
# Test Momentry Core API directly
# This bypasses n8n and tests the API directly
echo "=========================================="
echo "Testing Momentry Core API Directly"
echo "=========================================="
echo ""
# Test 1: Health check
echo "Test 1: Health Check"
curl -s http://localhost:3002/api/v1/health 2>&1 | head -5
echo ""
# Test 2: Search API
echo "Test 2: Search API"
echo "Query: 'charade'"
curl -s -X POST http://localhost:3002/api/v1/n8n/search \
-H "Content-Type: application/json" \
-d '{"query":"charade","limit":3}' | python3 -m json.tool
echo ""
# Test 3: List videos
echo "Test 3: List Videos"
curl -s http://localhost:3002/api/v1/videos | python3 -m json.tool 2>/dev/null || echo "No videos or endpoint error"
echo ""
echo "=========================================="
echo "If all tests show JSON responses, API is working!"
echo ""
echo "Next step: Use the API in n8n workflows"
echo "=========================================="

View File

@@ -0,0 +1,104 @@
#!/bin/bash
# Test Momentry Video RAG MCP Workflow
# Usage: ./test_workflow.sh [query] [limit] [uuid]
N8N_URL="http://localhost:5678"
WEBHOOK_PATH="webhook-test/video-rag-mcp"
echo "⚠️ 注意:使用 Test Webhook URL"
echo "(生產環境 Webhook 需要工作流程手動激活後才能使用)"
echo ""
# Default values
QUERY="${1:-charade}"
LIMIT="${2:-3}"
UUID="${3:-}"
echo "=========================================="
echo "Testing Video RAG Workflow"
echo "=========================================="
echo "Query: $QUERY"
echo "Limit: $LIMIT"
if [ -n "$UUID" ]; then
echo "UUID: $UUID"
fi
echo ""
# Build JSON payload
if [ -n "$UUID" ]; then
PAYLOAD=$(cat <<JSON
{
"query": "$QUERY",
"limit": $LIMIT,
"uuid": "$UUID"
}
JSON
)
else
PAYLOAD=$(cat <<JSON
{
"query": "$QUERY",
"limit": $LIMIT
}
JSON
)
fi
echo "Request:"
echo "$PAYLOAD" | python3 -m json.tool 2>/dev/null || echo "$PAYLOAD"
echo ""
echo "=========================================="
echo "Response:"
echo "=========================================="
echo "URL: ${N8N_URL}/${WEBHOOK_PATH}"
echo ""
# Make the request
RESPONSE=$(curl -s -X POST "${N8N_URL}/${WEBHOOK_PATH}" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
# Format and display response
echo "$RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE"
echo ""
echo "=========================================="
# Check if successful
if echo "$RESPONSE" | grep -q '"success": true'; then
echo "✅ Test PASSED"
# Extract and display summary
echo ""
echo "Summary:"
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if data.get('success'):
print(f\" Query: {data.get('query', 'N/A')}\")
print(f\" Total Found: {data.get('totalFound', 0)}\")
results = data.get('results', [])
print(f\" Results Count: {len(results)}\")
print()
print(' Top Results:')
for r in results[:3]:
print(f\" [{r.get('index')}] {r.get('title', 'Unknown')}\")
text = r.get('text', '')[:50]
print(f\" Text: {text}...\")
print(f\" Time: {r.get('startTime')}s - {r.get('endTime')}s\")
print(f\" Relevance: {r.get('relevance')}\")
print()
" 2>/dev/null || true
elif echo "$RESPONSE" | grep -q '"code": 404'; then
echo "❌ Webhook not found"
echo ""
echo "可能的解決方案:"
echo " 1. 在 n8n UI 中開啟工作流程: https://n8n.momentry.ddns.net"
echo " 2. 點擊右上角的 'Active' 開關"
echo " 3. 或者使用 test webhook: webhook-test/video-rag-mcp"
else
echo "❌ Test FAILED or no results found"
fi
echo "=========================================="