diff --git a/docs/API_ACCESS.md b/docs/API_ACCESS.md deleted file mode 100644 index f5dd3db..0000000 --- a/docs/API_ACCESS.md +++ /dev/null @@ -1,230 +0,0 @@ -# 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 個(句子 + 場景 + 時間) diff --git a/docs/API_CURL_EXAMPLES.md b/docs/API_CURL_EXAMPLES.md deleted file mode 100644 index e6a6388..0000000 --- a/docs/API_CURL_EXAMPLES.md +++ /dev/null @@ -1,586 +0,0 @@ -# Momentry API 使用說明 (curl 範例) - -| 項目 | 內容 | -|------|------| -| 版本 | V1.4 | -| 日期 | 2026-03-26 | -| Base URL | `http://localhost:3002` | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.4 | 2026-03-26 | 新增: 任務管理端點 (`/api/v1/jobs`, `/api/v1/jobs/:uuid`),更新註冊端點回應格式 | OpenCode | deepseek-reasoner | -| V1.3 | 2026-03-25 | 更新: n8n 搜尋回傳 `file_path` 取代 `media_url`,新增 API Key 驗證說明 | OpenCode | deepseek-reasoner | -| V1.2 | 2026-03-23 | 建立 curl 範例文件 | Warren | OpenCode / MiniMax M2.5 | -| V1.1 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 | - ---- - -> **狀態說明**: -> - ✅ **已實作**: 健康檢查、搜尋、影片管理端點 -> - ⚠️ **規劃中**: API Key 管理功能 -> - 🔧 **CLI**: 部分功能需使用命令列工具 - ---- - -## 目錄 - -1. [已實作端點](#1-已實作端點) -2. [API Key 管理](#2-api-key-管理-規劃中) -3. [影片管理](#3-影片管理) -4. [查詢與搜索](#4-查詢與搜索) -5. [系統狀態](#5-系統狀態) - ---- - -## URL 選擇指南 - -### 兩種 URL 的使用情境 - -| 環境 | URL | 說明 | -|------|-----|------| -| **本地開發** | `http://localhost:3002` | 直接訪問 API,繞過反向代理 | -| **外部訪問** | `https://api.momentry.ddns.net` | 通過 Caddy 反向代理訪問,需網路可達 | - -### 何時使用 localhost:3002 - -- ✅ 開發/測試環境 -- ✅ 直接在伺服器上操作 -- ✅ 當 Caddy/反向代理有問題時 -- ✅ 需要快速除錯時 - -### 何時使用 api.momentry.ddns.net - -- ✅ n8n workflow 中呼叫 API -- ✅ 外部系統整合 -- ✅ 生產環境 -- ✅ 從其他機器訪問 - -### 快速切換範例 - -```bash -# 本地測試 -curl http://localhost:3002/health - -# 外部測試(功能相同) -curl https://api.momentry.ddns.net/health -``` - -### 常見問題 - -**Q: 為什麼有兩個 URL?** -A: `localhost:3002` 是直接訪問,`api.momentry.ddns.net` 通過 Caddy 反向代理。 - -**Q: 兩者功能相同嗎?** -A: 是的,所有端點和功能完全相同。 - -**Q: 502 錯誤時怎麼辦?** -A: 如果 `api.momentry.ddns.net` 返回 502,檢查 Momentry API 服務是否運行: -```bash -launchctl list | grep momentry.api -# 如果未運行 -sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist -``` - ---- - -## API 認證 - -所有 `/api/v1/*` 端點(除了健康檢查)都需要 API Key 認證。請在請求標頭中加入: - -``` --H "X-API-Key: YOUR_API_KEY" -``` - -**目前示範使用的 API Key**: `demo_api_key_12345` - -> **注意**: 正式環境請使用安全的 API Key 管理機制。 - ---- - -## 1. 已實作端點 - -### 健康檢查 - -```bash -curl http://localhost:3002/health -``` - -**回應**: -```json -{"status":"ok","version":"0.1.0","uptime_ms":123456} -``` - -### 詳細健康檢查 - -```bash -curl http://localhost:3002/health/detailed -``` - ---- - -## 2. API Key 管理 *(規劃中)* - -> ⚠️ **此功能尚未實作**。以下為規劃中的 API 說明,僅供參考。 - -### 2.1 建立 API Key - -```bash -curl -X POST http://localhost:3002/api/v1/api-keys \ - -H "Content-Type: application/json" \ - -H "X-API-Key: your-admin-key" \ - -d '{ - "name": "my-service-key", - "key_type": "service", - "permissions": ["read", "write"], - "ttl_days": 90 - }' -``` - -### 2.2 列出所有 API Keys - -```bash -curl -X GET http://localhost:3002/api/v1/api-keys \ - -H "X-API-Key: your-admin-key" -``` - -### 2.3 驗證 API Key - -```bash -curl -X GET http://localhost:3002/api/v1/api-keys/validate \ - -H "X-API-Key: key-to-validate" -``` - -### 2.4 撤銷 API Key - -```bash -curl -X DELETE http://localhost:3002/api/v1/api-keys/msvc_a1b2c3d4_... \ - -H "X-API-Key: your-admin-key" -``` - -### 2.5 請求 Key 輪換 - -```bash -curl -X POST http://localhost:3002/api/v1/api-keys/msvc_a1b2c3d4_.../rotate \ - -H "X-API-Key: your-admin-key" \ - -H "Content-Type: application/json" \ - -d '{"reason": "scheduled_rotation"}' -``` - -### 2.6 取得統計資訊 - -```bash -curl -X GET http://localhost:3002/api/v1/api-keys/stats \ - -H "X-API-Key: your-admin-key" -``` - ---- - -## 3. 影片管理 - -### 3.1 註冊影片 ✅ - -```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": "a1b2c3d4e5f6g7h8", - "video_id": 1, - "job_id": 123, - "file_name": "video.mp4", - "duration": 120.5, - "width": 1920, - "height": 1080, - "already_exists": false -} -``` - -### 3.2 列出所有影片 ✅ - -```bash -curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos -``` - -### 3.3 查詢影片 ✅ - -```bash -# 依 UUID 查詢 -curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?uuid=a1b2c3d4e5f6g7h8" - -# 依路徑查詢 -curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4" -``` - -### 3.4 處理影片 🔧 *(CLI - 非 API)* - -影片處理需要使用 CLI 命令: - -```bash -# 處理影片(生成 ASR, CUT, YOLO, OCR, Face, Pose 資料) -cargo run --bin momentry -- process - -# 或處理多個影片 -cargo run --bin momentry -- process -``` - -### 3.5 取得處理進度 ✅ - -```bash -curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/progress/ -``` - -**回應範例**: - -```json -{ - "uuid": "a1b2c3d4e5f6g7h8", - "overall_progress": 75, - "processors": [ - { - "name": "asr", - "status": "complete", - "current": 100, - "total": 100, - "progress": 100, - "message": "7 segments" - }, - { - "name": "cut", - "status": "complete", - "current": 134, - "total": 134, - "progress": 100, - "message": "134 scenes" - }, - { - "name": "yolo", - "status": "progress", - "current": 5000, - "total": 14315, - "progress": 35, - "message": "frame 5000" - } - ] -} -``` - -### 3.6 任務管理 ✅ - -```bash -# 列出所有任務 -curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/jobs - -# 取得特定任務詳情 -curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/jobs/ -``` - -**任務列表回應範例**: -```json -{ - "jobs": [ - { - "id": 123, - "uuid": "a1b2c3d4e5f6g7h8", - "status": "pending", - "current_processor": null, - "progress_current": 0, - "progress_total": 100, - "created_at": "2026-03-26 10:30:00", - "started_at": null - } - ] -} -``` - -**任務詳情回應範例**: -```json -{ - "id": 123, - "uuid": "a1b2c3d4e5f6g7h8", - "status": "processing", - "current_processor": "asr", - "progress_current": 50, - "progress_total": 100, - "processors": [ - { - "processor_type": "asr", - "status": "complete", - "started_at": "2026-03-26 10:30:00", - "completed_at": "2026-03-26 10:35:00", - "duration_secs": 300.5, - "error_message": null - }, - { - "processor_type": "cut", - "status": "pending", - "started_at": null, - "completed_at": null, - "duration_secs": null, - "error_message": null - } - ], - "created_at": "2026-03-26 10:30:00", - "started_at": "2026-03-26 10:30:00", - "updated_at": "2026-03-26 10:35:00" -} -``` - ---- - -## 4. 查詢與搜索 - -### 4.1 語意搜尋 ✅ - -```bash -curl -X POST http://localhost:3002/api/v1/search \ - -H "Content-Type: application/json" \ - -H "X-API-Key: YOUR_API_KEY" \ - -d '{ - "query": "測試關鍵字", - "limit": 5 - }' -``` - -**回應範例**: - -```json -{ - "results": [ - { - "uuid": "a1b2c3d4e5f6g7h8", - "chunk_id": "sentence_0006", - "chunk_type": "sentence", - "start_time": 48.8, - "end_time": 55.44, - "text": "fun plot twists...", - "score": 0.526 - } - ], - "query": "測試關鍵字" -} -``` - -### 4.2 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": "測試關鍵字", - "limit": 5 - }' -``` - -**回應範例**: - -```json -{ - "query": "測試關鍵字", - "count": 2, - "hits": [ - { - "id": "c_001", - "vid": "a1b2c3d4e5f6g7h8", - "start": 48.8, - "end": 55.44, - "title": "Chunk sentence_0006", - "text": "fun plot twists...", - "score": 0.92, - "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4" - } - ] -} -``` - -### 4.3 混合搜尋 ✅ - -```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": "測試關鍵字", - "limit": 5 - }' -``` - ---- - -## 5. 系統狀態 - -### 5.1 健康檢查 ✅ - -```bash -curl http://localhost:3002/health -``` - -**回應**: -```json -{"status":"ok","version":"0.1.0","uptime_ms":123456} -``` - -### 5.2 詳細健康檢查 ✅ - -```bash -curl http://localhost:3002/health/detailed -``` - -**回應範例**: - -```json -{ - "status":"ok", - "version":"0.1.0", - "uptime_ms":123456, - "services":{ - "postgres":{"status":"ok","latency_ms":42,"error":null}, - "redis":{"status":"ok","latency_ms":0,"error":null}, - "qdrant":{"status":"ok","latency_ms":15,"error":null} - } -} -``` - ---- - -## 6. n8n Webhook 測試 - -### 測試 n8n Workflow - -**重要**: 測試前請先在 n8n UI 中點擊 "Execute workflow" 按鈕 - -```bash -# 測試 Video RAG Workflow (Test Mode) -curl -X POST http://localhost:5678/webhook-test/video-rag-mcp \ - -H "Content-Type: application/json" \ - -d '{"query":"charade","limit":3}' - -# 帶有 UUID 過濾的搜尋 -curl -X POST http://localhost:5678/webhook-test/video-rag-mcp \ - -H "Content-Type: application/json" \ - -d '{"query":"woody","limit":5,"uuid":"a1b10138a6bbb0cd"}' -``` - -### 生產環境 Webhook - -**注意**: 工作流程必須處於 Active 狀態 - -```bash -curl -X POST http://localhost:5678/webhook/video-rag-mcp \ - -H "Content-Type: application/json" \ - -d '{"query":"charade","limit":3}' -``` - -### n8n Webhook 常見問題 - -**Q: webhook-test 返回 404** -A: 需要在 n8n UI 中點擊 "Execute workflow" 按鈕後才能使用 test webhook - -**Q: webhook (生產環境) 返回 404** -A: 需要將工作流程切換為 Active 狀態 (右上角開關) - ---- - -## 附錄 - -### A. 服務 URL 列表 - -| 服務 | URL | -|------|-----| -| Momentry API (本地) | `http://localhost:3002` | -| Momentry API (外部) | `https://api.momentry.ddns.net` | -| n8n Web UI | `https://n8n.momentry.ddns.net` | -| n8n Webhook Test | `http://localhost:5678/webhook-test/{workflow-name}` | -| n8n Webhook Prod | `http://localhost:5678/webhook/{workflow-name}` | - -### B. 所有可用端點 - -| 端點 | 方法 | 狀態 | 說明 | -|------|------|------|------| -| `/health` | GET | ✅ | 健康檢查 | -| `/health/detailed` | GET | ✅ | 詳細健康檢查 | -| `/api/v1/register` | POST | ✅ | 註冊影片 | -| `/api/v1/search` | POST | ✅ | 語意搜尋 | -| `/api/v1/n8n/search` | POST | ✅ | n8n 格式搜尋 | -| `/api/v1/search/hybrid` | POST | ✅ | 混合搜尋 | -| `/api/v1/lookup` | GET | ✅ | 查詢影片 | -| `/api/v1/videos` | GET | ✅ | 列出所有影片 | -| `/api/v1/progress/:uuid` | GET | ✅ | 處理進度 | -| `/api/v1/jobs` | GET | ✅ | 任務列表 | -| `/api/v1/jobs/:uuid` | GET | ✅ | 任務詳情 | -| `/api/v1/api-keys` | * | ⚠️ | API Key 管理 (規劃中) | - -### C. 常見錯誤 - -| HTTP 狀態 | 說明 | 解決方式 | -|-----------|------|----------| -| 200 | 成功 | - | -| 400 | 請求格式錯誤 | 檢查 JSON 格式 | -| 404 | 端點不存在或資源未找到 | 確認端點 URL 正確 | -| 500 | 伺服器內部錯誤 | 檢查 API 服務日誌 | -| **502** | **Bad Gateway** | **API 服務未啟動,見下方說明** | - -#### 502 Bad Gateway 錯誤 - -**問題**: 外部 URL `https://api.momentry.ddns.net` 返回 502 - -**原因**: Momentry Core API 服務未啟動 - -**解決方式**: - -```bash -# 1. 檢查服務狀態 -launchctl list | grep momentry.api - -# 2. 如果未啟動,手動啟動 -sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist - -# 3. 或使用本地測試(繞過反向代理) -curl http://localhost:3002/health - -# 4. 檢查日誌 -tail -50 /Users/accusys/momentry/log/momentry_api.error.log -``` - -### D. 範例腳本 - -```bash -#!/bin/bash -# api_test.sh - API 測試腳本 - -API_URL="http://localhost:3002" - -# 健康檢查 -echo "=== Health Check ===" -curl -s "$API_URL/health" | jq . - -# 搜尋 -echo -e "\n=== Search ===" -curl -s -X POST "$API_URL/api/v1/search" \ - -H "Content-Type: application/json" \ - -H "X-API-Key: YOUR_API_KEY" \ - -d '{"query": "test", "limit": 3}' | jq . - -# 列出影片 -echo -e "\n=== Videos ===" -curl -s -H "X-API-Key: YOUR_API_KEY" "$API_URL/api/v1/videos" | jq '.videos | length' -``` - ---- - -## 相關文件 - -- [API_INDEX.md](./API_INDEX.md) - 文件總覽(起點) -- [API_ENDPOINTS.md](./API_ENDPOINTS.md) - 端點完整說明 -- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 使用範例 -- [API_WORDPRESS_GUIDE.md](./API_WORDPRESS_GUIDE.md) - WordPress 使用範例 diff --git a/docs/API_ENDPOINTS.md b/docs/API_ENDPOINTS.md deleted file mode 100644 index a641fef..0000000 --- a/docs/API_ENDPOINTS.md +++ /dev/null @@ -1,321 +0,0 @@ -# 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 範例 diff --git a/docs/API_EXAMPLES.md b/docs/API_EXAMPLES.md deleted file mode 100644 index b1ac92d..0000000 --- a/docs/API_EXAMPLES.md +++ /dev/null @@ -1,771 +0,0 @@ -# Momentry Core API 使用範例總覽 - -| 項目 | 內容 | -|------|------| -| 版本 | V2.1 | -| 日期 | 2026-03-26 | -| Base URL (本地) | `http://localhost:3002` | -| Base URL (外部) | `https://api.momentry.ddns.net` | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | -|------|------|------|--------| -| V2.0 | 2026-03-25 | 創建完整範例總覽 | OpenCode | -| V2.1 | 2026-03-26 | 更新API回應格式 (media_url→file_path) 與認證標頭 | OpenCode | - ---- - -## 快速參考 - -### 環境 URL 選擇 - -| 環境 | URL | 用途 | -|------|-----|------| -| **本地開發** | `http://localhost:3002` | 開發/測試,直接訪問 API | -| **外部訪問** | `https://api.momentry.ddns.net` | n8n、WordPress、curl 生產環境 | - -### 所有可用端點 - -| 方法 | 端點 | 說明 | -|------|------|------| -| GET | `/health` | 健康檢查 | -| GET | `/health/detailed` | 詳細健康檢查 | -| POST | `/api/v1/search` | 語意搜尋(標準格式) | -| POST | `/api/v1/n8n/search` | 語意搜尋(n8n 格式) | -| POST | `/api/v1/search/hybrid` | 混合搜尋 | -| POST | `/api/v1/register` | 註冊影片 | -| POST | `/api/v1/probe` | 探測影片資訊 | -| GET | `/api/v1/videos` | 列出所有影片 | -| GET | `/api/v1/lookup` | 查詢影片 | -| GET | `/api/v1/progress/:uuid` | 處理進度 | -| GET | `/api/v1/jobs` | 任務列表 | -| GET | `/api/v1/jobs/:uuid` | 任務詳情 | - ---- - -## 認證 - -### API Key - -所有 `/api/v1/*` 端點需要 API Key 認證。 - -```bash -# 添加 API Key Header -curl -H "X-API-Key: your-api-key" http://localhost:3002/api/v1/videos - -# 範例 -curl -H "X-API-Key: muser_f08e13ba967e4d8ea8fc542ad9f99ac8_1774416728_90472a35" \ - http://localhost:3002/api/v1/videos -``` - -### 響應狀態 - -| 狀態碼 | 說明 | -|--------|------| -| 200 | 成功 | -| 401 | 未授權(缺少或無效 API Key) | -| 500 | 伺服器錯誤 | - -### 建立 API Key - -```bash -./target/release/momentry api-key create "My Key" --key-type user -``` - ---- - -## 1. curl 範例 - -### 基本語法 - -```bash -# 格式 -curl [OPTIONS] URL - -# 常用選項 --X METHOD # HTTP 方法 (GET, POST, etc.) --H HEADER # 添加 HTTP 標頭 --d DATA # POST 請求體 --s # 靜默模式 --w FORMAT # 輸出額外信息 -``` - -### 1.1 健康檢查 - -```bash -# 基本健康檢查 -curl http://localhost:3002/health - -# 詳細健康檢查 -curl http://localhost:3002/health/detailed -``` - -**回應**: -```json -{"status":"ok","version":"0.1.0","uptime_ms":123456} -``` - -### 1.2 語意搜尋 - -```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": 5}' - -# 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": 5}' - -# 混合搜尋 -curl -X POST http://localhost:3002/api/v1/search/hybrid \ - -H "Content-Type: application/json" \ - -H "X-API-Key: YOUR_API_KEY" \ - -d '{"query": "charade", "limit": 5}' -``` - -**標準格式回應**: -```json -{ - "results": [ - { - "uuid": "a1b10138a6bbb0cd", - "chunk_id": "sentence_0001", - "chunk_type": "sentence", - "start_time": 48.8, - "end_time": 55.44, - "text": "fun plot twists...", - "score": 0.92 - } - ], - "query": "charade" -} -``` - -**n8n 格式回應**: -```json -{ - "query": "charade", - "count": 1, - "hits": [ - { - "id": "sentence_0001", - "vid": "a1b10138a6bbb0cd", - "start": 48.8, - "end": 55.44, - "title": "Chunk sentence_0001", - "text": "fun plot twists...", - "score": 0.92, - "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4" - } - ] -} -``` - -### 1.3 影片管理 - -```bash -# 列出所有影片 -curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/videos - -# 查詢特定影片(依 UUID) -curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?uuid=a1b10138a6bbb0cd" - -# 查詢特定影片(依路徑) -curl -H "X-API-Key: YOUR_API_KEY" "http://localhost:3002/api/v1/lookup?path=/path/to/video.mp4" - -# 取得處理進度 -curl -H "X-API-Key: YOUR_API_KEY" http://localhost:3002/api/v1/progress/a1b10138a6bbb0cd - -# 探測影片(不註冊) -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"}' - -# 註冊影片 -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"}' -``` - -### 1.4 批次測試腳本 - -```bash -#!/bin/bash -# api_test.sh - API 測試腳本 - -API_URL="http://localhost:3002" - -echo "=== 健康檢查 ===" -curl -s "$API_URL/health" | jq . - -echo -e "\n=== 語意搜尋 ===" -curl -s -X POST "$API_URL/api/v1/search" \ - -H "Content-Type: application/json" \ - -H "X-API-Key: YOUR_API_KEY" \ - -d '{"query": "charade", "limit": 3}' | jq . - -echo -e "\n=== 影片列表 ===" -curl -s -H "X-API-Key: YOUR_API_KEY" "$API_URL/api/v1/videos" | jq '.videos | length' -``` - -### 1.5 外部 URL 範例 - -```bash -# 使用外部 URL(需網路可達) -curl https://api.momentry.ddns.net/health - -# 外部搜尋 -curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \ - -H "Content-Type: application/json" \ - -H "X-API-Key: YOUR_API_KEY" \ - -d '{"query": "charade", "limit": 5}' -``` - ---- - -## 2. n8n 範例 - -### 2.1 HTTP Request Node 設定 - -``` -Node: HTTP Request -├── URL: https://api.momentry.ddns.net/api/v1/n8n/search -├── Method: POST -├── Authentication: None -├── Send Body: ✓ (checked) -├── Content Type: JSON -├── Body: -│ { -│ "query": "={{ $json.query }}", -│ "limit": "={{ $json.limit || 10 }}" -│ } -├── Send Headers: ✓ (checked) -└── Header Parameters: - └── X-API-Key: {{ $env.MOMENTRY_API_KEY }} -``` - -### 2.2 基本搜尋 Workflow - -```json -{ - "name": "Momentry Video Search", - "nodes": [ - { - "parameters": {}, - "name": "Manual Trigger", - "type": "n8n-nodes-base.manualTrigger", - "position": [250, 300] - }, - { - "parameters": { - "url": "https://api.momentry.ddns.net/api/v1/n8n/search", - "method": "POST", - "sendBody": true, - "contentType": "json", - "body": { - "query": "charade", - "limit": 3 - } - }, - "name": "Search Video API", - "type": "n8n-nodes-base.httpRequest", - "position": [450, 300] - } - ], - "connections": { - "Manual Trigger": { - "main": [[{"node": "Search Video API"}]] - } - } -} -``` - -### 2.3 Webhook 動態搜尋 - -```json -{ - "name": "Momentry Dynamic Search", - "nodes": [ - { - "parameters": { - "httpMethod": "POST", - "path": "search", - "responseMode": "lastNode" - }, - "name": "Webhook", - "type": "n8n-nodes-base.webhook", - "position": [250, 300] - }, - { - "parameters": { - "url": "https://api.momentry.ddns.net/api/v1/n8n/search", - "method": "POST", - "sendBody": true, - "contentType": "json", - "body": { - "query": "={{ JSON.stringify($json.body.query) }}", - "limit": "={{ $json.body.limit || 5 }}" - } - }, - "name": "Search API", - "type": "n8n-nodes-base.httpRequest", - "position": [450, 300] - } - ], - "connections": { - "Webhook": { - "main": [[{"node": "Search API"}]] - } - } -} -``` - -### 2.4 測試 Webhook - -```bash -# 測試模式(需先在 n8n UI 點擊 Execute) -curl -X POST http://localhost:5678/webhook-test/video-rag-mcp \ - -H "Content-Type: application/json" \ - -d '{"query":"charade","limit":3}' - -# 生產環境(需 workflow 為 Active 狀態) -curl -X POST http://localhost:5678/webhook/video-rag-mcp \ - -H "Content-Type: application/json" \ - -d '{"query":"charade","limit":3}' -``` - -### 2.5 健康檢查 Workflow - -```json -{ - "name": "Momentry Health Check", - "nodes": [ - { - "parameters": {}, - "name": "Manual Trigger", - "type": "n8n-nodes-base.manualTrigger", - "position": [250, 300] - }, - { - "parameters": { - "url": "https://api.momentry.ddns.net/health", - "method": "GET" - }, - "name": "Health Check", - "type": "n8n-nodes-base.httpRequest", - "position": [450, 300] - } - ], - "connections": { - "Manual Trigger": { - "main": [[{"node": "Health Check"}]] - } - } -} -``` - -### 2.6 錯誤處理 - -| 錯誤 | 原因 | 解決 | -|------|------|------| -| 502 Bad Gateway | API 服務未啟動 | `sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist` | -| "Your request is invalid" | Body 格式設定錯誤 | 確認 Content Type: JSON,Body 為有效 JSON | -| 404 on webhook-test | 未執行 workflow | 在 n8n UI 點擊 "Execute workflow" | - ---- - -## 3. WordPress 範例 - -### 3.1 PHP 基本用法 - -```php - 'charade', - 'limit' => 10 -]; - -$response = wp_remote_post($api_url, [ - 'headers' => ['Content-Type' => 'application/json'], - 'body' => json_encode($data), - 'timeout' => 30 -]); - -if (is_wp_error($response)) { - echo '錯誤: ' . $response->get_error_message(); -} else { - $body = json_decode(wp_remote_retrieve_body($response), true); - print_r($body['hits']); -} -?> -``` - -### 3.2 列出影片 - -```php - 30]); - -if (!is_wp_error($response)) { - $body = json_decode(wp_remote_retrieve_body($response), true); - foreach ($body['videos'] as $video) { - echo $video['file_name'] . "\n"; - } -} -?> -``` - -### 3.3 查詢特定影片 - -```php - 30]); - -if (!is_wp_error($response)) { - $video = json_decode(wp_remote_retrieve_body($response), true); - echo '檔案: ' . $video['file_name'] . "\n"; - echo '時長: ' . $video['duration'] . ' 秒'; -} -?> -``` - -### 3.4 JavaScript fetch - -```javascript -// 搜尋影片 -async function searchVideos(query, limit = 10) { - const response = await fetch('https://api.momentry.ddns.net/api/v1/n8n/search', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ query, limit }) - }); - - if (!response.ok) { - throw new Error('API 請求失敗'); - } - - return await response.json(); -} - -// 使用範例 -searchVideos('charade', 5) - .then(data => { - data.hits.forEach(hit => { - console.log(`${hit.text} (score: ${hit.score})`); - }); - }); -``` - -### 3.5 WordPress Shortcode - -在 `functions.php` 中註冊短碼: - -```php - '', - 'limit' => '10' - ], $atts); - - if (empty($atts['query'])) { - return '

請提供搜尋關鍵字

'; - } - - $response = wp_remote_post('https://api.momentry.ddns.net/api/v1/n8n/search', [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-API-Key' => 'YOUR_API_KEY' // 替換為實際的 API Key - ], - 'body' => json_encode([ - 'query' => $atts['query'], - 'limit' => (int)$atts['limit'] - ]), - 'timeout' => 30 - ]); - - if (is_wp_error($response)) { - return '

搜尋服務暫時無法使用

'; - } - - $data = json_decode(wp_remote_retrieve_body($response), true); - - if (empty($data['hits'])) { - return '

找不到相關結果

'; - } - - $output = '
    '; - foreach ($data['hits'] as $hit) { - // 注意: API 現在返回 file_path 而非 media_url - // 需要將文件路徑轉換為可訪問的 URL - $file_path = $hit['file_path']; - $video_url = convert_file_path_to_url($file_path); // 需要實作此函數 - - $output .= sprintf( - '
  • %s 播放
  • ', - esc_html($hit['text']), - $video_url, - $hit['start'] - ); - } - $output .= '
