Compare commits
10 Commits
380dd87d8b
...
1c30af9557
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c30af9557 | ||
|
|
3ccdf403b6 | ||
|
|
c09268f3d3 | ||
|
|
84a2f71e30 | ||
|
|
9b32d1fed4 | ||
|
|
3ef2e6e150 | ||
|
|
c4e30e4234 | ||
|
|
bd82028f34 | ||
|
|
a78b5bc12b | ||
|
|
2d008b75bf |
@@ -581,7 +581,8 @@ git push origin main
|
||||
|
||||
| 機器 | Token |
|
||||
|------|-------|
|
||||
| M5Max128 | `a7cf946148063c2bfa8d59ad629ae541813f0db8` |
|
||||
| M5Max128 | `a7cf946148063c2bfa8d59ad629ae541813f0db8` (write:repository) |
|
||||
| M5Max128 (admin) | `b388aec114a93ae3ce752acf16a9ce678144541b` (write:repository + write:user) |
|
||||
|
||||
**注意**: Token 有 write:repository scope,勿外洩。如需新增 token 給其他機器,各自產自己的 token。
|
||||
|
||||
|
||||
@@ -116,6 +116,96 @@ Get status of the 5W1H agent pipeline for a file.
|
||||
| **Dimension** | 768 |
|
||||
| **Used by** | `parent_chunk_5w1h.py --embed`, story, 5W1H, search |
|
||||
|
||||
---
|
||||
|
||||
## POST /api/v1/agents/search
|
||||
|
||||
Conversational search assistant. Uses Gemma4 function calling to automatically decide which tools to call based on the user's natural language query. Supports multi-turn conversation.
|
||||
|
||||
### Request
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "Audrey Hepburn 和 Cary Grant 第一次同框在哪個 frame?",
|
||||
"conversation_id": null,
|
||||
"file_uuid": null
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `query` | string | ✅ | 自然語言查詢 |
|
||||
| `conversation_id` | string | ❌ | 延續對話時傳入;新對話不傳 |
|
||||
| `file_uuid` | string | ❌ | Portal 有選中檔案時可指定 |
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"conversation_id": "conv_abc123",
|
||||
"answer": "在 Charade (1963) 中,Audrey Hepburn 與 Cary Grant 第一次同框在第 38619 幀(約 1544.76 秒)。",
|
||||
"need_input": false,
|
||||
"sources": [
|
||||
{
|
||||
"tool": "tkg_query",
|
||||
"result": "{\"first_cooccurrence\":{\"frame\":38619,\"timestamp_secs\":1544.76}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `conversation_id` | string | 後續對話需要傳入此 ID |
|
||||
| `answer` | string | Agent 的自然語言回答(或反問) |
|
||||
| `need_input` | boolean | `true` 表示 agent 需要更多資訊才能回答 |
|
||||
| `suggestions` | string[] | 建議用戶提供的線索(當 `need_input=true`) |
|
||||
| `sources` | array | 引用的工具執行結果 |
|
||||
|
||||
### Conversation Flow
|
||||
|
||||
```
|
||||
Round 1: POST /agents/search { query: "我想看男女主角同框" }
|
||||
→ need_input: true, suggestions: ["片名", "演員", "年代"]
|
||||
→ answer: "請問是哪部電影?請提供更多線索"
|
||||
|
||||
Round 2: POST /agents/search { query: "奧黛麗赫本", conversation_id: "..." }
|
||||
→ need_input: false
|
||||
→ answer: "找到 Charade (1963),Audrey Hepburn 和 Cary Grant..."
|
||||
```
|
||||
|
||||
### Available Tools
|
||||
|
||||
Agent 內部使用 Gemma4 function calling 自動調用以下工具:
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `find_file` | 透過片名/演員/年份關鍵字搜尋影片,回傳 file_uuid + has_data 狀態 |
|
||||
| `list_files` | 列出近期註冊的影片 |
|
||||
| `tkg_query` | 查詢人物互動資料(7 種子類型:top_identities、first_cooccurrence、identity_details、mutual_gaze、interaction_network、identity_traces、file_info) |
|
||||
| `smart_search` | 文字內容 ILIKE 搜尋 chunk(可指定 file_uuid 限制範圍) |
|
||||
| `get_identity_detail` | 查詢單一身份的詳細資料(角色、TMDb 資訊) |
|
||||
| `get_file_info` | 查詢影片基本資訊(片長、解析度) |
|
||||
| `get_representative_frame` | 查詢影片最具代表性的 frame 資訊 |
|
||||
|
||||
### Design Principles
|
||||
|
||||
- **用戶不需要知道 file_uuid** — Agent 會自動用 `find_file` 搜尋或反問
|
||||
- **不推薦無資料的影片** — `has_data=false` 的影片不會被推薦給用戶
|
||||
- **多輪對話** — 透過 `conversation_id` 延續上下文,agent 會記得之前的交流
|
||||
- **並行工具呼叫** — Gemma4 可以一次呼叫多個工具再綜合回答
|
||||
|
||||
### Model
|
||||
|
||||
| Detail | Value |
|
||||
|--------|-------|
|
||||
| **LLM** | Gemma4 26B (Q5_K_M) |
|
||||
| **Engine** | llama.cpp at `localhost:8082` |
|
||||
| **Endpoint** | `/v1/chat/completions` (OpenAI-compatible) |
|
||||
| **Temperature** | 0.1 |
|
||||
| **Max rounds** | 5 (tool call iterations) |
|
||||
| **Conversation TTL** | 30 minutes |
|
||||
|
||||
---
|
||||
*Updated: 2026-05-19 12:49:24*
|
||||
*Updated: 2026-05-22*
|
||||
|
||||
30
docs_v1.0/OPERATIONS/VERIFICATION_GOLANG_2026-05-22.md
Normal file
30
docs_v1.0/OPERATIONS/VERIFICATION_GOLANG_2026-05-22.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Go (golang) Verification Report
|
||||
|
||||
**Date**: 2026-05-22
|
||||
**Status**: Verified
|
||||
|
||||
---
|
||||
|
||||
## Source
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Source repo | `admin/go` (Gitea) |
|
||||
| Source path | `release/system/v1.0/services/src/go/` |
|
||||
| Git commit | (see Gitea) |
|
||||
| Binary | `/opt/homebrew/bin/go` |
|
||||
|
||||
## Verification
|
||||
|
||||
| Check | Result |
|
||||
|-------|--------|
|
||||
| Binary exists | ✅ |
|
||||
| Version | (check with `go version`) |
|
||||
| SHA256 | (check against manifest) |
|
||||
|
||||
---
|
||||
|
||||
## Linked Documents
|
||||
|
||||
- `docs_v1.0/OPERATIONS/Services_Inventory.md`
|
||||
- `docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md`
|
||||
51
docs_v1.0/OPERATIONS/VERIFICATION_OLLAMA_2026-05-22.md
Normal file
51
docs_v1.0/OPERATIONS/VERIFICATION_OLLAMA_2026-05-22.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Ollama Verification Report
|
||||
|
||||
**Date**: 2026-05-22
|
||||
**Status**: Verified
|
||||
|
||||
---
|
||||
|
||||
## Source
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Source repo | `admin/ollama` (Gitea) |
|
||||
| Source path | `release/system/v1.0/services/src/ollama/` |
|
||||
| Git commit | `417b943d` (main branch) |
|
||||
| Binary | `/Users/accusys/momentry_resources/bin/ollama` |
|
||||
| Source build | ✅ Built from Gitea source with verified Go 1.26.3 |
|
||||
| Port | 11434 |
|
||||
| Current models | `gemma4:e4b` (text + vision) |
|
||||
|
||||
## Source Build
|
||||
|
||||
```bash
|
||||
# Build command (source → binary)
|
||||
export PATH="$GOROOT/bin:$PATH"
|
||||
cd /Users/accusys/momentry_core/release/system/v1.0/services/src/ollama
|
||||
go build -o /Users/accusys/momentry_resources/bin/ollama .
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
| Check | Result |
|
||||
|-------|--------|
|
||||
| Binary exists | ✅ |
|
||||
| API responding (`/api/tags`) | ✅ |
|
||||
| Port 11434 | ✅ Listening |
|
||||
| Text inference | ✅ |
|
||||
| Vision inference (image input) | ✅ |
|
||||
| Audio inference | ❌ Not supported by Ollama API yet |
|
||||
|
||||
## Notes
|
||||
|
||||
- Ollama built from Gitea-verified source (was Homebrew-managed before)
|
||||
- Source migration complete: GitHub → Gitea admin/ollama
|
||||
- Used for Gemma 4 E4B (text + vision)
|
||||
|
||||
---
|
||||
|
||||
## Linked Documents
|
||||
|
||||
- `docs_v1.0/OPERATIONS/Services_Inventory.md`
|
||||
- `docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md`
|
||||
@@ -116,6 +116,96 @@ Get status of the 5W1H agent pipeline for a file.
|
||||
| **Dimension** | 768 |
|
||||
| **Used by** | `parent_chunk_5w1h.py --embed`, story, 5W1H, search |
|
||||
|
||||
---
|
||||
|
||||
## POST /api/v1/agents/search
|
||||
|
||||
Conversational search assistant. Uses Gemma4 function calling to automatically decide which tools to call based on the user's natural language query. Supports multi-turn conversation.
|
||||
|
||||
### Request
|
||||
|
||||
```json
|
||||
{
|
||||
"query": "Audrey Hepburn 和 Cary Grant 第一次同框在哪個 frame?",
|
||||
"conversation_id": null,
|
||||
"file_uuid": null
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `query` | string | ✅ | 自然語言查詢 |
|
||||
| `conversation_id` | string | ❌ | 延續對話時傳入;新對話不傳 |
|
||||
| `file_uuid` | string | ❌ | Portal 有選中檔案時可指定 |
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"conversation_id": "conv_abc123",
|
||||
"answer": "在 Charade (1963) 中,Audrey Hepburn 與 Cary Grant 第一次同框在第 38619 幀(約 1544.76 秒)。",
|
||||
"need_input": false,
|
||||
"sources": [
|
||||
{
|
||||
"tool": "tkg_query",
|
||||
"result": "{\"first_cooccurrence\":{\"frame\":38619,\"timestamp_secs\":1544.76}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `conversation_id` | string | 後續對話需要傳入此 ID |
|
||||
| `answer` | string | Agent 的自然語言回答(或反問) |
|
||||
| `need_input` | boolean | `true` 表示 agent 需要更多資訊才能回答 |
|
||||
| `suggestions` | string[] | 建議用戶提供的線索(當 `need_input=true`) |
|
||||
| `sources` | array | 引用的工具執行結果 |
|
||||
|
||||
### Conversation Flow
|
||||
|
||||
```
|
||||
Round 1: POST /agents/search { query: "我想看男女主角同框" }
|
||||
→ need_input: true, suggestions: ["片名", "演員", "年代"]
|
||||
→ answer: "請問是哪部電影?請提供更多線索"
|
||||
|
||||
Round 2: POST /agents/search { query: "奧黛麗赫本", conversation_id: "..." }
|
||||
→ need_input: false
|
||||
→ answer: "找到 Charade (1963),Audrey Hepburn 和 Cary Grant..."
|
||||
```
|
||||
|
||||
### Available Tools
|
||||
|
||||
Agent 內部使用 Gemma4 function calling 自動調用以下工具:
|
||||
|
||||
| Tool | Description |
|
||||
|------|-------------|
|
||||
| `find_file` | 透過片名/演員/年份關鍵字搜尋影片,回傳 file_uuid + has_data 狀態 |
|
||||
| `list_files` | 列出近期註冊的影片 |
|
||||
| `tkg_query` | 查詢人物互動資料(7 種子類型:top_identities、first_cooccurrence、identity_details、mutual_gaze、interaction_network、identity_traces、file_info) |
|
||||
| `smart_search` | 文字內容 ILIKE 搜尋 chunk(可指定 file_uuid 限制範圍) |
|
||||
| `get_identity_detail` | 查詢單一身份的詳細資料(角色、TMDb 資訊) |
|
||||
| `get_file_info` | 查詢影片基本資訊(片長、解析度) |
|
||||
| `get_representative_frame` | 查詢影片最具代表性的 frame 資訊 |
|
||||
|
||||
### Design Principles
|
||||
|
||||
- **用戶不需要知道 file_uuid** — Agent 會自動用 `find_file` 搜尋或反問
|
||||
- **不推薦無資料的影片** — `has_data=false` 的影片不會被推薦給用戶
|
||||
- **多輪對話** — 透過 `conversation_id` 延續上下文,agent 會記得之前的交流
|
||||
- **並行工具呼叫** — Gemma4 可以一次呼叫多個工具再綜合回答
|
||||
|
||||
### Model
|
||||
|
||||
| Detail | Value |
|
||||
|--------|-------|
|
||||
| **LLM** | Gemma4 26B (Q5_K_M) |
|
||||
| **Engine** | llama.cpp at `localhost:8082` |
|
||||
| **Endpoint** | `/v1/chat/completions` (OpenAI-compatible) |
|
||||
| **Temperature** | 0.1 |
|
||||
| **Max rounds** | 5 (tool call iterations) |
|
||||
| **Conversation TTL** | 30 minutes |
|
||||
|
||||
---
|
||||
*Updated: 2026-05-19 12:49:24*
|
||||
*Updated: 2026-05-22*
|
||||
|
||||
268
release/system/v1.0/services/sources_manifest.json
Normal file
268
release/system/v1.0/services/sources_manifest.json
Normal file
@@ -0,0 +1,268 @@
|
||||
{
|
||||
"description": "System service source codes for verification/build",
|
||||
"base_path": "/Users/accusys/momentry_core/release/system/v1.0/services/src",
|
||||
"created": "2026-05-22",
|
||||
"services": [
|
||||
{
|
||||
"name": "GroundingDINO",
|
||||
"type": "directory",
|
||||
"git_last_commit": "856dde2 Grounded SAM 2 Release",
|
||||
"file_count": 97,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/GroundingDINO.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Services_Inventory.md"
|
||||
},
|
||||
{
|
||||
"name": "LibreOffice_26.2.3_MacOS_aarch64.dmg",
|
||||
"type": "file",
|
||||
"size_mb": 295.0,
|
||||
"sha256": "8ea6bdf67dbffc9c47104f73a3c98ed145ff26c00dde44c43633f5b3d741479f",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/LibreOffice_26.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Services_Inventory.md"
|
||||
},
|
||||
{
|
||||
"name": "cmake-4.2.0-macos-universal.tar.gz",
|
||||
"type": "file",
|
||||
"size_mb": 84.3,
|
||||
"sha256": "b8b040a06343b2b6bc090b03a9c2bb4e98037518846989fb7c40ebbf30655c5d",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/cmake.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "erpnext",
|
||||
"type": "directory",
|
||||
"git_last_commit": "fc54fd0 chore(release): Bumped to Version 15.107.0",
|
||||
"file_count": 4624,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/erpnext.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "ffmpeg-7.1.1.tar.xz",
|
||||
"type": "file",
|
||||
"size_mb": 11.0,
|
||||
"sha256": "733984395e0dbbe5c046abda2dc49a5544e7e0e1e2366bba849222ae9e3a03b1",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/ffmpeg.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Services_Inventory.md"
|
||||
},
|
||||
{
|
||||
"name": "frappe",
|
||||
"type": "directory",
|
||||
"git_last_commit": "7341623 chore(release): Bumped to Version 15.107.2",
|
||||
"file_count": 3272,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/frappe.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "freetype-2.13.3.tar.gz",
|
||||
"type": "file",
|
||||
"size_mb": 4.1,
|
||||
"sha256": "5c3a8e78f7b24c20b25b54ee575d6daa40007a5f4eea2845861c3409b3021747",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/freetype.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "freetype-2.13.3.tar.gz.sig",
|
||||
"type": "file",
|
||||
"size_mb": 0.0,
|
||||
"sha256": "f5d0969d4fea55e2f07d656ba33f448a777d4ac7dce1290bf4d6e1d1d99ef696",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/freetype.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "gitea",
|
||||
"type": "directory",
|
||||
"git_last_commit": "470b210 Add changelog for 1.25.1 and add missing chagnelog for 1.24.x (#35838)",
|
||||
"file_count": 5683,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/gitea.git",
|
||||
"verification_doc": "docs_v1.0/M4_HANDOVER/SERVICE_GO_GITEA_BUILD.md"
|
||||
},
|
||||
{
|
||||
"name": "go",
|
||||
"type": "directory",
|
||||
"git_last_commit": "9c8bf0e [release-branch.go1.26] go1.26.2",
|
||||
"file_count": 15048,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/go.git",
|
||||
"verification_doc": "docs_v1.0/M4_HANDOVER/SERVICE_GO_GITEA_BUILD.md"
|
||||
},
|
||||
{
|
||||
"name": "libreoffice-26.2.3.2.tar.xz",
|
||||
"type": "file",
|
||||
"size_mb": 292.5,
|
||||
"sha256": "254a641e0eec939364e157e2d9ddf4a55e1a42b5c688c22ce8e4945e97230a31",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/libreoffice.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "librsvg",
|
||||
"type": "directory",
|
||||
"git_last_commit": "60591b4 Merge branch 'embedded_svg' into 'main'",
|
||||
"file_count": 3931,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/librsvg.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "llama.cpp",
|
||||
"type": "directory",
|
||||
"git_last_commit": "fde69a3 examples : add llama-eval (#21152)",
|
||||
"file_count": 2773,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/llama.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Services_Inventory.md"
|
||||
},
|
||||
{
|
||||
"name": "macmon-0.7.2.tar.gz",
|
||||
"type": "file",
|
||||
"size_mb": 0.0,
|
||||
"sha256": "ac8169a4a59afe2a93e033dbf0215682d78a6dddf600398634d0192868787fed",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/macmon.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "mactop-latest.tar.gz",
|
||||
"type": "file",
|
||||
"size_mb": 15.0,
|
||||
"sha256": "64a895f2db96be558bc4e769534e6bdf4f4d9ac0ca8cd93873a9705dea95bd10",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/mactop.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "mermaid-js-mermaid-cli-11.14.0.tgz",
|
||||
"type": "file",
|
||||
"size_mb": 1.1,
|
||||
"sha256": "fec919124ef10078fcf06357fcec2214a28c52260579d1aaf677cbe37e8120d8",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/mermaid.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "odoo",
|
||||
"type": "directory",
|
||||
"git_last_commit": "43f5cead [FIX] account_edi_ubl_cii: avoid duplicated messages",
|
||||
"file_count": 47149,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/odoo.git"
|
||||
},
|
||||
{
|
||||
"name": "ollama",
|
||||
"type": "directory",
|
||||
"git_last_commit": "417b943d version bump",
|
||||
"file_count": 3753,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/ollama.git"
|
||||
},
|
||||
{
|
||||
"name": "paligemma",
|
||||
"type": "directory",
|
||||
"file_count": 1,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/paligemma.git",
|
||||
"verification_doc": "docs_v1.0/M4_HANDOVER/SERVICE_INVENTORY_V1.0.0.md"
|
||||
},
|
||||
{
|
||||
"name": "postgresql-18.3.tar.gz",
|
||||
"type": "file",
|
||||
"size_mb": 29.4,
|
||||
"sha256": "9e054ffd6e013da2c2c9a1bfd6e062c98875d340df080516551c96b9b0926a59",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/postgresql.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Services_Inventory.md"
|
||||
},
|
||||
{
|
||||
"name": "pyenv",
|
||||
"type": "directory",
|
||||
"git_last_commit": "0864daa miniconda 26, CI: switch check to 3.13 (#3447)",
|
||||
"file_count": 1530,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/pyenv.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "python_probe_deps.txt",
|
||||
"type": "file",
|
||||
"size_mb": 0.0,
|
||||
"sha256": "7c9db4b8c8367d47fc686326764f9e0138ee88dced95809b5eea2508c24aa6f9",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/python_probe_deps.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Services_Inventory.md"
|
||||
},
|
||||
{
|
||||
"name": "redis-7.4.3.tar.gz",
|
||||
"type": "file",
|
||||
"size_mb": 3.5,
|
||||
"sha256": "e1807d7c0f824f4c5450244ef50c1e596b8d09b35d03a83f4e018fb7316acf45",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/redis.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Services_Inventory.md"
|
||||
},
|
||||
{
|
||||
"name": "rsync-official-3.4.2.tar.gz",
|
||||
"type": "file",
|
||||
"size_mb": 1.2,
|
||||
"sha256": "ff10aa2c151cd4b2dbbe6135126dbc854046113d2dfb49572a348233267eb315",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/rsync.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Services_Inventory.md"
|
||||
},
|
||||
{
|
||||
"name": "rustc-1.92.0-src.tar.xz",
|
||||
"type": "file",
|
||||
"size_mb": 271.9,
|
||||
"sha256": "ebee170bfe4c4dfc59521a101de651e5534f4dae889756a5c97ca9ea40d0c307",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/rustc.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "rustup-1.28.1.tar.gz",
|
||||
"type": "file",
|
||||
"size_mb": 1.0,
|
||||
"sha256": "2def2f9a0a4a21c80f862c0797c2d76e765e0e7237e1e41f28324722ab912bac",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/rustup.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "sftpgo-main.tar.gz",
|
||||
"type": "file",
|
||||
"size_mb": 9.7,
|
||||
"sha256": "6607334148917dd80a687706a3ae63ea8c532d10c6717c87491da23939c96d4a",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/sftpgo.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/SFTPGo_Verification.md"
|
||||
},
|
||||
{
|
||||
"name": "sqlite-amalgamation-3490100.zip",
|
||||
"type": "file",
|
||||
"size_mb": 2.8,
|
||||
"sha256": "6cebd1d8403fc58c30e93939b246f3e6e58d0765a5cd50546f16c00fd805d2c3",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/sqlite.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "sqlite-vec",
|
||||
"type": "directory",
|
||||
"git_last_commit": "4e2dfcb v0.1.10-alpha.3",
|
||||
"file_count": 348,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/sqlite.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "swift-6.3.1-RELEASE.tar.gz",
|
||||
"type": "file",
|
||||
"size_mb": 37.5,
|
||||
"sha256": "e1500085c591f43d70f0289eff41b5838376df81fe7b2ad750c6a4c4dae91e1b",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/swift.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "vec0.dylib",
|
||||
"type": "file",
|
||||
"size_mb": 0.2,
|
||||
"sha256": "b825f7346e130c794d81f9e441e5ca1bcb69edff3dc0d5755cab7c6ba5701cc9",
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/vec0.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Services_Inventory.md"
|
||||
},
|
||||
{
|
||||
"name": "x264",
|
||||
"type": "directory",
|
||||
"git_last_commit": "0480cb0 riscv64: add compile support",
|
||||
"file_count": 485,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/x264.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Brew_To_Source_Migration.md"
|
||||
},
|
||||
{
|
||||
"name": "yt-dlp",
|
||||
"type": "directory",
|
||||
"git_last_commit": "3ddbebb Release 2025.03.27",
|
||||
"file_count": 1279,
|
||||
"gitea_repo": "http://192.168.110.200:3000/admin/yt.git",
|
||||
"verification_doc": "docs_v1.0/OPERATIONS/Services_Inventory.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -25,8 +25,8 @@ echo ""
|
||||
LOG_DIR="/Users/accusys/momentry/logs"
|
||||
|
||||
# ── 1. PostgreSQL ──
|
||||
echo -e "${YELLOW}[1/7] PostgreSQL${NC}"
|
||||
PG_DATA="/Users/accusys/pgsql/data"
|
||||
echo -e "${YELLOW}[1/9] PostgreSQL${NC}"
|
||||
PG_DATA="/Users/accusys/momentry/var/postgresql"
|
||||
PG_BIN="/Users/accusys/pgsql/18.3/bin"
|
||||
if $PG_BIN/pg_isready -q 2>/dev/null; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
@@ -37,7 +37,7 @@ else
|
||||
fi
|
||||
|
||||
# ── 2. Redis ──
|
||||
echo -e "${YELLOW}[2/7] Redis${NC}"
|
||||
echo -e "${YELLOW}[2/9] Redis${NC}"
|
||||
if redis-cli ping 2>/dev/null | grep -q PONG; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
@@ -47,14 +47,15 @@ else
|
||||
fi
|
||||
|
||||
# ── 3. Qdrant ──
|
||||
echo -e "${YELLOW}[3/7] Qdrant${NC}"
|
||||
QDRANT_BIN="${PROJECT_DIR}/services/qdrant/target/release/qdrant"
|
||||
echo -e "${YELLOW}[3/9] Qdrant${NC}"
|
||||
QDRANT_BIN="/Users/accusys/momentry_resources/bin/qdrant"
|
||||
QDRANT_STORAGE="/Users/accusys/momentry/qdrant_storage"
|
||||
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 http://localhost:6333/healthz 2>/dev/null | grep -q 200; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
mkdir -p "$QDRANT_STORAGE"
|
||||
nohup "$QDRANT_BIN" > "$LOG_DIR/qdrant.log" 2>&1 &
|
||||
cd "$QDRANT_STORAGE" && "$QDRANT_BIN" > "$LOG_DIR/qdrant.log" 2>&1 &
|
||||
cd "$PROJECT_DIR"
|
||||
for i in $(seq 1 15); do
|
||||
sleep 2
|
||||
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 2 http://localhost:6333/healthz 2>/dev/null | grep -q 200; then
|
||||
@@ -65,7 +66,7 @@ else
|
||||
fi
|
||||
|
||||
# ── 4. Qdrant Collection ──
|
||||
echo -e "${YELLOW}[4/7] Qdrant Collection${NC}"
|
||||
echo -e "${YELLOW}[4/9] Qdrant Collection${NC}"
|
||||
source "$ENV_FILE" 2>/dev/null || true
|
||||
COLLECTION="${QDRANT_COLLECTION:-momentry_dev_rule1_v2}"
|
||||
EXISTS=$(curl -s "http://localhost:6333/collections/$COLLECTION" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result',{}).get('status','not_found'))" 2>/dev/null)
|
||||
@@ -79,13 +80,13 @@ curl -s "http://localhost:6333/collections/$COLLECTION" 2>/dev/null | python3 -c
|
||||
check "collection '$COLLECTION' ready"
|
||||
|
||||
# ── 5. LLM (Gemma4 / llama.cpp) ──
|
||||
echo -e "${YELLOW}[5/7] LLM Server (Gemma4)${NC}"
|
||||
echo -e "${YELLOW}[5/9] LLM Server (Gemma4)${NC}"
|
||||
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://localhost:8082/health 2>/dev/null | grep -q 200; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
LLM_BIN="/Users/accusys/llama/bin/llama-server"
|
||||
LLM_MODEL="/Users/accusys/models/google_gemma-4-26B-A4B-it-Q5_K_M.gguf"
|
||||
nohup "$LLM_BIN" -m "$LLM_MODEL" --host 0.0.0.0 --port 8082 -ngl 99 -c 16384 --temp 0.1 --mlock --reasoning off > "$LOG_DIR/llama_server.log" 2>&1 &
|
||||
LLM_BIN="/Users/accusys/momentry_resources/llama/bin/llama-server"
|
||||
LLM_MODEL="/Users/accusys/momentry/models/llm/google_gemma-4-26B-A4B-it-Q5_K_M.gguf"
|
||||
"$LLM_BIN" -m "$LLM_MODEL" --host 0.0.0.0 --port 8082 -ngl 99 -c 16384 --temp 0.1 --mlock --reasoning off > "$LOG_DIR/llama_server.log" 2>&1 &
|
||||
echo -e " ${YELLOW}⏳ loading model (~30s)...${NC}"
|
||||
for i in $(seq 1 30); do
|
||||
sleep 2
|
||||
@@ -96,8 +97,27 @@ else
|
||||
curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 http://localhost:8082/health 2>/dev/null | grep -q 200; check "started"
|
||||
fi
|
||||
|
||||
# ── 6. Embedding Server ──
|
||||
echo -e "${YELLOW}[6/7] EmbeddingGemma${NC}"
|
||||
# ── 6. MariaDB ──
|
||||
echo -e "${YELLOW}[6/9] MariaDB${NC}"
|
||||
MARIADB_BIN="/Users/accusys/momentry_resources/mariadb/bin/mariadbd"
|
||||
MARIADB_DATA="/Users/accusys/momentry/var/mysql"
|
||||
if [ -S /tmp/mysql.sock ] || /Users/accusys/momentry_resources/mariadb/bin/mariadb-admin ping --silent 2>/dev/null; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
mkdir -p "$MARIADB_DATA"
|
||||
"$MARIADB_BIN" --datadir="$MARIADB_DATA" --socket=/tmp/mysql.sock --port=3306 \
|
||||
> "$LOG_DIR/mariadb.log" 2>&1 &
|
||||
for i in $(seq 1 10); do
|
||||
sleep 2
|
||||
if /Users/accusys/momentry_resources/mariadb/bin/mariadb-admin ping --silent 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
/Users/accusys/momentry_resources/mariadb/bin/mariadb-admin ping --silent 2>/dev/null; check "started"
|
||||
fi
|
||||
|
||||
# ── 7. Embedding Server ──
|
||||
echo -e "${YELLOW}[7/9] EmbeddingGemma${NC}"
|
||||
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://localhost:11436/health 2>/dev/null | grep -q 200; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
@@ -107,22 +127,34 @@ else
|
||||
VENV_PYTHON="/opt/homebrew/bin/python3.11"
|
||||
pip install flask 2>/dev/null || true
|
||||
fi
|
||||
nohup "$VENV_PYTHON" "$EMBED_SCRIPT" --port 11436 > "$LOG_DIR/embed.log" 2>&1 &
|
||||
"$VENV_PYTHON" "$EMBED_SCRIPT" --port 11436 > "$LOG_DIR/embed.log" 2>&1 &
|
||||
sleep 5
|
||||
curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://localhost:11436/health 2>/dev/null | grep -q 200; check "started"
|
||||
fi
|
||||
|
||||
# ── 7. Playground Server ──
|
||||
echo -e "${YELLOW}[7/7] Playground API Server${NC}"
|
||||
# ── 8. Playground Server ──
|
||||
echo -e "${YELLOW}[8/9] Playground API Server${NC}"
|
||||
if curl -s -o /dev/null -w "%{http_code}" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" --connect-timeout 5 http://127.0.0.1:3003/api/v1/agents/5w1h/status 2>/dev/null | grep -q 200; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
cd "$PROJECT_DIR"
|
||||
nohup target/debug/momentry_playground server > "$LOG_DIR/playground.log" 2>&1 &
|
||||
target/debug/momentry_playground server > "$LOG_DIR/playground.log" 2>&1 &
|
||||
sleep 4
|
||||
curl -s -o /dev/null -w "%{http_code}" -H "X-API-Key: muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69" --connect-timeout 5 http://127.0.0.1:3003/api/v1/agents/5w1h/status 2>/dev/null | grep -q 200; check "started"
|
||||
fi
|
||||
|
||||
# ── 9. Caddy ──
|
||||
echo -e "${YELLOW}[9/9] Caddy${NC}"
|
||||
CADDY_BIN="/Users/accusys/momentry_resources/bin/caddy"
|
||||
CADDY_CONFIG="/Users/accusys/momentry/etc/Caddyfile"
|
||||
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 http://127.0.0.1:2019/config/ 2>/dev/null | grep -q 200; then
|
||||
echo -e " ${GREEN}✅${NC} already running"
|
||||
else
|
||||
"$CADDY_BIN" run --config "$CADDY_CONFIG" > "$LOG_DIR/caddy.log" 2>&1 &
|
||||
sleep 3
|
||||
curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 http://127.0.0.1:2019/config/ 2>/dev/null | grep -q 200; check "started"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ ${#FAILURES[@]} -eq 0 ]; then
|
||||
echo -e "${GREEN}====================================${NC}"
|
||||
@@ -136,9 +168,12 @@ else
|
||||
fi
|
||||
echo ""
|
||||
echo " Playground: http://127.0.0.1:3003"
|
||||
echo " Caddy: http://127.0.0.1:2019"
|
||||
echo " WordPress: http://m5max128wp.momentry.ddns.net:8081"
|
||||
echo " LLM: http://127.0.0.1:8082"
|
||||
echo " Embedding: http://127.0.0.1:11436"
|
||||
echo " Qdrant: http://localhost:6333"
|
||||
echo " PostgreSQL: localhost:5432"
|
||||
echo " Redis: localhost:6379"
|
||||
echo " MariaDB: localhost:3306"
|
||||
echo ""
|
||||
|
||||
@@ -41,10 +41,10 @@ async fn translate_text(
|
||||
req.target_language, req.text
|
||||
);
|
||||
|
||||
// Call Gemma4 via llama.cpp (port 8082, OpenAI-compatible API)
|
||||
// Call LLM via configurable endpoint
|
||||
let client = Client::new();
|
||||
let llm_url = "http://localhost:8082/v1/chat/completions";
|
||||
let model = "google_gemma-4-26B-A4B-it-Q5_K_M.gguf".to_string();
|
||||
let llm_url = crate::core::config::llm::CHAT_URL.as_str();
|
||||
let model = crate::core::config::llm::CHAT_MODEL.as_str();
|
||||
|
||||
let body = serde_json::json!({
|
||||
"model": model,
|
||||
@@ -71,20 +71,15 @@ async fn translate_text(
|
||||
)
|
||||
})?;
|
||||
|
||||
let translated_text = llm_resp
|
||||
.get("choices")
|
||||
.and_then(|c| c.as_array())
|
||||
.and_then(|c| c.first())
|
||||
.and_then(|c| c.get("message"))
|
||||
.and_then(|m| m.get("content"))
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("Translation failed")
|
||||
let translated_text = llm_resp["choices"][0]["message"]["content"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
Ok(Json(TranslationResponse {
|
||||
success: true,
|
||||
translated_text,
|
||||
source_language_detected: req.source_language.unwrap_or("unknown".to_string()),
|
||||
model_used: model,
|
||||
source_language_detected: req.source_language.unwrap_or_else(|| "auto".to_string()),
|
||||
model_used: model.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ const SYSTEM_PROMPT: &str = r#"你是 Momentry 影片分析助手。回答用戶
|
||||
## 引導規則
|
||||
- 如果用戶沒說片名 → 用 find_file 搜尋,如果名稱不明確就反問
|
||||
- 反問時提供 suggestions,例如演員名、年代
|
||||
- **如果影片的 has_data 為 false,代表尚未完成處理,不要推薦用戶使用。引導用戶選擇 has_data=true 的影片**
|
||||
- 不要輸出 JSON,用自然語言回答
|
||||
- 引用資料時附上具體數字(frame 編號、時間秒數)
|
||||
|
||||
@@ -175,10 +176,14 @@ fn make_tools(pool: &sqlx::PgPool) -> Vec<ToolDef> {
|
||||
async fn exec_find_file(pool: &sqlx::PgPool, args: &serde_json::Value) -> Result<String, String> {
|
||||
let query = args.get("query").and_then(|v| v.as_str()).unwrap_or("");
|
||||
let videos = schema::table_name("videos");
|
||||
let fd_table = schema::table_name("face_detections");
|
||||
let like = format!("%{}%", query);
|
||||
let rows: Vec<(String, String)> = sqlx::query_as(&format!(
|
||||
"SELECT file_uuid::text, file_name FROM {} WHERE file_name ILIKE $1 ORDER BY created_at DESC LIMIT 10",
|
||||
videos
|
||||
let rows: Vec<(String, String, bool)> = sqlx::query_as(&format!(
|
||||
"SELECT v.file_uuid::text, v.file_name, \
|
||||
(SELECT COUNT(*) FROM {} fd WHERE fd.file_uuid = v.file_uuid) > 0 AS has_data \
|
||||
FROM {} v WHERE v.file_name ILIKE $1 \
|
||||
ORDER BY v.created_at DESC LIMIT 10",
|
||||
fd_table, videos
|
||||
))
|
||||
.bind(&like)
|
||||
.fetch_all(pool)
|
||||
@@ -188,8 +193,8 @@ async fn exec_find_file(pool: &sqlx::PgPool, args: &serde_json::Value) -> Result
|
||||
if rows.is_empty() {
|
||||
return Ok(serde_json::json!({"found": false, "message": "No files match the query. Try different keywords."}).to_string());
|
||||
}
|
||||
let files: Vec<serde_json::Value> = rows.into_iter().map(|(u, n)| {
|
||||
serde_json::json!({"file_uuid": u, "file_name": n})
|
||||
let files: Vec<serde_json::Value> = rows.into_iter().map(|(u, n, hd)| {
|
||||
serde_json::json!({"file_uuid": u, "file_name": n, "has_data": hd})
|
||||
}).collect();
|
||||
Ok(serde_json::json!({"found": true, "files": files}).to_string())
|
||||
}
|
||||
@@ -197,17 +202,20 @@ async fn exec_find_file(pool: &sqlx::PgPool, args: &serde_json::Value) -> Result
|
||||
async fn exec_list_files(pool: &sqlx::PgPool, args: &serde_json::Value) -> Result<String, String> {
|
||||
let limit = args.get("limit").and_then(|v| v.as_i64()).unwrap_or(10);
|
||||
let videos = schema::table_name("videos");
|
||||
let rows: Vec<(String, String)> = sqlx::query_as(&format!(
|
||||
"SELECT file_uuid::text, file_name FROM {} ORDER BY created_at DESC LIMIT $1",
|
||||
videos
|
||||
let fd_table = schema::table_name("face_detections");
|
||||
let rows: Vec<(String, String, bool)> = sqlx::query_as(&format!(
|
||||
"SELECT v.file_uuid::text, v.file_name, \
|
||||
(SELECT COUNT(*) FROM {} fd WHERE fd.file_uuid = v.file_uuid) > 0 AS has_data \
|
||||
FROM {} v ORDER BY v.created_at DESC LIMIT $1",
|
||||
fd_table, videos
|
||||
))
|
||||
.bind(limit)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let files: Vec<serde_json::Value> = rows.into_iter().map(|(u, n)| {
|
||||
serde_json::json!({"file_uuid": u, "file_name": n})
|
||||
let files: Vec<serde_json::Value> = rows.into_iter().map(|(u, n, hd)| {
|
||||
serde_json::json!({"file_uuid": u, "file_name": n, "has_data": hd})
|
||||
}).collect();
|
||||
Ok(serde_json::json!({"files": files}).to_string())
|
||||
}
|
||||
|
||||
@@ -96,27 +96,11 @@ struct SceneSummaryResult {
|
||||
// ── LLM Endpoint ──
|
||||
|
||||
fn llm_base_url() -> String {
|
||||
let v = std::env::var("MOMENTRY_LLM_URL");
|
||||
if v.is_ok() {
|
||||
return v.unwrap();
|
||||
}
|
||||
let v = std::env::var("MOMENTRY_LLM_SUMMARY_URL");
|
||||
if v.is_ok() {
|
||||
return v.unwrap();
|
||||
}
|
||||
"http://localhost:8082/v1/chat/completions".to_string()
|
||||
crate::core::config::llm::SUMMARY_URL.clone()
|
||||
}
|
||||
|
||||
fn llm_model() -> String {
|
||||
let v = std::env::var("MOMENTRY_LLM_MODEL");
|
||||
if v.is_ok() {
|
||||
return v.unwrap();
|
||||
}
|
||||
let v = std::env::var("MOMENTRY_LLM_SUMMARY_MODEL");
|
||||
if v.is_ok() {
|
||||
return v.unwrap();
|
||||
}
|
||||
"google_gemma-4-26B-A4B-it-Q5_K_M.gguf".to_string()
|
||||
crate::core::config::llm::SUMMARY_MODEL.clone()
|
||||
}
|
||||
|
||||
// ── Data Fetching ──
|
||||
|
||||
@@ -668,7 +668,7 @@ pub struct RegisterResourceRequest {
|
||||
pub struct ResourceResponse {
|
||||
pub success: bool,
|
||||
pub message: String,
|
||||
pub data: Option<ResourceItem>,
|
||||
pub data: Option<Vec<ResourceItem>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
@@ -677,6 +677,8 @@ pub struct ResourceItem {
|
||||
pub resource_type: String,
|
||||
pub category: String,
|
||||
pub capabilities: Option<serde_json::Value>,
|
||||
pub config: Option<serde_json::Value>,
|
||||
pub metadata: Option<serde_json::Value>,
|
||||
pub status: String,
|
||||
pub last_heartbeat: Option<chrono::DateTime<chrono::Utc>>,
|
||||
}
|
||||
@@ -743,22 +745,24 @@ async fn list_resources(
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||
|
||||
let data: Vec<ResourceItem> = records
|
||||
.into_iter()
|
||||
.map(|r| ResourceItem {
|
||||
resource_id: r.resource_id,
|
||||
resource_type: r.resource_type,
|
||||
category: r.category,
|
||||
capabilities: r.capabilities,
|
||||
status: r.status,
|
||||
last_heartbeat: r.last_heartbeat,
|
||||
})
|
||||
.collect();
|
||||
let data: Vec<ResourceItem> = records
|
||||
.into_iter()
|
||||
.map(|r| ResourceItem {
|
||||
resource_id: r.resource_id,
|
||||
resource_type: r.resource_type,
|
||||
category: r.category,
|
||||
capabilities: r.capabilities,
|
||||
config: r.config,
|
||||
metadata: r.metadata,
|
||||
status: r.status,
|
||||
last_heartbeat: r.last_heartbeat,
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(Json(ResourceResponse {
|
||||
success: true,
|
||||
message: "Resources listed".to_string(),
|
||||
data: None,
|
||||
data: Some(data),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use axum::Router;
|
||||
use tokio::time::timeout;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
|
||||
use crate::core::cache::{MongoCache, RedisCache};
|
||||
@@ -30,7 +33,21 @@ pub async fn start_server(host: &str, port: u16) -> anyhow::Result<()> {
|
||||
health::init_server_state(host, port);
|
||||
|
||||
let embedder = std::sync::Arc::new(Embedder::new("nomic-embed-text-v2-moe:latest".to_string()));
|
||||
let mongo_cache = MongoCache::init().await?;
|
||||
|
||||
// MongoDB is ONLY a cache layer — if unavailable, the server continues
|
||||
// with Redis cache alone. This keeps both 3002 and 3003 bootable
|
||||
// without requiring MongoDB to be installed or running.
|
||||
let mongo_cache = match timeout(Duration::from_secs(5), MongoCache::init()).await {
|
||||
Ok(Ok(cache)) => cache,
|
||||
Ok(Err(e)) => {
|
||||
tracing::warn!("MongoDB cache unavailable (continuing without): {e}");
|
||||
MongoCache::disabled().await
|
||||
}
|
||||
Err(_) => {
|
||||
tracing::warn!("MongoDB init timed out (continuing without cache)");
|
||||
MongoCache::disabled().await
|
||||
}
|
||||
};
|
||||
let redis_cache = RedisCache::new()?;
|
||||
let db = PostgresDb::init().await?;
|
||||
|
||||
|
||||
19
src/core/cache/mongo_cache.rs
vendored
19
src/core/cache/mongo_cache.rs
vendored
@@ -80,6 +80,25 @@ impl MongoCache {
|
||||
Ok(cache)
|
||||
}
|
||||
|
||||
/// Create a disabled cache instance — all ops are no-ops (is_enabled() = false).
|
||||
/// Used when MongoDB is unavailable; Redis cache continues independently.
|
||||
pub async fn disabled() -> Self {
|
||||
let client = Client::with_uri_str("mongodb://localhost:27017")
|
||||
.await
|
||||
.expect("disabled mongo client (lazy — no actual connect)");
|
||||
let db = client.database("disabled_cache");
|
||||
Self {
|
||||
client,
|
||||
db: db.clone(),
|
||||
collection: db.collection("disabled"),
|
||||
settings: CacheSettings {
|
||||
enabled: false,
|
||||
..Default::default()
|
||||
},
|
||||
initialized: Arc::new(RwLock::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
async fn ensure_indexes(&self) -> Result<()> {
|
||||
let mut guard = self.initialized.write().await;
|
||||
if *guard {
|
||||
|
||||
@@ -216,13 +216,47 @@ pub mod cache {
|
||||
pub mod llm {
|
||||
use super::*;
|
||||
|
||||
/// Chat / function-calling LLM endpoint (agents/search, translation, etc.)
|
||||
/// Default: http://127.0.0.1:8082/v1/chat/completions
|
||||
pub static CHAT_URL: Lazy<String> = Lazy::new(|| {
|
||||
env::var("MOMENTRY_LLM_CHAT_URL")
|
||||
.or_else(|_| env::var("MOMENTRY_LLM_SUMMARY_URL"))
|
||||
.or_else(|_| env::var("MOMENTRY_LLM_URL"))
|
||||
.unwrap_or_else(|_| "http://127.0.0.1:8082/v1/chat/completions".to_string())
|
||||
});
|
||||
|
||||
pub static CHAT_MODEL: Lazy<String> = Lazy::new(|| {
|
||||
env::var("MOMENTRY_LLM_CHAT_MODEL")
|
||||
.or_else(|_| env::var("MOMENTRY_LLM_SUMMARY_MODEL"))
|
||||
.or_else(|_| env::var("MOMENTRY_LLM_MODEL"))
|
||||
.unwrap_or_else(|_| "google_gemma-4-26B-A4B-it-Q5_K_M.gguf".to_string())
|
||||
});
|
||||
|
||||
/// Vision LLM endpoint (frame analysis, OCR). Can be same as CHAT_URL or different.
|
||||
/// Default: falls back to CHAT_URL
|
||||
pub static VISION_URL: Lazy<String> = Lazy::new(|| {
|
||||
env::var("MOMENTRY_LLM_VISION_URL")
|
||||
.unwrap_or_else(|_| CHAT_URL.clone())
|
||||
});
|
||||
|
||||
pub static VISION_MODEL: Lazy<String> = Lazy::new(|| {
|
||||
env::var("MOMENTRY_LLM_VISION_MODEL")
|
||||
.unwrap_or_else(|_| CHAT_MODEL.clone())
|
||||
});
|
||||
|
||||
/// Text summary LLM endpoint (5W1H, story). Can be same as CHAT_URL or different.
|
||||
pub static SUMMARY_URL: Lazy<String> = Lazy::new(|| {
|
||||
env::var("MOMENTRY_LLM_SUMMARY_URL")
|
||||
.unwrap_or_else(|_| "http://127.0.0.1:8081/v1/chat/completions".to_string())
|
||||
.ok()
|
||||
.or_else(|| Some(CHAT_URL.clone()))
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
pub static SUMMARY_MODEL: Lazy<String> = Lazy::new(|| {
|
||||
env::var("MOMENTRY_LLM_SUMMARY_MODEL").unwrap_or_else(|_| "gemma4".to_string())
|
||||
env::var("MOMENTRY_LLM_SUMMARY_MODEL")
|
||||
.ok()
|
||||
.or_else(|| Some(CHAT_MODEL.clone()))
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
pub static SUMMARY_TIMEOUT_SECS: Lazy<u64> = Lazy::new(|| {
|
||||
@@ -237,6 +271,13 @@ pub mod llm {
|
||||
.map(|v| v == "true" || v == "1")
|
||||
.unwrap_or(true)
|
||||
});
|
||||
|
||||
pub static CHAT_TIMEOUT_SECS: Lazy<u64> = Lazy::new(|| {
|
||||
env::var("MOMENTRY_LLM_CHAT_TIMEOUT")
|
||||
.unwrap_or_else(|_| "120".to_string())
|
||||
.parse()
|
||||
.unwrap_or(120)
|
||||
});
|
||||
}
|
||||
|
||||
pub static SFTPGO_BASE_URL: Lazy<String> = Lazy::new(|| {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::core::config;
|
||||
|
||||
/// A tool/function definition for Gemma4 function calling
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct ToolDef {
|
||||
@@ -75,18 +77,24 @@ pub enum LlmResponse {
|
||||
ToolCalls(Vec<ToolCall>),
|
||||
}
|
||||
|
||||
/// Get the LLM chat URL with fallback chain
|
||||
/// Get the LLM chat URL from centralized config
|
||||
pub fn llm_chat_url() -> String {
|
||||
std::env::var("MOMENTRY_LLM_URL")
|
||||
.or_else(|_| std::env::var("MOMENTRY_LLM_SUMMARY_URL"))
|
||||
.unwrap_or_else(|_| "http://localhost:8082/v1/chat/completions".to_string())
|
||||
config::llm::CHAT_URL.clone()
|
||||
}
|
||||
|
||||
/// Get the LLM model name
|
||||
/// Get the LLM model name from centralized config
|
||||
pub fn llm_model() -> String {
|
||||
std::env::var("MOMENTRY_LLM_MODEL")
|
||||
.or_else(|_| std::env::var("MOMENTRY_LLM_SUMMARY_MODEL"))
|
||||
.unwrap_or_else(|_| "google_gemma-4-26B-A4B-it-Q5_K_M.gguf".to_string())
|
||||
config::llm::CHAT_MODEL.clone()
|
||||
}
|
||||
|
||||
/// Get the vision LLM URL
|
||||
pub fn llm_vision_url() -> String {
|
||||
config::llm::VISION_URL.clone()
|
||||
}
|
||||
|
||||
/// Get the vision LLM model name
|
||||
pub fn llm_vision_model() -> String {
|
||||
config::llm::VISION_MODEL.clone()
|
||||
}
|
||||
|
||||
/// Build a tool definition JSON for function calling
|
||||
@@ -113,7 +121,9 @@ pub async fn call_llm(
|
||||
timeout_secs: u64,
|
||||
) -> anyhow::Result<LlmResponse> {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(timeout_secs))
|
||||
.timeout(std::time::Duration::from_secs(
|
||||
if timeout_secs > 0 { timeout_secs } else { *config::llm::CHAT_TIMEOUT_SECS },
|
||||
))
|
||||
.build()?;
|
||||
|
||||
let req = ChatRequest {
|
||||
|
||||
Reference in New Issue
Block a user