feat: backup architecture docs, source code, and scripts
This commit is contained in:
198
docs_v1.0/REFERENCE/API_3002_VS_3003_COMPARISON.md
Normal file
198
docs_v1.0/REFERENCE/API_3002_VS_3003_COMPARISON.md
Normal 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 需要重新編譯部署 |
|
||||
230
docs_v1.0/REFERENCE/API_ACCESS.md
Normal file
230
docs_v1.0/REFERENCE/API_ACCESS.md
Normal 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 個(句子 + 場景 + 時間)
|
||||
321
docs_v1.0/REFERENCE/API_ENDPOINTS.md
Normal file
321
docs_v1.0/REFERENCE/API_ENDPOINTS.md
Normal 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 範例
|
||||
106
docs_v1.0/REFERENCE/API_ERROR_CODES.md
Normal file
106
docs_v1.0/REFERENCE/API_ERROR_CODES.md
Normal 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
|
||||
129
docs_v1.0/REFERENCE/API_INDEX.md
Normal file
129
docs_v1.0/REFERENCE/API_INDEX.md
Normal 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 設計
|
||||
731
docs_v1.0/REFERENCE/API_KEY_DESIGN.md
Normal file
731
docs_v1.0/REFERENCE/API_KEY_DESIGN.md
Normal 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/)
|
||||
532
docs_v1.0/REFERENCE/API_QUICK_REFERENCE.md
Normal file
532
docs_v1.0/REFERENCE/API_QUICK_REFERENCE.md
Normal 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 工作流程整合
|
||||
310
docs_v1.0/REFERENCE/API_REFERENCE.md
Normal file
310
docs_v1.0/REFERENCE/API_REFERENCE.md
Normal 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` |
|
||||
407
docs_v1.0/REFERENCE/API_TRAINING_MARCOM.md
Normal file
407
docs_v1.0/REFERENCE/API_TRAINING_MARCOM.md
Normal 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 |
|
||||
559
docs_v1.0/REFERENCE/DEVELOPMENT_LOG.md
Normal file
559
docs_v1.0/REFERENCE/DEVELOPMENT_LOG.md
Normal 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 Keys(UUID 必要)**:
|
||||
- job:{uuid}, progress:{uuid}, metrics:{uuid}
|
||||
|
||||
**Per-Processor Keys(UUID + 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 實時推送
|
||||
- [ ] 移動端響應式設計
|
||||
187
docs_v1.0/REFERENCE/DOCUMENT_EMBEDDING_STRATEGY.md
Normal file
187
docs_v1.0/REFERENCE/DOCUMENT_EMBEDDING_STRATEGY.md
Normal 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
|
||||
538
docs_v1.0/REFERENCE/JSON_OUTPUT_SPEC.md
Normal file
538
docs_v1.0/REFERENCE/JSON_OUTPUT_SPEC.md
Normal 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) - 監控配置
|
||||
647
docs_v1.0/REFERENCE/MODULE_STANDARDIZATION_SPECIFICATION.md
Normal file
647
docs_v1.0/REFERENCE/MODULE_STANDARDIZATION_SPECIFICATION.md
Normal 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)*
|
||||
*狀態: 草案*
|
||||
303
docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md
Normal file
303
docs_v1.0/REFERENCE/MOMENTRY_CORE_REDIS_KEYS.md
Normal 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 Keys(UUID 必要)
|
||||
|
||||
- 每個視頻獨立處理狀態
|
||||
- 即時進度追蹤
|
||||
|
||||
### 3.3 Per-Processor Keys(UUID + 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 狀態監控
|
||||
353
docs_v1.0/REFERENCE/MOMENTRY_RAG_PRESENTATION.md
Normal file
353
docs_v1.0/REFERENCE/MOMENTRY_RAG_PRESENTATION.md
Normal 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
|
||||
```
|
||||
127
docs_v1.0/REFERENCE/Momentry_Core_API.postman_collection.json
Normal file
127
docs_v1.0/REFERENCE/Momentry_Core_API.postman_collection.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
106
docs_v1.0/REFERENCE/N8N_API_FIX_SUMMARY.md
Normal file
106
docs_v1.0/REFERENCE/N8N_API_FIX_SUMMARY.md
Normal 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/
|
||||
321
docs_v1.0/REFERENCE/N8N_VIDEO_SEARCH_SUCCESS.md
Normal file
321
docs_v1.0/REFERENCE/N8N_VIDEO_SEARCH_SUCCESS.md
Normal 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`)
|
||||
467
docs_v1.0/REFERENCE/NODEJS.md
Normal file
467
docs_v1.0/REFERENCE/NODEJS.md
Normal 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 監控腳本
|
||||
830
docs_v1.0/REFERENCE/PENDING_ISSUES.md
Normal file
830
docs_v1.0/REFERENCE/PENDING_ISSUES.md
Normal 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 用戶設定
|
||||
412
docs_v1.0/REFERENCE/PLAYGROUND_BINARY_IMPLEMENTATION.md
Normal file
412
docs_v1.0/REFERENCE/PLAYGROUND_BINARY_IMPLEMENTATION.md
Normal 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 |
|
||||
122
docs_v1.0/REFERENCE/PORTAL_DEVELOPMENT_PLAN.md
Normal file
122
docs_v1.0/REFERENCE/PORTAL_DEVELOPMENT_PLAN.md
Normal 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. 是否需要匯出報表功能?
|
||||
586
docs_v1.0/REFERENCE/PYTHON.md
Normal file
586
docs_v1.0/REFERENCE/PYTHON.md
Normal 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 監控腳本
|
||||
119
docs_v1.0/REFERENCE/ROOT_API_INDEX.md
Normal file
119
docs_v1.0/REFERENCE/ROOT_API_INDEX.md
Normal 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) - 待解決問題追蹤
|
||||
528
docs_v1.0/REFERENCE/ROOT_API_REFERENCE.md
Normal file
528
docs_v1.0/REFERENCE/ROOT_API_REFERENCE.md
Normal 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` |
|
||||
519
docs_v1.0/REFERENCE/ROOT_JSON_OUTPUT_SPEC.md
Normal file
519
docs_v1.0/REFERENCE/ROOT_JSON_OUTPUT_SPEC.md
Normal 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) - 監控配置
|
||||
301
docs_v1.0/REFERENCE/ROOT_N8N_VIDEO_SEARCH_SUCCESS.md
Normal file
301
docs_v1.0/REFERENCE/ROOT_N8N_VIDEO_SEARCH_SUCCESS.md
Normal 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`)
|
||||
450
docs_v1.0/REFERENCE/ROOT_NODEJS.md
Normal file
450
docs_v1.0/REFERENCE/ROOT_NODEJS.md
Normal 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 監控腳本
|
||||
1347
docs_v1.0/REFERENCE/ROOT_VIDEO_PROCESSING_SPEC.md
Normal file
1347
docs_v1.0/REFERENCE/ROOT_VIDEO_PROCESSING_SPEC.md
Normal file
File diff suppressed because it is too large
Load Diff
1010
docs_v1.0/REFERENCE/RUST_DEVELOPMENT.md
Normal file
1010
docs_v1.0/REFERENCE/RUST_DEVELOPMENT.md
Normal file
File diff suppressed because it is too large
Load Diff
1092
docs_v1.0/REFERENCE/SERVICES.md
Normal file
1092
docs_v1.0/REFERENCE/SERVICES.md
Normal file
File diff suppressed because it is too large
Load Diff
504
docs_v1.0/REFERENCE/SFTPGO_DEMO_USER.md
Normal file
504
docs_v1.0/REFERENCE/SFTPGO_DEMO_USER.md
Normal 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` 目錄
|
||||
- **監控**: 所有上傳都有日誌記錄
|
||||
- **生產環境**: 正式環境應使用更強的密碼和金鑰認證
|
||||
499
docs_v1.0/REFERENCE/USER_MANUAL.md
Normal file
499
docs_v1.0/REFERENCE/USER_MANUAL.md
Normal 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` | 版本記錄 |
|
||||
280
docs_v1.0/REFERENCE/VERSION_MANAGEMENT.md
Normal file
280
docs_v1.0/REFERENCE/VERSION_MANAGEMENT.md
Normal 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
|
||||
```
|
||||
1365
docs_v1.0/REFERENCE/VIDEO_PROCESSING_SPEC.md
Normal file
1365
docs_v1.0/REFERENCE/VIDEO_PROCESSING_SPEC.md
Normal file
File diff suppressed because it is too large
Load Diff
264
docs_v1.0/REFERENCE/VIDEO_REGISTRATION.md
Normal file
264
docs_v1.0/REFERENCE/VIDEO_REGISTRATION.md
Normal 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 端點總覽 |
|
||||
818
docs_v1.0/REFERENCE/n8n_workflow_core_v1.2.json
Normal file
818
docs_v1.0/REFERENCE/n8n_workflow_core_v1.2.json
Normal 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"
|
||||
}
|
||||
123
docs_v1.0/REFERENCE/n8n_workflow_simple.json
Normal file
123
docs_v1.0/REFERENCE/n8n_workflow_simple.json
Normal 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"
|
||||
}
|
||||
89
docs_v1.0/REFERENCE/n8n_workflow_simple_test.json
Normal file
89
docs_v1.0/REFERENCE/n8n_workflow_simple_test.json
Normal 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
|
||||
}
|
||||
109
docs_v1.0/REFERENCE/n8n_workflow_video_rag_mcp.json
Normal file
109
docs_v1.0/REFERENCE/n8n_workflow_video_rag_mcp.json
Normal 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
|
||||
}
|
||||
138
docs_v1.0/REFERENCE/n8n_workflow_video_search.json
Normal file
138
docs_v1.0/REFERENCE/n8n_workflow_video_search.json
Normal 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
100
docs_v1.0/REFERENCE/test_all.sh
Executable 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"
|
||||
33
docs_v1.0/REFERENCE/test_momentry_api.sh
Executable file
33
docs_v1.0/REFERENCE/test_momentry_api.sh
Executable 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 "=========================================="
|
||||
104
docs_v1.0/REFERENCE/test_workflow.sh
Executable file
104
docs_v1.0/REFERENCE/test_workflow.sh
Executable 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 "=========================================="
|
||||
Reference in New Issue
Block a user