'; - - return $output; -}); -?> -``` - -**使用方式**: -``` -[momentry_search query="charade" limit="5"] -``` - -### 3.6 WordPress REST API Endpoint - -在 WordPress REST API 中註冊自定義端點: - -```php - 'POST', - 'callback' => function($request) { - $response = wp_remote_post( - 'https://api.momentry.ddns.net/api/v1/n8n/search', - [ - 'headers' => ['Content-Type' => 'application/json'], - 'body' => json_encode([ - 'query' => $request->get_param('query'), - 'limit' => $request->get_param('limit', 10) - ]) - ] - ); - - if (is_wp_error($response)) { - return new WP_Error('api_error', 'API 請求失敗'); - } - - return json_decode(wp_remote_retrieve_body($response)); - } - ]); -}); -?> -``` - -**呼叫方式**: -``` -POST /wp-json/momentry/v1/search -Body: {"query": "charade", "limit": 5} -``` - ---- - -## 4. 回應格式說明 - -### 4.1 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": "fun plot twists...", - "score": 0.92, - "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4" - } - ] -} -``` - -### 4.2 標準格式 (`/api/v1/search`) - -```json -{ - "results": [ - { - "uuid": "a1b10138a6bbb0cd", - "chunk_id": "sentence_0001", - "chunk_type": "sentence", - "start_time": 48.8, - "end_time": 55.44, - "text": "fun plot twists...", - "score": 0.92 - } - ], - "query": "charade" -} -``` - -### 4.3 健康檢查 - -```json -{ - "status": "ok", - "version": "0.1.0", - "uptime_ms": 123456 -} -``` - -### 4.4 詳細健康檢查 - -```json -{ - "status": "ok", - "version": "0.1.0", - "uptime_ms": 123456, - "services": { - "postgres": {"status": "ok", "latency_ms": 42, "error": null}, - "redis": {"status": "ok", "latency_ms": 0, "error": null}, - "qdrant": {"status": "ok", "latency_ms": 15, "error": null}, - "mongodb": {"status": "ok", "latency_ms": 0, "error": null} - } -} -``` - -### 4.5 處理進度 - -```json -{ - "uuid": "a1b10138a6bbb0cd", - "file_name": "video.mp4", - "duration": 120.5, - "overall_progress": 75, - "processors": [ - {"name": "asr", "status": "complete", "progress": 100}, - {"name": "cut", "status": "complete", "progress": 100}, - {"name": "yolo", "status": "progress", "progress": 35} - ] -} -``` - -### 4.6 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" - } - ] -} -``` - ---- - -## 5. HTTP 狀態碼 - -| 狀態 | 說明 | 解決 | -|------|------|------| -| 200 | 成功 | - | -| 400 | 請求格式錯誤 | 檢查 JSON 格式 | -| 404 | 端點或資源不存在 | 確認 URL 正確 | -| 500 | 伺服器內部錯誤 | 檢查 API 服務日誌 | -| 502 | API 服務未啟動 | 見下方說明 | - -### 502 Bad Gateway 解決 - -```bash -# 檢查服務狀態 -launchctl list | grep momentry.api - -# 啟動服務 -sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist - -# 或使用本地測試 -curl http://localhost:3002/health -``` - ---- - -## 6. 常見問題 - -### Q: 為什麼有兩個 URL? - -| URL | 用途 | -|-----|------| -| `localhost:3002` | 直接訪問,繞過反向代理 | -| `api.momentry.ddns.net` | 通過 Caddy 反向代理 | - -### Q: 兩者功能相同嗎? - -是的,所有端點和功能完全相同。 - -### Q: n8n webhook-test 返回 404? - -需在 n8n UI 中點擊 "Execute workflow" 按鈕後才能使用測試 Webhook。 - -### Q: 生產環境 webhook 返回 404? - -需將 workflow 切換為 Active 狀態(右上角開關)。 - ---- - -## 相關文件 - -- [API_INDEX.md](./API_INDEX.md) - 文件總覽 -- [API_ENDPOINTS.md](./API_ENDPOINTS.md) - 端點完整說明 -- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 詳細指南 -- [API_WORDPRESS_GUIDE.md](./API_WORDPRESS_GUIDE.md) - WordPress 詳細指南 diff --git a/docs/API_INDEX.md b/docs/API_INDEX.md deleted file mode 100644 index 6467593..0000000 --- a/docs/API_INDEX.md +++ /dev/null @@ -1,119 +0,0 @@ -# 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) - 待解決問題追蹤 diff --git a/docs/API_KEY_ARCHITECTURE.md b/docs/API_KEY_ARCHITECTURE.md deleted file mode 100644 index ff0a4d1..0000000 --- a/docs/API_KEY_ARCHITECTURE.md +++ /dev/null @@ -1,195 +0,0 @@ -# API Key Management System Architecture - -## System Overview - -``` -┌─────────────────────────────────────────────────────────────────────────────────┐ -│ API Key Management System │ -├─────────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ CLI │ │ HTTP API │ │ Service │ │ External │ │ -│ │ Layer │────▶│ Layer │────▶│ Layer │────▶│ Services │ │ -│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ ▼ ▼ ▼ ▼ │ -│ ┌─────────────────────────────────────────────────────────────────────────┐ │ -│ │ Core Modules │ │ -│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ -│ │ │ Service │ │Validator│ │ Anomaly │ │Rotation │ │ Cleanup │ │ │ -│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ -│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ -│ │ │ Webhook │ │Encrypt │ │Blacklist│ │ Report │ │ Error │ │ │ -│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ -│ └─────────────────────────────────────────────────────────────────────────┘ │ -│ │ │ │ │ -│ ▼ ▼ ▼ │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ PostgreSQL │ │ Redis │ │ External │ │ -│ │ (Storage) │ │ (Cache) │ │ (Gitea/n8n)│ │ -│ └─────────────┘ └─────────────┘ └─────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────────┘ -``` - -## Module Dependencies - -``` - ┌──────────────┐ - │ models.rs │ - │ (Types) │ - └──────┬───────┘ - │ - ┌──────────────────┼──────────────────┐ - │ │ │ - ▼ ▼ ▼ -┌───────────────┐ ┌───────────────┐ ┌───────────────┐ -│ service.rs │ │ error.rs │ │ validator.rs │ -│ (Core CRUD) │ │ (Errors) │ │ (Cache+Rate) │ -└───────┬───────┘ └───────────────┘ └───────────────┘ - │ - │ ┌───────────────────────────────┐ - │ │ │ - ▼ ▼ ▼ -┌───────────────┐ ┌───────────────┐ ┌───────────────┐ -│ anomaly.rs │ │ rotation.rs │ │ blacklist.rs │ -│ (Detection) │ │ (Rotation) │ │ (IP Block) │ -└───────────────┘ └───────────────┘ └───────────────┘ -``` - -## Request Flow - -``` -Client Request - │ - ▼ -┌─────────────┐ -│ CLI/API │ -└──────┬──────┘ - │ - ▼ -┌─────────────┐ ┌─────────────┐ -│ Rate Limit │────▶│ IP Blacklist│ -│ Check │ │ Check │ -└──────┬──────┘ └──────┬──────┘ - │ │ - └─────────┬─────────┘ - │ - ▼ - ┌───────────────┐ - │ Hash API Key │ - └───────┬───────┘ - │ - ▼ - ┌───────────────┐ ┌───────────────┐ - │ Cache Lookup │────▶│ PostgreSQL │ - └───────┬───────┘ │ Lookup │ - │ └───────┬───────┘ - │ │ - └──────────┬──────────┘ - │ - ▼ - ┌───────────────┐ - │ Validate │ - │ (Status, │ - │ Expiry) │ - └───────┬───────┘ - │ - ┌─────────────┼─────────────┐ - │ │ │ - ▼ ▼ ▼ - ┌──────────┐ ┌──────────┐ ┌──────────┐ - │ Valid │ │ Invalid │ │ Error │ - │ Response│ │ Response │ │ Response │ - └──────────┘ └──────────┘ └──────────┘ -``` - -## Database Schema - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ PostgreSQL │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ api_keys │ │ api_key_audit_ │ │ -│ ├─────────────────┤ │ log │ │ -│ │ id │ ├─────────────────┤ │ -│ │ key_id │─────▶│ id │ │ -│ │ key_hash │ │ key_id (FK) │ │ -│ │ name │ │ action │ │ -│ │ key_type │ │ ip_address │ │ -│ │ status │ │ details │ │ -│ │ expires_at │ └─────────────────┘ │ -│ │ ... │ │ -│ └─────────────────┘ ┌─────────────────┐ │ -│ │ api_key_anomalies│ │ -│ ┌─────────────────┐ ├─────────────────┤ │ -│ │ gitea_tokens │ │ id │ │ -│ ├─────────────────┤ │ key_id (FK) │ │ -│ │ id │ │ anomaly_type │ │ -│ │ gitea_token_id │ │ severity │ │ -│ │ token_name │ │ details │ │ -│ │ scopes │ └─────────────────┘ │ -│ └─────────────────┘ │ -│ │ -│ ┌─────────────────┐ │ -│ │ n8n_api_keys │ │ -│ ├─────────────────┤ │ -│ │ id │ │ -│ │ n8n_key_id │ │ -│ │ label │ │ -│ └─────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -## External Integrations - -``` -┌─────────────────────────────────────────────────────────────────────────────────┐ -│ External Integrations │ -├─────────────────────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ Gitea │ │ n8n │ │ Webhook │ │ -│ ├─────────────────┤ ├─────────────────┤ ├─────────────────┤ │ -│ │ • Create Token │ │ • Create API Key│ │ • Key Created │ │ -│ │ • List Tokens │ │ • List API Keys │ │ • Key Revoked │ │ -│ │ • Delete Token │ │ • Delete API Key│ │ • Anomaly │ │ -│ │ • Verify Token │ │ • Verify │ │ • Rate Limited │ │ -│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────────────────┘ -``` - -## Security Layers - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Security Layers │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ Layer 1: Network │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ • IP Blacklist │ │ -│ │ • Rate Limiting │ │ -│ └─────────────────────────────────────────────────────────┘ │ -│ │ -│ Layer 2: Authentication │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ • API Key Hash (SHA256) │ │ -│ │ • Constant-time Comparison │ │ -│ │ • Key Validation (Status, Expiry) │ │ -│ └─────────────────────────────────────────────────────────┘ │ -│ │ -│ Layer 3: Monitoring │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ • Anomaly Detection │ │ -│ │ • Audit Logging (Encrypted) │ │ -│ │ • Webhook Notifications │ │ -│ └─────────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` diff --git a/docs/API_KEY_INTEGRATION_TESTS.md b/docs/API_KEY_INTEGRATION_TESTS.md deleted file mode 100644 index 366610c..0000000 --- a/docs/API_KEY_INTEGRATION_TESTS.md +++ /dev/null @@ -1,236 +0,0 @@ -# API Key Management Integration Tests - -## Test Environment Setup - -### Prerequisites - -```bash -# Start services -sudo launchctl load /Library/LaunchDaemons/com.momentry.postgresql.plist -sudo launchctl load /Library/LaunchDaemons/com.momentry.redis.plist - -# Set environment variables -export DATABASE_URL="postgres://accusys@localhost:5432/momentry" -export REDIS_URL="redis://:accusys@localhost:6379" -export GITEA_URL="http://localhost:3000" -export N8N_URL="https://n8n.momentry.ddns.net" -``` - -### Run Tests - -```bash -# Run all unit tests -cargo test --lib - -# Run API key specific tests -cargo test --lib api_key - -# Run with output -cargo test --lib -- --nocapture -``` - ---- - -## Test Cases - -### 1. API Key Creation - -```bash -# Test: Create a service key -momentry api-key create test-key --key-type service --ttl 90 - -# Expected Output: -# ✅ API Key created successfully! -# Key ID: msvc_... -# API Key: msvc_... -# Expires: 2026-06-19 -``` - -### 2. API Key Validation - -```bash -# Test: Validate the created key -momentry api-key validate --key "msvc_..." - -# Expected Output: -# ✅ API Key is valid -# Key ID: msvc_... -# Name: test-key -# Type: service -``` - -### 3. API Key Listing - -```bash -# Test: List all keys -momentry api-key list - -# Expected Output: -# 📋 API Key List -# ┌────────────────────────────────────────────────────────────────────────────┐ -# │ Status │ Name │ Type │ Usage │ Last Used │ -# ├────────────────────────────────────────────────────────────────────────────┤ -# │ ✓ active │ test-key │ "service" │ 0 │ never │ -# └────────────────────────────────────────────────────────────────────────────┘ -``` - -### 4. API Key Statistics - -```bash -# Test: Show statistics -momentry api-key stats - -# Expected Output: -# 📊 API Key Statistics -# ┌─────────────────────────────────────────┐ -# │ Total Keys: 1 │ -# │ Active Keys: 1 │ -# │ Expired Keys: 0 │ -# └─────────────────────────────────────────┘ -``` - -### 5. Gitea Token Creation - -```bash -# Test: Create Gitea token -momentry gitea create \ - --username admin \ - --password "Test3200Test3200Test3200" \ - --token-name "test-token" \ - --scopes "read:repository,write:repository" - -# Expected Output: -# ✅ Gitea Token created successfully! -# Token ID: ... -# SHA1: ... -``` - -### 6. n8n API Key Creation - -```bash -# Test: Create n8n API key -momentry n8n create \ - --api-key "existing-n8n-key" \ - --label "test-key" \ - --expires-in-days 90 - -# Expected Output: -# ✅ n8n API Key created successfully! -# Key ID: ... -# API Key: ... -``` - ---- - -## Automated Test Script - -```bash -#!/bin/bash -# integration_test.sh - -set -e - -echo "=== API Key Integration Tests ===" - -# 1. Create API key -echo "1. Testing API key creation..." -momentry api-key create integration-test --key-type service --ttl 30 -echo "✅ API key created" - -# 2. List keys -echo "2. Testing API key listing..." -momentry api-key list -echo "✅ API key list OK" - -# 3. Show stats -echo "3. Testing statistics..." -momentry api-key stats -echo "✅ Statistics OK" - -# 4. Test Gitea integration -echo "4. Testing Gitea integration..." -GITEA_URL="http://localhost:3000" \ -momentry gitea list --username admin --password "Test3200Test3200Test3200" -echo "✅ Gitea integration OK" - -echo "" -echo "=== All Tests Passed ===" -``` - ---- - -## Unit Test Coverage - -| Module | Tests | Status | -|--------|-------|--------| -| `models.rs` | 0 | ✅ | -| `service.rs` | 5 | ✅ | -| `validator.rs` | 2 | ✅ | -| `gitea.rs` | 3 | ✅ | -| `n8n.rs` | 2 | ✅ | -| `rotation.rs` | 4 | ✅ | -| `anomaly.rs` | 0 | ✅ | -| `blacklist.rs` | 5 | ✅ | -| `encryption.rs` | 2 | ✅ | -| `webhook.rs` | 2 | ✅ | -| `error.rs` | 3 | ✅ | -| `report.rs` | 1 | ✅ | -| `cleanup.rs` | 1 | ✅ | -| **Total** | **30** | **✅** | - ---- - -## CI/CD Integration - -### GitHub Actions / Gitea Actions - -```yaml -name: API Key Tests - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - services: - postgres: - image: postgres:15 - env: - POSTGRES_USER: accusys - POSTGRES_DB: momentry_test - ports: - - 5432:5432 - redis: - image: redis:7 - ports: - - 6379:6379 - - steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - run: cargo test --lib api_key -``` - ---- - -## Troubleshooting - -### Common Issues - -1. **Database connection failed** - ```bash - # Check PostgreSQL status - pg_isready -h localhost -p 5432 - ``` - -2. **Redis connection failed** - ```bash - # Check Redis status - redis-cli -a accusys ping - ``` - -3. **Gitea authentication failed** - ```bash - # Verify credentials - curl -u admin:password http://localhost:3000/api/v1/user - ``` diff --git a/docs/API_KEY_MANAGEMENT.md b/docs/API_KEY_MANAGEMENT.md deleted file mode 100644 index 9dd308f..0000000 --- a/docs/API_KEY_MANAGEMENT.md +++ /dev/null @@ -1,713 +0,0 @@ -# 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 -momentry api-key revoke --reason "安全原因" - -# 輪換管理 -momentry api-key rotate # 正常輪換 -momentry api-key force-rotate # 強制輪換 -momentry api-key rotation-status # 查看輪換狀態 - -# 異常管理 -momentry api-key suspend --reason "異常使用" -momentry api-key unsuspend -momentry api-key blacklist # 列入黑名單 - -# 審計 -momentry api-key audit --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 \ - --password \ - --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 \ - --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 \ - --password \ - --token-name -``` - -#### 查詢本地記錄 - -```bash -# 查詢已納管的 Token 記錄 -momentry gitea verify --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: " https://n8n.example.com/api/v1/workflows -``` - -### 12.5 CLI 命令 - -#### 建立 API Key - -```bash -# 基本用法 -momentry n8n create \ - --api-key \ - --label \ - --expires-in-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 -``` - -**輸出範例:** -``` -📋 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 \ - --label -``` - -#### 查詢本地記錄 - -```bash -# 查詢已納管的 API Key 記錄 -momentry n8n verify --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/) diff --git a/docs/API_KEY_OPTIMIZATION.md b/docs/API_KEY_OPTIMIZATION.md deleted file mode 100644 index b118521..0000000 --- a/docs/API_KEY_OPTIMIZATION.md +++ /dev/null @@ -1,399 +0,0 @@ -# API Key Management 優化計畫 - -| 項目 | 內容 | -|------|------| -| 版本 | V1.0 | -| 日期 | 2026-03-21 | -| 狀態 | 規劃中 | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.0 | 2026-03-21 | 創建優化計畫 | OpenCode | - | - ---- - -## 任務編碼規則 - -``` -AKO-{類別}-{序號} -AKO = API Key Optimization -類別: - - CODE = 程式碼品質 - - PERF = 效能優化 - - SEC = 安全性 - - FEAT = 功能增強 - - DOC = 文件 -``` - ---- - -## Phase 1: 程式碼品質 (CODE) - -| 編碼 | 任務 | 描述 | 優先級 | 預估工時 | 狀態 | -|------|------|------|--------|----------|------| -| AKO-CODE-01 | 修復 from_str 警告 | 重命名為 `parse_scope` 或實作 `FromStr` trait | 🔴 高 | 0.5h | ⏳ 待辦 | -| AKO-CODE-02 | 函數參數重構 | 使用 Config struct 減少參數數量 | 🔴 高 | 1h | ⏳ 待辦 | -| AKO-CODE-03 | 抽象 CRUD Trait | 建立 `ExternalTokenStore` trait 統一 Gitea/n8n | 🟡 中 | 3h | ⏳ 待辦 | -| AKO-CODE-04 | 錯誤處理統一 | 使用 `thiserror` 定義自訂錯誤類型 | 🟡 中 | 2h | ⏳ 待辦 | - -### AKO-CODE-01 細節 - -```rust -// Before -impl GiteaScope { - pub fn from_str(s: &str) -> Option { ... } -} - -// After: Option A - Rename -impl GiteaScope { - pub fn parse(s: &str) -> Option { ... } -} - -// After: Option B - Implement FromStr -impl std::str::FromStr for GiteaScope { - type Err = (); - fn from_str(s: &str) -> Result { ... } -} -``` - -### AKO-CODE-02 細節 - -```rust -// Before -pub async fn create_api_key( - &self, - key_id: &str, - key_hash: &str, - key_prefix: &str, - name: &str, - key_type: &str, - user_id: Option, - service_name: Option<&str>, - permissions: &serde_json::Value, - expires_at: Option>, -) -> Result - -// After -pub struct CreateApiKeyConfig<'a> { - pub key_id: &'a str, - pub key_hash: &'a str, - pub key_prefix: &'a str, - pub name: &'a str, - pub key_type: &'a str, - pub user_id: Option, - pub service_name: Option<&'a str>, - pub permissions: &'a serde_json::Value, - pub expires_at: Option>, -} - -pub async fn create_api_key(&self, config: CreateApiKeyConfig<'_>) -> Result -``` - -### AKO-CODE-03 細節 - -```rust -#[async_trait] -pub trait ExternalTokenStore { - async fn create(&self, record: T) -> Result; - async fn get_by_label(&self, label: &str) -> Result>; - async fn list(&self) -> Result>; - async fn delete(&self, label: &str) -> Result<()>; - async fn update_verification(&self, label: &str) -> Result<()>; -} -``` - ---- - -## Phase 2: 效能優化 (PERF) - -| 編碼 | 任務 | 描述 | 優先級 | 預估工時 | 狀態 | -|------|------|------|--------|----------|------| -| AKO-PERF-01 | 連線池配置外部化 | 使用環境變數控制 max_connections | 🟡 中 | 0.5h | ⏳ 待辦 | -| AKO-PERF-02 | API Key 驗證快取 | 使用 Moka 快取減少資料庫查詢 | 🔴 高 | 2h | ⏳ 待辦 | -| AKO-PERF-03 | 批次查詢優化 | 合併多次查詢為單一 SQL | 🟡 中 | 1h | ⏳ 待辦 | -| AKO-PERF-04 | 非同步日誌寫入 | 使用 channel 非同步寫入審計日誌 | 🟢 低 | 2h | ⏳ 待辦 | - -### AKO-PERF-01 細節 - -```rust -// Before -let pool_options = PgPoolOptions::new() - .max_connections(10) - .acquire_timeout(std::time::Duration::from_secs(60)); - -// After -let max_conn = std::env::var("DB_MAX_CONNECTIONS") - .unwrap_or_else(|_| "10".to_string()) - .parse() - .unwrap_or(10); - -let pool_options = PgPoolOptions::new() - .max_connections(max_conn) - .acquire_timeout(std::time::Duration::from_secs(60)); -``` - -### AKO-PERF-02 細節 - -```rust -use moka::future::Cache; -use std::time::Duration; - -pub struct ApiKeyCache { - cache: Cache, -} - -pub struct CachedApiKey { - pub record: ApiKeyRecord, - pub cached_at: chrono::DateTime, -} - -impl ApiKeyCache { - pub fn new(ttl_seconds: u64, max_capacity: u64) -> Self { - Self { - cache: Cache::builder() - .time_to_live(Duration::from_secs(ttl_seconds)) - .max_capacity(max_capacity) - .build(), - } - } - - pub async fn get(&self, key_hash: &str) -> Option { - self.cache.get(key_hash).await.map(|c| c.record) - } - - pub async fn insert(&self, key_hash: String, record: ApiKeyRecord) { - self.cache.insert(key_hash, CachedApiKey { - record, - cached_at: chrono::Utc::now(), - }).await; - } - - pub async fn invalidate(&self, key_hash: &str) { - self.cache.invalidate(key_hash).await; - } -} -``` - ---- - -## Phase 3: 安全性 (SEC) - -| 編碼 | 任務 | 描述 | 優先級 | 預估工時 | 狀態 | -|------|------|------|--------|----------|------| -| AKO-SEC-01 | Constant-time 比較 | 使用 `subtle` crate 防止 timing attack | 🔴 高 | 0.5h | ⏳ 待辦 | -| AKO-SEC-02 | Rate Limiter | 限制驗證失敗重試次數 | 🔴 高 | 2h | ⏳ 待辦 | -| AKO-SEC-03 | IP 黑名單 | 支援封鎖特定 IP | 🟡 中 | 1.5h | ⏳ 待辦 | -| AKO-SEC-04 | 審計日誌加密 | 敏感欄位加密儲存 | 🟡 中 | 2h | ⏳ 待辦 | -| AKO-SEC-05 | Key 強度檢查 | 驗證建立的 Key 符合強度要求 | 🟢 低 | 1h | ⏳ 待辦 | - -### AKO-SEC-01 細節 - -```rust -use subtle::ConstantTimeEq; - -// Before -if stored_hash == computed_hash { - // valid -} - -// After -if bool::from(stored_hash.as_bytes().ct_eq(computed_hash.as_bytes())) { - // valid -} -``` - -### AKO-SEC-02 細節 - -```rust -use moka::future::Cache; - -pub struct RateLimiter { - attempts: Cache, - max_attempts: u32, - window_seconds: u64, -} - -pub struct AttemptInfo { - pub count: u32, - pub first_attempt: chrono::DateTime, - pub locked_until: Option>, -} - -impl RateLimiter { - pub async fn check(&self, identifier: &str) -> Result<()> { - if let Some(info) = self.attempts.get(identifier).await { - if let Some(locked_until) = info.locked_until { - if chrono::Utc::now() < locked_until { - anyhow::bail!("Account locked until {}", locked_until); - } - } - } - Ok(()) - } - - pub async fn record_failure(&self, identifier: &str) -> Result<()> { - let mut info = self.attempts.get(identifier).await - .unwrap_or(AttemptInfo { - count: 0, - first_attempt: chrono::Utc::now(), - locked_until: None, - }); - - info.count += 1; - - if info.count >= self.max_attempts { - info.locked_until = Some( - chrono::Utc::now() + chrono::Duration::seconds(self.window_seconds as i64) - ); - } - - self.attempts.insert(identifier.to_string(), info).await; - Ok(()) - } - - pub async fn record_success(&self, identifier: &str) { - self.attempts.invalidate(identifier).await; - } -} -``` - ---- - -## Phase 4: 功能增強 (FEAT) - -| 編碼 | 任務 | 描述 | 優先級 | 預估工時 | 狀態 | -|------|------|------|--------|----------|------| -| AKO-FEAT-01 | 批量建立 Key | 支援 JSON 檔案批量匯入 | 🟡 中 | 3h | ⏳ 待辦 | -| AKO-FEAT-02 | 批量撤銷 Key | 支援條件式批量撤銷 | 🟡 中 | 2h | ⏳ 待辦 | -| AKO-FEAT-03 | Key 匯出 | 匯出 Key 列表(不含明文) | 🟢 低 | 1.5h | ⏳ 待辦 | -| AKO-FEAT-04 | Key 匯入 | 匯入 Key 元數據 | 🟢 低 | 1.5h | ⏳ 待辦 | -| AKO-FEAT-05 | Webhook 通知 | 異常發生時發送 Webhook | 🟡 中 | 3h | ⏳ 待辦 | -| AKO-FEAT-06 | Email 通知 | Key 到期前提醒 | 🟢 低 | 4h | ⏳ 待辦 | -| AKO-FEAT-07 | 統計報表 | 生成使用統計報表 | 🟢 低 | 2h | ⏳ 待辦 | -| AKO-FEAT-08 | 清理過期記錄 | 自動清理過期的 Key 記錄 | 🟢 低 | 1h | ⏳ 待辦 | - -### AKO-FEAT-01 細節 - -```json -// keys.json -{ - "keys": [ - { - "name": "ci-service-1", - "key_type": "service", - "permissions": ["read", "write"], - "ttl_days": 90 - }, - { - "name": "ci-service-2", - "key_type": "service", - "permissions": ["read"], - "ttl_days": 180 - } - ] -} -``` - -```bash -momentry api-key batch-create --file keys.json -``` - -### AKO-FEAT-05 細節 - -```rust -pub struct WebhookConfig { - pub url: String, - pub secret: String, - pub events: Vec, -} - -pub enum WebhookEvent { - KeyCreated, - KeyRevoked, - KeyExpired, - AnomalyDetected, - RotationRequired, -} - -pub struct WebhookNotifier { - client: Client, - config: WebhookConfig, -} - -impl WebhookNotifier { - pub async fn notify(&self, event: WebhookEvent, payload: serde_json::Value) -> Result<()> { - if !self.config.events.contains(&event) { - return Ok(()); - } - - let signature = self.sign(&payload); - - self.client.post(&self.config.url) - .header("X-Webhook-Signature", signature) - .json(&serde_json::json!({ - "event": event, - "timestamp": chrono::Utc::now(), - "payload": payload, - })) - .send() - .await?; - - Ok(()) - } -} -``` - ---- - -## Phase 5: 文件 (DOC) - -| 編碼 | 任務 | 描述 | 優先級 | 預估工時 | 狀態 | -|------|------|------|--------|----------|------| -| AKO-DOC-01 | API 文件自動生成 | 使用 `utoipa` 生成 OpenAPI | 🟢 低 | 3h | ⏳ 待辦 | -| AKO-DOC-02 | CHANGELOG.md | 建立變更日誌 | 🟢 低 | 1h | ⏳ 待辦 | -| AKO-DOC-03 | 架構圖 | 添加系統架構圖 | 🟢 低 | 2h | ⏳ 待辦 | -| AKO-DOC-04 | 整合測試文件 | 記錄整合測試流程 | 🟢 低 | 1h | ⏳ 待辦 | - ---- - -## 總工時估算 - -| Phase | 工時 | 任務數 | -|-------|------|--------| -| CODE | 6.5h | 4 | -| PERF | 5.5h | 4 | -| SEC | 7h | 5 | -| FEAT | 18h | 8 | -| DOC | 7h | 4 | -| **總計** | **44h** | **25** | - ---- - -## 環境變數 - -```bash -# 效能 -DB_MAX_CONNECTIONS=10 -CACHE_TTL_SECONDS=300 -CACHE_MAX_CAPACITY=10000 - -# 安全 -RATE_LIMIT_MAX_ATTEMPTS=5 -RATE_LIMIT_WINDOW_SECONDS=900 - -# 通知 -WEBHOOK_URL=https://example.com/webhook -WEBHOOK_SECRET=your-secret -``` - ---- - -## 參考文件 - -- `docs/API_KEY_MANAGEMENT.md` - API Key 管理系統設計 -- `docs/PENDING_ISSUES.md` - 待解決問題追蹤 -- `src/core/api_key/` - API Key 模組 diff --git a/docs/API_N8N_GUIDE.md b/docs/API_N8N_GUIDE.md deleted file mode 100644 index fc567b9..0000000 --- a/docs/API_N8N_GUIDE.md +++ /dev/null @@ -1,222 +0,0 @@ -# n8n 呼叫 Momentry API 指南 - -| 項目 | 內容 | -|------|------| -| 建立者 | Warren | -| 建立時間 | 2026-03-23 | -| 文件版本 | V1.1 | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.0 | 2026-03-23 | 創建文件 | Warren | OpenCode / MiniMax M2.5 | -| V1.1 | 2026-03-26 | 新增 API Key 驗證說明,更新 HTTP Request Node 設定 | OpenCode | deepseek-reasoner | - ---- - -**用途**: 在 n8n workflow 中呼叫 Momentry API - ---- - -## API URL - -在 n8n HTTP Request Node 中,**請使用外部 URL**: - -``` -https://api.momentry.ddns.net -``` - -> ⚠️ **不要使用** `localhost:3002`,因為 n8n 需要從外部訪問 API。 - ---- - -## 常用端點 - -| 方法 | 端點 | 說明 | -|------|------|------| -| GET | `/health` | 健康檢查 | -| POST | `/api/v1/n8n/search` | 語意搜尋(推薦) | -| GET | `/api/v1/videos` | 列出所有影片 | -| GET | `/api/v1/lookup` | 查詢影片 | -| GET | `/api/v1/progress/:uuid` | 處理進度 | -| GET | `/api/v1/jobs` | 任務列表 | -| GET | `/api/v1/jobs/:uuid` | 任務詳情 | - ---- - -## HTTP Request Node 設定 - -### 語意搜尋(推薦) - -``` -Node: HTTP Request -├── URL: https://api.momentry.ddns.net/api/v1/n8n/search -├── Method: POST -├── Authentication: None -├── Send Body: ✓ (checked) -├── Content Type: JSON -├── Body: -│ { -│ "query": "={{ $json.query }}", -│ "limit": "={{ $json.limit || 10 }}" -│ } -├── Send Headers: ✓ (checked) -└── Header Parameters: - └── X-API-Key: {{ $env.MOMENTRY_API_KEY }} -``` - -### 測試用(固定關鍵字) - -``` -Node: HTTP Request -├── URL: https://api.momentry.ddns.net/api/v1/n8n/search -├── Method: POST -├── Send Body: ✓ -├── Content Type: JSON -├── Body: -│ { -│ "query": "charade", -│ "limit": 3 -│ } -├── Send Headers: ✓ (checked) -└── Header Parameters: - └── X-API-Key: {{ $env.MOMENTRY_API_KEY }} -``` - ---- - -## 完整 Workflow 範例 - -### 基本搜尋 Workflow - -```json -{ - "name": "Momentry Video Search", - "nodes": [ - { - "parameters": {}, - "name": "Manual Trigger", - "type": "n8n-nodes-base.manualTrigger", - "position": [250, 300] - }, - { - "parameters": { - "url": "https://api.momentry.ddns.net/api/v1/n8n/search", - "method": "POST", - "sendBody": true, - "contentType": "json", - "body": { - "query": "charade", - "limit": 3 - } - }, - "name": "Search Video API", - "type": "n8n-nodes-base.httpRequest", - "position": [450, 300] - } - ], - "connections": { - "Manual Trigger": { - "main": [[{"node": "Search Video API"}]] - } - } -} -``` - ---- - -## 動態查詢 Workflow - -```json -{ - "name": "Momentry Dynamic Search", - "nodes": [ - { - "parameters": { - "httpMethod": "POST", - "path": "search", - "responseMode": "lastNode" - }, - "name": "Webhook", - "type": "n8n-nodes-base.webhook", - "position": [250, 300] - }, - { - "parameters": { - "url": "https://api.momentry.ddns.net/api/v1/n8n/search", - "method": "POST", - "sendBody": true, - "contentType": "json", - "body": { - "query": "={{ JSON.stringify($json.body.query) }}", - "limit": "={{ $json.body.limit || 5 }}" - } - }, - "name": "Search API", - "type": "n8n-nodes-base.httpRequest", - "position": [450, 300] - } - ], - "connections": { - "Webhook": { - "main": [[{"node": "Search API"}]] - } - } -} -``` - ---- - -## 常見錯誤 - -### 錯誤: 502 Bad Gateway - -**原因**: API 服務未啟動 - -**解決**: -```bash -sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist -``` - -### 錯誤: "Your request is invalid" - -**原因**: Body 格式設定錯誤 - -**正確設定**: -- `Content Type`: JSON -- `Body`: 必須是有效的 JSON 物件 - ---- - -## curl 測試 - -在終端機中測試 API: - -> **注意**: 所有 `/api/v1/*` 端點都需要 API Key 驗證。請設定環境變數或直接替換 API Key。 - -```bash -# 設定環境變數(使用您的 API Key) -export MOMENTRY_API_KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -``` - -```bash -# 健康檢查 -curl https://api.momentry.ddns.net/health - -# 搜尋測試 (需要 API Key) -curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \ - -H "Content-Type: application/json" \ - -H "X-API-Key: $MOMENTRY_API_KEY" \ - -d '{"query":"charade","limit":3}' -``` - ---- - -## 相關文件 - -- [API_INDEX.md](./API_INDEX.md) - 文件總覽 -- [API_ENDPOINTS.md](./API_ENDPOINTS.md) - 端點完整說明 -- [N8N_HTTP_REQUEST_GUIDE.md](./N8N_HTTP_REQUEST_GUIDE.md) - HTTP Request 詳細設定 diff --git a/docs/API_QUICK_REFERENCE.md b/docs/API_QUICK_REFERENCE.md deleted file mode 100644 index 84f34c0..0000000 --- a/docs/API_QUICK_REFERENCE.md +++ /dev/null @@ -1,532 +0,0 @@ -# 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 工作流程整合 \ No newline at end of file diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md deleted file mode 100644 index 08f53e0..0000000 --- a/docs/API_REFERENCE.md +++ /dev/null @@ -1,528 +0,0 @@ -# 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 ` | -| Check progress | `GET /api/v1/progress/` | -| Search | `POST /api/v1/search` | -| List videos | `GET /api/v1/videos` | -| Lookup | `GET /api/v1/lookup?uuid=` | -| Toggle cache | `POST /api/v1/config/cache` | -| Delete video | `POST /api/v1/unregister` | diff --git a/docs/API_TRAINING_MARCOM.md b/docs/API_TRAINING_MARCOM.md deleted file mode 100644 index 8c8ae8d..0000000 --- a/docs/API_TRAINING_MARCOM.md +++ /dev/null @@ -1,391 +0,0 @@ -# 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 -``` - -#### 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}" -``` - -### 查詢處理任務狀態 - -```bash -curl -s -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \ - "https://api.momentry.ddns.net/api/v1/jobs/{uuid}" -``` - ---- - -## 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 -取得單一影片詳情 - -### 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", - "chunk_type": "sentence", - "start_frame": 318545, - "end_frame": 318665, - "fps": 59.94, - "start_time": 5314.31, - "end_time": 5316.32, - "text": "influenced by a vital way,", - "score": 0.68 - } - ] -} -``` - -**與 /search 的差異**: -| 欄位 | `/search` | `/n8n/search` | -|------|-----------|----------------| -| 影片 UUID | `uuid` | `vid` | -| Chunk ID | `chunk_id` | `id` | -| 開始時間 | `start_time` | `start_time` | -| 結束時間 | `end_time` | `end_time` | -| 相似度分數 | `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 | -| V1.5 | 2026-04-17 | 修正 API Key 格式、統一 n8n/search 欄位名稱 (start/end → start_time/end_time) | OpenCode | diff --git a/docs/API_WORDPRESS_GUIDE.md b/docs/API_WORDPRESS_GUIDE.md deleted file mode 100644 index f53ab30..0000000 --- a/docs/API_WORDPRESS_GUIDE.md +++ /dev/null @@ -1,325 +0,0 @@ -# WordPress 呼叫 Momentry API 指南 - -| 項目 | 內容 | -|------|------| -| 版本 | V1.1 | -| 日期 | 2026-03-25 | -| 用途 | 在 WordPress 中呼叫 Momentry API | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.1 | 2026-03-25 | 更新: n8n 搜尋回傳 `file_path` 取代 `media_url`,新增 API Key 驗證說明 | OpenCode | deepseek-reasoner | -| V1.0 | 2026-03-23 | 創建 WordPress API 指南 | Warren | OpenCode / MiniMax M2.5 | - ---- - -## API URL - -在 WordPress 中呼叫 API,**請使用外部 URL**: - -``` -https://api.momentry.ddns.net -``` - -> ⚠️ WordPress 運行於瀏覽器端,無法直接訪問 `localhost`。 - ---- - -## API 認證 - -所有 `/api/v1/*` 端點(除了健康檢查)都需要 API Key 認證。請在請求標頭中加入: - -``` -'headers' => ['Content-Type' => 'application/json', 'X-API-Key' => 'YOUR_API_KEY'] -``` - -**目前示範使用的 API Key**: `demo_api_key_12345` - -> **注意**: 正式環境請使用安全的 API Key 管理機制,避免在客戶端 JavaScript 中暴露 API Key。 - ---- - -## 常用端點 - -| 方法 | 端點 | 說明 | -|------|------|------| -| GET | `/health` | 健康檢查 | -| POST | `/api/v1/search` | 語意搜尋(標準格式) | -| GET | `/api/v1/videos` | 列出所有影片 | -| GET | `/api/v1/lookup` | 查詢影片 | - ---- - -## PHP 範例 - -### 基本搜尋 - -```php - 'charade', - 'limit' => 10 -]; - -$response = wp_remote_post($api_url, [ - 'headers' => ['Content-Type' => 'application/json', 'X-API-Key' => 'YOUR_API_KEY'], - 'body' => json_encode($data), - 'timeout' => 30 -]); - -if (is_wp_error($response)) { - echo '錯誤: ' . $response->get_error_message(); -} else { - $body = json_decode(wp_remote_retrieve_body($response), true); - print_r($body['hits']); -} -?> -``` - -### 列出所有影片 - -```php - ['X-API-Key' => 'YOUR_API_KEY'], - 'timeout' => 30 -]); - -if (!is_wp_error($response)) { - $body = json_decode(wp_remote_retrieve_body($response), true); - foreach ($body['videos'] as $video) { - echo $video['file_name'] . "\n"; - } -} -?> -``` - -### 查詢特定影片 - -```php - ['X-API-Key' => 'YOUR_API_KEY'], - 'timeout' => 30 -]); - -if (!is_wp_error($response)) { - $video = json_decode(wp_remote_retrieve_body($response), true); - echo '檔案: ' . $video['file_name'] . "\n"; - echo '時長: ' . $video['duration'] . ' 秒'; -} -?> -``` - ---- - -## JavaScript 範例 - -### 使用 fetch - -```javascript -// 搜尋影片 -async function searchVideos(query, limit = 10) { - const response = await fetch('https://api.momentry.ddns.net/api/v1/n8n/search', { - method: 'POST', - headers: { 'Content-Type': 'application/json', 'X-API-Key': 'YOUR_API_KEY' }, - body: JSON.stringify({ query, limit }) - }); - - if (!response.ok) { - throw new Error('API 請求失敗'); - } - - return await response.json(); -} - -// 使用範例 -searchVideos('charade', 5) - .then(data => { - data.hits.forEach(hit => { - console.log(`${hit.text} (score: ${hit.score})`); - }); - }); -``` - ---- - -## WordPress Shortcode 範例 - -在 `functions.php` 中註冊短碼: - -```php - '', - 'limit' => '10' - ], $atts); - - if (empty($atts['query'])) { - return '

請提供搜尋關鍵字

'; - } - - $response = wp_remote_post('https://api.momentry.ddns.net/api/v1/n8n/search', [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-API-Key' => 'YOUR_API_KEY' // 替換為實際的 API Key - ], - 'body' => json_encode([ - 'query' => $atts['query'], - 'limit' => (int)$atts['limit'] - ]), - 'timeout' => 30 - ]); - - if (is_wp_error($response)) { - return '

搜尋服務暫時無法使用

'; - } - - $data = json_decode(wp_remote_retrieve_body($response), true); - - if (empty($data['hits'])) { - return '

找不到相關結果

'; - } - - $output = '
    '; - foreach ($data['hits'] as $hit) { - // 注意: API 現在返回 file_path 而非 media_url - // 需要將文件路徑轉換為可訪問的 URL - $file_path = $hit['file_path']; - $video_url = convert_file_path_to_url($file_path); // 需要實作此函數 - - $output .= sprintf( - '
  • %s 播放
  • ', - esc_html($hit['text']), - $video_url, - $hit['start'] - ); - } - $output .= '
'; - - return $output; -}); -?> -``` - -**使用方式**: -``` -[momentry_search query="charade" limit="5"] -``` - ---- - -## REST API Endpoint (WP >= 5.0) - -在 WordPress REST API 中註冊自定義端點: - -```php - 'POST', - 'callback' => function($request) { - $response = wp_remote_post( - 'https://api.momentry.ddns.net/api/v1/n8n/search', - [ - 'headers' => ['Content-Type' => 'application/json', 'X-API-Key' => 'YOUR_API_KEY'], - 'body' => json_encode([ - 'query' => $request->get_param('query'), - 'limit' => $request->get_param('limit', 10) - ]) - ] - ); - - if (is_wp_error($response)) { - return new WP_Error('api_error', 'API 請求失敗'); - } - - return json_decode(wp_remote_retrieve_body($response)); - } - ]); -}); -?> -``` - -**呼叫方式**: -``` -POST /wp-json/momentry/v1/search -Body: {"query": "charade", "limit": 5} -``` - ---- - -## 常見錯誤 - -### 錯誤: cURL error 7 - -**原因**: 無法連接到 API - -**檢查**: -1. API 服務是否啟動 -2. 網路是否可達 - -### 錯誤: 502 Bad Gateway - -**原因**: API 服務未啟動 - -**解決**: -```bash -sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist -``` - ---- - -## curl 測試 - -在終端機中測試: - -```bash -# 健康檢查 -curl https://api.momentry.ddns.net/health - -# 搜尋測試 -curl -X POST https://api.momentry.ddns.net/api/v1/n8n/search \ - -H "Content-Type: application/json" \ - -d '{"query":"charade","limit":5}' -``` - ---- - -## 相關文件 - -- [API_INDEX.md](./API_INDEX.md) - 文件總覽 -- [API_ENDPOINTS.md](./API_ENDPOINTS.md) - 端點完整說明 -- [API_N8N_GUIDE.md](./API_N8N_GUIDE.md) - n8n 使用範例 diff --git a/docs/API_WORKFLOW_WORDPRESS_N8N.md b/docs/API_WORKFLOW_WORDPRESS_N8N.md deleted file mode 100644 index ac6007c..0000000 --- a/docs/API_WORKFLOW_WORDPRESS_N8N.md +++ /dev/null @@ -1,461 +0,0 @@ -# Momentry API 使用流程 - -> **目標**: 從影片上傳到搜尋的完整流程 -> **適用**: WordPress / n8n 整合 -> **版本**: V1.0 | **日期**: 2026-03-25 - ---- - -## 流程總覽 - -``` -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ 1. 上傳 │ → │ 2. 註冊 │ → │ 3. 確認 │ → │ 4. 處理 │ → │ 5. 搜尋 │ -│ SFTPGo │ │ 自動完成 │ │ UUID │ │ 查詢進度 │ │ 測試 │ -└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ -``` - ---- - -## Step 1: 上傳影片 - -### 方式 A: SFTP 上傳(推薦) - -```bash -# 連線資訊 -主機: sftpgo.momentry.ddns.net -連接埠: 2022 -用戶名: demo -密碼: demopassword123 -``` - -使用 FileZilla 或 SFTP 客戶端上傳到 `/` 目錄 - -### 方式 B: SFTP 命令列 - -```bash -sshpass -p "demopassword123" sftp -P 2022 demo@sftpgo.momentry.ddns.net -``` - -上傳後確認檔案在 SFTPGo 中的位置 - ---- - -## Step 2: 自動註冊 - -上傳後,系統會自動: -1. 偵測新檔案 -2. 計算 UUID(SHA256) -3. 建立資料庫記錄 - -**無需手動操作** - ---- - -## Step 3: 確認註冊成功 - -### 查詢所有影片 - -```bash -curl -s -H "X-API-Key: YOUR_API_KEY" \ - "https://api.momentry.ddns.net/api/v1/videos" | jq '.videos | length' -``` - -### 查詢特定檔案 - -```bash -curl -s -H "X-API-Key: YOUR_API_KEY" \ - "https://api.momentry.ddns.net/api/v1/videos" | jq '.videos[] | select(.file_name | contains("你的檔案名"))' -``` - -### 預期回應 - -```json -{ - "uuid": "952f5854b9febad1", - "file_path": "/Users/accusys/momentry/var/sftpgo/data/demo/你的檔案.mp4", - "file_name": "你的檔案.mp4", - "duration": 123.45, - "width": 1920, - "height": 1080 -} -``` - -**確認要點**: -- ✅ UUID 已產生(16位 hex) -- ✅ `file_path` 正確 -- ✅ `duration` > 0 - ---- - -## Step 4: 查詢處理進度 - -### 取得任務 UUID - -```bash -# 從影片資訊取得 job_id -curl -s -H "X-API-Key: YOUR_API_KEY" \ - "https://api.momentry.ddns.net/api/v1/videos" | \ - jq '.videos[] | select(.file_name == "你的檔案.mp4") | {uuid, job_id}' -``` - -### 查詢任務狀態 - -```bash -curl -s -H "X-API-Key: YOUR_API_KEY" \ - "https://api.momentry.ddns.net/api/v1/jobs/{uuid}" -``` - -### 任務狀態說明 - -| status | 說明 | 動作 | -|--------|------|------| -| `pending` | 等待處理 | 等待中 | -| `processing` | 處理中 | 繼續輪詢 | -| `completed` | 已完成 | 可進入 Step 5 | -| `failed` | 處理失敗 | 檢查錯誤 | - -### n8n 輪詢範例 - -```javascript -// n8n Workflow: 檢查處理狀態 -const jobUuid = $input.item.json.job_uuid; - -const response = await fetch( - `https://api.momentry.ddns.net/api/v1/jobs/${jobUuid}`, - { - headers: { - "X-API-Key": "YOUR_API_KEY" - } - } -); - -const job = await response.json(); - -// 狀態檢查 -if (job.status === 'completed') { - return [{ json: { done: true, video_uuid: job.video_uuid } }]; -} else { - return [{ json: { done: false, status: job.status } }]; -} -``` - ---- - -## Step 5: 搜尋測試 - -處理完成後,資料會入庫到向量資料庫,可進行搜尋測試。 - -### 測試向量搜尋 - -```bash -curl -s -X POST "https://api.momentry.ddns.net/api/v1/search" \ - -H "X-API-Key: YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "query": "測試關鍵字", - "limit": 5 - }' -``` - -### 取得分段(Chunk)內容 - -搜尋結果會返回影片分段(Chunk),包含可播放的時間軸資訊: - -```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 - } - ] -} -``` - -**Chunk 欄位說明**: -| 欄位 | 說明 | -|------|------| -| `uuid` | 影片 UUID(用於取得影片網址) | -| `chunk_id` | 分段 ID | -| `chunk_type` | 分段類型(sentence/cut/time/trace/story) | -| `start_time` | 開始時間(秒) | -| `end_time` | 結束時間(秒) | -| `text` | 語音內容文字 | -| `score` | 相似度分數(0-1) | - -### 播放分段 - -取得 Chunk 後可組合成播放網址: - -``` -影片網址?start={start_time}&end={end_time} -``` - -範例: -``` -https://wp.momentry.ddns.net/video.mp4?start=5309.08&end=5311.08 -``` - ---- - -## 完整 n8n Workflow 範例 - -``` -┌──────────────┐ -│ 觸發 (定時) │ -└──────┬───────┘ - ▼ -┌──────────────┐ ┌──────────────┐ -│ 查詢影片 │────►│ 比對新檔案 │ -│ /videos │ │ │ -└──────┬───────┘ └──────────────┘ - │ │ - ▼ ▼ -┌──────────────┐ ┌──────────────┐ -│ 等待處理 │◄────│ 輪詢任務狀態 │ -│ /jobs/:uuid │ │ /jobs/:uuid │ -└──────┬───────┘ └──────────────┘ - │ - ▼ (completed) -┌──────────────┐ -│ 搜尋測試 │ -│ /search │ -└──────────────┘ -``` - ---- - -## 快速參考 - -| 步驟 | API | 用途 | -|------|-----|------| -| 查詢影片 | `GET /api/v1/videos` | 確認上傳成功 | -| 查詢任務 | `GET /api/v1/jobs/:uuid` | 查看處理進度 | -| 搜尋內容 | `POST /api/v1/search` | 測試搜尋功能 | - ---- - -## WordPress PHP 範例 - -### 基本設定 - -```php - $method, - 'headers' => [ - 'X-API-Key' => self::API_KEY, - 'Content-Type' => 'application/json', - ], - 'timeout' => 30, - ]; - - if ($data !== null) { - $args['body'] = json_encode($data); - } - - $response = wp_remote_request($url, $args); - - if (is_wp_error($response)) { - throw new Exception($response->get_error_message()); - } - - return json_decode(wp_remote_retrieve_body($response), true); - } - - public static function getVideos(): array { - return self::request('GET', '/api/v1/videos'); - } - - public static function getVideo(string $uuid): array { - return self::request('GET', "/api/v1/videos/{$uuid}"); - } - - public static function getJob(string $uuid): array { - return self::request('GET', "/api/v1/jobs/{$uuid}"); - } - - public static function search(string $query, int $topK = 5): array { - return self::request('POST', '/api/v1/search', [ - 'query' => $query, - 'top_k' => $topK, - ]); - } -} -``` - -### Step 3: 確認註冊成功 - -```php - '', - 'limit' => 10, - ], $atts); - - if (empty($atts['query'])) { - return '

請輸入搜尋關鍵字

'; - } - - try { - $results = Momentry_API::search($atts['query'], $atts['limit']); - - if (empty($results['results'])) { - return '

找不到相關結果

'; - } - - $html = '
'; - $html .= '

搜尋結果: ' . esc_html($atts['query']) . '

'; - $html .= '
    '; - - foreach ($results['results'] as $result) { - $video_uuid = $result['uuid']; - $start = $result['start_time'] ?? 0; - $end = $result['end_time'] ?? 0; - $text = $result['text'] ?? '無文字描述'; - - $html .= '
  • '; - $html .= ''; - $html .= '播放 ' . $start . 's - ' . $end . 's'; - $html .= ''; - $html .= '
    '; - $html .= '相似度: ' . round($result['score'] * 100) . '%'; - $html .= '
    '; - $html .= esc_html($text); - $html .= '
  • '; - } - - $html .= '
'; - return $html; - - } catch (Exception $e) { - return '

搜尋服務暫時無法使用

'; - } -}); -``` - -**使用方式**: -```html -[momentry_search query="關鍵字" limit="5"] -``` - ---- - -## 完整 n8n Workflow 範例 - -``` -┌──────────────┐ -│ 觸發 (定時) │ -└──────┬───────┘ - ▼ -┌──────────────┐ ┌──────────────┐ -│ 查詢影片 │────►│ 比對新檔案 │ -│ /videos │ │ │ -└──────┬───────┘ └──────────────┘ - │ │ - ▼ ▼ -┌──────────────┐ ┌──────────────┐ -│ 等待處理 │◄────│ 輪詢任務狀態 │ -│ /jobs/:uuid │ │ /jobs/:uuid │ -└──────┬───────┘ └──────────────┘ - │ - ▼ (completed) -┌──────────────┐ -│ 搜尋測試 │ -│ /search │ -└──────────────┘ -``` - ---- - -**注意**: -- 處理時間視影片長度而定(1分鐘影片約需 2-5 分鐘處理) -- 大量影片時建議分批上傳 - ---- - -## 附錄:版本歷史 - -| 版本 | 日期 | 內容 | 操作人 | -|------|------|------|--------| -| V1.0 | 2026-03-25 | 初版建立 | OpenCode | -| V1.1 | 2026-03-25 | 新增 Chunk 取得與播放說明、Shortcode 範例 | OpenCode | -| V1.2 | 2026-03-25 | 修正 SFTPGo 主機名稱為 sftpgo.momentry.ddns.net | OpenCode | diff --git a/docs/ARCHITECTURE_EVALUATION.md b/docs/ARCHITECTURE_EVALUATION.md deleted file mode 100644 index ca95826..0000000 --- a/docs/ARCHITECTURE_EVALUATION.md +++ /dev/null @@ -1,331 +0,0 @@ -# 架構優化待評估事項 - -| 項目 | 內容 | -|------|------| -| 建立者 | OpenCode | -| 建立時間 | 2026-03-21 | -| 文件版本 | V1.0 | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | -|------|------|------|--------| -| V1.0 | 2026-03-21 | 創建文件 | OpenCode | -| V1.1 | 2026-03-22 | 新增 TigerGraph/GraphRAG 說故事評估 | OpenCode | - ---- - -## 架構優化項目 - -### 1. PostgreSQL → Redis 故障轉移 - -**說明**: 當 PostgreSQL 不可用時,降級到 Redis 作為臨時存儲 - -**複雜度**: 中 - -**影響範圍**: -- `src/core/db/postgres_db.rs` -- `src/core/db/redis_client.rs` - -**風險**: -- 數據一致性問題 -- 需要定義轉移策略 - -**優先級**: 待評估 - ---- - -### 2. 連接池監控 - -**說明**: 添加 PostgreSQL 和 Redis 連接池指標到 Prometheus - -**複雜度**: 低 - -**影響範圍**: -- `src/core/db/postgres_db.rs` -- `src/core/db/redis_client.rs` -- `src/api/` (新增 metrics endpoint) - -**風險**: 低 - -**優先級**: 待評估 - ---- - -### 3. Processor 重試機制 - -**說明**: 當 processor 失敗時自動重試 - -**複雜度**: 中 - -**影響範圍**: -- `src/core/processor/executor.rs` (新增 `run_with_retry` 方法) -- `src/core/processor/mod.rs` (導出 `RetryConfig`) - -**風險**: -- 無限重試風險 → 已通過 `max_attempts` 控制 -- 需要指數退避 → 已實現 - -**優先級**: ✅ 已完成 (2026-03-21) - -**實作內容**: -- `RetryConfig` 結構體 (可配置重試次數、初始延遲、最大延遲、退避倍數) -- `run_with_retry()` 方法 (自動重試 + 指數退避) -- 單元測試覆蓋 - -**使用範例**: -```rust -use crate::core::processor::{PythonExecutor, RetryConfig}; - -let executor = PythonExecutor::new()?; -let config = RetryConfig::new(3).with_delay(1000).with_max_delay(30000); - -executor.run_with_retry( - "asr_processor.py", - &["--input", "/path/to/video"], - Some(&uuid), - "asr", - Some(Duration::from_secs(3600)), - Some(config), -).await?; -``` - ---- - -### 4. PyO3 整合 - -**說明**: Python/Rust 直接調用,移除子進程調用 - -**複雜度**: 高 - -**影響範圍**: -- `src/core/processor/executor.rs` (重寫) -- Python 模組 (修改為可直接 import) - -**風險**: -- Python GIL 問題 -- 依賴版本兼容性 -- 需要大量重寫 - -**優先級**: 低 (長期目標) - ---- - -### 5. HTTP 健康端點 - -**說明**: 添加 `/health` API 用於外部監控 - -**複雜度**: 低 - -**影響範圍**: -- `src/api/server.rs` (新增路由) - -**風險**: 低 - -**優先級**: ✅ 已完成 (2026-03-21) - -**實作內容**: -- `GET /health` - 基本健康檢查 (status, version, uptime) -- `GET /health/detailed` - 詳細健康檢查 (PostgreSQL, Redis, Qdrant 狀態和延遲) - ---- - -### 6. Gitea Actions CI/CD - -**說明**: 配置 Gitea Actions 自動化 CI/CD,在合併前執行檢查 - -**複雜度**: 中 - -**影響範圍**: -- `.gitea/workflows/` (新增 workflow 文件) - -**優點**: -- 強制執行檢查,無法跳過 -- 跨設備一致 -- PR 審查前自動檢查 - -**風險**: 低 - -**優先級**: 待評估 - ---- - -### 7. Commit Message Lint - -**說明**: 規範化提交訊息格式 (Conventional Commits) - -**複雜度**: 低 - -**影響範圍**: -- `.git/hooks/commit-msg` (新增 hook) -- `~/dotfiles/hooks/commit-msg` - -**風險**: 低 - -**優先級**: ✅ 已完成 (2026-03-21) - -**實作內容**: -- 驗證格式: `(): ` -- 有效類型: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert -- 警告: 第一行超過 72 字符 - -**範例**: -``` -feat(api): add health check endpoint -fix(db): resolve connection pool issue -docs: update README -``` - ---- - -### 8. 自動化安裝腳本 - -**說明**: 創建腳本一次安裝所有開發工具 - -**複雜度**: 低 - -**影響範圍**: -- `scripts/install-dev-tools.sh` (新增) - -**風險**: 低 - -**優先級**: 待評估 - ---- - -## 評估標準 - -| 標準 | 說明 | -|------|------| -| 業務價值 | 對用戶有何幫助 | -| 技術風險 | 實現難度和潛在問題 | -| 維護成本 | 未來維護負擔 | -| 依賴性 | 對其他系統的影響 | - ---- - -## 評估記錄 - -| 項目 | 評估日期 | 決策 | 原因 | -|------|----------|------|------| -| PostgreSQL → Redis 故障轉移 | 待評估 | - | - | -| 連接池監控 | 待評估 | - | - | -| Processor 重試機制 | 2026-03-21 | 已完成 | - | -| PyO3 整合 | 待評估 | - | - | -| HTTP 健康端點 | 2026-03-21 | 已完成 | - | -| Gitea Actions CI/CD | 待評估 | - | - | -| Commit Message Lint | 2026-03-21 | 已完成 | - | -| 自動化安裝腳本 | 待評估 | - | - | - ---- - -## 9. TigerGraph / Knowledge Graph 圖譜說故事 - -**說明**: 使用知識圖譜 (Knowledge Graph) 增強視頻敘事 (Storytelling) 和 RAG 檢索 - -**複雜度**: 高 - -**研究來源**: -- [TigerGraph Agentic GraphRAG](https://www.tigergraph.com/blog/agentic-graphrag-gives-ai-a-playbook-for-smarter-retrieval/) (2025-12-15) -- [TigerGraph GraphRAG GitHub](https://github.com/tigergraph/graphrag) (v1.2.0, 2026-03-11) -- [GraphRAG in 2026: Practitioner's Guide](https://medium.com/graph-praxis/graph-rag-in-2026-a-practitioners-guide-to-what-actually-works-dca4962e7517) (2026-02-22) -- [GraphRAG Complete Guide](https://medium.com/@brian-curry-research/graphrag-the-complete-guide-to-graph-powered-retrieval-augmented-generation-eeb58a6bb4d1) (2026-02-11) - -### 核心概念 - -| 概念 | 說明 | -|------|------| -| **GraphRAG** | 結合知識圖譜與 RAG,比傳統向量檢索更智能 | -| **知識圖譜** | 實體 (Entity) + 關係 (Relationship) 的結構化表示 | -| **多跳推理** | Multi-hop traversal,可連接多個相關節點 | -| **混合檢索** | Graph traversal + Vector similarity 結合 | - -### 對 Momentry 的潛在應用 - -``` -視頻場景 → 實體識別 → 關係建立 → 故事圖譜 - ↓ ↓ ↓ ↓ - CUT [人物, 物品, 動作] [誰做了什麼, 什麼導致什麼] [敘事鏈] -``` - -**1. 敘事圖譜構建 (Narrative Graph)** -- 從 Story/Chunks 模組提取實體 -- 建立場景之間的因果關係 -- 追蹤角色互動和情節發展 - -**2. 故事檢索增強** -```python -# 現有: Parent-child chunks -parent_chunk: "場景描述" -child_chunks: [詳細內容] - -# 加入圖譜: -場景A --led_to--> 場景B -角色X --interacted_with--> 角色Y -主題Y --related_to--> 主題Z -``` - -**3. 查詢模式** - -| 查詢類型 | 傳統 RAG | GraphRAG | -|----------|----------|----------| -| 事實查找 | ✅ "這個場景在說什麼" | ✅ | -| 主題推理 | ❌ "這個視頻的主要情節" | ✅ Global search | -| 多跳關係 | ❌ | ✅ "A導致B,B導致C" | -| 可解釋性 | ❌ | ✅ 關係路徑可追溯 | - -### 實作方案 - -**方案 A: TigerGraph Cloud (推薦)** -- ✅ 原生 Graph + Vector 混合查詢 -- ✅ GraphRAG 官方支援 -- ✅ 200GB 免費額度 -- ❌ 雲端依賴,延遲敏感場景需考慮 - -**方案 B: Neo4j + Qdrant** -- ✅ 成熟開源生態 -- ✅ LangChain/LlamaIndex 整合 -- ❌ 需要維護兩個系統 - -**方案 C: 自建混合架構** -- PostgreSQL + Neo4j (或Typesense) -- 利用現有 BM25 + 向量檢索基礎 -- ❌ 開發成本高 - -### 技術棧整合建議 - -```rust -// 現有架構 -Vector Search (Qdrant) ← BM25 (PostgreSQL) - -// 加入 GraphRAG -Knowledge Graph (TigerGraph/Neo4j) - ↓ - 混合檢索 ← Vector + Graph traversal -``` - -### 優先級: 待評估 - -**考慮因素**: -- 用戶是否需要複雜的故事情節查詢? -- 實體識別 (NER) 成本是否可以接受? -- 與現有 BM25 + Vector 混合搜索的比較優勢? - ---- - -## 10. LazyGraphRAG / FastGraphRAG 成本優化 - -**說明**: GraphRAG 索引成本高昂,LazyGraphRAG 推遲圖譜構建到查詢時 - -**來源**: [GraphRAG in 2026](https://medium.com/graph-praxis/graph-rag-in-2026-a-practitioners-guide-to-what-actually-works-dca4962e7517) - -**Microsoft GraphRAG 問題**: $33K 索引大型數據集 - -**替代方案**: -- **LazyGraphRAG**: 按需構建,查詢時再建立子圖 -- **FastGraphRAG**: 優化索引管道,10-90% 成本節省 -- **HippoRAG**: 使用 Personalised PageRank 優化遍歷 - -**優先級**: 待評估 (作為 GraphRAG 的一部分) diff --git a/docs/BACKUP_VERSIONING.md b/docs/BACKUP_VERSIONING.md deleted file mode 100644 index a486482..0000000 --- a/docs/BACKUP_VERSIONING.md +++ /dev/null @@ -1,450 +0,0 @@ -# Momentry 備份版本管理規範 - -| 項目 | 內容 | -|------|------| -| 建立者 | Warren / OpenCode | -| 建立時間 | 2026-03-25 | -| 文件版本 | V1.0 | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | -|------|------|------|--------| -| V1.0 | 2026-03-25 | 建立備份版本管理規範 | OpenCode | - ---- - -## 1. 概述 - -本文檔定義 Momentry 系統的備份版本管理規範,確保新舊架構之間的回滾相容性。 - -### 1.1 版本定義 - -| 版本 | 日期 | 說明 | -|------|------|------| -| v1 | 2026-03-18 | 初始備份架構(不包含新架構組件)| -| v2 | 2026-03-25 | 新架構備份(包含 monitor_jobs, processor_results, Output 目錄)| - -### 1.2 備份版本格式 - -| 版本 | 檔案命名格式 | -|------|-------------| -| v1 | `{service}_{type}_{YYYYMMDD}_{HHMMSS}.{ext}` | -| v2 | `{service}_{type}_v2_{YYYYMMDD}_{HHMMSS}.{ext}` | - -### 1.3 各版本涵蓋範圍 - -| 組件 | v1 | v2 | -|------|-----|-----| -| PostgreSQL (videos, chunks) | ✅ | ✅ | -| PostgreSQL (monitor_jobs) | ❌ | ✅ | -| PostgreSQL (processor_results) | ❌ | ✅ | -| Redis | ✅ | ✅ | -| MongoDB Cache | ⚠️ | ⚠️ | -| Output (probe.json) | ❌ | ✅ | - -> ⚠️ MongoDB 備份目前存在路徑問題,正在修復中 - ---- - -## 2. 備份版本識別 - -### 2.1 檔名識別 - -```bash -# 識別版本 -detect_version() { - local backup_file=$1 - if echo "$backup_file" | grep -q "_v2_"; then - echo "v2" - else - echo "v1" - fi -} - -# 使用範例 -detect_version "postgresql_db_momentry_v2_20260325_030000.sql.gz" -# 輸出: v2 - -detect_version "postgresql_db_momentry_20260324_030000.sql.gz" -# 輸出: v1 -``` - -### 2.2 內容識別 - -```bash -# 檢查是否為 v2 備份 -is_v2_backup() { - local backup_file=$1 - gzip -dc "$backup_file" 2>/dev/null | grep -q "monitor_jobs" && echo "yes" || echo "no" -} - -# 檢查是否包含 processor_results -has_processor_results() { - local backup_file=$1 - gzip -dc "$backup_file" 2>/dev/null | grep -q "processor_results" && echo "yes" || echo "no" -} -``` - -### 2.3 檔案大小比較 - -| 版本 | PostgreSQL 備份大小 | 說明 | -|------|---------------------|------| -| v1 | ~18-19 MB | 基本資料表 | -| v2 | >19 MB | 包含新表格和索引 | - ---- - -## 3. 回滾策略 - -### 3.1 回滾流程圖 - -``` -┌─────────────────────────────────────────┐ -│ 選擇還原目標 │ -└─────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────┐ -│ 選擇備份版本 │ -│ ┌───────────┐ ┌───────────┐ │ -│ │ v1 備份 │ │ v2 備份 │ │ -│ └───────────┘ └───────────┘ │ -└─────────────────────────────────────────┘ - │ - ┌─────────┴─────────┐ - ▼ ▼ - ┌──────────┐ ┌──────────┐ - │ v1 回滾 │ │ v2 回滾 │ - └──────────┘ └──────────┘ - │ │ - ▼ ▼ - ┌──────────┐ ┌──────────┐ - │ 基本資料庫 │ │ 完整還原 │ - └──────────┘ └──────────┘ -``` - -### 3.2 回滾矩陣 - -| 還原目標 | v1 備份 | v2 備份 | -|----------|---------|---------| -| 基本資料庫 | ✅ | ✅ | -| + monitor_jobs | ❌ | ✅ | -| + processor_results | ❌ | ✅ | -| + Output 檔案 | ❌ | ✅ | -| + MongoDB Cache | ⚠️ | ⚠️ | - -### 3.3 回滾相容性說明 - -#### v1 → v2(支援) - -- v1 備份可以還原到 v2 架構 -- 新架構組件會從空白狀態開始 -- 不會造成資料損壞 - -#### v2 → v1(⚠️ 警告) - -``` -⚠️ v2 回滾到 v1 可能導致資料丟失 - -影響範圍: -- monitor_jobs 資料會消失 -- processor_results 資料會消失 -- Output 檔案參照可能失效 - -建議: -1. 在還原前建立 v2 快照 -2. 或使用隔離還原(staging restore) -``` - ---- - -## 4. 還原腳本保護機制 - -### 4.1 還原前檢查 - -```bash -# 還原前檢查版本相容性 -pre_restore_check() { - local backup_file=$1 - local version=$(detect_version "$backup_file") - local current_db_version=$(check_current_db_version) - - echo "備份版本: $version" - echo "目前版本: $current_db_version" - - # v2 → v1: 警告但允許(使用者需確認) - if [ "$version" = "v1" ] && [ "$current_db_version" = "v2" ]; then - echo "⚠️ 警告:即將回滾到 v1" - echo "影響:monitor_jobs 和 processor_results 資料將被清除" - read -p "確認繼續?(y/N): " confirm - [ "$confirm" != "y" ] && exit 1 - fi - - # v1 → v2: 直接允許 - if [ "$version" = "v1" ] && [ "$current_db_version" = "v2" ]; then - echo "ℹ️ 提示:新架構組件將重新初始化" - fi - - # v2 → v2: 直接允許 - # v1 → v1: 直接允許 -} -``` - -### 4.2 隔離還原(Staging Restore) - -```bash -# 只還原到暫存資料庫,不影響生產 -restore_to_staging() { - local backup_file=$1 - local version=$(detect_version "$backup_file") - - echo "執行隔離還原..." - echo "版本: $version" - - # 建立暫存資料庫 - PGPASSWORD="$PG_PASSWORD" psql -U "$PG_USER" -d postgres << EOF - DROP DATABASE IF EXISTS momentry_staging; - CREATE DATABASE momentry_staging; - EOF - - # 還原到暫存資料庫 - PGPASSWORD="$PG_PASSWORD" pg_restore -U "$PG_USER" -d "momentry_staging" \ - --no-owner --no-acl "$backup_file" - - echo "✅ 還原完成:momentry_staging" - echo "驗證命令:psql -U accusys -d momentry_staging -c '\\dt'" -} -``` - -### 4.3 版本驗證命令 - -```bash -# 識別所有備份版本 -ls /Users/accusys/momentry/backup/daily/postgresql/*.sql.gz | \ - xargs -I {} sh -c 'echo "{}: $(detect_version {})"' - -# 驗證 v2 備份內容 -verify_v2_backup() { - local backup_file=$1 - - echo "驗證備份: $backup_file" - - # 檢查 monitor_jobs - if gzip -dc "$backup_file" | grep -q "monitor_jobs"; then - echo "✅ 包含 monitor_jobs" - else - echo "❌ 缺少 monitor_jobs" - return 1 - fi - - # 檢查 processor_results - if gzip -dc "$backup_file" | grep -q "processor_results"; then - echo "✅ 包含 processor_results" - else - echo "❌ 缺少 processor_results" - return 1 - fi - - echo "✅ v2 備份驗證通過" -} -``` - ---- - -## 5. 版本遷移 - -### 5.1 v1 → v2 遷移步驟 - -| 步驟 | 說明 | 驗證 | -|------|------|------| -| 1 | 確認所有 v1 備份已完成 | `ls *.sql.gz \| grep -v v2` | -| 2 | 修改 `backup_all.sh` 加入 v2 標記 | 確認 TIMESTAMP 包含 `v2_` | -| 3 | 修正 MongoDB 路徑 | 確認指向正確目錄 | -| 4 | 新增 Output 目錄備份 | 確認 probe.json 被備份 | -| 5 | 執行測試備份 | 驗證命名格式正確 | -| 6 | 驗證 v2 備份完整性 | `verify_v2_backup` | -| 7 | 正式啟用 v2 備份 | 確認 crontab 使用新版 | - -### 5.2 遷移驗證清單 - -```bash -#!/bin/bash -# verify_v2_migration.sh - -echo "=== v2 遷移驗證 ===" - -# 1. 檢查備份腳本 -echo "1. 檢查備份腳本..." -if grep -q "v2_" /Users/accusys/momentry/scripts/backup_all.sh; then - echo " ✅ 版本標記已啟用" -else - echo " ❌ 版本標記未啟用" -fi - -# 2. 檢查 MongoDB 路徑 -echo "2. 檢查 MongoDB 路徑..." -if grep -q "/opt/homebrew/var/mongodb" /Users/accusys/momentry/scripts/backup_all.sh; then - echo " ✅ MongoDB 路徑已修正" -else - echo " ❌ MongoDB 路徑未修正" -fi - -# 3. 檢查 Output 目錄備份 -echo "3. 檢查 Output 目錄備份..." -if grep -q "momentry_output" /Users/accusys/momentry/scripts/backup_all.sh; then - echo " ✅ Output 目錄備份已啟用" -else - echo " ❌ Output 目錄備份未啟用" -fi - -# 4. 檢查最新備份 -echo "4. 檢查最新備份..." -latest_backup=$(ls -t /Users/accusys/momentry/backup/daily/postgresql/*.sql.gz 2>/dev/null | head -1) -if [ -n "$latest_backup" ]; then - version=$(detect_version "$latest_backup") - echo " 最新備份: $(basename $latest_backup)" - echo " 版本: $version" - if [ "$version" = "v2" ]; then - verify_v2_backup "$latest_backup" - fi -fi - -echo "=== 驗證完成 ===" -``` - ---- - -## 6. 疑難排解 - -### 6.1 常見問題 - -| 問題 | 原因 | 解決方案 | -|------|------|----------| -| 無法識別版本 | 檔名被修改 | 使用內容分析 `gzip -dc \| grep "monitor_jobs"` | -| v2 備份還原失敗 | 磁碟空間不足 | 清理空間後重試 | -| v1 還原覆蓋 v2 | 操作失誤 | 使用隔離還原保護生產資料 | -| MongoDB 備份為空 | 路徑錯誤 | 修正為 `/opt/homebrew/var/mongodb` | - -### 6.2 緊急回滾流程 - -```bash -#!/bin/bash -# emergency_restore.sh - -set -e - -BACKUP_FILE=$1 -VERSION=$2 - -echo "=== 緊急回滾 ===" -echo "備份檔案: $BACKUP_FILE" -echo "目標版本: $VERSION" - -# 1. 建立當前狀態快照 -echo "1. 建立當前狀態快照..." -NOW=$(date +%Y%m%d_%H%M%S) -pg_dump -U accusys -d momentry | gzip > "/tmp/momentry_emergency_$NOW.sql.gz" -echo " 快照: /tmp/momentry_emergency_$NOW.sql.gz" - -# 2. 執行還原 -echo "2. 執行還原..." -gunzip -c "$BACKUP_FILE" | psql -U accusys -d momentry - -# 3. 驗證 -echo "3. 驗證還原..." -psql -U accusys -d momentry -c "SELECT COUNT(*) FROM monitor_jobs;" - -echo "=== 回滾完成 ===" -``` - ---- - -## 7. 備份清單(v2) - -### 7.1 每日備份(v2 格式) - -| 服務 | 備份項目 | 檔案命名 | 說明 | -|------|----------|----------|------| -| PostgreSQL | momentry | `postgresql_db_momentry_v2_{date}_{time}.sql.gz` | 完整資料庫 | -| PostgreSQL | video_register | `postgresql_db_video_register_v2_{date}_{time}.sql.gz` | 影片註冊資料 | -| Redis | RDB | `redis_rdb_v2_{date}_{time}.rdb` | Redis 快照 | -| MongoDB | 資料 | `mongodb_data_v2_{date}_{time}.tar.gz` | MongoDB 資料 | -| n8n | 資料+DB | `n8n_{date}_{time}.tar.gz`, `n8n_db_{date}_{time}.sql.gz` | n8n 完整 | -| SFTPGo | 配置+DB | `sftpgo_{date}_{time}.tar.gz`, `sftpgo_db_{date}_{time}.sql.gz` | SFTPGo | -| Gitea | 資料 | `gitea_{date}_{time}.tar.gz` | Gitea | -| Output | 檔案 | `momentry_output_v2_{date}_{time}.tar.gz` | probe.json 等 | - -### 7.2 備份保留策略 - -| 類型 | 保留期限 | 位置 | -|------|----------|------| -| 每日備份 | 7 天 | `backup/daily/` | -| 每週備份 | 4 週 | `backup/weekly/` | -| 每月備份 | 12 個月 | `backup/monthly/` | -| 歸檔 | 1 年+ | `backup/archive/` | - ---- - -## 8. 相關文件 - -| 文件 | 說明 | -|------|------| -| [SERVICES.md](./SERVICES.md) | 服務說明 | -| [MOMENTRY_CORE_MONITORING.md](./MOMENTRY_CORE_MONITORING.md) | 監控規範 | -| `/Users/accusys/momentry/scripts/backup_all.sh` | 備份腳本 | -| `/Users/accusys/momentry/scripts/restore_all.sh` | 還原腳本 | -| `/Users/accusys/momentry_core_0.1/monitor/storage/backup_monitor.sh` | 備份監控 | - ---- - -## 附錄 A:v2 備份完整性檢查清單 - -```bash -#!/bin/bash -# check_v2_integrity.sh - -BACKUP_DIR="/Users/accusys/momentry/backup/daily" - -echo "=== v2 備份完整性檢查 ===" - -# 檢查 PostgreSQL -echo "1. PostgreSQL..." -pg_backup=$(ls -t "$BACKUP_DIR/postgresql"/postgresql_db_momentry_v2_*.sql.gz 2>/dev/null | head -1) -if [ -n "$pg_backup" ]; then - echo " 備份: $(basename $pg_backup)" - verify_v2_backup "$pg_backup" -else - echo " ❌ 未找到 v2 備份" -fi - -# 檢查 Output -echo "2. Output 目錄..." -output_backup=$(ls -t "$BACKUP_DIR/momentry"/momentry_output_v2_*.tar.gz 2>/dev/null | head -1) -if [ -n "$output_backup" ]; then - echo " 備份: $(basename $output_backup)" - echo " ✅ Output 備份已存在" -else - echo " ❌ 未找到 Output 備份" -fi - -# 檢查 MongoDB -echo "3. MongoDB..." -mongo_backup=$(ls -t "$BACKUP_DIR/mongodb"/mongodb_data_v2_*.tar.gz 2>/dev/null | head -1) -if [ -n "$mongo_backup" ]; then - size=$(stat -f%z "$mongo_backup" 2>/dev/null || stat -c%s "$mongo_backup" 2>/dev/null) - echo " 備份: $(basename $mongo_backup)" - echo " 大小: $size bytes" - if [ "$size" -gt 1000 ]; then - echo " ✅ MongoDB 備份有效" - else - echo " ⚠️ MongoDB 備份可能為空" - fi -else - echo " ❌ 未找到 MongoDB 備份" -fi - -echo "=== 檢查完成 ===" -``` diff --git a/docs/BUILD_VERSION_RECORD.md b/docs/BUILD_VERSION_RECORD.md deleted file mode 100644 index aa45da3..0000000 --- a/docs/BUILD_VERSION_RECORD.md +++ /dev/null @@ -1,667 +0,0 @@ -# Momentry Core 版本紀錄 - -## 版本命名規則 - -### Main Version (主版本) -``` -v{major}.{minor} -例: v0.1, v0.2, v1.0 -``` - -### Build Version (建置版本) -``` -v{major}.{minor}.{YYYYMMDD_HHMMSS} -例: v0.1.20260325_143000 -``` - ---- - -## 版本紀錄存放位置 - -``` -/Users/accusys/momentry/versions/ -├── current/ # 目前使用版本 -│ ├── binary # 當前 binary -│ └── version.json # 版本資訊 -│ -├── releases/ # Release 版本存放 -│ ├── v0.1/ -│ │ ├── v0.1.20260325_143000/ -│ │ │ ├── binary -│ │ │ └── version.json -│ │ ├── v0.1.20260324_100000/ -│ │ │ └── ... -│ │ └── release.json # v0.1 版本總覽 -│ │ -│ └── v0.2/ -│ └── ... -│ -└── changelog.json # 全域版本變更記錄 -``` - ---- - -## version.json 格式 - -```json -{ - "version": "v0.1.20260325_143000", - "main_version": "v0.1", - "build_timestamp": "2026-03-25T14:30:00+08:00", - "git_commit": "83ae050", - "git_branch": "main", - "git_message": "fix: save probe.json to OUTPUT_DIR instead of current directory", - "features": [ - "API Key Authentication", - "Job Worker System" - ], - "fixes": [ - "get_processor_results_by_job column mapping" - ], - "deployed_at": "2026-03-25T15:00:00+08:00", - "deployed_by": "opencode", - "status": "production" -} -``` - ---- - -## release.json 格式 (主版本總覽) - -```json -{ - "version": "v0.1", - "status": "production", - "created_at": "2026-03-14T10:00:00+08:00", - "current_build": "v0.1.20260325_143000", - "builds": [ - { - "build": "v0.1.20260325_143000", - "date": "2026-03-25", - "commits": ["83ae050", "171c36a"], - "summary": "fix: save probe.json, add v2 backup versioning" - }, - { - "build": "v0.1.20260324_100000", - "date": "2026-03-24", - "commits": ["89fbfd6", "3edaf01"], - "summary": "feat: add POST /api/v1/probe endpoint" - } - ], - "changelog": [ - "## v0.1.20260325_143000", - "- 修復 processor_results 欄位映射錯誤", - "- 添加 API Key 認證", - "", - "## v0.1.20260324_100000", - "- 新增 Probe API" - ] -} -``` - ---- - -## changelog.json 格式 (全域變更記錄) - -```json -{ - "updated_at": "2026-03-25T14:30:00+08:00", - "versions": { - "v0.1": { - "status": "production", - "current_build": "v0.1.20260325_143000", - "build_count": 12 - }, - "v0.0": { - "status": "deprecated", - "final_build": "v0.0.20260310_090000" - } - }, - "recent_changes": [ - { - "version": "v0.1.20260325_143000", - "date": "2026-03-25", - "changes": [ - {"type": "fix", "description": "get_processor_results_by_job column mapping"}, - {"type": "feat", "description": "API Key Authentication"} - ] - } - ] -} -``` - ---- - -## Release Script - -### /Users/accusys/momentry/scripts/release.sh - -```bash -#!/bin/bash -set -e - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -PROJECT_DIR="/Users/accusys/momentry_core_0.1" -VERSIONS_DIR="/Users/accusys/momentry/versions" -BACKUP_DIR="/Users/accusys/momentry/backup/bin" -CURRENT_BIN="/Users/accusys/momentry/bin/momentry" - -# 顏色輸出 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } - -# 解析命令列參數 -MAIN_VERSION="" -while [[ $# -gt 0 ]]; do - case $1 in - -v|--version) - MAIN_VERSION="$2" - shift 2 - ;; - *) - log_error "Unknown option: $1" - exit 1 - ;; - esac -done - -if [ -z "$MAIN_VERSION" ]; then - log_error "請指定主版本: ./release.sh -v v0.1" - exit 1 -fi - -log_info "開始 Release ${MAIN_VERSION}..." - -# 1. 取得 Git 資訊 -GIT_COMMIT=$(git -C "$PROJECT_DIR" rev-parse --short HEAD) -GIT_BRANCH=$(git -C "$PROJECT_DIR" rev-parse --abbrev-ref HEAD) -GIT_MESSAGE=$(git -C "$PROJECT_DIR" log -1 --pretty=%s) -BUILD_TIMESTAMP=$(date +%Y%m%d_%H%M%S) -BUILD_VERSION="${MAIN_VERSION}.${BUILD_TIMESTAMP}" - -log_info "Build Version: ${BUILD_VERSION}" -log_info "Git Commit: ${GIT_COMMIT}" - -# 2. 創建版本目錄 -BUILD_DIR="${VERSIONS_DIR}/releases/${MAIN_VERSION}/${BUILD_VERSION}" -mkdir -p "$BUILD_DIR" -mkdir -p "${VERSIONS_DIR}/current" -mkdir -p "$BACKUP_DIR" - -# 3. 停止 Production Service -log_info "停止 Production Service..." -sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist 2>/dev/null || true - -# 4. 備份當前 Binary -if [ -f "$CURRENT_BIN" ]; then - OLD_VERSION=$(cat "${VERSIONS_DIR}/current/version.json" 2>/dev/null | jq -r '.version // "unknown"') - log_info "備份當前版本: $OLD_VERSION" - cp "$CURRENT_BIN" "${BACKUP_DIR}/momentry_${OLD_VERSION}_$(date +%Y%m%d_%H%M%S)" -fi - -# 5. 編譯 Release 版本 -log_info "編譯 Release 版本..." -cd "$PROJECT_DIR" -cargo build --release --bin momentry - -# 6. 複製到版本目錄 -log_info "複製到版本目錄..." -cp target/release/momentry "${BUILD_DIR}/binary" -cp target/release/momentry "$CURRENT_BIN" - -# 7. 生成 version.json -cat > "${BUILD_DIR}/version.json" << EOF -{ - "version": "${BUILD_VERSION}", - "main_version": "${MAIN_VERSION}", - "build_timestamp": "$(date -Iseconds)", - "git_commit": "${GIT_COMMIT}", - "git_branch": "${GIT_BRANCH}", - "git_message": "${GIT_MESSAGE}", - "features": [], - "fixes": [], - "deployed_at": null, - "deployed_by": null, - "status": "built" -} -EOF - -# 8. 更新 current -cp "${BUILD_DIR}/version.json" "${VERSIONS_DIR}/current/version.json" - -# 9. 更新 changelog.json -UPDATE_CHANGELOG=" -import json -from datetime import datetime - -changelog_path = '${VERSIONS_DIR}/changelog.json' -build_info = { - 'version': '${BUILD_VERSION}', - 'date': datetime.now().strftime('%Y-%m-%d'), - 'commit': '${GIT_COMMIT}', - 'message': '${GIT_MESSAGE}' -} - -try: - with open(changelog_path, 'r') as f: - changelog = json.load(f) -except FileNotFoundError: - changelog = {'updated_at': '', 'versions': {}, 'recent_changes': []} - -changelog['updated_at'] = datetime.now().isoformat() -if '${MAIN_VERSION}' not in changelog['versions']: - changelog['versions']['${MAIN_VERSION}'] = {'status': 'building', 'build_count': 0} - -changelog['versions']['${MAIN_VERSION}']['build_count'] += 1 -changelog['versions']['${MAIN_VERSION}']['current_build'] = '${BUILD_VERSION}' -changelog['recent_changes'].insert(0, build_info) - -with open(changelog_path, 'w') as f: - json.dump(changelog, f, indent=2, ensure_ascii=False) -" -python3 -c "$UPDATE_CHANGELOG" - -# 10. 啟動 Production Service -log_info "啟動 Production Service..." -sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist - -# 11. 驗證 -sleep 3 -if curl -s http://localhost:3002/health > /dev/null; then - log_info "✓ Release 成功!" - log_info "版本: ${BUILD_VERSION}" - log_info "目錄: ${BUILD_DIR}" -else - log_error "✗ Release 失敗!請檢查服務狀態。" - exit 1 -fi -``` - ---- - -## 查詢版本指令 - -### 查詢目前版本 -```bash -cat /Users/accusys/momentry/versions/current/version.json -``` - -### 查詢所有 Release -```bash -ls -la /Users/accusys/momentry/versions/releases/ -``` - -### 查詢版本歷史 -```bash -cat /Users/accusys/momentry/versions/changelog.json | python3 -m json.tool -``` - -### 查詢特定主版本 -```bash -ls /Users/accusys/momentry/versions/releases/v0.1/ -``` - ---- - -## 版本狀態 - -| 狀態 | 說明 | -|------|------| -| `building` | 建置中 | -| `built` | 已建置,未部署 | -| `testing` | 測試中 | -| `production` | 正式環境使用中 | -| `deprecated` | 已棄用 | -| `archived` | 已封存 | - ---- - -## 版本流程圖 - -``` -develop (git branch) - │ - ▼ -feature/bugfix commit - │ - ▼ -develop ──────────────────┐ - │ │ - │ (merge to main) │ - ▼ │ -main (git) │ - │ │ - ▼ │ -Build v0.1.20260325_143000 - │ │ - ├──► testing (3003) │ - │ │ - │ (approve) │ - ▼ ▼ -v0.1 ───────────────────┘ - │ - ├──► releases/v0.1/v0.1.20260325_143000/ - │ - ├──► current/ (production) - │ - ▼ -changelog.json (update) -``` - ---- - -## Release Note (版本發布說明) - -### Release Note 存放位置 - -``` -/Users/accusys/momentry/versions/releases/{主版本}/{建置版本}/ -├── binary -├── version.json -└── RELEASE_NOTE.md # 發布說明 (Markdown) -``` - -### Release Note 範本 - -```markdown -# Momentry Core v0.1.20260325_143000 Release Note - -## 版本資訊 -- **Build Version**: v0.1.20260325_143000 -- **Main Version**: v0.1 -- **Build Date**: 2026-03-25 14:30:00 -- **Git Commit**: 83ae050 - -## 新功能 (Features) - -### API Key 認證系統 -- 添加 API Key 認證中介層 -- 所有 `/api/v1/*` 端點需要 `X-API-Key` header -- 支援 API Key 使用追蹤和審計日誌 - -### Job Worker 系統 -- 新增 Job Worker 二進位檔 -- 支援最多 2 個並發處理器 -- 新增 `/api/v1/jobs/:uuid` 端點查詢任務詳情 - -## 錯誤修復 (Bug Fixes) - -| Issue | 描述 | -|-------|------| -| #001 | 修復 `get_processor_results_by_job` 欄位映射錯誤 | -| #002 | 修復 API Key 驗證時區處理問題 | - -## API 變更 (API Changes) - -### 新增端點 -| Method | Endpoint | 說明 | -|--------|----------|------| -| GET | `/api/v1/jobs` | 取得所有任務列表 | -| GET | `/api/v1/jobs/:uuid` | 取得特定任務詳情 | - -### 認證變更 -| 端點 | 舊版 | 新版 | -|------|------|------| -| `/api/v1/*` | 公開 | 需要 API Key | - -## 升級指南 - -### 從舊版升級 -1. 備份當前版本 -2. 停止服務 -3. 替換 binary -4. 重啟服務 -5. 更新 API Key 配置 - -### API Key 配置 -```bash -# 請求範例 -curl -H "X-API-Key: your_api_key" \ - "http://localhost:3002/api/v1/videos" -``` - -## 已知問題 (Known Issues) - -- 暫無 - -## 相關文檔 - -- [API 文檔](../docs/API_INDEX.md) -- [版本管理規範](../docs/VERSION_MANAGEMENT.md) - ---- - -## Release Note 自動生成 Script - -### /Users/accusys/momentry/scripts/generate_release_note.sh - -```bash -#!/bin/bash -set -e - -BUILD_VERSION=$1 -MAIN_VERSION=$2 -BUILD_DIR="/Users/accusys/momentry/versions/releases/${MAIN_VERSION}/${BUILD_VERSION}" - -# 取得 Git 資訊 -GIT_COMMITS=$(git log --oneline -10) -GIT_CHANGES=$(git diff --stat HEAD~5..HEAD) - -cat > "${BUILD_DIR}/RELEASE_NOTE.md" << EOF -# Momentry Core ${BUILD_VERSION} Release Note - -## 版本資訊 -- **Build Version**: ${BUILD_VERSION} -- **Main Version**: ${MAIN_VERSION} -- **Build Date**: $(date '+%Y-%m-%d %H:%M:%S') -- **Git Commit**: $(git rev-parse --short HEAD) - -## 變更內容 - -### Commit 記錄 -\`\`\` -${GIT_COMMITS} -\`\`\` - -### 變更統計 -\`\`\` -${GIT_CHANGES} -\`\`\` - -## 新功能 - -## 錯誤修復 - -## API 變更 - -## 升級指南 - -## 已知問題 - -EOF - -echo "Release Note 生成完成: ${BUILD_DIR}/RELEASE_NOTE.md" -``` - ---- - -## Release Note 查詢 - -### 查詢所有 Release Note -```bash -find /Users/accusys/momentry/versions/releases -name "RELEASE_NOTE.md" -``` - -### 查看特定版本 Release Note -```bash -cat /Users/accusys/momentry/versions/releases/v0.1/v0.1.20260325_143000/RELEASE_NOTE.md -``` - -### 查詢最新版本 Release Note -```bash -cat /Users/accusys/momentry/versions/current/RELEASE_NOTE.md -``` - ---- - -## Release Note 範例 - -### 完整 Release Note 範例 - -\`\`\`markdown -# Momentry Core v0.1.20260325_143000 Release Note - -## 版本資訊 -| 項目 | 內容 | -|------|------| -| Build Version | v0.1.20260325_143000 | -| Main Version | v0.1 | -| Build Date | 2026-03-25 14:30:00 | -| Git Commit | 83ae050 | -| Status | ✅ Production | - -## 新功能 (Features) - -### 1. API Key 認證系統 -添加完整的 API Key 認證系統,保護所有 API 端點。 - -**功能:** -- SHA256 key hash 驗證 -- 使用統計追蹤 -- 審計日誌記錄 -- 異常檢測 - -**API 使用方式:** -\`\`\`bash -curl -H "X-API-Key: your_key" \\ - "http://localhost:3002/api/v1/videos" -\`\`\` - -### 2. Job Worker 系統 -新增獨立的 Job Worker 處理影片處理任務。 - -**特性:** -- 最多 2 個並發處理器 -- Polling-based 任務獲取 -- 自動進度追蹤 - -## 錯誤修復 (Bug Fixes) - -| Issue | 描述 | 嚴重性 | -|-------|------|--------| -| #001 | 修復 `get_processor_results_by_job` TIMESTAMP 欄位映射 | 🔴 高 | -| #002 | 修復 3002 port 衝突問題 | 🟡 中 | - -## API 變更 - -### 新增端點 -| Method | Endpoint | 說明 | -|--------|----------|------| -| GET | `/api/v1/jobs` | 取得任務列表 | -| GET | `/api/v1/jobs/:uuid` | 取得任務詳情 | - -### 端點認證狀態 -| 端點 | 認證需求 | -|------|----------| -| `/health` | ❌ 不需要 | -| `/api/v1/*` | ✅ 需要 `X-API-Key` | - -## 升級指南 - -### 前置需求 -- PostgreSQL 資料庫 -- Redis 伺服器 -- MongoDB 快取 - -### 升級步驟 -1. **備份當前版本** - \`\`\`bash - cp /Users/accusys/momentry/bin/momentry \\ - /Users/accusys/momentry/backup/bin/momentry_$(date +%Y%m%d) - \`\`\` - -2. **停止服務** - \`\`\`bash - sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist - \`\`\` - -3. **替換 Binary** - \`\`\`bash - cp v0.1.20260325_143000/binary /Users/accusys/momentry/bin/momentry - \`\`\` - -4. **重啟服務** - \`\`\`bash - sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist - \`\`\` - -5. **驗證** - \`\`\`bash - curl http://localhost:3002/health - \`\`\` - -## 已知問題 (Known Issues) - -- 暫無 - -## 技術細節 - -### 認證流程 -\`\`\` -Client Request - │ - ▼ -[X-API-Key Header] ──► Middleware - │ │ - │ ▼ - │ Hash Key (SHA256) - │ │ - │ ▼ - │ DB Lookup - │ │ - │ ▼ - │ Validate Status - │ │ - ▼ ▼ -Handler ◄────────────────────┘ -\`\`\` - -### 資料庫變更 -\`\`\`sql --- 新增 duration_secs 欄位 -ALTER TABLE processor_results -ADD COLUMN IF NOT EXISTS duration_secs DOUBLE PRECISION; -\`\`\` - -## 回滾指南 - -如需回滾到上一版本: -\`\`\`bash -# 1. 停止服務 -sudo launchctl unload /Library/LaunchDaemons/com.momentry.api.plist - -# 2. 恢復舊版 -cp /Users/accusys/momentry/backup/bin/momentry_v0.1.20260324_100000 \\ - /Users/accusys/momentry/bin/momentry - -# 3. 重啟服務 -sudo launchctl load /Library/LaunchDaemons/com.momentry.api.plist -\`\`\` - -## 聯繫與支援 - -- **Issue Tracker**: https://gitea.momentry.ddns.net/momentry/momentry_core/issues -- **文檔**: https://docs.momentry.ddns.net - ---- - -*Generated: $(date '+%Y-%m-%d %H:%M:%S')* -\`\`\` - -``` diff --git a/docs/CACHE_ARCHITECTURE_PLAN.md b/docs/CACHE_ARCHITECTURE_PLAN.md deleted file mode 100644 index 7d9001e..0000000 --- a/docs/CACHE_ARCHITECTURE_PLAN.md +++ /dev/null @@ -1,1106 +0,0 @@ -# Momentry Core 分層緩存架構開發計劃 - -**版本**: V1.0 -**日期**: 2026-03-24 -**目標**: 實現 Redis + MongoDB 分層緩存架構 - ---- - -## 1. 概述 - -### 1.1 目標 - -在 Momentry Core 中實現分層緩存架構: -- **小型、高頻存取** → Redis -- **中型、查詢導向** → MongoDB - -### 1.2 現有架構 - -| 組件 | 現況 | 用途 | -|------|------|------| -| Redis | ✅ 已實現 | Job 進度、Pub/Sub、健康檢查、API Key(Moka) | -| MongoDB | ⚠️ HTTP 驅動 | 僅用於存儲 chunks | -| 內存緩存 | Moka + RwLock | API Key、視頻記錄 | - -### 1.3 目標架構 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ Layer 1: Redis Cache │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ Job Progress │ │ Health Status │ │ -│ │ (已有) │ │ (新增) │ │ -│ └─────────────────┘ └─────────────────┘ │ -│ ┌─────────────────┐ │ -│ │ Video Meta 熱讀 │ │ -│ │ (新增) │ │ -│ └─────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - │ Cache Miss - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ Layer 2: MongoDB Cache │ -│ Collection: momento.cache │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ Videos List │ │ Search Results │ │ -│ │ (新增) │ │ (新增) │ │ -│ └─────────────────┘ └─────────────────┘ │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ Hybrid Search │ │ N8n Search │ │ -│ │ (新增) │ │ (新增) │ │ -│ └─────────────────┘ └─────────────────┘ │ -└─────────────────────────────────────────────────────────────┘ - │ Cache Miss - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ PostgreSQL / Qdrant │ -└─────────────────────────────────────────────────────────────┘ -``` - ---- - -## 2. 技術棧變更 - -### 2.1 Cargo.toml 變更 - -```toml -# 現有 -mongodb = { version = "2", features = ["tokio-sync"] } - -# 變更為 -mongodb = { version = "2", features = ["tokio-comp", "bson"] } -``` - -**說明**: -- `tokio-comp`: 啟用 async tokio runtime 支持 -- `bson`: BSON 序列化/反序列化支持 - ---- - -## 3. 新增模組結構 - -### 3.1 目錄結構 - -``` -src/core/ -├── cache/ # 新增目錄 -│ ├── mod.rs # 模組入口 -│ ├── mongo_cache.rs # MongoDB 緩存實現 -│ ├── redis_cache.rs # Redis 緩存封裝 -│ ├── keys.rs # Cache Key 工具函數 -│ └── config.rs # 緩存配置 -├── db/ -│ ├── mod.rs # 新增 cache 導出 -│ ├── mongodb_db.rs # 重構為原生驅動 -│ └── ... -└── ... -``` - -### 3.2 文件清單 - -| 操作 | 文件路徑 | 說明 | -|------|----------|------| -| 新增 | `src/core/cache/mod.rs` | Cache 模組入口 | -| 新增 | `src/core/cache/mongo_cache.rs` | MongoDB 緩存實現 | -| 新增 | `src/core/cache/redis_cache.rs` | Redis 緩存封裝 | -| 新增 | `src/core/cache/keys.rs` | Cache Key 工具函數 | -| 新增 | `src/core/cache/config.rs` | 緩存配置 | -| 修改 | `src/core/db/mongodb_db.rs` | 改用原生 `mongodb` crate | -| 修改 | `src/core/db/mod.rs` | 導出新增模組 | -| 修改 | `src/api/server.rs` | 整合緩存到 API handlers | -| 修改 | `src/core/config.rs` | 添加 MongoDB 緩存配置 | -| 修改 | `Cargo.toml` | 更新 mongodb feature | - ---- - -## 4. 配置設計 - -### 4.1 環境變數 - -```bash -# MongoDB Cache 配置 (新增) -MONGODB_URL=mongodb://localhost:27017 -MONGODB_CACHE_ENABLED=true -MONGODB_CACHE_TTL_VIDEOS=300 # 5 分鐘 -MONGODB_CACHE_TTL_SEARCH=300 # 5 分鐘 -MONGODB_CACHE_TTL_HYBRID_SEARCH=600 # 10 分鐘 -MONGODB_CACHE_TTL_VIDEO_META=3600 # 60 分鐘 - -# Redis Cache 配置 (新增) -REDIS_CACHE_TTL_HEALTH=30 # 30 秒 -REDIS_CACHE_TTL_VIDEO_META=3600 # 60 分鐘 -``` - -### 4.2 config.rs 結構 - -```rust -// src/core/config.rs - -pub mod cache { - use super::*; - - pub static MONGODB_URL: Lazy = Lazy::new(|| { - env::var("MONGODB_URL") - .unwrap_or_else(|_| "mongodb://localhost:27017".to_string()) - }); - - pub static MONGODB_CACHE_ENABLED: Lazy = Lazy::new(|| { - env::var("MONGODB_CACHE_ENABLED") - .unwrap_or_else(|_| "true".to_string()) - .parse() - .unwrap_or(true) - }); - - pub static MONGODB_CACHE_TTL_VIDEOS: Lazy = Lazy::new(|| { - env::var("MONGODB_CACHE_TTL_VIDEOS") - .unwrap_or_else(|_| "300".to_string()) - .parse() - .unwrap_or(300) - }); - - pub static MONGODB_CACHE_TTL_SEARCH: Lazy = Lazy::new(|| { - env::var("MONGODB_CACHE_TTL_SEARCH") - .unwrap_or_else(|_| "300".to_string()) - .parse() - .unwrap_or(300) - }); - - pub static MONGODB_CACHE_TTL_HYBRID_SEARCH: Lazy = Lazy::new(|| { - env::var("MONGODB_CACHE_TTL_HYBRID_SEARCH") - .unwrap_or_else(|_| "600".to_string()) - .parse() - .unwrap_or(600) - }); - - pub static MONGODB_CACHE_TTL_VIDEO_META: Lazy = Lazy::new(|| { - env::var("MONGODB_CACHE_TTL_VIDEO_META") - .unwrap_or_else(|_| "3600".to_string()) - .parse() - .unwrap_or(3600) - }); - - pub static REDIS_CACHE_TTL_HEALTH: Lazy = Lazy::new(|| { - env::var("REDIS_CACHE_TTL_HEALTH") - .unwrap_or_else(|_| "30".to_string()) - .parse() - .unwrap_or(30) - }); -} -``` - ---- - -## 5. MongoDB Cache 設計 - -### 5.1 Collection 結構 - -```javascript -// Collection: momento.cache -// Database: momento - -{ - "_id": ObjectId("..."), - "key": "videos:list:page=1:limit=20", - "value": { - "videos": [ - { - "uuid": "xxx", - "file_path": "/path/to/video.mp4", - "file_name": "video.mp4", - "duration": 120.5, - "width": 1920, - "height": 1080 - } - ] - }, - "category": "videos", - "created_at": ISODate("2026-03-24T08:00:00Z"), - "expires_at": ISODate("2026-03-24T08:05:00Z"), - "hit_count": 0, - "last_access": ISODate("2026-03-24T08:00:00Z") -} -``` - -### 5.2 索引設計 - -```javascript -// TTL Index - 自動刪除過期文檔 -db.momento.cache.createIndex( - { "expires_at": 1 }, - { expireAfterSeconds: 0 } -) - -// 唯一索引 - 防止重複 key -db.momento.cache.createIndex( - { "key": 1 }, - { unique: true } -) - -// 分類索引 - 批量失效用 -db.momento.cache.createIndex({ "category": 1 }) -``` - -### 5.3 CacheEntry 結構 - -```rust -// src/core/cache/mongo_cache.rs - -use serde::{Deserialize, Serialize}; -use bson::oid::ObjectId; -use chrono::{DateTime, Utc}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CacheEntry { - #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] - pub id: Option, - - pub key: String, - pub value: serde_json::Value, - pub category: String, - - pub created_at: DateTime, - pub expires_at: DateTime, - - #[serde(default)] - pub hit_count: i64, - - #[serde(default)] - pub last_access: DateTime, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CacheConfig { - pub enabled: bool, - pub ttl_videos: u64, - pub ttl_search: u64, - pub ttl_hybrid_search: u64, - pub ttl_video_meta: u64, -} -``` - ---- - -## 6. API 緩存策略 - -### 6.1 緩存矩陣 - -| API | Cache Layer | Key Pattern | TTL | 失效時機 | -|-----|-------------|-------------|-----|----------| -| `GET /api/v1/videos` | MongoDB | `videos:list:page={p}:limit={l}` | 5min | register/delete | -| `GET /api/v1/lookup` | Redis | `momentry:cache:video:{uuid}` | 60min | update/delete | -| `POST /api/v1/search` | MongoDB | `search:{hash}` | 5min | vectorize | -| `POST /api/v1/search/hybrid` | MongoDB | `search:hybrid:{hash}` | 10min | vectorize | -| `POST /api/v1/n8n/search` | MongoDB | `search:n8n:{hash}` | 5min | vectorize | -| `GET /health` | Redis | `momentry:cache:health` | 30s | - | - -### 6.2 Cache Key 命名規範 - -```rust -// src/core/cache/keys.rs - -pub mod keys { - pub const CATEGORY_VIDEOS: &str = "videos"; - pub const CATEGORY_SEARCH: &str = "search"; - pub const CATEGORY_HYBRID_SEARCH: &str = "hybrid_search"; - pub const CATEGORY_N8N_SEARCH: &str = "n8n_search"; - pub const CATEGORY_VIDEO_META: &str = "video_meta"; - pub const CATEGORY_HEALTH: &str = "health"; - - pub fn videos_list(page: usize, limit: usize) -> String { - format!("videos:list:page={}:limit={}", page, limit) - } - - pub fn video_meta(uuid: &str) -> String { - format!("video:{}", uuid) - } - - pub fn search(query_hash: &str) -> String { - format!("search:{}", query_hash) - } - - pub fn hybrid_search(query_hash: &str) -> String { - format!("search:hybrid:{}", query_hash) - } - - pub fn n8n_search(query_hash: &str) -> String { - format!("search:n8n:{}", query_hash) - } - - pub fn health() -> String { - "health:basic".to_string() - } -} -``` - ---- - -## 7. 實現細節 - -### 7.1 MongoCache 實現 - -```rust -// src/core/cache/mongo_cache.rs - -use anyhow::Result; -use bson::{doc, oid::ObjectId}; -use chrono::{Duration, Utc}; -use mongodb::{Client, Collection, Database}; -use serde::{de::DeserializeOwned, Serialize}; -use std::sync::Arc; - -use super::keys; -use super::config::CacheConfig; -use crate::core::config::cache as cache_config; - -#[derive(Clone)] -pub struct MongoCache { - client: Client, - db: Database, - collection: Collection, - config: CacheConfig, -} - -impl MongoCache { - pub async fn init() -> Result { - let uri = cache_config::MONGODB_URL.as_str(); - let client = Client::uri(uri).await?; - let db = client.database("momento"); - let collection = db.collection::("cache"); - - let config = CacheConfig { - enabled: *cache_config::MONGODB_CACHE_ENABLED, - ttl_videos: *cache_config::MONGODB_CACHE_TTL_VIDEOS, - ttl_search: *cache_config::MONGODB_CACHE_TTL_SEARCH, - ttl_hybrid_search: *cache_config::MONGODB_CACHE_TTL_HYBRID_SEARCH, - ttl_video_meta: *cache_config::MONGODB_CACHE_TTL_VIDEO_META, - }; - - // Ensure indexes exist - Self::ensure_indexes(&collection).await?; - - Ok(Self { - client, - db, - collection, - config, - }) - } - - async fn ensure_indexes(collection: &Collection) -> Result<()> { - use mongodb::IndexModel; - - // TTL Index - let ttl_index = IndexModel::builder() - .keys(doc! { "expires_at": 1 }) - .options( - mongodb::options::IndexOptions::builder() - .expire_after(std::time::Duration::from_secs(0)) - .build() - ) - .build(); - - // Unique key index - let key_index = IndexModel::builder() - .keys(doc! { "key": 1 }) - .options( - mongodb::options::IndexOptions::builder() - .unique(true) - .build() - ) - .build(); - - collection.create_indexes([ttl_index, key_index]).await?; - Ok(()) - } - - pub async fn get(&self, key: &str) -> Result> { - if !self.config.enabled { - return Ok(None); - } - - let filter = doc! { "key": key }; - let result = self.collection.find_one(filter).await?; - - if let Some(entry) = result { - // Update hit count and last_access - let update = doc! { - "$inc": { "hit_count": 1 }, - "$set": { "last_access": Utc::now() } - }; - self.collection.update_one(doc! { "_id": entry.id }, update).await?; - - // Deserialize value - let value = serde_json::from_value(entry.value)?; - Ok(Some(value)) - } else { - Ok(None) - } - } - - pub async fn set(&self, key: &str, value: &T, ttl_secs: u64, category: &str) -> Result<()> { - if !self.config.enabled { - return Ok(()); - } - - let now = Utc::now(); - let expires_at = now + Duration::seconds(ttl_secs as i64); - let json_value = serde_json::to_value(value)?; - - let entry = CacheEntry { - id: None, - key: key.to_string(), - value: json_value, - category: category.to_string(), - created_at: now, - expires_at, - hit_count: 0, - last_access: now, - }; - - let filter = doc! { "key": key }; - let update = doc! { - "$set": { - "value": &entry.value, - "category": &entry.category, - "expires_at": entry.expires_at, - "last_access": entry.last_access, - }, - "$setOnInsert": { - "key": &entry.key, - "created_at": entry.created_at, - "hit_count": 0i64, - } - }; - - self.collection.update_one(filter, update).await?; - Ok(()) - } - - pub async fn invalidate_category(&self, category: &str) -> Result { - if !self.config.enabled { - return Ok(0); - } - - let result = self.collection.delete_many(doc! { "category": category }).await?; - Ok(result.deleted_count) - } - - pub async fn invalidate_prefix(&self, prefix: &str) -> Result { - if !self.config.enabled { - return Ok(0); - } - - let filter = doc! { "key": { "$regex": &format!("^{}", prefix) } }; - let result = self.collection.delete_many(filter).await?; - Ok(result.deleted_count) - } - - pub async fn get_or_fetch(&self, key: &str, ttl_secs: u64, category: &str, fetcher: F) -> Result - where - F: FnOnce() -> Fut, - Fut: std::future::Future>, - T: DeserializeOwned + Serialize, - { - // Try cache first - if let Some(cached) = self.get::(key).await? { - tracing::debug!("Cache hit for key: {}", key); - return Ok(cached); - } - - // Cache miss - fetch from source - tracing::debug!("Cache miss for key: {}", key); - let value = fetcher().await?; - - // Store in cache - self.set(key, &value, ttl_secs, category).await?; - - Ok(value) - } -} -``` - -### 7.2 RedisCache 實現 - -```rust -// src/core/cache/redis_cache.rs - -use anyhow::Result; -use redis::AsyncCommands; -use serde::{de::DeserializeOwned, Serialize}; -use std::time::Duration; - -use crate::core::config::cache as cache_config; - -#[derive(Clone)] -pub struct RedisCache { - client: crate::core::db::RedisClient, -} - -impl RedisCache { - pub fn new() -> Result { - let client = crate::core::db::RedisClient::new()?; - Ok(Self { client }) - } - - pub async fn get(&self, key: &str) -> Result> { - let mut conn = self.client.get_conn_internal().await?; - let value: Option = conn.get(key).await?; - - match value { - Some(json) => { - let result = serde_json::from_str(&json)?; - Ok(Some(result)) - } - None => Ok(None), - } - } - - pub async fn set(&self, key: &str, value: &T, ttl_secs: u64) -> Result<()> { - let mut conn = self.client.get_conn_internal().await?; - let json = serde_json::to_string(value)?; - let _: String = conn.set_ex(key, json, ttl_secs).await?; - Ok(()) - } - - pub async fn delete(&self, key: &str) -> Result<()> { - let mut conn = self.client.get_conn_internal().await?; - let _: () = conn.del(key).await?; - Ok(()) - } - - pub async fn invalidate_pattern(&self, pattern: &str) -> Result { - let mut conn = self.client.get_conn_internal().await?; - let keys: Vec = conn.keys(pattern).await?; - let count = keys.len() as u64; - - if !keys.is_empty() { - let _: () = conn.del(keys).await?; - } - - Ok(count) - } - - pub async fn get_or_fetch(&self, key: &str, ttl_secs: u64, fetcher: F) -> Result - where - F: FnOnce() -> Fut, - Fut: std::future::Future>, - T: DeserializeOwned + Serialize, - { - // Try cache first - if let Some(cached) = self.get::(key).await? { - return Ok(cached); - } - - // Cache miss - let value = fetcher().await?; - self.set(key, &value, ttl_secs).await?; - Ok(value) - } - - pub async fn get_health(&self) -> Result> { - let mut conn = self.client.get_conn_internal().await?; - let key = "momentry:cache:health"; - let value: Option = conn.get(key).await?; - Ok(value) - } - - pub async fn set_health(&self, status: &str) -> Result<()> { - let ttl = *cache_config::REDIS_CACHE_TTL_HEALTH; - let mut conn = self.client.get_conn_internal().await?; - let key = "momentry:cache:health"; - let _: String = conn.set_ex(key, status, ttl).await?; - Ok(()) - } -} -``` - ---- - -## 8. API Handler 整合 - -### 8.1 AppState 擴展 - -```rust -// src/api/server.rs - -#[derive(Clone)] -struct AppState { - embedder: Arc, - embedder_model: String, - mongo_cache: Arc, // 新增 - redis_cache: Arc, // 新增 -} -``` - -### 8.2 Videos List Handler - -```rust -// src/api/server.rs - -use crate::core::cache::{MongoCache, RedisCache, keys}; - -async fn list_videos( - State(state): State, - Query(params): Query, -) -> Result, StatusCode> { - let page = params.page.unwrap_or(1); - let limit = params.limit.unwrap_or(20); - let cache_key = keys::videos_list(page, limit); - - // Try cache first - let video_infos = state.mongo_cache - .get_or_fetch::<_, _, VideosResponse>( - &cache_key, - 300, // 5 min TTL - keys::CATEGORY_VIDEOS, - || async { - let db = PostgresDb::init().await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - - let videos = db.list_videos().await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - - let video_infos: Vec = videos - .into_iter() - .map(|v| VideoInfoResponse { - uuid: v.uuid, - file_path: v.file_path, - file_name: v.file_name, - duration: v.duration, - width: v.width, - height: v.height, - }) - .collect(); - - Ok(VideosResponse { videos: video_infos }) - }, - ) - .await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - - Ok(Json(video_infos)) -} -``` - -### 8.3 Lookup Handler - -```rust -// src/api/server.rs - -async fn lookup( - State(state): State, - Query(query): Query, -) -> Result, StatusCode> { - if let Some(path) = query.path { - let uuid = crate::uuid::compute_uuid_from_path(&path); - return Ok(Json(LookupResponse { - uuid, - file_path: None, - file_name: None, - duration: None, - })); - } - - if let Some(uuid) = query.uuid { - let cache_key = keys::video_meta(&uuid); - - // Try Redis cache first, fallback to DB - let video = state.redis_cache - .get_or_fetch::<_, _, Option>( - &cache_key, - 3600, // 60 min TTL - || async { - let db = PostgresDb::init().await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - db.get_video_by_uuid(&uuid).await - .map_err(|e| anyhow::anyhow!(e)) - }, - ) - .await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - - if let Some(v) = video { - return Ok(Json(LookupResponse { - uuid: v.uuid, - file_path: Some(v.file_path), - file_name: Some(v.file_name), - duration: Some(v.duration), - })); - } - } - - Err(StatusCode::NOT_FOUND) -} -``` - -### 8.4 Search Handler - -```rust -// src/api/server.rs - -use sha2::{Sha256, Digest}; - -async fn search( - State(state): State, - Json(req): Json, -) -> Result, StatusCode> { - let limit = req.limit.unwrap_or(10); - - // Generate cache key from query hash - let query_for_hash = serde_json::json!({ - "query": req.query, - "limit": limit, - "uuid": req.uuid, - }); - let query_hash = format!("{:x}", Sha256::digest(&serde_json::to_string(&query_for_hash).unwrap())); - let cache_key = keys::search(&query_hash); - - let response = state.mongo_cache - .get_or_fetch::<_, _, SearchResponse>( - &cache_key, - 300, // 5 min TTL - keys::CATEGORY_SEARCH, - || async { - // Original search logic here - let query_vector = state.embedder.embed_query(&req.query).await - .map_err(|e| anyhow::anyhow!("Embedding failed: {}", e))?; - - let qdrant = QdrantDb::init().await - .map_err(|e| anyhow::anyhow!("Qdrant init failed: {}", e))?; - let pg = PostgresDb::init().await - .map_err(|e| anyhow::anyhow!("PG init failed: {}", e))?; - - let search_results = if let Some(ref uuid) = req.uuid { - let query_f64: Vec = query_vector.iter().map(|&x| x as f64).collect(); - qdrant.search_in_uuid(&query_f64, uuid, limit).await? - } else { - let query_f64: Vec = query_vector.iter().map(|&x| x as f64).collect(); - qdrant.search(&query_f64, limit).await? - }; - - let mut results = Vec::new(); - for r in search_results { - if let Some(chunk) = pg.get_chunk_by_chunk_id(&r.chunk_id).await.ok().flatten() { - let text = chunk.content.get("text") - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - results.push(SearchResult { - uuid: chunk.uuid, - chunk_id: chunk.chunk_id, - chunk_type: chunk.chunk_type.as_str().to_string(), - start_time: chunk.start_time, - end_time: chunk.end_time, - text, - score: r.score, - }); - } - } - - Ok(SearchResponse { results, query: req.query }) - }, - ) - .await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - - Ok(Json(response)) -} -``` - -### 8.5 Health Handler - -```rust -// src/api/server.rs - -async fn health(State(state): State) -> Json { - // Try Redis cache first - if let Some(status) = state.redis_cache.get_health().await.ok().flatten() { - return Json(HealthResponse { - status, - version: env!("CARGO_PKG_VERSION").to_string(), - uptime_ms: get_uptime_ms(), - }); - } - - // Cache miss - compute and cache - let status = "ok".to_string(); - state.redis_cache.set_health(&status).await.ok(); - - Json(HealthResponse { - status, - version: env!("CARGO_PKG_VERSION").to_string(), - uptime_ms: get_uptime_ms(), - }) -} -``` - -### 8.6 Register Handler (緩存失效) - -```rust -// src/api/server.rs - -async fn register( - State(state): State, - Json(req): Json, -) -> Result, StatusCode> { - // ... existing registration logic ... - - let video_id = db.register_video(&record).await - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - - // Invalidate videos list cache - state.mongo_cache.invalidate_prefix("videos:list:").await.ok(); - - Ok(Json(RegisterResponse { - uuid, - video_id, - file_name, - duration, - width, - height, - })) -} -``` - ---- - -## 9. 失效策略 - -### 9.1 寫操作觸發失效 - -| 操作 | 失效範圍 | -|------|----------| -| `POST /api/v1/register` | `videos:*` | -| 刪除視頻 | `video:{uuid}`, `videos:*` | -| 更新視頻 | `video:{uuid}` | -| 向量更新 | `search:*`, `search:hybrid:*`, `search:n8n:*` | - -### 9.2 失效實現 - -```rust -// Invalidation helper methods - -impl MongoCache { - pub async fn invalidate_videos_list(&self) -> Result { - self.invalidate_category(keys::CATEGORY_VIDEOS).await - } - - pub async fn invalidate_video(&self, uuid: &str) -> Result { - let key = keys::video_meta(uuid); - let count = self.invalidate_prefix(&key).await?; - Ok(count + self.invalidate_videos_list().await?) - } - - pub async fn invalidate_all_search(&self) -> Result { - let count = self.invalidate_category(keys::CATEGORY_SEARCH).await?; - let count2 = self.invalidate_category(keys::CATEGORY_HYBRID_SEARCH).await?; - let count3 = self.invalidate_category(keys::CATEGORY_N8N_SEARCH).await?; - Ok(count + count2 + count3) - } -} -``` - ---- - -## 10. 實現步驟 - -### Phase 1: 基礎設施 - -| 步驟 | 任務 | 檔案 | -|------|------|------| -| 1.1 | 更新 Cargo.toml mongodb feature | `Cargo.toml` | -| 1.2 | 添加 MongoDB 配置到 config.rs | `src/core/config.rs` | -| 1.3 | 創建 cache 模組目錄 | `src/core/cache/` | -| 1.4 | 實現 CacheEntry 和 keys 工具 | `src/core/cache/keys.rs` | -| 1.5 | 實現 CacheConfig | `src/core/cache/config.rs` | -| 1.6 | 重構 MongoDb 使用原生驅動 | `src/core/db/mongodb_db.rs` | -| 1.7 | 實現 MongoCache | `src/core/cache/mongo_cache.rs` | -| 1.8 | 實現 RedisCache | `src/core/cache/redis_cache.rs` | -| 1.9 | 更新 db/mod.rs 導出 | `src/core/db/mod.rs` | - -### Phase 2: API 整合 - -| 步驟 | 任務 | 檔案 | -|------|------|------| -| 2.1 | 擴展 AppState | `src/api/server.rs` | -| 2.2 | 整合 list_videos 緩存 | `src/api/server.rs` | -| 2.3 | 整合 lookup 緩存 | `src/api/server.rs` | -| 2.4 | 整合 search 緩存 | `src/api/server.rs` | -| 2.5 | 整合 hybrid_search 緩存 | `src/api/server.rs` | -| 2.6 | 整合 n8n_search 緩存 | `src/api/server.rs` | -| 2.7 | 整合 health 緩存 | `src/api/server.rs` | -| 2.8 | 添加 register 緩存失效 | `src/api/server.rs` | - -### Phase 3: 測試驗證 - -| 步驟 | 任務 | -|------|------| -| 3.1 | cargo check | -| 3.2 | cargo build | -| 3.3 | cargo clippy | -| 3.4 | cargo fmt | -| 3.5 | cargo test | -| 3.6 | 手動 API 測試 | - ---- - -## 11. 測試策略 - -### 11.1 單元測試 - -```rust -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_cache_key_generation() { - assert_eq!( - keys::videos_list(1, 20), - "videos:list:page=1:limit=20" - ); - assert_eq!( - keys::video_meta("abc123"), - "video:abc123" - ); - } - - #[tokio::test] - async fn test_cache_hit_miss() { - let cache = MongoCache::init().await.unwrap(); - - // Set value - cache.set("test_key", &"test_value".to_string(), 60, "test").await.unwrap(); - - // Get value - let value: Option = cache.get("test_key").await.unwrap(); - assert_eq!(value, Some("test_value".to_string())); - - // Invalidate - cache.invalidate_category("test").await.unwrap(); - - // Get again - let value: Option = cache.get("test_key").await.unwrap(); - assert_eq!(value, None); - } -} -``` - -### 11.2 API 測試腳本 - -```bash -# Test cache hit -curl -s http://localhost:8080/api/v1/videos | jq .videos | wc -l -# Should return cached count - -# Force cache miss (wait for TTL or invalidate) -curl -s -X POST http://localhost:8080/api/v1/register \ - -H "Content-Type: application/json" \ - -d '{"path": "/path/to/new/video.mp4"}' - -# Verify cache was invalidated -curl -s http://localhost:8080/api/v1/videos | jq .videos | wc -l -# Should trigger fresh query -``` - ---- - -## 12. 監控指標 - -### 12.1 日誌 - -```rust -// 在 cache 命中/未命中時記錄 -tracing::debug!("Cache hit for key: {}", key); -tracing::debug!("Cache miss for key: {}", key); - -// 在失效時記錄 -tracing::info!("Invalidated {} entries in category: {}", count, category); -``` - -### 12.2 可選指標 - -| 指標 | 描述 | -|------|------| -| `cache_hit_total` | Cache 命中總數 | -| `cache_miss_total` | Cache 未命中總數 | -| `cache_invalidations_total` | 緩存失效總數 | -| `cache_operation_duration_seconds` | 緩存操作延遲 | - ---- - -## 13. 風險與緩解 - -| 風險 | 影響 | 緩解措施 | -|------|------|----------| -| MongoDB 連接失敗 | 降級到無緩存 | 緩存操作添加 `.ok()` 錯誤處理 | -| 緩存數據過期不一致 | 用戶看到舊數據 | 合理的 TTL 值 + 寫時失效 | -| 緩存 key 衝突 | 返回錯誤數據 | 使用 SHA256 hash 確保唯一性 | -| 緩存空間膨脹 | 記憶體/磁碟佔用過大 | TTL 自動過期 + 最大條目限制 | - ---- - -## 14. 預期效益 - -| 指標 | 改善前 | 預期改善後 | -|------|--------|------------| -| `GET /api/v1/videos` 延遲 | ~200ms | ~20ms (Cache Hit) | -| `GET /api/v1/lookup` 延遲 | ~50ms | ~5ms (Cache Hit) | -| `POST /api/v1/search` 延遲 | ~500ms | ~50ms (Cache Hit) | -| 資料庫負載 | 100% | ~30% | -| API 吞吐量 | 100 RPS | ~300 RPS | - ---- - -## 15. 附錄 - -### A. MongoDB 初始化腳本 - -```javascript -// 初始化 momento.cache collection 和索引 -use momento; - -db.cache.drop(); - -db.cache.insertOne({ - key: "init", - value: { initialized: true }, - category: "system", - created_at: new Date(), - expires_at: new Date(Date.now() + 86400000), - hit_count: 0, - last_access: new Date() -}); - -db.cache.createIndex( - { "expires_at": 1 }, - { expireAfterSeconds: 0 } -); - -db.cache.createIndex( - { "key": 1 }, - { unique: true } -); - -db.cache.createIndex({ "category": 1 }); - -db.cache.deleteOne({ key: "init" }); - -print("Cache collection initialized successfully"); -``` - -### B. 環境變數參考 - -```bash -# .env 或 shell 環境 -MONGODB_URL=mongodb://localhost:27017 -MONGODB_CACHE_ENABLED=true -MONGODB_CACHE_TTL_VIDEOS=300 -MONGODB_CACHE_TTL_SEARCH=300 -MONGODB_CACHE_TTL_HYBRID_SEARCH=600 -MONGODB_CACHE_TTL_VIDEO_META=3600 - -REDIS_CACHE_TTL_HEALTH=30 -REDIS_CACHE_TTL_VIDEO_META=3600 -``` diff --git a/docs/CHUNK_DATA_STRUCTURE.md b/docs/CHUNK_DATA_STRUCTURE.md deleted file mode 100644 index 3c473a9..0000000 --- a/docs/CHUNK_DATA_STRUCTURE.md +++ /dev/null @@ -1,379 +0,0 @@ -# Momentry Chunk 資料結構說明 - -> **對象**: marcom 團隊 -> **版本**: V1.0 | **日期**: 2026-03-25 - ---- - -## 1. 什麼是 Chunk? - -Chunk(片段)是影片處理後的最小單位。當影片上傳後,系統會自動: - -1. **分析** - 偵測場景、人臉、姿態 -2. **轉換** - 語音轉文字(ASR) -3. **分段** - 將內容切割成可搜尋的片段 -4. **向量化** - 產生可搜尋的特徵向量 - -每個 Chunk 就是一個**可獨立搜尋的內容單位**。 - ---- - -## 2. Chunk 資料結構 - -### 2.1 主要欄位 - -| 欄位名 | 類型 | 說明 | 範例 | -|--------|------|------|------| -| `uuid` | 字串 (32) | 影片唯一識別碼 | `952f5854b9febad1` | -| `chunk_id` | 字串 (64) | Chunk 唯一識別碼 | `asr_00001` | -| `chunk_index` | 整數 | Chunk 順序號碼 | `1` | -| `chunk_type` | 字串 (32) | Chunk 類型 | `sentence` | -| `start_time` | 浮點數 | 開始時間(秒) | `12.5` | -| `end_time` | 浮點數 | 結束時間(秒) | `18.3` | -| `content` | JSONB | 詳細內容 | 見下方 | -| `vector_id` | 字串 (64) | 向量 ID | `vec_12345` | -| `text_content` | 文字 | 純文字內容 | `這是一段話` | -| `fps` | 浮點數 | 影片幀率 | `24.0` | -| `start_frame` | 整數 | 開始幀數 | `300` | -| `end_frame` | 整數 | 結束幀數 | `439` | -| `frame_count` | 整數 | 總幀數 | `139` | - -### 2.2 Chunk 類型說明 - -| 類型 | ID | 說明 | 來源處理器 | -|------|-----|------|-----------| -| `sentence` | `sentence` | 語音轉文字片段 | ASR 處理 | -| `time` | `time_based` | 固定時間分段 | 系統自動切割 | -| `cut` | `cut` | 場景變化片段 | CUT 處理 | -| `trace` | `trace` | 軌跡追蹤片段 | YOLO 追蹤處理 | -| `story` | `story` | 故事線片段(父子區塊) | Story 分析處理 | - -**父子區塊關係**: -- `story` 是**父區塊**,可包含多個 `sentence`、`cut`、`trace` 子區塊 -- 透過 `parent_chunk_id` 和 `child_chunk_ids` 建立階層關係 - ---- - -## 3. Content JSON 結構 - -每個 Chunk 的 `content` 欄位包含詳細的處理結果: - -### 3.1 ASR Chunk(語音轉文字) - -```json -{ - "text": "今天天氣非常好,我們去郊外踏青吧", - "words": [ - { - "word": "今天", - "start": 12.5, - "end": 12.8, - "confidence": 0.95 - }, - { - "word": "天氣", - "start": 12.8, - "end": 13.1, - "confidence": 0.92 - } - ], - "language": "zh-TW", - "speaker": null -} -``` - -### 3.2 Cut Chunk(場景偵測) - -```json -{ - "scenes": [ - { - "scene_id": "cut_001", - "start_time": 12.5, - "end_time": 45.2, - "transition": "cut", - "confidence": 0.98 - } - ] -} -``` - -### 3.3 Trace Chunk(軌跡追蹤) - -```json -{ - "track_id": "track_001", - "object_class": "person", - "frames": [ - { - "frame": 300, - "bbox": [120, 80, 200, 300], - "confidence": 0.95 - }, - { - "frame": 301, - "bbox": [122, 82, 202, 302], - "confidence": 0.94 - } - ], - "total_frames": 180 -} -``` - -### 3.4 Story Chunk(故事線) - -```json -{ - "story_id": "story_001", - "title": "開場介紹", - "summary": "主持人介紹節目主題", - "child_chunk_ids": ["sentence_00001", "sentence_00002", "cut_00001"], - "tags": ["intro", "host"] -} -``` - -### 3.5 Metadata(其他偵測資訊) - -人臉(Face)、文字辨識(OCR)、姿態(Pose)等偵測結果會附加在 `metadata` 欄位: - -```json -{ - "metadata": { - "faces": [ - { - "bbox": [120, 80, 200, 180], - "confidence": 0.87, - "emotion": "neutral" - } - ], - "ocr": { - "text": "MOMENTRY", - "confidence": 0.96 - }, - "pose": { - "keypoints": [ - {"name": "nose", "x": 192, "y": 85, "confidence": 0.95} - ] - } - } -} -``` - ---- - -## 4. 時間格式說明 - -### 4.1 秒數格式(常用) - -``` -格式: 秒.幀數 -範例: 1234.60 = 第 1234 秒 + 第 60 幀 -``` - -### 4.2 時間軸格式 - -``` -格式: HH:MM:SS.FF -範例: 00:20:34.12 = 20分34秒12幀 -``` - -### 4.3 幀數計算 - -``` -幀數 = 秒數 × fps -例如: 12.5秒 × 24fps = 300幀 -``` - ---- - -## 5. 實際資料範例 - -假設有一個影片,包含以下處理結果: - -### 5.1 語音片段 - -```json -{ - "uuid": "952f5854b9febad1", - "chunk_id": "asr_00001", - "chunk_type": "sentence", - "start_time": 12.5, - "end_time": 18.3, - "content": { - "text": "今天天氣非常好,我們去郊外踏青吧", - "language": "zh-TW" - }, - "text_content": "今天天氣非常好,我們去郊外踏青吧", - "start_frame": 300, - "end_frame": 439, - "fps": 24.0 -} -``` - -### 5.2 場景片段 - -```json -{ - "uuid": "952f5854b9febad1", - "chunk_id": "cut_00001", - "chunk_type": "cut", - "start_time": 45.0, - "end_time": 120.5, - "content": { - "scenes": [{ - "scene_id": "cut_001", - "transition": "cut", - "confidence": 0.98 - }] - }, - "start_frame": 1080, - "end_frame": 2892, - "fps": 24.0 -} -``` - ---- - -## 6. 如何使用 Chunk - -### 6.1 API 取得 Chunk - -使用搜尋 API 取得 Chunk: - -```bash -curl -X POST "https://api.momentry.ddns.net/api/v1/search" \ - -H "X-API-Key: YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "query": "關鍵字", - "limit": 10 - }' -``` - -**指定影片搜尋**: -```bash -curl -X POST "https://api.momentry.ddns.net/api/v1/search" \ - -H "X-API-Key: YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "query": "關鍵字", - "uuid": "39567a0eb16f39fd", - "limit": 5 - }' -``` - -### 6.2 搜尋相關片段 - -當使用者搜尋「天氣」時,系統會: - -1. 將「天氣」轉換為向量 -2. 在向量資料庫中搜尋相似向量 -3. 找到相關的 Chunk -4. 返回時間軸和內容 - -### 6.3 播放指定片段 - -取得 Chunk 後可播放: - -``` -開始時間: 12.5 秒 -結束時間: 18.3 秒 -影片 UUID: 39567a0eb16f39fd -``` - -**播放器連結格式**: -``` -/player?uuid={uuid}&start={start_time}&end={end_time} -``` - -### 6.4 組合多個 Chunk - -多個相關 Chunk 可以組合成一個章節或故事線。 - -### 6.5 Story Chunk(父子關係) - -Story Chunk 可包含多個子 Chunk: - -```json -{ - "chunk_id": "story_001", - "chunk_type": "story", - "content": { - "story_id": "story_001", - "title": "開場介紹", - "child_chunk_ids": ["sentence_00001", "sentence_00002", "cut_00001"] - } -} -``` - ---- - -## 7. API 回應格式 - -### /search 回應 - -```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": "關鍵字" -} -``` - -### /n8n/search 回應 - -```json -{ - "query": "關鍵字", - "count": 1, - "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" - } - ] -} -``` - -> **注意**: `file_path` 是影片的實際路徑,可用於本地播放。 - ---- - -## 8. 快速參考 - -| 項目 | 說明 | -|------|------| -| UUID | 影片唯一識別碼(16位 hex) | -| Chunk ID | 片段識別碼(如 `sentence_00001`) | -| chunk_type | 片段類型(sentence/time/cut/trace/story) | -| start_time | 開始時間(秒) | -| end_time | 結束時間(秒) | -| text_content | 純文字內容 | -| content | 詳細 JSON 結構 | -| metadata | 人臉、OCR、姿態等偵測結果 | -| parent_chunk_id | 父區塊 ID(用於 story 區塊) | -| child_chunk_ids | 子區塊 ID 列表(story 區塊專用) | | - ---- - -## 附錄:版本歷史 - -| 版本 | 日期 | 內容 | 操作人 | -|------|------|------|--------| -| V1.0 | 2026-03-25 | 初版建立 | OpenCode | -| V1.1 | 2026-03-25 | 新增 API 取得 Chunk 方式、播放連結格式 | OpenCode | diff --git a/docs/CHUNK_DESIGN.md b/docs/CHUNK_DESIGN.md deleted file mode 100644 index 0865204..0000000 --- a/docs/CHUNK_DESIGN.md +++ /dev/null @@ -1,534 +0,0 @@ -# Momentry Core 數據管理設計文檔 (v4) - -| 項目 | 內容 | -|------|------| -| 建立者 | Warren | -| 建立時間 | 2026-03-17 | -| 文件版本 | V1.0 | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.0 | 2026-03-17 | 創建文件 | Warren | OpenCode / MiniMax M2.5 | - ---- - -## 0. 核心概念:雙 UUID 系統 - -為減少資料庫大小,在現有的 videos 表中增加內部 ID 映射: - -### 0.1 設計原則 - -- **external_uuid**: 用戶可見的識別碼(如 UUID) -- **id**: 資料庫自動產生的內部 ID (SERIAL),節省空間 -- **映射關係**: 透過 videos 表的 `id` 欄位關聯 - -### 0.2 videos 表 (檔案映射表) - -現有結構,增加 `id` 作為內部 ID: - -```sql --- 現有 videos 表結構 -CREATE TABLE videos ( - id SERIAL PRIMARY KEY, -- 內部 ID (自動產生) - uuid VARCHAR(32) UNIQUE NOT NULL, -- 外部 UUID (用戶可見) - file_name VARCHAR(255) NOT NULL, - file_path TEXT, - duration DOUBLE PRECISION, - width INTEGER, - height INTEGER, - fps DOUBLE PRECISION, - probe_json JSONB, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE INDEX idx_videos_uuid ON videos(uuid); -``` - -### 0.3 對照的好處 - -| 方式 | 儲存空間 (1000個視頻,每個1000個chunk) | -|------|---------------------------------------| -| 直接用 uuid (32字元) | ~32MB | -| 使用 id (4字元) | ~4MB | - -## 1. 數據流架構 - -``` -┌─────────────────────────────────────────────────────────────────────────────┐ -│ 輸入階段 │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ 視頻文件 │→ │ ffprobe │ │ ASR │ │ YOLO │ │ -│ │ (.mp4) │→ │ (probe) │→ │ (asr) │→ │ (yolo) │ │ -│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ -│ │ -│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ -│ │ ASRX │ │ CUT │ │ OCR │ │ FACE │ │ -│ │ (asrx) │→ │ (cut) │→ │ (ocr) │→ │ (face) │ │ -│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────────────────────────────────┐ -│ Pre-Chunk / Frame 階段 │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ pre_chunks 表 │ │ -│ │ file_id → videos.id (FK) │ │ -│ │ ┌─────────────────────────────────────────────────────────────┐ │ │ -│ │ │ type=sentence │ from asr, asrx │ 句子邊界範圍 │ │ │ -│ │ │ type=cut │ from cut detection │ 場景切換範圍 │ │ │ -│ │ │ type=time │ from time split │ 固定時間範圍 (10s) │ │ │ -│ │ │ type=trace │ from yolo trace │ 物件追蹤範圍 │ │ │ -│ │ └─────────────────────────────────────────────────────────────┘ │ │ -│ └─────────────────────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ frames 表 │ │ -│ │ file_id → videos.id (FK) │ │ -│ │ - yolo 每幀識別結果 │ │ -│ │ - ocr 每幀識別結果 │ │ -│ │ - face 每幀識別結果 (如需要) │ │ -│ │ - 單一圖像識別結果 → 直接入 frame │ │ -│ └─────────────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────────────────────────────────┐ -│ Chunk 階段 │ -│ │ -│ ┌─────────────────────────────────────────────────────────────────────┐ │ -│ │ chunks 表 │ │ -│ │ file_id → videos.id (FK) │ │ -│ │ │ │ -│ │ 組合規則1: pre_chunk → chunk (直接轉換) │ │ -│ │ ┌─────────────────────────────────────────────────────────────┐ │ │ -│ │ │ sentence_pre_chunk → sentence_chunk │ │ │ -│ │ │ cut_pre_chunk → cut_chunk │ │ │ -│ │ │ time_pre_chunk → time_chunk │ │ │ -│ │ │ trace_pre_chunk → trace_chunk │ │ │ -│ │ └─────────────────────────────────────────────────────────────┘ │ │ -│ │ │ │ -│ │ 組合規則2: pre_chunk + frame 內容 → chunk (集合內容) │ │ -│ │ ┌─────────────────────────────────────────────────────────────┐ │ │ -│ │ │ sentence_pre_chunk + 涵蓋範圍內的 frames → 豐富的 sentence_chunk │ │ │ -│ │ │ time_pre_chunk + 涵蓋範圍內的 frames → 豐富的 time_chunk │ │ │ -│ │ └─────────────────────────────────────────────────────────────┘ │ │ -│ └─────────────────────────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────────────────────────────────────┐ -│ Vector 階段 │ -│ ┌──────────────────────┐ ┌──────────────────────┐ │ -│ │ PostgreSQL vectors │ │ Qdrant vectors │ │ -│ │ (chunk_vectors) │ │ (chunk_v3) │ │ -│ └──────────────────────┘ └──────────────────────┘ │ -└─────────────────────────────────────────────────────────────────────────────┘ -``` - -## 2. Pre-Chunk 類型定義 - -### 2.1 Pre-Chunk 來源與類型對照表 - -| 來源類型 | source_type | 產出 Pre-Chunk Type | 說明 | -|---------|-------------|---------------------|------| -| ASR ( Whisper ) | asr | sentence | 句子邊界 | -| ASRX ( with timestamps ) | asrx | sentence | 帶時間戳的句子 | -| CUT (場景檢測) | cut | cut | 場景切換點 | -| TIME (固定時間) | time | time | 每 10 秒 | -| YOLO Trace | yolo_trace | trace | 物件追蹤軌跡 | -| YOLO (單幀) | yolo | **→ frame** | 不入 pre_chunk | -| OCR (單幀) | ocr | **→ frame** | 不入 pre_chunk | -| FACE (單幀) | face | **→ frame** | 不入 pre_chunk | -| PROBE | probe | metadata | 視頻元數據 | - -### 2.2 Pre-Chunk Schema - -```sql -CREATE TABLE pre_chunks ( - id SERIAL PRIMARY KEY, - - -- 檔案識別 (使用 videos 表的內部 ID 以節省空間) - file_id INTEGER NOT NULL REFERENCES videos(id), - - -- 來源識別 - source_type VARCHAR(32) NOT NULL, -- 'asr', 'asrx', 'cut', 'time', 'yolo_trace', 'probe' - source_file TEXT, -- 原始 JSON 文件路徑 - - -- Chunk 類型 - chunk_type VARCHAR(32) NOT NULL, -- 'sentence', 'cut', 'time', 'trace' - - -- 時間範圍 - start_time DOUBLE PRECISION NOT NULL, - end_time DOUBLE PRECISION NOT NULL, - - -- Frame 範圍 (精確到 frame) - start_frame INTEGER NOT NULL, - end_frame INTEGER NOT NULL, - - -- FPS (用於 frame 計算) - fps DOUBLE PRECISION NOT NULL, - - -- 原始 JSON 內容 - raw_json JSONB NOT NULL, - - -- 解析後的文字內容 (如有) - text_content TEXT, - - -- 處理狀態 - processed BOOLEAN DEFAULT FALSE, - chunk_id VARCHAR(64), -- 轉換後的 chunk_id - - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - - UNIQUE(file_id, source_type, start_frame, end_frame) -); - -CREATE INDEX idx_pre_chunks_file_id ON pre_chunks(file_id); -CREATE INDEX idx_pre_chunks_type ON pre_chunks(file_id, chunk_type); -CREATE INDEX idx_pre_chunks_time ON pre_chunks(file_id, start_time, end_time); -CREATE INDEX idx_pre_chunks_frame ON pre_chunks(file_id, start_frame, end_frame); -CREATE INDEX idx_pre_chunks_processed ON pre_chunks(file_id, processed); -``` - -## 3. Frame 管理原則 - -### 3.1 哪些數據進入 Frame - -只儲存**單一圖像識別**的結果: -- YOLO 每幀檢測結果 -- OCR 每幀識別結果 -- FACE 每幀檢測結果 - -### 3.2 Frame Schema - -```sql -CREATE TABLE frames ( - id SERIAL PRIMARY KEY, - - -- 檔案識別 (使用 videos 表的內部 ID 以節省空間) - file_id INTEGER NOT NULL REFERENCES videos(id), - - frame_number INTEGER NOT NULL, - timestamp DOUBLE PRECISION NOT NULL, - fps DOUBLE PRECISION NOT NULL, - - -- YOLO 結果 (JSONB 陣列) - yolo_objects JSONB, - - -- OCR 結果 (JSONB 陣列) - ocr_results JSONB, - - -- Face 結果 (JSONB 陣列) - face_results JSONB, - - -- 原始幀圖像路徑 (可選) - frame_path TEXT, - - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - - UNIQUE(file_id, frame_number) -); - -CREATE INDEX idx_frames_file_id ON frames(file_id); -CREATE INDEX idx_frames_frame ON frames(file_id, frame_number); -CREATE INDEX idx_frames_timestamp ON frames(file_id, timestamp); -``` - -## 4. Chunk 組合規則 - -### 4.1 組合規則 1: 直接轉換 (rule_1) - -將 pre_chunk 直接轉換為 chunk: - -``` -sentence_pre_chunk → sentence_chunk (rule: "rule_1") -cut_pre_chunk → cut_chunk (rule: "rule_1") -time_pre_chunk → time_chunk (rule: "rule_1") -trace_pre_chunk → trace_chunk (rule: "rule_1") -``` - -### 4.2 組合規則 2: 集合內容 (rule_2) - -將 pre_chunk 與其時間區間內的所有 frame 識別結果集合: - -``` -sentence_pre_chunk + frames[在 start_time~end_time 範圍內] → 豐富的 sentence_chunk (rule: "rule_2") -time_pre_chunk + frames[在 start_time~end_time 範圍內] → 豐富的 time_chunk (rule: "rule_2") -``` - -### 4.3 Chunk Schema - -```sql -CREATE TABLE chunks ( - id SERIAL PRIMARY KEY, - - -- 檔案識別 (使用 videos 表的內部 ID 以節省空間) - file_id INTEGER NOT NULL REFERENCES videos(id), - - chunk_id VARCHAR(64) NOT NULL, - chunk_index INTEGER NOT NULL, - chunk_type VARCHAR(32) NOT NULL, -- 'sentence', 'cut', 'time', 'trace' - - -- 組合規則 (payload 中記錄) - -- rule: 'rule_1' = 直接轉換, 'rule_2' = 集合內容 - - -- 時間範圍 - start_time DOUBLE PRECISION NOT NULL, - end_time DOUBLE PRECISION NOT NULL, - - -- Frame 範圍 (精確到 frame) - start_frame INTEGER NOT NULL, - end_frame INTEGER NOT NULL, - - -- FPS - fps DOUBLE PRECISION NOT NULL, - - -- 主要內容 - text_content TEXT, - - -- 完整內容 (JSONB) - 包含 rule 欄位 - content JSONB NOT NULL, - - -- 來源的 pre_chunk IDs - pre_chunk_ids INTEGER[], - - -- 包含的 frame 數量 - frame_count INTEGER DEFAULT 0, - - -- 向量 ID - vector_id VARCHAR(64), - - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - - UNIQUE(file_id, chunk_id) -); - -CREATE INDEX idx_chunks_file_id ON chunks(file_id); -CREATE INDEX idx_chunks_type ON chunks(file_id, chunk_type); -CREATE INDEX idx_chunks_time ON chunks(file_id, start_time, end_time); -CREATE INDEX idx_chunks_frame ON chunks(file_id, start_frame, end_frame); -CREATE INDEX idx_chunks_vector ON chunks(vector_id); -``` - -## 5. 處理流程範例 - -### 5.1 輸入數據 - -假設視頻長度 30 秒,fps=30: - -| 來源 | 產出 | -|------|------| -| ASR | 3 個 sentence_pre_chunk (每句約 10s) | -| CUT | 2 個 cut_pre_chunk (場景 1, 場景 2) | -| TIME | 3 個 time_pre_chunk (0-10s, 10-20s, 20-30s) | -| YOLO | 900 個 frame 記錄 (每幀) | -| OCR | 依實際識別結果入 frame | - -### 5.2 Chunk 產出 - -**使用規則 1 (直接轉換):** -- rule: "rule_1" -- 3 個 sentence_chunk -- 2 個 cut_chunk -- 3 個 time_chunk - -**使用規則 2 (集合內容):** -- rule: "rule_2" -- 3 個 sentence_chunk (各含涵蓋時間範圍內的 yolo/ocr 結果) -- 3 個 time_chunk (各含涵蓋時間範圍內的 yolo/ocr 結果) - -## 8. 數據示例 - -### 8.1 videos 表 (檔案映射) - -```json -{ - "id": 1, - "uuid": "abc123def456", - "file_name": "video_001.mp4", - "file_path": "/path/to/video_001.mp4", - "duration": 300.5, - "width": 1920, - "height": 1080, - "fps": 30.0 -} -``` - -### 8.2 pre_chunks 表 (使用 file_id 關聯 videos) - -```json -{ - "file_id": 1, - "source_type": "asr", - "chunk_type": "sentence", - "start_time": 0.0, - "end_time": 5.5, - "start_frame": 0, - "end_frame": 165, - "fps": 30.0, - "raw_json": {...}, - "text_content": "This is the first sentence" -} -``` - -### 8.3 frames 表 (使用 file_id 關聯 videos) - -```json -{ - "file_id": 1, - "frame_number": 300, - "timestamp": 10.0, - "fps": 30.0, - "yolo_objects": [ - {"class": "person", "confidence": 0.9, "bbox": [100, 50, 200, 150]}, - {"class": "car", "confidence": 0.85, "bbox": [50, 100, 150, 180]} - ], - "ocr_results": [], - "face_results": [] -} -``` - -### 8.4 chunks 表 (使用 file_id 關聯 videos) - -```json -{ - "file_id": 1, - "chunk_id": "sentence_0001", - "chunk_type": "sentence", - "rule": "rule_2", - "start_time": 10.0, - "end_time": 15.5, - "start_frame": 300, - "end_frame": 465, - "fps": 30.0, - "text_content": "The second sentence from the audio", - "content": { - "rule": "rule_2", - "asr_text": "The second sentence from the audio", - "objects": [ - {"class": "person", "first_frame": 300, "last_frame": 450, "appears_in_frames": [300, 310, 320, ...]}, - {"class": "car", "first_frame": 350, "last_frame": 465, "appears_in_frames": [350, 360, ...]} - ], - "ocr": [...], - "faces": [...] - }, - "pre_chunk_ids": [5], - "frame_count": 301 -} -``` - -### 8.5 chunk_vectors 表 (使用 file_id 關聯 videos) - -```json -{ - "file_id": 1, - "chunk_id": "sentence_0001", - "chunk_type": "sentence", - "start_time": 10.0, - "end_time": 15.5, - "embedding": "[0.1, 0.2, ...]", - "metadata": {"text": "The second sentence..."} -} -``` - -### 8.6 Qdrant Payload - -```json -{ - "file_uuid": "abc123def456", - "chunk_id": "sentence_0001", - "chunk_type": "sentence", - "start_time": 10.0, - "end_time": 15.5, - "text": "The second sentence from the audio" -} -``` - -## 7. 向量管理原則 - -### 7.1 Vector Schema - -```sql --- Chunk 向量表 (PostgreSQL) -CREATE TABLE chunk_vectors ( - id SERIAL PRIMARY KEY, - - -- 檔案識別 (使用 videos 表的內部 ID 以節省空間) - file_id INTEGER NOT NULL REFERENCES videos(id), - - chunk_id VARCHAR(64) NOT NULL, - chunk_type VARCHAR(32) NOT NULL, - - -- 向量數據 - embedding TEXT, -- JSON 格式的向量 - embedding_vector VECTOR(768), -- pgvector 類型 (如可用) - - -- 時間範圍 (用於時間查詢) - start_time DOUBLE PRECISION, - end_time DOUBLE PRECISION, - - -- Metadata - metadata JSONB, - - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - - UNIQUE(chunk_id) -); - --- 索引 -CREATE INDEX idx_chunk_vectors_file_id ON chunk_vectors(file_id); -``` - -### 7.2 Qdrant Collection - -- Collection 名稱: `chunks_v3` -- Vector 維度: 768 (nomic-embed-text) -- Payload 包含: `file_uuid`, `chunk_id`, `chunk_type`, `start_time`, `end_time`, `text` - -> **注意**: Qdrant 中仍使用 uuid (字串),因為需要可讀性和跨系統整合。PostgreSQL 內部使用 videos.id (整數) 以節省空間。 - -## 9. 設計原則總結 - -1. **單一圖像識別 → Frame**: yolo, ocr, face 等單幀識別結果直接入 frame 表 -2. **時間序列識別 → Pre-Chunk**: asr, asrx, cut, time, trace 等有時間範圍的結果入 pre_chunk 表 -3. **組合規則 1 (直接)**: pre_chunk → chunk (保持原有邊界) -4. **組合規則 2 (集合)**: pre_chunk + frames → chunk (加入識別內容) -5. **精確到 Frame**: 所有時間範圍都記錄 start_frame, end_frame -6. **雙向量存儲**: 同時支持 PostgreSQL 和 Qdrant -7. **跨視頻搜索**: 透過 videos 表的 uuid 進行搜索,內部使用 id 節省空間 -8. **空間優化**: 內部表使用 videos.id (4 bytes) 而非 uuid (32 bytes) - -## 10. 查詢範例 - -### 10.1 跨視頻搜索所有 chunk - -```sql --- 搜索所有視頻中包含 "hello" 的 chunk -SELECT c.*, v.uuid, v.file_name -FROM chunks c -JOIN videos v ON c.file_id = v.id -WHERE c.text_content ILIKE '%hello%'; -``` - -### 10.2 查詢特定視頻的 chunk - -```sql --- 查詢 uuid 為 'abc123' 的視頻的所有 chunk -SELECT c.* -FROM chunks c -JOIN videos v ON c.file_id = v.id -WHERE v.uuid = 'abc123'; -``` - -### 10.3 按時間範圍搜索 - -```sql --- 搜索所有視頻在 10-20 秒範圍內的 chunk -SELECT c.*, v.uuid -FROM chunks c -JOIN videos v ON c.file_id = v.id -WHERE c.start_time >= 10.0 AND c.end_time <= 20.0; -``` diff --git a/docs/CHUNK_SPEC.md b/docs/CHUNK_SPEC.md deleted file mode 100644 index 3b90667..0000000 --- a/docs/CHUNK_SPEC.md +++ /dev/null @@ -1,1113 +0,0 @@ -# Video Chunk 切分規範 - -| 項目 | 內容 | -|------|------| -| 建立者 | Warren | -| 建立時間 | 2026-03-16 | -| 文件版本 | V1.0 | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.0 | 2026-03-16 | 創建文件 | Warren | OpenCode / MiniMax M2.5 | - ---- - -本文檔定義 Momentry Core 系統中影片 chunks 的切分原則與資料結構。 - ---- - -## 1. Chunk 概述 - -### 1.1 設計原則 - -1. **允許重疊**: 不同類型的 chunk 可以重疊(如語句 chunk 與時間 chunk) -2. **Frame 精確度**: 時間坐標精確到影片 frame -3. **多元分類**: 支援語句、場景、時間三種分割方式 - -### 1.2 Chunk 類型 - -| 類型 | 說明 | 是否可重疊 | -|------|------|------------| -| **Sentence** | 語句分割 | ✅ 可與其他類型重疊 | -| **Cut** | 場景切割 | ✅ 可與其他類型重疊 | -| **TimeBased** | 時間長度切割 | ✅ 可與其他類型重疊 | - ---- - -## 2. 時間坐標系統 - -### 2.1 時間格式 - -所有時間使用 **秒** 為單位,精確到 **微秒** (浮點數): - -```json -{ - "start_time": 10.5, - "end_time": 15.75 -} -``` - -### 2.2 Frame 計算 - -``` -frame_number = floor(time_in_seconds * fps) -time_at_frame = frame_number / fps -``` - -**範例**: -- 影片 FPS: 24/1 (24 fps) -- 時間: 10.5 秒 -- Frame: floor(10.5 * 24) = 252 -- 校驗: 252 / 24 = 10.5 秒 ✅ - -### 2.3 Frame 資訊結構 - -```json -{ - "start_time": 10.5, - "start_frame": 252, - "end_time": 15.75, - "end_frame": 378, - "fps": "24/1", - "fps_value": 24.0 -} -``` - ---- - -## 3. 三種切分方式 - -### 3.1 Sentence (語句分割) - -**原則**: -- 根據 ASR 語音識別結果 -- 每個識別的語句為一個 chunk -- 文字內容來自 ASR 輸出 - -**範例**: - -``` -ASR 輸出: -[ - {"start": 10.0, "end": 15.0, "text": "Hello world"}, - {"start": 15.0, "end": 20.0, "text": "This is a test"}, - {"start": 20.0, "end": 25.5, "text": "Processing video"} -] - -轉換為 Chunks: -┌────────────────────────────────────────┐ -│ chunk_0001: 10.0s - 15.0s "Hello world" │ -├────────────────────────────────────────┤ -│ chunk_0002: 15.0s - 20.0s "This is a test" │ -├────────────────────────────────────────┤ -│ chunk_0003: 20.0s - 25.5s "Processing video" │ -└────────────────────────────────────────┘ -``` - -### 3.2 Cut (場景切割) - -**原則**: -- 根據影片鏡頭變化 (scene change / cut detection) -- 使用 ffmpeg 或 Python (scenedetect) 偵測 -- 每個場景為一個 chunk - -**偵測方法**: - -```bash -# 使用 ffmpeg 偵測場景變化 -ffmpeg -i input.mp4 -filter:v "select='gt(scene,0.3)',showinfo" -f null - -``` - -**範例**: - -``` -場景偵測結果: -[ - {"start": 0.0, "end": 45.2, "scene_id": 1}, - {"start": 45.2, "end": 120.5, "scene_id": 2}, - {"start": 120.5, "end": 180.0, "scene_id": 3} -] - -轉換為 Chunks: -┌────────────────────────────────────────┐ -│ chunk_0001: 0.0s - 45.2s (Scene 1) │ -├────────────────────────────────────────┤ -│ chunk_0002: 45.2s - 120.5s (Scene 2) │ -├────────────────────────────────────────┤ -│ chunk_0003: 120.5s - 180.0s (Scene 3) │ -└────────────────────────────────────────┘ -``` - -### 3.3 TimeBased (時間長度切割) - -**原則**: -- 固定時間長度切割 -- 預設 **10 秒** 為一個 chunk -- 最後一個 chunk 可能不足 10 秒 -- **支援重疊** (可設定 overlap 秒數) - -**參數配置**: - -| 參數 | 預設值 | 說明 | -|------|--------|------| -| duration | 10.0 | 每個 chunk 時長 (秒) | -| overlap | 0.0 | 重疊時長 (秒) | - -**範例** (無重疊): - -``` -影片時長: 35 秒, duration=10 - -Chunks: -┌────────────────────────────────────────┐ -│ chunk_0001: 0.0s - 10.0s │ -├────────────────────────────────────────┤ -│ chunk_0002: 10.0s - 20.0s │ -├────────────────────────────────────────┤ -│ chunk_0003: 20.0s - 30.0s │ -├────────────────────────────────────────┤ -│ chunk_0004: 30.0s - 35.0s (不足10秒) │ -└────────────────────────────────────────┘ -``` - -**範例** (有重疊, overlap=2): - -``` -影片時長: 35 秒, duration=10, overlap=2 - -Chunks: -┌────────────────────────────────────────┐ -│ chunk_0001: 0.0s - 10.0s │ -├────────────────────────────────────────┤ -│ chunk_0002: 8.0s - 18.0s (重疊 2秒) │ -├────────────────────────────────────────┤ -│ chunk_0003: 16.0s - 26.0s (重疊 2秒) │ -├────────────────────────────────────────┤ -│ chunk_0004: 24.0s - 34.0s (重疊 2秒) │ -├────────────────────────────────────────┤ -│ chunk_0005: 32.0s - 35.0s (重疊+不足) │ -└────────────────────────────────────────┘ -``` - ---- - -## 4. Chunk 資料結構 - -### 4.1 基本結構 - -```json -{ - "uuid": "1636719dc31f78ac", - "chunk_id": "sentence_0001", - "chunk_index": 1, - "chunk_type": "sentence", - "start_time": 10.5, - "start_frame": 252, - "end_time": 15.75, - "end_frame": 378, - "fps": "24/1", - "fps_value": 24.0, - "content": { - "text": "Hello world, this is a test" - }, - "metadata": { - "source": "asr", - "confidence": 0.95, - "language": "en" - } -} -``` - -### 4.2 欄位說明 - -| 欄位 | 類型 | 必填 | 說明 | -|------|------|------|------| -| `uuid` | String | ✅ | 影片 UUID (16 字元) | -| `chunk_id` | String | ✅ | Chunk 唯一 ID | -| `chunk_index` | Integer | ✅ | Chunk 索引 (從 0 開始) | -| `chunk_type` | String | ✅ | 類型: sentence/cut/time_based | -| `start_time` | Float | ✅ | 開始時間 (秒) | -| `start_frame` | Integer | ✅ | 開始 frame 編號 | -| `end_time` | Float | ✅ | 結束時間 (秒) | -| `end_frame` | Integer | ✅ | 結束 frame 編號 | -| `fps` | String | ✅ | FPS 表示 (如 "24/1") | -| `fps_value` | Float | ✅ | FPS 數值 (如 24.0) | -| `content` | Object | ✅ | 內容 (見下文) | -| `metadata` | Object | ❌ | 額外資訊 (見下文) | - -### 4.3 Content 結構 - -根據 `chunk_type` 不同,content 結構也不同: - -#### Sentence Content - -```json -{ - "content": { - "text": "Hello world, this is a test message", - "text_normalized": "hello world this is a test message", - "word_count": 7, - "char_count": 34 - } -} -``` - -| 欄位 | 類型 | 說明 | -|------|------|------| -| `text` | String | 原始識別文字 | -| `text_normalized` | String | 正規化文字 (小寫,去除標點) | -| `word_count` | Integer | 字詞數量 | -| `char_count` | Integer | 字元數量 | - -#### Cut Content - -```json -{ - "content": { - "scene_id": 2, - "scene_number": 2, - "transition_type": "cut", - "scene_change_score": 0.95 - } -} -``` - -| 欄位 | 類型 | 說明 | -|------|------|------| -| `scene_id` | Integer | 場景 ID | -| `scene_number` | Integer | 場景編號 | -| `transition_type` | String | 轉場類型: cut/dissolve/fade | -| `scene_change_score` | Float | 場景變化分數 (0-1) | - -#### TimeBased Content - -```json -{ - "content": { - "duration": 10.0, - "is_last": false, - "segment_number": 3, - "total_segments": 10 - } -} -``` - -| 欄位 | 類型 | 說明 | -|------|------|------| -| `duration` | Float | 時長 (秒) | -| `is_last` | Boolean | 是否最後一個 chunk | -| `segment_number` | Integer | 分段編號 | -| `total_segments` | Integer | 總分段數 | - -### 4.4 Metadata 結構 - -```json -{ - "metadata": { - "source": "asr", - "confidence": 0.95, - "language": "en", - "model": "tiny", - "created_at": "2026-03-16T10:00:00Z" - } -} -``` - -| 欄位 | 類型 | 說明 | -|------|------|------| -| `source` | String | 來源: asr/scene_detect/time_based | -| `confidence` | Float | 信心度 (0-1) | -| `language` | String | 語言代碼 | -| `model` | String | 使用模型 | -| `created_at` | String | 創建時間 (ISO 8601) | - ---- - -## 5. Chunk ID 命名規範 - -### 5.1 格式 - -``` -{chunk_type}_{chunk_index:04} -``` - -| 類型 | 前綴 | 範例 | -|------|------|------| -| Sentence | `sentence_` | `sentence_0001` | -| Cut | `cut_` | `cut_0001` | -| TimeBased | `time_based_` | `time_based_0001` | - -### 5.2 編號規則 - -- 從 **0** 開始 -- 使用 **4 位數** 補零 -- 按時間順序遞增 - ---- - -## 6. 資料庫 Schema - -### 6.1 PostgreSQL Table - -```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, - start_frame BIGINT NOT NULL, - end_time DOUBLE PRECISION NOT NULL, - end_frame BIGINT NOT NULL, - fps VARCHAR(16) NOT NULL, - fps_value DOUBLE PRECISION NOT NULL, - content JSONB NOT NULL, - metadata JSONB, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - UNIQUE(uuid, chunk_id) -); - --- 索引 -CREATE INDEX idx_chunks_uuid ON chunks(uuid); -CREATE INDEX idx_chunks_type ON chunks(chunk_type); -CREATE INDEX idx_chunks_time ON chunks(start_time, end_time); -CREATE INDEX idx_chunks_uuid_type ON chunks(uuid, chunk_type); -``` - -### 6.2 查詢範例 - -```sql --- 查詢影片所有 chunks -SELECT * FROM chunks WHERE uuid = '1636719dc31f78ac'; - --- 查詢特定類型的 chunks -SELECT * FROM chunks WHERE uuid = '1636719dc31f78ac' AND chunk_type = 'sentence'; - --- 查詢時間範圍內的 chunks -SELECT * FROM chunks -WHERE uuid = '1636719dc31f78ac' -AND start_time <= 30.0 AND end_time >= 20.0; - --- 查詢時間範圍內的所有 chunks (混合類型) -SELECT * FROM chunks -WHERE uuid = '1636719dc31f78ac' -AND start_time <= 30.0 AND end_time >= 20.0 -ORDER BY chunk_type, chunk_index; -``` - ---- - -## 7. Rust 資料結構 - -### 7.1 Chunk 定義 - -```rust -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "snake_case")] -pub enum ChunkType { - Sentence, - Cut, - TimeBased, -} - -impl ChunkType { - pub fn as_str(&self) -> &'static str { - match self { - ChunkType::Sentence => "sentence", - ChunkType::Cut => "cut", - ChunkType::TimeBased => "time_based", - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Chunk { - pub uuid: String, - pub chunk_id: String, - pub chunk_index: u32, - pub chunk_type: ChunkType, - pub start_time: f64, - pub start_frame: i64, - pub end_time: f64, - pub end_frame: i64, - pub fps: String, - pub fps_value: f64, - pub content: serde_json::Value, - pub metadata: Option, -} -``` - -### 7.2 建立 Chunk - -```rust -impl Chunk { - pub fn new( - uuid: String, - chunk_index: u32, - chunk_type: ChunkType, - start_time: f64, - end_time: f64, - fps: &str, - content: serde_json::Value, - ) -> Self { - let fps_value = parse_fps(fps); - let start_frame = (start_time * fps_value) as i64; - let end_frame = (end_time * fps_value) as i64; - let chunk_id = format!("{}_{:04}", chunk_type.as_str(), chunk_index); - - Self { - uuid, - chunk_id, - chunk_index, - chunk_type, - start_time, - start_frame, - end_time, - end_frame, - fps: fps.to_string(), - fps_value, - content, - metadata: None, - } - } -} -``` - ---- - -## 8. 時間切割器實作 - -### 8.1 TimeBasedSplitter - -```rust -pub struct TimeBasedSplitter { - pub duration: f64, // 每個 chunk 時長 (秒) - pub overlap: f64, // 重疊時長 (秒) -} - -impl TimeBasedSplitter { - pub fn new(duration: f64, overlap: f64) -> Self { - Self { duration, overlap } - } - - pub fn split(&self, uuid: &str, video_duration: f64, fps: f64) -> Vec { - let mut chunks = Vec::new(); - let step = self.duration - self.overlap; - let mut current_time = 0.0; - let mut index = 0; - - while current_time < video_duration { - let end_time = (current_time + self.duration).min(video_duration); - - let chunk = Chunk::new( - uuid.to_string(), - index, - ChunkType::TimeBased, - current_time, - end_time, - &format!("{:.0}/1", fps as u32), - serde_json::json!({ - "duration": end_time - current_time, - "is_last": end_time >= video_duration, - "segment_number": index + 1, - }), - ); - chunks.push(chunk); - - current_time += step; - index += 1; - } - - chunks - } -} -``` - -### 8.2 使用範例 - -```rust -// 建立時間切割器 (10秒, 無重疊) -let splitter = TimeBasedSplitter::new(10.0, 0.0); -let chunks = splitter.split(&uuid, video_duration, 24.0); - -// 建立時間切割器 (10秒, 2秒重疊) -let splitter = TimeBasedSplitter::new(10.0, 2.0); -let chunks = splitter.split(&uuid, video_duration, 24.0); -``` - ---- - -## 9. 處理流程 - -### 9.1 完整流程 - -``` -1. Register (註冊影片) - └── 取得 UUID, video_duration, fps - -2. Probe (探測影片) - └── 取得 streams, format, fps - -3. 產生 Sentence Chunks - └── 讀取 ASR 輸出 - └── 為每個 segment 建立 chunk - -4. 產生 Cut Chunks - └── 執行場景偵測 - └── 為每個 scene 建立 chunk - -5. 產生 TimeBased Chunks - └── 使用 TimeBasedSplitter - └── 為每個時間段建立 chunk - -6. 儲存至資料庫 - └── 批次寫入 PostgreSQL -``` - -### 9.2 輸出範例 - -``` -影片: 35 秒, FPS: 24 - -Sentence Chunks (3 個): - sentence_0000: 0.0s - 10.0s (252 frames) - sentence_0001: 10.0s - 20.0s (480 frames) - sentence_0002: 20.0s - 35.0s (840 frames) - -Cut Chunks (3 個): - cut_0000: 0.0s - 15.0s (360 frames) - cut_0001: 15.0s - 28.0s (672 frames) - cut_0002: 28.0s - 35.0s (168 frames) - -TimeBased Chunks (4 個, 重疊 2秒): - time_based_0000: 0.0s - 10.0s (240 frames) - time_based_0001: 8.0s - 18.0s (240 frames) - time_based_0002: 16.0s - 26.0s (240 frames) - time_based_0003: 24.0s - 35.0s (264 frames) -``` - ---- - -## 10. 資料庫儲存 - -### 10.1 PostgreSQL 儲存 - -#### Table Schema - -```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, - start_frame BIGINT NOT NULL, - end_time DOUBLE PRECISION NOT NULL, - end_frame BIGINT NOT NULL, - fps VARCHAR(16) NOT NULL, - fps_value DOUBLE PRECISION NOT NULL, - content JSONB NOT NULL, - metadata JSONB, - vector_id VARCHAR(64), - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - UNIQUE(uuid, chunk_id) -); - --- 索引 -CREATE INDEX idx_chunks_uuid ON chunks(uuid); -CREATE INDEX idx_chunks_type ON chunks(chunk_type); -CREATE INDEX idx_chunks_time ON chunks(start_time, end_time); -CREATE INDEX idx_chunks_uuid_type ON chunks(uuid, chunk_type); -CREATE INDEX idx_chunks_vector_id ON chunks(vector_id); -``` - -#### 儲存範例 - -```rust -pub async fn store_chunk_to_postgres(db: &PostgresDb, chunk: &Chunk) -> Result<()> { - sqlx::query!( - r#" - INSERT INTO chunks ( - uuid, chunk_id, chunk_index, chunk_type, - start_time, start_frame, end_time, end_frame, - fps, fps_value, content, metadata, vector_id - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT (uuid, chunk_id) DO UPDATE SET - content = EXCLUDED.content, - metadata = EXCLUDED.metadata, - vector_id = EXCLUDED.vector_id, - updated_at = NOW() - "#, - chunk.uuid, - chunk.chunk_id, - chunk.chunk_index as i32, - chunk.chunk_type.as_str(), - chunk.start_time, - chunk.start_frame, - chunk.end_time, - chunk.end_frame, - chunk.fps, - chunk.fps_value, - serde_json::to_value(&chunk.content)?, - serde_json::to_value(&chunk.metadata)?, - chunk.vector_id, - ) - .execute(&db.pool) - .await?; - Ok(()) -} -``` - ---- - -### 10.2 MongoDB 儲存 - -#### Collection Schema - -```javascript -// chunks collection -{ - _id: ObjectId, - uuid: "1636719dc31f78ac", - chunk_id: "sentence_0001", - chunk_index: 1, - chunk_type: "sentence", - start_time: 10.5, - start_frame: 252, - end_time: 15.75, - end_frame: 378, - fps: "24/1", - fps_value: 24.0, - content: { - text: "Hello world, this is a test", - text_normalized: "hello world this is a test", - word_count: 7, - char_count: 34 - }, - metadata: { - source: "asr", - confidence: 0.95, - language: "en" - }, - vector_id: "vec_sentence_0001", - created_at: ISODate("2026-03-16T10:00:00Z"), - updated_at: ISODate("2026-03-16T10:00:00Z") -} - -// 索引 -db.chunks.createIndex({ uuid: 1 }) -db.chunks.createIndex({ chunk_type: 1 }) -db.chunks.createIndex({ start_time: 1, end_time: 1 }) -db.chunks.createIndex({ vector_id: 1 }) -db.chunks.createIndex({ uuid: 1, chunk_type: 1 }) -``` - -#### 儲存範例 - -```rust -pub async fn store_chunk_to_mongodb(db: &MongoDb, chunk: &Chunk) -> Result<()> { - let doc = bson::doc! { - "uuid": chunk.uuid, - "chunk_id": chunk.chunk_id, - "chunk_index": chunk.chunk_index, - "chunk_type": chunk.chunk_type.as_str(), - "start_time": chunk.start_time, - "start_frame": chunk.start_frame, - "end_time": chunk.end_time, - "end_frame": chunk.end_frame, - "fps": chunk.fps, - "fps_value": chunk.fps_value, - "content": serde_json::to_value(&chunk.content)?, - "metadata": serde_json::to_value(&chunk.metadata)?, - "vector_id": chunk.vector_id, - "created_at": chrono::Utc::now(), - "updated_at": chrono::Utc::now() - }; - - let collection = db.database("momentry").collection("chunks"); - collection.update_one( - doc! { "uuid": &chunk.uuid, "chunk_id": &chunk.chunk_id }, - doc! { "$set": doc }, - UpdateOptions::builder().upsert(true).build(), - ).await?; - Ok(()) -} -``` - ---- - -## 11. 向量儲存設計 - -### 11.1 設計原則 - -**統一向量 ID 格式**,確保 Qdrant 與 PostgreSQL 相容: - -``` -{chunk_type}_{chunk_index:04} - -範例: -sentence_0001 -cut_0002 -time_based_0015 -``` - -### 11.2 Qdrant Collection - -#### 建立 Collection - -```bash -# 使用 Qdrant client 建立 collection -curl -X PUT http://localhost:6333/collections/chunks \ - -H "Content-Type: application/json" \ - -H "api-key: Test3200Test3200Test3200" \ - -d '{ - "vectors": { - "size": 768, - "distance": "Cosine" - } - }' -``` - -#### Point 結構 - -```json -{ - "id": "sentence_0001", - "vector": [0.123, -0.456, ...], - "payload": { - "uuid": "1636719dc31f78ac", - "chunk_id": "sentence_0001", - "chunk_type": "sentence", - "chunk_index": 1, - "start_time": 10.5, - "end_time": 15.75, - "text": "Hello world, this is a test", - "metadata": { - "confidence": 0.95, - "language": "en" - } - } -} -``` - -#### Rust 結構 - -```rust -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct VectorPoint { - pub id: String, - pub vector: Vec, - pub payload: VectorPayload, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct VectorPayload { - pub uuid: String, - pub chunk_id: String, - pub chunk_type: String, - pub chunk_index: u32, - pub start_time: f64, - pub end_time: f64, - #[serde(skip_serializing_if = "Option::is_none")] - pub text: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub scene_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub segment_number: Option, - pub metadata: Option, -} -``` - -### 11.3 PostgreSQL Vector 儲存 - -#### Table Schema - -```sql --- 使用 pgvector 擴展 -CREATE EXTENSION IF NOT EXISTS vector; - -CREATE TABLE chunk_vectors ( - id BIGSERIAL PRIMARY KEY, - vector_id VARCHAR(64) NOT NULL UNIQUE, - uuid VARCHAR(16) NOT NULL, - chunk_id VARCHAR(64) NOT NULL, - chunk_type VARCHAR(32) NOT NULL, - chunk_index INTEGER NOT NULL, - start_time DOUBLE PRECISION NOT NULL, - end_time DOUBLE PRECISION NOT NULL, - embedding vector(768) NOT NULL, - metadata JSONB, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - - FOREIGN KEY (uuid, chunk_id) REFERENCES chunks(uuid, chunk_id) -); - --- 向量檢索索引 (IVFFlat) -CREATE INDEX idx_chunk_vectors_embedding -ON chunk_vectors -USING ivfflat (embedding vector_cosine_ops) -WITH (lists = 100); - --- 查詢索引 -CREATE INDEX idx_chunk_vectors_uuid ON chunk_vectors(uuid); -CREATE INDEX idx_chunk_vectors_type ON chunk_vectors(chunk_type); -``` - -#### 儲存範例 - -```rust -pub async fn store_vector_to_postgres(db: &PostgresDb, point: &VectorPoint) -> Result<()> { - sqlx::query!( - r#" - INSERT INTO chunk_vectors ( - vector_id, uuid, chunk_id, chunk_type, chunk_index, - start_time, end_time, embedding, metadata - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT (vector_id) DO UPDATE SET - embedding = EXCLUDED.embedding, - metadata = EXCLUDED.metadata - "#, - point.id, - point.payload.uuid, - point.payload.chunk_id, - point.payload.chunk_type, - point.payload.chunk_index as i32, - point.payload.start_time, - point.payload.end_time, - point.vector, - serde_json::to_value(&point.payload.metadata)?, - ) - .execute(&db.pool) - .await?; - Ok(()) -} -``` - ---- - -## 12. 查詢範例 - -### 12.1 語義搜尋 (Semantic Search) - -#### 查詢類型 1: 相似文字搜尋 - -```rust -// 搜尋與問句相似的 chunks -pub async fn semantic_search( - qdrant: &QdrantDb, - query: &str, - limit: usize, -) -> Result> { - // 1. 將問句向量化 - let query_vector = embed_text(query).await?; - - // 2. 搜尋 Qdrant - let results = qdrant.search( - "chunks", - &query_vector, - limit, - Some(&Filter::must([ - Condition::Match("chunk_type", "sentence"), - ])), - ).await?; - - Ok(results) -} - -// 使用範例 -let results = semantic_search(&qdrant, "找出有人在說話的片段", 10).await?; -for r in results { - println!("{}: {:.3}", r.payload.chunk_id, r.score); - println!(" Time: {}s - {}s", r.payload.start_time, r.payload.end_time); - println!(" Text: {:?}", r.payload.text); -} -``` - -#### 查詢類型 2: 語音/文字混合搜尋 - -```sql --- PostgreSQL: 搜尋特定文字的 chunks -SELECT - c.chunk_id, - c.chunk_type, - c.start_time, - c.end_time, - c.content->>'text' as text, - v.embedding <=> query_embedding('找出開車的場景') as similarity -FROM chunks c -LEFT JOIN chunk_vectors v ON c.chunk_id = v.chunk_id -WHERE c.chunk_type = 'sentence' -AND c.content->>'text' ILIKE '%car%' -ORDER BY v.embedding <=> query_embedding('找出開車的場景') -LIMIT 10; -``` - -### 12.2 時間範圍搜尋 - -#### 查詢類型 3: 特定時間範圍 - -```rust -// 找出 30-60 秒之間的所有 chunks -pub async fn search_by_time_range( - db: &PostgresDb, - uuid: &str, - start: f64, - end: f64, -) -> Result> { - let chunks = sqlx::query_as!( - Chunk, - r#" - SELECT * FROM chunks - WHERE uuid = $1 - AND start_time < $3 - AND end_time > $2 - ORDER BY chunk_type, chunk_index - "#, - uuid, start, end - ) - .fetch_all(&db.pool) - .await?; - Ok(chunks) -} - -// 使用範例 -let chunks = search_by_time_range(&db, "1636719dc31f78ac", 30.0, 60.0).await?; -``` - -```javascript -// MongoDB: 時間範圍查詢 -db.chunks.find({ - uuid: "1636719dc31f78ac", - start_time: { $lt: 60 }, - end_time: { $gt: 30 } -}).sort({ chunk_type: 1, chunk_index: 1 }) -``` - -### 12.3 混合搜尋 (Hybrid Search) - -#### 查詢類型 4: 文字關鍵詞 + 向量相似度 - -```rust -// 結合關鍵詞匹配與向量相似度 -pub async fn hybrid_search( - db: &PostgresDb, - qdrant: &QdrantDb, - query: &str, - keywords: &[&str], - limit: usize, -) -> Result> { - // 1. 向量搜尋 - let query_vector = embed_text(query).await?; - let vector_results = qdrant.search("chunks", &query_vector, limit * 2, None).await?; - - // 2. 關鍵詞過濾 - let keyword_filter: Vec<_> = keywords.iter() - .map(|k| format!("%{}%", k)) - .collect(); - - let filtered: Vec<_> = vector_results.into_iter() - .filter(|r| { - if let Some(text) = &r.payload.text { - keyword_filter.iter().any(|k| text.contains(k.as_str())) - } else { - false - } - }) - .take(limit) - .collect(); - - Ok(filtered) -} -``` - -### 12.4 場景搜尋 - -#### 查詢類型 5: 找出特定場景 - -```sql --- PostgreSQL: 找出特定場景 ID 的 chunks -SELECT * FROM chunks -WHERE uuid = '1636719dc31f78ac' -AND chunk_type = 'cut' -AND (content->>'scene_id')::int = 5; - --- 找出包含轉場效果的 chunks -SELECT * FROM chunks -WHERE uuid = '1636719dc31f78ac' -AND chunk_type = 'cut' -AND content->>'transition_type' = 'dissolve'; -``` - -### 12.5 影片摘要 - -#### 查詢類型 6: 產生影片摘要 - -```sql --- 合併影片所有語句 -SELECT - string_agg(content->>'text', ' ' ORDER BY start_time) as full_transcript -FROM chunks -WHERE uuid = '1636719dc31f78ac' -AND chunk_type = 'sentence' -AND content->>'text' IS NOT NULL; - --- 按場景聚合文字 -SELECT - content->>'scene_id' as scene, - string_agg(content->>'text', ' ' ORDER BY start_time) as scene_text -FROM chunks -WHERE uuid = '1636719dc31f78ac' -AND chunk_type = 'cut' -GROUP BY content->>'scene_id' -ORDER BY MIN(start_time); -``` - -### 12.6 常見查詢模式 - -| 查詢類型 | 描述 | 資料庫 | SQL/程式碼 | -|----------|------|--------|-------------| -| 語義搜尋 | 找相似內容 | Qdrant | `search(vector, limit)` | -| 關鍵詞搜尋 | 精確文字匹配 | PostgreSQL | `ILIKE '%keyword%'` | -| 時間範圍 | 特定時段 | Both | `start_time < end AND end_time > start` | -| 場景搜尋 | 特定鏡頭 | PostgreSQL | `scene_id = N` | -| 混合搜尋 | 向量+關鍵詞 | Both |結合以上兩種 | -| 摘要產生 | 合併文字 | PostgreSQL | `string_agg()` | - ---- - -## 13. 資料庫選擇建議 - -### 13.1 儲存策略 - -| 資料類型 | 主要儲存 | 備份/查詢 | 說明 | -|----------|----------|-----------|------| -| **Chunk 元數據** | PostgreSQL | MongoDB | 結構化查詢為主 | -| **向量資料** | Qdrant | PostgreSQL | 向量搜尋為主 | -| **全文檢索** | PostgreSQL | - | 關鍵詞搜尋 | -| **日誌/歷史** | MongoDB | - | 靈活性為主 | - -### 13.2 讀寫模式 - -| 場景 | 寫入 | 讀取 | -|------|------|------| -| **影片處理** | PostgreSQL + Qdrant | - | -| **語義搜尋** | - | Qdrant | -| **時間軸瀏覽** | - | PostgreSQL | -| **系統分析** | MongoDB | MongoDB | - ---- - -## 14. 相關文件 - -- [JSON_OUTPUT_SPEC.md](./JSON_OUTPUT_SPEC.md) - JSON 輸出規範 -- [RUST_DEVELOPMENT.md](./RUST_DEVELOPMENT.md) - Rust 開發規範 -- [AGENTS.md](../AGENTS.md) - 開發規範 diff --git a/docs/DEMO_MANUAL.md b/docs/DEMO_MANUAL.md deleted file mode 100644 index 4fb683e..0000000 --- a/docs/DEMO_MANUAL.md +++ /dev/null @@ -1,686 +0,0 @@ -# Momentry Core API 示範手冊 - -| 項目 | 內容 | -|------|------| -| 建立者 | OpenCode | -| 建立時間 | 2026-03-25 | -| 文件版本 | V1.0 | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.0 | 2026-03-25 | 創建示範手冊,包含 Demo API Key 與完整範例 | OpenCode | deepseek-reasoner | - ---- - -**狀態**: 完成 - ---- - -## 快速開始 - -### Demo API Key - -``` -API Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69 -Key ID: muser_68600856036340bcafc01930eb4bd839 -過期日: 2027-03-25 -``` - -### 測試連線 - -```bash -curl http://localhost:3002/health -``` - -```json -{"status":"ok","version":"0.1.0","uptime_ms":456464} -``` - -### 測試認證 - -```bash -curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \ - http://localhost:3002/api/v1/videos | jq '.videos | length' -``` - -```json -13 -``` - ---- - -## 環境 URL - -| 環境 | URL | 用途 | -|------|-----|------| -| **本地開發** | `http://localhost:3002` | 本機開發測試 | -| **外部訪問** | `https://api.momentry.ddns.net` | n8n/WordPress/curl 生產環境 | - ---- - -## 端點總覽 - -| 方法 | 端點 | 說明 | 認證 | -|------|------|------|------| -| GET | `/health` | 健康檢查 | 公開 | -| GET | `/health/detailed` | 詳細健康檢查 | 公開 | -| POST | `/api/v1/register` | 註冊影片 | 需要 | -| POST | `/api/v1/probe` | 探測影片資訊 | 需要 | -| POST | `/api/v1/search` | 語意搜尋 | 需要 | -| POST | `/api/v1/n8n/search` | n8n 格式搜尋 | 需要 | -| POST | `/api/v1/search/hybrid` | 混合搜尋 | 需要 | -| GET | `/api/v1/videos` | 列出所有影片 | 需要 | -| GET | `/api/v1/lookup` | 查詢影片 UUID | 需要 | -| GET | `/api/v1/progress/:uuid` | 處理進度 | 需要 | -| GET | `/api/v1/jobs` | 任務列表 | 需要 | -| GET | `/api/v1/jobs/:uuid` | 任務詳情 | 需要 | - ---- - -## 1. curl 範例 - -### 基本格式 - -```bash -curl -H "X-API-Key: YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - URL -``` - -### 1.1 健康檢查(公開) - -```bash -# 基本健康檢查 -curl http://localhost:3002/health - -# 詳細健康檢查(含服務狀態) -curl http://localhost:3002/health/detailed -``` - -### 1.2 列出影片 - -```bash -curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \ - http://localhost:3002/api/v1/videos | jq '.' -``` - -```json -{ - "videos": [ - { - "uuid": "952f5854b9febad1", - "file_name": "ExaSAN PCIe series - Director Ou Yu-Zhi Shares His Experience.mp4", - "duration": 159.637188, - "width": 640, - "height": 360 - }, - ... - ] -} -``` - -### 1.3 搜尋影片 - -```bash -curl -X POST \ - -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \ - -H "Content-Type: application/json" \ - -d '{"query": "ExaSAN", "limit": 5}' \ - http://localhost:3002/api/v1/search | jq '.' -``` - -```json -{ - "results": [ - { - "uuid": "952f5854b9febad1", - "chunk_id": "...", - "text": "...", - "score": 0.85, - "start_time": 0.0, - "end_time": 5.0 - } - ], - "total": 1, - "query": "ExaSAN", - "took_ms": 123 -} -``` - -### 1.4 查詢進度 - -```bash -curl -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" \ - http://localhost:3002/api/v1/progress/952f5854b9febad1 | jq '.' -``` - -```json -{ - "uuid": "952f5854b9febad1", - "overall_progress": 67, - "current_processor": "yolo", - "processors": [ - {"name": "asr", "status": "completed"}, - {"name": "cut", "status": "completed"}, - {"name": "yolo", "status": "running"} - ] -} -``` - ---- - -## 2. n8n 範例 - -### 2.1 HTTP Request 節點設定 - -``` -Method: POST -URL: https://api.momentry.ddns.net/api/v1/search -Authentication: None (使用 Header) - -Headers: -┌─────────────────────┬──────────────────────────────────────────────────┐ -│ Name │ Value │ -├─────────────────────┼──────────────────────────────────────────────────┤ -│ X-API-Key │ muser_68600856036340bcafc01930eb4bd839_... │ -│ Content-Type │ application/json │ -└─────────────────────┴──────────────────────────────────────────────────┘ - -Body Content (JSON): -{ - "query": "{{ $json.search_term }}", - "limit": 5 -} -``` - -### 2.2 n8n 搜尋 Workflow - -```json -{ - "nodes": [ - { - "name": "Manual Trigger", - "type": "n8n-nodes-base.manualTrigger", - "position": [250, 300] - }, - { - "name": "Set Search Term", - "type": "n8n-nodes-base.set", - "parameters": { - "values": { - "json": { - "search_term": "ExaSAN" - } - } - }, - "position": [450, 300] - }, - { - "name": "Search Videos", - "type": "n8n-nodes-base.httpRequest", - "parameters": { - "method": "POST", - "url": "https://api.momentry.ddns.net/api/v1/search", - "authentication": "genericCredentialType", - "genericAuthType": "httpHeaderAuth", - "sendHeaders": true, - "headerParameters": { - "parameters": [ - { - "name": "X-API-Key", - "value": "muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" - } - ] - }, - "sendBody": true, - "bodyContentType": "json", - "specifyBody": "json", - "jsonBody": "={{ { \"query\": $json.search_term, \"limit\": 5 } }}" - }, - "position": [650, 300] - }, - { - "name": "Process Results", - "type": "n8n-nodes-base.code", - "parameters": { - "jsCode": "// Extract video results\nconst results = $input.first().json.results;\nreturn results.map(r => ({\n uuid: r.uuid,\n text: r.text,\n score: r.score,\n time: `${r.start_time}s - ${r.end_time}s`\n}));" - }, - "position": [850, 300] - } - ], - "connections": { - "Manual Trigger": { - "main": [[{"node": "Set Search Term"}]] - }, - "Set Search Term": { - "main": [[{"node": "Search Videos"}]] - }, - "Search Videos": { - "main": [[{"node": "Process Results"}]] - } - } -} -``` - -### 2.3 n8n 列出影片 Workflow - -```json -{ - "nodes": [ - { - "name": "Get Videos", - "type": "n8n-nodes-base.httpRequest", - "parameters": { - "method": "GET", - "url": "https://api.momentry.ddns.net/api/v1/videos", - "sendHeaders": true, - "headerParameters": { - "parameters": [ - { - "name": "X-API-Key", - "value": "muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" - } - ] - } - }, - "position": [450, 300] - }, - { - "name": "Extract Video List", - "type": "n8n-nodes-base.code", - "parameters": { - "jsCode": "const videos = $input.first().json.videos;\nreturn videos.map(v => ({\n json: {\n uuid: v.uuid,\n name: v.file_name,\n duration: Math.round(v.duration) + 's',\n resolution: `${v.width}x${v.height}`\n }\n}));" - }, - "position": [650, 300] - }, - { - "name": "Slack Notification", - "type": "n8n-nodes-base.slack", - "parameters": { - "channel": "#momentry", - "text": "=Found {{ $json.length }} videos:\n{{ $json.map(v => `• ${v.name} (${v.duration})`).join(`\n`) }}" - }, - "position": [850, 300] - } - ] -} -``` - -### 2.4 n8n 定時同步 Workflow - -```json -{ - "nodes": [ - { - "name": "Schedule Trigger", - "type": "n8n-nodes-base.scheduleTrigger", - "parameters": { - "rule": { - "interval": [{"field": "hours", "hours": 1}] - } - }, - "position": [250, 300] - }, - { - "name": "Get Pending Videos", - "type": "n8n-nodes-base.httpRequest", - "parameters": { - "method": "GET", - "url": "https://api.momentry.ddns.net/api/v1/videos" - }, - "position": [450, 300] - }, - { - "name": "Filter Processing", - "type": "n8n-nodes-base.filter", - "parameters": { - "conditions": { - "options": {"caseSensitive": true}, - "conditions": [ - {"id": "status", "leftValue": "{{ $json.status }}", "rightValue": "processing"} - ] - } - }, - "position": [650, 300] - } - ] -} -``` - ---- - -## 3. WordPress 範例 - -### 3.1 PHP 函數庫 - -```php - [ - 'X-API-Key' => self::API_KEY, - 'Content-Type' => 'application/json', - ], - 'timeout' => 30, - ]; - - if ($method === 'POST') { - $args['method'] = 'POST'; - $args['body'] = json_encode($data); - } - - $response = wp_remote_request($url, $args); - - if (is_wp_error($response)) { - throw new Exception($response->get_error_message()); - } - - return json_decode(wp_remote_retrieve_body($response), true); - } - - /** - * 列出所有影片 - */ - public function list_videos(): array { - return $this->request('/api/v1/videos'); - } - - /** - * 搜尋影片內容 - */ - public function search(string $query, int $limit = 10): array { - return $this->request('/api/v1/search', [ - 'query' => $query, - 'limit' => $limit, - ], 'POST'); - } - - /** - * 取得影片進度 - */ - public function get_progress(string $uuid): array { - return $this->request("/api/v1/progress/{$uuid}"); - } - - /** - * 檢查健康狀態 - */ - public function health_check(): array { - return $this->request('/health'); - } -} -``` - -### 3.2 短代碼 (Shortcode) - -```php - 10, - ], $atts); - - $api = new Momentry_API(); - - try { - $result = $api->list_videos(); - $videos = array_slice($result['videos'], 0, $atts['limit']); - - ob_start(); - ?> -
-

影片列表

-
    - -
  • - -
    - - UUID: - | 時長: - -
  • - -
-
- 載入失敗: ' . esc_html($e->getMessage()) . '

'; - } -}); - -// 搜尋短代碼 -add_shortcode('momentry_search', function($atts, $content = '') { - $query = sanitize_text_field($content); - - if (empty($query)) { - return '

請提供搜尋關鍵字

'; - } - - $api = new Momentry_API(); - - try { - $result = $api->search($query); - - ob_start(); - ?> -
-

」搜尋結果

- -

沒有找到相關結果

- -
    - -
  • - - - -
    - 相似度: % -
  • - -
- -
- 搜尋失敗: ' . esc_html($e->getMessage()) . '

'; - } -}); -``` - -### 3.3 使用方式 - -在 WordPress 頁面或文章中: - -``` -[momentry_videos limit="5"] - -[momentry_search]ExaSAN[/momentry_search] -``` - -### 3.4 REST API 整合 - -```php - 'GET', - 'callback' => function(WP_REST_Request $request) { - $query = sanitize_text_field($request->get_param('q')); - - if (empty($query)) { - return new WP_Error('missing_query', '需要搜尋關鍵字', ['status' => 400]); - } - - $api = new Momentry_API(); - $result = $api->search($query); - - return new WP_REST_Response($result, 200); - }, - 'permission_callback' => '__return_true', - ]); -}); - -// 使用方式: GET /wp-json/momentry/v1/search?q=ExaSAN -``` - ---- - -## 4. 疑難排解 - -### 4.1 常見錯誤 - -| 錯誤 | 原因 | 解決方案 | -|------|------|----------| -| `401 Unauthorized` | API Key 無效或過期 | 檢查 API Key 是否正確 | -| `500 Internal Server Error` | 伺服器錯誤 | 檢查 `/health/detailed` 服務狀態 | -| `Connection Timeout` | 網路問題 | 確認 `api.momentry.ddns.net` 可達 | - -### 4.2 測試腳本 - -```bash -#!/bin/bash -# test_api.sh - Momentry API 測試腳本 - -API_KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -BASE_URL="http://localhost:3002" - -echo "=== 1. 健康檢查 ===" -curl -s "$BASE_URL/health" | jq . -echo "" - -echo "=== 2. 列出影片 ===" -curl -s -H "X-API-Key: $API_KEY" "$BASE_URL/api/v1/videos" | jq '.videos | length' -echo "" - -echo "=== 3. 搜尋測試 ===" -curl -s -X POST -H "X-API-Key: $API_KEY" \ - -H "Content-Type: application/json" \ - -d '{"query": "test", "limit": 3}' \ - "$BASE_URL/api/v1/search" | jq '.results | length' -echo "" - -echo "=== 完成 ===" -``` - -### 4.3 驗證腳本 - -```bash -#!/bin/bash -# verify_auth.sh - 驗證 API Key - -API_KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" -BASE_URL="http://localhost:3002" - -# 測試 1: 無 API Key -echo "測試 1: 無 API Key" -RESULT=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/api/v1/videos") -[ "$RESULT" = "401" ] && echo "✅ 正確拒絕 (401)" || echo "❌ 預期 401,實際 $RESULT" - -# 測試 2: 有 API Key -echo "測試 2: 有 API Key" -RESULT=$(curl -s -H "X-API-Key: $API_KEY" "$BASE_URL/api/v1/videos") -echo "$RESULT" | jq -e '.videos' > /dev/null && echo "✅ 成功取得資料" || echo "❌ 取得資料失敗" - -# 測試 3: 無效 API Key -echo "測試 3: 無效 API Key" -RESULT=$(curl -s -o /dev/null -w "%{http_code}" -H "X-API-Key: invalid_key" "$BASE_URL/api/v1/videos") -[ "$RESULT" = "401" ] && echo "✅ 正確拒絕 (401)" || echo "❌ 預期 401,實際 $RESULT" -``` - ---- - -## 5. API Key 管理 - -### 5.1 建立新 API Key - -```bash -# 本地建立 -./target/release/momentry api-key create "My App" --key-type user --ttl 90 -``` - -### 5.2 列出 API Keys - -```bash -./target/release/momentry api-key list -``` - -### 5.3 驗證 API Key - -```bash -./target/release/momentry api-key validate --key "YOUR_API_KEY" -``` - -### 5.4 撤銷 API Key - -```bash -./target/release/momentry api-key revoke --key "YOUR_API_KEY" -``` - ---- - -## 附錄 - -### A. 影片 UUID 說明 - -UUID 是基於檔案路徑的 SHA256 哈希前 16 位: - -``` -/Users/accusys/momentry/var/sftpgo/data/demo/video.mp4 - ↓ -SHA256 Hash - ↓ -9760d0820f0cf9a7 -``` - -### B. 處理器狀態 - -| 狀態 | 說明 | -|------|------| -| `pending` | 等待處理 | -| `running` | 處理中 | -| `completed` | 已完成 | -| `failed` | 失敗 | - -### C. 支援的處理器 - -- **ASR**: 語音識別 -- **CUT**: 場景剪切 -- **YOLO**: 物件偵測 - -### D. 聯絡支援 - -- Email: support@momentry.ddns.net -- 文件: https://docs.momentry.ddns.net -- GitHub: https://github.com/anomalyco/momentry diff --git a/docs/DEVELOPMENT_LOG.md b/docs/DEVELOPMENT_LOG.md deleted file mode 100644 index 3c30a41..0000000 --- a/docs/DEVELOPMENT_LOG.md +++ /dev/null @@ -1,540 +0,0 @@ -# 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/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/MOMENTRY_CORE_REDIS_KEYS.md` | Redis Key 設計規範 | -| 2026-03-18 | `docs/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/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/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 - -# 列出備份 -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 - -# 查詢進度 -curl http://127.0.0.1:3002/api/v1/progress/ -``` - ---- - -## 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 實時推送 -- [ ] 移動端響應式設計 diff --git a/docs/DOCS_STANDARD.md b/docs/DOCS_STANDARD.md deleted file mode 100644 index 6b5705a..0000000 --- a/docs/DOCS_STANDARD.md +++ /dev/null @@ -1,474 +0,0 @@ -# 文件創建規範 - -| 項目 | 內容 | -|------|------| -| 建立者 | Warren | -| 建立時間 | 2026-03-18 | -| 文件版本 | V1.0 | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.0 | 2026-03-18 | 創建文件規範 | Warren | OpenCode / MiniMax M2.5 | - ---- - -本文檔定義 Momentry Core 專案中文件的命名規範、格式標準和結構要求。 - ---- - -## 1. 檔案命名規範 - -### 命名模式 - -所有文件必須使用以下命名模式: - -| 文件類型 | 模式 | 範例 | -|----------|------|------| -| 安裝指南 | `INSTALL_.md` | `INSTALL_POSTGRESQL.md` | -| 開發指南 | `DEVELOP_.md` | `DEVELOP_API.md` | -| API 參考 | `API_REFERENCE.md` | `API_REFERENCE.md` | -| 規格文件 | `_SPEC.md` | `CHUNK_SPEC.md` | -| 設計文件 | `_DESIGN.md` | `CHUNK_DESIGN.md` | -| 服務總覽 | `SERVICES.md` | `SERVICES.md` | -| 其他文件 | `.md` | `README.md` | - -### 命名規則 - -- 使用 **大駝峰** (PascalCase) 命名法 -- 服務名稱使用 **全大寫** (e.g., `POSTGRESQL`, `SFTPGO`) -- 英文優先,縮寫保持大寫 -- 使用底線 `_` 作為單詞分隔符 -- 副檔名統一使用 `.md` (Markdown) - -### 禁止事項 - -- 不允許使用中文檔名 -- 不允許空格 -- 不允許混合大小寫 (如 `Install_PostgreSQL.md`) - ---- - -## 2. 文件結構模板 - -### 安裝指南結構 - -```markdown -# <服務名稱> 安裝指南 (部署類型) - -## 概述 - -本文檔說明如何... - ---- - -## 當前狀態 - -| 項目 | 狀態 | -|------|------| -| <服務名> | ✅ 已安裝 v<版本號> | -| Port | <端口號> | -| ... | ... | - ---- - -## 安裝步驟 - -### Step 1: <步驟名稱> - -<說明內容> - -```bash -# 代碼範例 -command --option value -``` - -### Step 2: <步驟名稱> -... - ---- - -## 卸載步驟 - -### Step 1: <步驟名稱> -... - ---- - -## 故障排除 - -### <問題名稱> - -<解決方案> - ---- - -## 檔案位置 - -| 類型 | 路徑 | 說明 | -|------|------|------| -| 安裝 | /path/to/install | 說明 | -... - ---- - -## 常用指令 - -```bash -# 驗證 -command verify - -# 查看版本 -command --version -``` - ---- - -## 版本資訊 - -- 版本: <版本號> -- 安裝日期: <日期> -``` - ---- - -### 規格文件結構 - -```markdown -# <名稱> 規格文件 - -## 概述 - -<簡短描述> - ---- - -## 詳細規格 - -### 1. <功能模組> - -#### 欄位定義 - -| 欄位 | 類型 | 必填 | 說明 | -|------|------|------|------| -| field1 | string | Yes | 說明 | - -#### 資料結構 - -```json -{ - "example": "data" -} -``` - ---- - -## 限制條件 - -- <限制1> -- <限制2> - ---- - -## 相關文件 - -- `RELATED_FILE.md` - 相關說明 -``` - ---- - -## 3. 格式標準 - -### Markdown 格式 - -| 項目 | 標準 | -|------|------| -| 標題層級 | H1 (`#`) → H2 (`##`) → H3 (`###`) | -| 水平線 | 使用 `---` 分隔主要章節 | -| 程式碼區塊 | 使用三個反引號 ``` 並標註語言 | -| 表格 | 使用 `|` 和 `-` 對齊 | -| 強調 | 使用 `**粗體**` 和 `*斜體*` | - -### 程式碼區塊語言標註 - -```bash -# Bash -```bash -command -``` - -```json -# JSON -```json -{"key": "value"} -``` - -```rust -# Rust -```rust -fn main() {} -``` - -```yaml -# YAML -key: value -``` - -### 表格格式 - -```markdown -| Header 1 | Header 2 | Header 3 | -|----------|----------|----------| -| Cell 1 | Cell 2 | Cell 3 | -| Cell 4 | Cell 5 | Cell 6 | -``` - -### 列表格式 - -- 使用 `-` 作為無序列表標記 -- 使用數字 `1.` 作為有序列表標記 -- 縮進使用 2 個空格 - ---- - -## 4. 語言規範 - -### 標題語言 - -| 區域 | 語言 | -|------|------| -| 主要內容 | 繁體中文 | -| 技術術語 | 英文保留 | -| 命令和代碼 | 英文 | -| 文件標題 | 繁體中文 | - -### 常用術語對照 - -| 英文 | 中文 | -|------|------| -| Install | 安裝 | -| Configure/Config | 配置/設定 | -| Uninstall | 卸載 | -| Troubleshooting | 故障排除 | -| Status | 狀態 | -| Documentation | 文件 | -| Guide | 指南 | -| Overview | 概述 | -| Specification | 規格 | -| Current Status | 當前狀態 | -| Default | 預設 | -| Required | 必填 | -| Optional | 選填 | -| Example | 範例 | - -### 標點符號 - -- 中文內容使用全形標點:`,`、`。`、`:`、`(`、`)` -- 英文/程式內容使用半形標點:`:`、`(`、`)` -- 命令行使用 `` `command` `` 格式 - ---- - -## 5. 內容要求 - -### 必需章節 - -每份文件必須包含: - -1. **標題** - 文件名稱 -2. **概述** - 檔案用途說明 -3. **版本/狀態資訊** - 當前狀態 -4. **檔案位置** - 重要路徑列表 -5. **常用指令** - 基本操作命令 - -### 版本資訊格式 - -每份文件頂部必須包含以下資訊: - -```markdown -| 項目 | 內容 | -|------|------| -| 建立者 | <姓名> | -| 建立時間 | | -| 文件版本 | V1.0 | -``` - -版本歷史表: - -```markdown ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 | -``` - ---- - -### 版本資訊章節格式 - -```markdown ---- - -## 版本資訊 - -- 版本: <版本號> -- 安裝日期: -- 文件更新: -``` - -### 狀態標記 - -| 狀態 | 標記 | -|------|------| -| 已安裝 | ✅ 已安裝 v | -| 未安裝 | ❌ 未安裝 | -| 可選 | ⚙️ 可選 | -| 進行中 | 🔄 進行中 | - ---- - -## 6. 示例文件 - -### 正確範例 - -```markdown -# PostgreSQL 安裝指南 (本地部署) - -| 項目 | 內容 | -|------|------| -| 建立者 | Warren | -| 建立時間 | 2026-03-18 | -| 文件版本 | V1.0 | - ---- - -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 | - ---- - -## 概述 - -本文檔說明如何在 macOS 上安裝 PostgreSQL... - ---- - -## 當前狀態 - -| 項目 | 狀態 | -|------|------| -| PostgreSQL | ✅ 已安裝 v16.2 | -| Port | 5432 | - ---- - -## 安裝步驟 - -### Step 1: 安裝 PostgreSQL - -```bash -brew install postgresql@16 -``` - -### Step 2: 啟動服務 - -```bash -brew services start postgresql@16 -``` - ---- - -## 檔案位置 - -| 類型 | 路徑 | -|------|------| -| 配置文件 | /path/to/config | -| 數據目錄 | /path/to/data | - ---- - -## 版本資訊 - -- 版本: 16.2 -- 安裝日期: 2026-03-01 -``` - -### 錯誤範例 - -``` -❌ PostgreSQL安裝.md # 中文檔名 -❌ install-postgresql.md # 全部小寫 -❌ Install PostgreSQL.md # 空格 -❌ postgresql_install.md # 非標準命名 -``` - ---- - -## 7. 文件審查清單 - -創建新文件時,請確認: - -- [ ] 檔案命名符合 `INSTALL_*.md` 或其他標準模式 -- [ ] 文件包含頂部資訊表(建立者、建立時間、版本) -- [ ] 文件包含版本歷史表 -- [ ] 文件包含概述章節 -- [ ] 文件包含當前狀態/版本資訊 -- [ ] 文件包含檔案位置章節 -- [ ] 文件包含常用指令章節 -- [ ] 使用統一的 Markdown 格式 -- [ ] 使用繁體中文作為主要語言 -- [ ] 程式碼區塊標註語言類型 -- [ ] 表格格式正確 -- [ ] 章節使用 `---` 分隔 - -### 頂部資訊表範本 - -```markdown -| 項目 | 內容 | -|------|------| -| 建立者 | Warren | -| 建立時間 | 2026-03-18 | -| 文件版本 | V1.0 | -``` - -### 版本歷史表範本 - -```markdown -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.0 | 2026-03-18 | 創建文件 | Warren | OpenCode / MiniMax M2.5 | -``` - ---- - -## 8. 更新現有文件 - -當更新現有文件時: - -1. 更新 **版本資訊** 中的日期 -2. 如有必要,更新版本號 -3. 記錄重大變更於 `CHANGELOG.md` 或 `DEVELOPMENT_LOG.md` - ---- - -## 附錄:文件類型參考 - -| 前綴 | 用途 | 位置 | -|------|------|------| -| `INSTALL_` | 服務安裝指南 | `/docs/` | -| `DEVELOP_` | 開發指南 | `/docs/` | -| `*_SPEC.md` | 規格定義 | `/docs/` | -| `*_DESIGN.md` | 設計文件 | `/docs/` | -| `API_REFERENCE.md` | API 參考文件 | `/docs/` | -| `README.md` | 專案總覽 | `/` | -| `AGENTS.md` | AI 代理指令 | `/` | -| `CHANGELOG.md` | 變更日誌 | `/` | diff --git a/docs/DOCUMENT_EMBEDDING_STRATEGY.md b/docs/DOCUMENT_EMBEDDING_STRATEGY.md deleted file mode 100644 index 4d6597d..0000000 --- a/docs/DOCUMENT_EMBEDDING_STRATEGY.md +++ /dev/null @@ -1,167 +0,0 @@ -# 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 diff --git a/docs/FILE_CHANGE_MANAGEMENT.md b/docs/FILE_CHANGE_MANAGEMENT.md deleted file mode 100644 index ca285ed..0000000 --- a/docs/FILE_CHANGE_MANAGEMENT.md +++ /dev/null @@ -1,323 +0,0 @@ -# 文件修改管理規範 v1.0 - -| 項目 | 內容 | -|------|------| -| 建立者 | Warren | -| 建立時間 | 2026-03-22 | -| 文件版本 | V1.0 | - ---- - -## 1. 概述 - -本文檔定義 Momentry 專案的文件修改流程,確保不同工具/模型對文件的一致性理解,防止誤修改並保留完整的修改紀錄。 - -### 1.1 適用範圍 - -- 所有 `.md` 文件(技術文檔、安裝指南、API 文件等) -- 所有 `.rs` 文件(Rust 源代碼) -- 所有 `.sh` 文件(Shell 腳本) -- 所有 `.yaml` / `.yml` 文件(配置文件) -- 所有 `.json` 文件(配置及數據文件) - -### 1.2 核心原則 - -1. **先讀後改**:修改前必須完整閱讀相關文件 -2. **預檢清單**:修改前執行預檢查步驟 -3. **變更對照**:修改後必須比對差異 -4. **驗證確認**:變更後執行驗證測試 -5. **完整紀錄**:所有修改必須記錄於版本歷史 - ---- - -## 2. 修改前預檢清單 - -### 2.1 文件閱讀要求 - -修改文件前,必須完成以下閱讀: - -| 步驟 | 項目 | 說明 | -|------|------|------| -| 1 | 閱讀完整文件 | 不可僅閱讀部分章節 | -| 2 | 理解文件用途 | 確認文件的目標讀者 | -| 3 | 確認現有術語 | 使用一致的術語和命名 | -| 4 | 查閱相關文件 | 確認相關聯的文件 | - -### 2.2 預檢問題清單 - -在修改前回答以下問題: - -``` -□ 1. 此修改是否影響其他文件? -□ 2. 此修改是否與現有規範衝突? -□ 3. 此修改是否需要更新版本歷史? -□ 4. 此修改是否需要新增測試? -□ 5. 此修改是否需要通知相關人員? -□ 6. 此修改是否有破壞性變更(Breaking Change)? -``` - -### 2.3 預檢命令 - -修改前執行以下命令確認現有狀態: - -```bash -# 1. 確認 git 狀態 -git status - -# 2. 檢查相關文件的最新版本 -git log -3 --oneline - -# 3. 查看現有版本歷史 -cat docs/.md | grep -A 20 "版本歷史" -``` - ---- - -## 3. 文件修改流程 - -### 3.1 標準修改流程 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ Step 1: 閱讀 │ -│ ├─ 完整閱讀目標文件 │ -│ └─ 閱讀相關聯文件 │ -├─────────────────────────────────────────────────────────────┤ -│ Step 2: 預檢 │ -│ ├─ 回答預檢問題清單 │ -│ └─ 執行預檢命令 │ -├─────────────────────────────────────────────────────────────┤ -│ Step 3: 規劃 │ -│ ├─ 說明修改內容 │ -│ └─ 列出變更差異 │ -├─────────────────────────────────────────────────────────────┤ -│ Step 4: 修改 │ -│ ├─ 執行修改 │ -│ └─ 更新版本歷史 │ -├─────────────────────────────────────────────────────────────┤ -│ Step 5: 驗證 │ -│ ├─ 執行 lint/format 檢查 │ -│ └─ 執行相關測試 │ -├─────────────────────────────────────────────────────────────┤ -│ Step 6: 提交 │ -│ └─ 撰寫清晰的 commit message │ -└─────────────────────────────────────────────────────────────┘ -``` - -### 3.2 預修改彙報格式 - -在執行修改前,必須先彙報以下內容: - -```markdown -## 檔案 -`` - -## 修改原因 -<說明修改的目的> - -## 變更內容 -```diff -- <刪除的內容> -+ <新增的內容> -``` - -## 版本歷史更新 -| 版本 | 日期 | 內容 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| Vx.x | YYYY-MM-DD | <修改說明> | <操作者> | <使用的工具> | -``` - -### 3.3 版本歷史格式 - -每個文件頂部必須包含版本歷史表: - -```markdown -## 版本歷史 - -| 版本 | 日期 | 目的 | 操作人 | 工具/模型 | -|------|------|------|--------|-----------| -| V1.0 | 2026-03-15 | 創建文件 | Warren | OpenCode / MiniMax M2.5 | -| V1.1 | 2026-03-22 | 更新內容 | Warren | OpenCode / big-pickle | -``` - ---- - -## 4. 變更對照 - -### 4.1 diff 對照 - -修改後必須提供 diff 對照: - -```bash -git diff -``` - -### 4.2 變更類型分類 - -| 類型 | 標記 | 說明 | -|------|------|------| -| 新增 | `+` | 新增內容 | -| 刪除 | `-` | 刪除內容 | -| 修改 | `~` | 修改內容 | -| 移動 | `↕` | 移動位置 | -| 格式 | `@` | 格式變更 | - -### 4.3 變更確認清單 - -``` -□ 1. diff 輸出已確認 -□ 2. 變更符合預期 -□ 3. 無意外變更 -□ 4. 版本歷史已更新 -□ 5. 其他關聯文件已檢查 -``` - ---- - -## 5. 驗證流程 - -### 5.1 自動化驗證 - -修改後執行以下自動化檢查: - -```bash -# Rust 文件 -cargo fmt -- --check -cargo clippy --lib -cargo test --lib - -# Python 文件 -ruff check -ruff format --check - -# Markdown 文件 -markdownlint - -# Shell 文件 -shellcheck -S error -``` - -### 5.2 手動驗證清單 - -``` -□ 1. 文件語法正確 -□ 2. 連結有效 -□ 3. 格式一致 -□ 4. 術語一致 -□ 5. 版本歷史完整 -□ 6. 變更記錄清晰 -``` - ---- - -## 6. 提交規範 - -### 6.1 Commit Message 格式 - -``` -: - - - -