LawrenceBai commited on
Commit
dffbff8
·
0 Parent(s):

feat: Initial commit for Hugging Face deployment

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .DS_Store +0 -0
  2. .cursor/rules/reviewgate.mdc +131 -0
  3. .gitattributes +3 -0
  4. Dockerfile +32 -0
  5. Procfile +1 -0
  6. app.py +279 -0
  7. env_example.txt +11 -0
  8. features/keypoints/eat_001_aug_rotate_keypoints.npy +3 -0
  9. features/keypoints/eat_001_aug_shift_keypoints.npy +3 -0
  10. features/keypoints/eat_001_keypoints.npy +3 -0
  11. features/keypoints/eat_002_aug_rotate_keypoints.npy +3 -0
  12. features/keypoints/eat_002_aug_shift_keypoints.npy +3 -0
  13. features/keypoints/eat_002_keypoints.npy +3 -0
  14. features/keypoints/eat_003_aug_rotate_keypoints.npy +3 -0
  15. features/keypoints/eat_003_keypoints.npy +3 -0
  16. features/keypoints/eat_004_aug_flip_keypoints.npy +3 -0
  17. features/keypoints/eat_004_aug_shift_keypoints.npy +3 -0
  18. features/keypoints/eat_004_keypoints.npy +3 -0
  19. features/keypoints/eat_005_aug_flip_keypoints.npy +3 -0
  20. features/keypoints/eat_005_keypoints.npy +3 -0
  21. features/keypoints/eat_006_keypoints.npy +3 -0
  22. features/keypoints/eat_007_aug_flip_keypoints.npy +3 -0
  23. features/keypoints/eat_007_keypoints.npy +3 -0
  24. features/keypoints/eat_008_aug_flip_keypoints.npy +3 -0
  25. features/keypoints/eat_008_keypoints.npy +3 -0
  26. features/keypoints/eat_009_aug_flip_keypoints.npy +3 -0
  27. features/keypoints/eat_009_aug_rotate_keypoints.npy +3 -0
  28. features/keypoints/eat_009_aug_shift_keypoints.npy +3 -0
  29. features/keypoints/eat_009_keypoints.npy +3 -0
  30. features/keypoints/eat_010_aug_flip_keypoints.npy +3 -0
  31. features/keypoints/eat_010_aug_rotate_keypoints.npy +3 -0
  32. features/keypoints/eat_010_aug_shift_keypoints.npy +3 -0
  33. features/keypoints/eat_010_keypoints.npy +3 -0
  34. features/keypoints/eat_011_aug_shift_keypoints.npy +3 -0
  35. features/keypoints/eat_011_keypoints.npy +3 -0
  36. features/keypoints/eat_012_aug_shift_keypoints.npy +3 -0
  37. features/keypoints/eat_012_keypoints.npy +3 -0
  38. features/keypoints/eat_013_keypoints.npy +3 -0
  39. features/keypoints/eat_014_aug_shift_keypoints.npy +3 -0
  40. features/keypoints/eat_014_keypoints.npy +3 -0
  41. features/keypoints/eat_015_aug_flip_keypoints.npy +3 -0
  42. features/keypoints/eat_015_aug_shift_keypoints.npy +3 -0
  43. features/keypoints/eat_015_keypoints.npy +3 -0
  44. features/keypoints/eat_016_aug_flip_keypoints.npy +3 -0
  45. features/keypoints/eat_016_aug_shift_keypoints.npy +3 -0
  46. features/keypoints/eat_016_keypoints.npy +3 -0
  47. features/keypoints/eat_017_aug_rotate_keypoints.npy +3 -0
  48. features/keypoints/eat_017_aug_shift_keypoints.npy +3 -0
  49. features/keypoints/eat_017_keypoints.npy +3 -0
  50. features/keypoints/eat_018_keypoints.npy +3 -0
.DS_Store ADDED
Binary file (8.2 kB). View file
 
.cursor/rules/reviewgate.mdc ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ description:
3
+ globs:
4
+ alwaysApply: true
5
+ ---
6
+ ---
7
+ description:
8
+ globs:
9
+ alwaysApply: true
10
+ ---
11
+ # AI Agent Protocol: Self-Contained Mandatory Interactive Final Review Loop with Auto-Script Creation (Version 3)
12
+
13
+ ## 0. User Advisory & Safety:
14
+ This is an advanced, experimental rule. It instructs the AI Agent to check for, potentially create, and then execute a Python script (`final_review_gate.py`) in your project's root directory to facilitate an interactive review loop. By using this rule, you acknowledge that the AI will attempt file system operations (read, write, execute) as directed. Ensure the AI's execution environment has appropriate permissions if you expect script auto-creation to work, and be aware of the security implications.
15
+
16
+ ## 1. Core Principle & Context:
17
+ Your primary task processing includes a **mandatory final interactive review stage** driven by user input through a specific Python script (`final_review_gate.py`). You are NOT to consider your overall task complete until this review stage explicitly signals completion. This protocol leverages your known capability to run commands in a sub-terminal (displayed within the chat interface) and read their output.
18
+
19
+ ## Phase 0: Ensure `final_review_gate.py` Script Exists
20
+ (This phase is executed ONCE per user request that triggers this overall protocol, or if the script is missing or its content is incorrect.)
21
+
22
+ 1. **Define Script Details:**
23
+ * **Script Name:** `final_review_gate.py`
24
+ * **Target Location:** Directly in the root of the current project/workspace.
25
+ * **Python Script Content (ensure this exact content is used):**
26
+ ```python
27
+ # final_review_gate.py
28
+ import sys
29
+ import os
30
+
31
+ if __name__ == "__main__":
32
+ # Try to make stdout unbuffered for more responsive interaction.
33
+ # This might not work on all platforms or if stdout is not a TTY,
34
+ # but it's a good practice for this kind of interactive script.
35
+ try:
36
+ sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', buffering=1)
37
+ except Exception:
38
+ pass # Ignore if unbuffering fails, e.g., in certain environments
39
+
40
+ try:
41
+ sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', buffering=1)
42
+ except Exception:
43
+ pass # Ignore
44
+
45
+ print("--- FINAL REVIEW GATE ACTIVE ---", flush=True)
46
+ print("AI has completed its primary actions. Awaiting your review or further sub-prompts.", flush=True)
47
+ print("Type your sub-prompt, or one of: 'TASK_COMPLETE', 'Done', 'Quit', 'q' to signal completion.", flush=True) # MODIFIED
48
+
49
+ active_session = True
50
+ while active_session:
51
+ try:
52
+ # Signal that the script is ready for input.
53
+ # The AI doesn't need to parse this, but it's good for user visibility.
54
+ print("REVIEW_GATE_AWAITING_INPUT:", end="", flush=True)
55
+
56
+ line = sys.stdin.readline()
57
+
58
+ if not line: # EOF
59
+ print("--- REVIEW GATE: STDIN CLOSED (EOF), EXITING SCRIPT ---", flush=True)
60
+ active_session = False
61
+ break
62
+
63
+ user_input = line.strip()
64
+
65
+ # Check for exit conditions
66
+ if user_input.upper() in ['TASK_COMPLETE', 'DONE', 'QUIT', 'Q']: # MODIFIED: Empty string no longer exits
67
+ print(f"--- REVIEW GATE: USER SIGNALED COMPLETION WITH '{user_input.upper()}' ---", flush=True)
68
+ active_session = False
69
+ break
70
+ elif user_input: # If there's any other non-empty input (and not a completion command)
71
+ # This is the critical line the AI will "listen" for.
72
+ print(f"USER_REVIEW_SUB_PROMPT: {user_input}", flush=True)
73
+ # If user_input was empty (and not a completion command),
74
+ # the loop simply continues, and "REVIEW_GATE_AWAITING_INPUT:" will be printed again.
75
+
76
+ except KeyboardInterrupt:
77
+ print("--- REVIEW GATE: SESSION INTERRUPTED BY USER (KeyboardInterrupt) ---", flush=True)
78
+ active_session = False
79
+ break
80
+ except Exception as e:
81
+ print(f"--- REVIEW GATE SCRIPT ERROR: {e} ---", flush=True)
82
+ active_session = False
83
+ break
84
+
85
+ print("--- FINAL REVIEW GATE SCRIPT EXITED ---", flush=True)
86
+ ```
87
+
88
+ 2. **Ensure Script Existence and Correctness:**
89
+ a. Use your file system tools to check if the file `final_review_gate.py` exists in the project root.
90
+ b. **If the file `final_review_gate.py` does NOT exist OR if its content does not EXACTLY match the Python script provided in section "Phase 0.1.Python Script Content" (you may need to read the existing file and compare):**
91
+ i. You MUST create or overwrite the `final_review_gate.py` file in the project root.
92
+ ii. The content of this file MUST be *exactly* the Python script content provided above. Ensure all indentation, comments, and lines are preserved perfectly. Use your "create file" or "edit file" tool.
93
+ iii. After creating/overwriting it, announce: "The `final_review_gate.py` script has been created/updated in the project root with the latest interaction enhancements."
94
+ c. **If the file `final_review_gate.py` already exists and its content is correct:** You may skip announcing its creation.
95
+ d. If you encounter any errors checking for, reading, or creating/writing the file (e.g., permission issues), you MUST report this to the user. State clearly: "Error managing `final_review_gate.py`: [specific error]. Cannot proceed with interactive review." and then conclude the interaction for the main task as best as you can without this interactive step.
96
+
97
+ ## Phase 1: Primary Task Execution
98
+ 1. Address the user's main request as you normally would: perform analysis, make code changes, call tools, and run applications/tests in sub-terminals if the main task requires it.
99
+ * **Application Execution Confirmation:** Before attempting to run an entire application (e.g., a web server, a full front-end build, a long-running background process), you MUST ask the user for explicit confirmation. For example: "I am about to run the [application name/command]. Is it okay to proceed, or is it possibly already running?"
100
+ * This confirmation is NOT required for running individual test scripts, linters, build tools for specific components, or other non-application-level commands necessary for task completion.
101
+ * If the user indicates an application is already running or denies permission, adapt your strategy accordingly (e.g., focus on tests or specific file executions).
102
+ 2. Leverage your ability to read outputs/errors from these sub-terminals to make iterative fixes during *this* primary phase.
103
+
104
+ ## Phase 2: Mandatory Final Interactive Review Script Execution
105
+ 1. **Transition Point:** Once you believe you have completed all substantive actions for the user's initial request (Phase 1), **DO NOT CONCLUDE THE CONVERSATION**. You MUST now transition to this Final Interactive Review phase.
106
+ 2. **Action:** Execute the `final_review_gate.py` script (which you ensured exists and is correct in Phase 0) from the project root.
107
+ * Determine the correct Python interpreter command (e.g., `python`, `python3`) available in the execution environment. A robust command attempt would be `python3 ./final_review_gate.py` and if that fails, try `python ./final_review_gate.py`.
108
+ * **Crucially, the terminal for this script MUST run directly within the chat window, and its output MUST be continuously visible to the user as it is generated.** It must NOT pop open as a separate window or run hidden in the background. You MUST wait for the script (and its terminal interaction) to fully complete and exit before you proceed with any further actions or attempt to conclude the overall task. Your processing will be blocked by this script's execution.
109
+ 3. **Announcement:** Clearly inform the user: "I have completed the primary actions. Now initiating the mandatory Final Interactive Review by running `./final_review_gate.py`. The review terminal is now active **in this chat window, and its output will be displayed live**. Please provide any sub-prompts directly into that script's input area, or type 'TASK_COMPLETE', 'Done', 'Quit', or 'q' when satisfied." (MODIFIED: Removed "or simply press Enter")
110
+
111
+ ## Phase 3: Interactive Review Loop (Monitoring Script Output)
112
+ 1. **Active Monitoring & Display:** Continuously monitor the standard output (stdout) of the launched `final_review_gate.py` script **as it appears live in the chat window**. Ensure the user sees all script output, including the `REVIEW_GATE_AWAITING_INPUT:` prompt. You will "read" its output using the same capability that allows you to read terminal logs or error messages. The script will loop and re-prompt if an empty input is given.
113
+ 2. **User Sub-Prompt Detection:** When the script's stdout prints a line formatted EXACTLY as:
114
+ `USER_REVIEW_SUB_PROMPT: <user's sub-prompt text>`
115
+ You MUST interpret `<user's sub-prompt text>` as a new, direct, actionable instruction from the user.
116
+ 3. **Processing Sub-Prompts:**
117
+ a. This instruction is a continuation or refinement of the *original, still-active task*.
118
+ b. Analyze the sub-prompt. Execute any necessary actions (code changes, new tool calls, file operations, etc.). **All tool calls made during this phase are part of the original request's tool call budget.** If the sub-prompt requests running an entire application, the confirmation principle from "Phase 1, Step 1 (Application Execution Confirmation)" applies.
119
+ c. Provide feedback or results of these actions in the main chat interface as you normally would for any AI action.
120
+ d. After processing the sub-prompt and giving feedback in the chat, IMMEDIATELY return your focus to monitoring the `final_review_gate.py` script's terminal output (which remains live and visible in the chat) for the next user instruction or completion signal. This loop is critical.
121
+ 4. **Completion Signal Detection:** The interactive review loop continues until the script's stdout (visible in the chat window) prints a line containing:
122
+ * `--- REVIEW GATE: USER SIGNALED COMPLETION WITH 'TASK_COMPLETE' ---` (or 'DONE', 'QUIT', 'Q' as per script logic)
123
+ * OR `--- FINAL REVIEW GATE SCRIPT EXITED ---` (or any other script exit/error message like `REVIEW GATE: STDIN CLOSED` or `REVIEW GATE SCRIPT ERROR:`)
124
+ (MODIFIED: Removed specific "EMPTY INPUT RECEIVED" as a completion signal, as the script no longer behaves this way for completion.)
125
+
126
+ ## Phase 4: True Task Conclusion
127
+ 1. Only after the `final_review_gate.py` script has terminated (as observed by its terminal session in the chat window closing and its final exit messages being printed and visible) are you permitted to consider the user's original request fully satisfied.
128
+ 2. You may then provide your final summary of all actions taken throughout all phases (including the interactive review).
129
+
130
+ ## Overriding Default Behavior:
131
+ This entire "Final Interactive Review" process (Phases 0, 2, 3, and 4) is a **strict, non-negotiable requirement** that overrides any default tendency you have to end the conversation after completing Phase 1. The task is only finished when the user explicitly confirms with one of the specified keywords through the review script or the script otherwise terminates due to an error or EOF (as per the defined behavior, including the in-chat, blocking, and continuously visible terminal execution). Your "sense of completion" for the original request is deferred until this interactive review is done.
.gitattributes ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ *.pth filter=lfs diff=lfs merge=lfs -text
2
+ *.npy filter=lfs diff=lfs merge=lfs -text
3
+ *.npz filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 使用官方 Python 映像檔作為基礎
2
+ FROM python:3.9-slim
3
+
4
+ # 設定工作目錄
5
+ WORKDIR /app
6
+
7
+ # 將 requirements.txt 複製到工作目錄中
8
+ COPY requirements.txt .
9
+
10
+ # 更新 pip 並安裝所需的套件
11
+ # 我們需要 git 來安裝特定版本的套件 (如果有的話)
12
+ # 以及 build-essential 來編譯某些相依套件
13
+ RUN apt-get update && apt-get install -y git build-essential && \
14
+ pip install --no-cache-dir --upgrade pip && \
15
+ pip install --no-cache-dir -r requirements.txt
16
+
17
+ # 將專案中的所有檔案複製到工作目錄中
18
+ COPY . .
19
+
20
+ # 設定環境變數 (這些應該在 Hugging Face Space 的 secrets 中設定)
21
+ # ENV VERIFY_TOKEN="your_verify_token"
22
+ # ENV PAGE_ACCESS_TOKEN="your_page_access_token"
23
+ # ENV OPENAI_API_KEY="your_openai_api_key"
24
+
25
+ # 開放應用程式運行的埠口
26
+ EXPOSE 8000
27
+
28
+ # 使用 Gunicorn 啟動應用程式
29
+ # --bind 0.0.0.0:8000 讓它可以從外部被存取
30
+ # --workers 1 對於免費方案,一個 worker 通常是比較穩定的選擇
31
+ # --timeout 120 增加超時時間,以應對可能的模型載入或長時間的請求
32
+ CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "1", "--timeout", "120", "app:app"]
Procfile ADDED
@@ -0,0 +1 @@
 
 
1
+ web: gunicorn app:app
app.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import requests
4
+ from flask import Flask, request, jsonify
5
+ from datetime import datetime
6
+ import threading
7
+ import time
8
+
9
+ # 匯入我們的手語處理器
10
+ from sign_language_processor import VideoSignLanguageRecognizer
11
+
12
+ app = Flask(__name__)
13
+
14
+ # 從環境變數取得設定
15
+ VERIFY_TOKEN = os.environ.get('VERIFY_TOKEN', 'your_verify_token')
16
+ PAGE_ACCESS_TOKEN = os.environ.get('PAGE_ACCESS_TOKEN', 'your_page_access_token')
17
+ FACEBOOK_API_URL = 'https://graph.facebook.com/v18.0/me/messages'
18
+
19
+ # --- Initializations ---
20
+ # 初始化手語辨識器
21
+ # 注意:你需要確保 'models/sign_language_model.pth' 和 'labels.csv' 存在
22
+ try:
23
+ recognizer = VideoSignLanguageRecognizer(
24
+ model_path='models/sign_language_model.pth'
25
+ )
26
+ print("✅ 手語辨識模型載入成功")
27
+ except Exception as e:
28
+ recognizer = None
29
+ print(f"❌ 載入模型失敗: {e}")
30
+ print("👉 請確保 'models/sign_language_model.pth' 和 'labels.csv' 檔案存在於專案根目錄")
31
+
32
+ @app.route('/', methods=['GET'])
33
+ def home():
34
+ return "統一手語辨識服務正在運行中!🚀"
35
+
36
+ @app.route('/webhook', methods=['GET'])
37
+ def verify_webhook():
38
+ """驗證 Webhook - Facebook 會呼叫這個來驗證你的服務"""
39
+ mode = request.args.get('hub.mode')
40
+ token = request.args.get('hub.verify_token')
41
+ challenge = request.args.get('hub.challenge')
42
+
43
+ if mode and token:
44
+ if mode == 'subscribe' and token == VERIFY_TOKEN:
45
+ print("Webhook 驗證成功!")
46
+ return challenge
47
+ else:
48
+ print("驗證失敗 - token 不正確")
49
+ return "驗證失敗", 403
50
+
51
+ return "需要驗證參數", 400
52
+
53
+ @app.route('/webhook', methods=['POST'])
54
+ def handle_webhook():
55
+ """處理從 Messenger 來的訊息"""
56
+ try:
57
+ data = request.get_json()
58
+
59
+ if data.get('object') == 'page':
60
+ for entry in data.get('entry', []):
61
+ for messaging_event in entry.get('messaging', []):
62
+ if messaging_event.get('message'):
63
+ handle_message(messaging_event)
64
+ elif messaging_event.get('postback'):
65
+ handle_postback(messaging_event)
66
+
67
+ return "EVENT_RECEIVED", 200
68
+
69
+ except Exception as e:
70
+ print(f"處理 webhook 時發生錯誤: {e}")
71
+ return "錯誤", 500
72
+
73
+ @app.route('/receive_recognition_result', methods=['POST'])
74
+ def receive_recognition_result():
75
+ """接收來自本地手語辨識服務的結果"""
76
+ try:
77
+ data = request.get_json()
78
+
79
+ if not data:
80
+ return jsonify({"status": "error", "message": "沒有收到資料"}), 400
81
+
82
+ sender_id = data.get('sender_id')
83
+ recognition_result = data.get('recognition_result', '無法辨識')
84
+ confidence = data.get('confidence', 0)
85
+ timestamp = data.get('timestamp', '')
86
+
87
+ if not sender_id:
88
+ return jsonify({"status": "error", "message": "缺少 sender_id"}), 400
89
+
90
+ print(f"📝 收到手語辨識結果 - 用戶:{sender_id}")
91
+ print(f"🎯 辨識結果:{recognition_result}")
92
+ print(f"📊 信心度:{confidence}")
93
+
94
+ # 只發送純粹的辨識結果句子
95
+ send_message(sender_id, recognition_result)
96
+
97
+ return jsonify({
98
+ "status": "success",
99
+ "message": "辨識結果已發送給用戶"
100
+ })
101
+
102
+ except Exception as e:
103
+ print(f"處理辨識結果時發生錯誤:{e}")
104
+ return jsonify({"status": "error", "message": str(e)}), 500
105
+
106
+ def handle_message(messaging_event):
107
+ """處理一般訊息"""
108
+ sender_id = messaging_event['sender']['id']
109
+ message = messaging_event.get('message', {})
110
+ message_text = message.get('text', '')
111
+ attachments = message.get('attachments', [])
112
+
113
+ print(f"收到訊息 from {sender_id}: {message_text}")
114
+
115
+ # 檢查是否有影片附件
116
+ if attachments:
117
+ for attachment in attachments:
118
+ if attachment.get('type') == 'video':
119
+ video_url = attachment.get('payload', {}).get('url')
120
+ if video_url:
121
+ # 提示用戶我們已收到影片並開始處理
122
+ send_message(sender_id, "收到您的影片了,正在為您處理手語辨識,請稍候...")
123
+
124
+ # 在背景處理影片,避免阻塞
125
+ thread = threading.Thread(
126
+ target=process_video_and_reply,
127
+ args=(video_url, sender_id)
128
+ )
129
+ thread.start()
130
+ return
131
+ else:
132
+ send_message(sender_id, f"抱歉,目前只支援影片格式的手語辨識喔!")
133
+ return
134
+
135
+ # 處理文字訊息
136
+ if message_text:
137
+ send_message(sender_id, f"你好!請傳送一段手語影片,我會試著為您翻譯。")
138
+
139
+ def handle_postback(messaging_event):
140
+ """處理 postback 事件(按鈕點擊等)"""
141
+ sender_id = messaging_event['sender']['id']
142
+ postback_payload = messaging_event['postback']['payload']
143
+
144
+ print(f"收到 postback from {sender_id}: {postback_payload}")
145
+
146
+ send_message(sender_id, f"收到 postback:{postback_payload}")
147
+
148
+ def send_message(recipient_id, message_text):
149
+ """發送訊息給使用者"""
150
+ headers = {
151
+ 'Content-Type': 'application/json'
152
+ }
153
+
154
+ data = {
155
+ 'recipient': {'id': recipient_id},
156
+ 'message': {'text': message_text}
157
+ }
158
+
159
+ params = {
160
+ 'access_token': PAGE_ACCESS_TOKEN
161
+ }
162
+
163
+ try:
164
+ response = requests.post(
165
+ FACEBOOK_API_URL,
166
+ headers=headers,
167
+ params=params,
168
+ json=data,
169
+ timeout=30
170
+ )
171
+ response.raise_for_status()
172
+ print(f"訊息發送成功給 {recipient_id}")
173
+ except requests.exceptions.RequestException as e:
174
+ print(f"發送訊息失敗 to {recipient_id}: {e}")
175
+
176
+ def send_quick_reply(recipient_id, message_text, quick_replies):
177
+ """發送快速回覆選項"""
178
+ headers = {
179
+ 'Content-Type': 'application/json'
180
+ }
181
+
182
+ data = {
183
+ 'recipient': {'id': recipient_id},
184
+ 'message': {
185
+ 'text': message_text,
186
+ 'quick_replies': quick_replies
187
+ }
188
+ }
189
+
190
+ params = {
191
+ 'access_token': PAGE_ACCESS_TOKEN
192
+ }
193
+
194
+ requests.post(
195
+ FACEBOOK_API_URL,
196
+ headers=headers,
197
+ params=params,
198
+ json=data
199
+ )
200
+
201
+ def download_video_local(video_url, sender_id):
202
+ """下載影片到本地"""
203
+ try:
204
+ # 生成檔案名稱
205
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
206
+ filename = f"video_{sender_id}_{timestamp}.mp4"
207
+ file_path = os.path.join('received_videos', filename)
208
+
209
+ print(f"開始下載影片到本地:{video_url}")
210
+
211
+ # 下載影片
212
+ response = requests.get(video_url, stream=True, timeout=30)
213
+ response.raise_for_status()
214
+
215
+ # 寫入檔案
216
+ with open(file_path, 'wb') as f:
217
+ for chunk in response.iter_content(chunk_size=8192):
218
+ if chunk:
219
+ f.write(chunk)
220
+
221
+ file_size = os.path.getsize(file_path)
222
+ print(f"🎬 影片下載完成:{file_path} ({file_size} bytes)")
223
+ return file_path
224
+
225
+ except Exception as e:
226
+ print(f"下載影片失敗:{e}")
227
+ return None
228
+
229
+ def process_video_and_reply(video_url, sender_id):
230
+ """下載、處理影片,並回傳結果"""
231
+ if not recognizer:
232
+ print("❌ 辨識器未初始化,無法處理影片。")
233
+ send_message(sender_id, "抱歉,後端辨識服務目前無法使用,請稍後再試。")
234
+ return
235
+
236
+ # 1. 下載影片
237
+ print(f"🎬 開始下載影片 for user {sender_id} from {video_url}")
238
+ video_path = download_video_local(video_url, sender_id)
239
+
240
+ if not video_path:
241
+ print(f"❌ 影片下載失敗 for user {sender_id}")
242
+ send_message(sender_id, "抱歉,無法順利下載您的影片,請再試一次。")
243
+ return
244
+
245
+ # 2. 處理影片並進行手語辨識
246
+ try:
247
+ print(f"🤖 開始進行手語辨識 for {video_path}")
248
+ result = recognizer.process_video(video_path)
249
+
250
+ recognition_result = result.get('recognition_result', '無法辨識')
251
+ confidence = result.get('confidence', 0)
252
+
253
+ print(f"✅ 辨識完成 for user {sender_id} - 結果: {recognition_result} (信心度: {confidence:.2f})")
254
+
255
+ # 3. 將結果發送給用戶
256
+ reply_text = f"辨識結果:\n{recognition_result}"
257
+ send_message(sender_id, reply_text)
258
+
259
+ except Exception as e:
260
+ print(f"❌ 辨識過程中發生錯誤 for {video_path}: {e}")
261
+ send_message(sender_id, "抱歉,在辨識過程中發生了未預期的錯誤。")
262
+
263
+ finally:
264
+ # 4. 刪除本地影片檔案
265
+ if os.path.exists(video_path):
266
+ try:
267
+ os.remove(video_path)
268
+ print(f"🗑️ 已刪除暫存影片:{video_path}")
269
+ except Exception as e:
270
+ print(f"❌ 刪除影片失敗 {video_path}: {e}")
271
+
272
+ if __name__ == '__main__':
273
+ # 建立影片接收資料夾
274
+ if not os.path.exists('received_videos'):
275
+ os.makedirs('received_videos')
276
+
277
+ port = int(os.environ.get('PORT', 8000))
278
+ print(f"🚀 伺服器將在 http://localhost:{port} 上啟動")
279
+ app.run(host='0.0.0.0', port=port, debug=True)
env_example.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Facebook Messenger Bot 設定
2
+ VERIFY_TOKEN=your_custom_verify_token_here
3
+ PAGE_ACCESS_TOKEN=your_facebook_page_access_token_here
4
+
5
+ # 本地手語辨識服務地址 (使用 ngrok 建立的公開URL)
6
+ LOCAL_RECEIVER_URL=https://your-ngrok-url.ngrok-free.app
7
+
8
+ # Render 會自動設定這些環境變數,不需要手動設定
9
+ # PORT=5000 (自動設定)
10
+ # RENDER_EXTERNAL_URL=https://your-app.onrender.com (自動設定)
11
+ # RENDER_SERVICE_NAME=your-app (自動設定)
features/keypoints/eat_001_aug_rotate_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5494afe40dcec1dee46f85e8b4283b3218628466dcbb6e9e1a91174d75eda216
3
+ size 61328
features/keypoints/eat_001_aug_shift_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6147caedc17038157f3ce4a6a4fc558faaedc04bf78c2afd69d0b3c4a2e7ed39
3
+ size 61328
features/keypoints/eat_001_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:489f58d5eaeedebab524a95bbdfa94833fd4b1ac8109964c07e1ba4b98a3d8fe
3
+ size 61328
features/keypoints/eat_002_aug_rotate_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:907f9427411108739789e004eb78b0f328d621cb7dcd3d0f2d1a0ea50ab2b550
3
+ size 57728
features/keypoints/eat_002_aug_shift_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:964c159d4371b9fde4dcad040260bffa75762c711e43b60767568941e0d3d777
3
+ size 57728
features/keypoints/eat_002_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ae39b66215c8a0e8ff5a02a7c0be1e6929ffd94d5efc1ab532df04cb0b057a0b
3
+ size 57728
features/keypoints/eat_003_aug_rotate_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ddc562c327cd56f0a97e5d33e9f9896ff5f8b6509dba4428d344397c626c94be
3
+ size 46928
features/keypoints/eat_003_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2f1185ea872a962cf5def940a1b30cedffbab81bbc623c734e4d3a5ccda1c868
3
+ size 46928
features/keypoints/eat_004_aug_flip_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ad4fd90ad42251c47ed109837d467a733325b1073af584ea27c84e72d160498f
3
+ size 34328
features/keypoints/eat_004_aug_shift_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c52768a3ec7d1893146f3f64ee1b14884a276a2f9d552eb174de7259f47c9d65
3
+ size 34328
features/keypoints/eat_004_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4078b2bafa818d2523aebb310d4d9f9f3e468f5c9b1983cadefa59130a922d07
3
+ size 34328
features/keypoints/eat_005_aug_flip_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:781e25e1d57b0cddef6d02e8aa66f1bc1ae6463cca31e29568f2469f74a32857
3
+ size 93728
features/keypoints/eat_005_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fab968d2c2faab6fa4110220cf0b27f10e941b6cd5aea089ad6920863305c178
3
+ size 93728
features/keypoints/eat_006_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:abecfd8f819ab514b5816e89b3f1ab668a71fee8e19e119af24aaa5ac2d074e0
3
+ size 118928
features/keypoints/eat_007_aug_flip_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:80295094306e7fbb9d0d211de45f977f4a076caf3e4fd48092842569c1d77166
3
+ size 111728
features/keypoints/eat_007_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:db7847fe2decc5c2d66f48b9559002dc47935f4f15d04fce6a1b3a6d4432dbe6
3
+ size 111728
features/keypoints/eat_008_aug_flip_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b9afd2e1c35af074e916f773b154b91c9fd28e9f0444580b50dc6a7fcb9ed0bf
3
+ size 104528
features/keypoints/eat_008_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2806038c2e3d2d69adf419e612e2a537d38c7905fd35527f7f625807754f4f29
3
+ size 104528
features/keypoints/eat_009_aug_flip_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:368059a10ff26bfd92bfc48e5062e2097c3f01129264dd40cc68e8bad5dd30de
3
+ size 68528
features/keypoints/eat_009_aug_rotate_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:089539eaf10bcfbe3985bc8a1db59a57fe924ace200a9487b56663525da41b92
3
+ size 68528
features/keypoints/eat_009_aug_shift_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b6664e58930dd0865832fc8b0dc9dfc33c56e409549815e02778b64bb7cd7e36
3
+ size 68528
features/keypoints/eat_009_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7897d96cf671ea70c90818bb9148067be859bab2f7c0c0cae075726e7b185460
3
+ size 68528
features/keypoints/eat_010_aug_flip_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8e85795b6010a3f45319fc2dcec6d4ec69dd494e742173bc0688264fdbccb58d
3
+ size 142328
features/keypoints/eat_010_aug_rotate_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:60f1bebee96b506c5ccd67edeb5d41c6adaf20007a8c2f0cbeb0f9ef8d8c39f0
3
+ size 142328
features/keypoints/eat_010_aug_shift_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c36c680af76043ff03f18149bf577dc7c19a5ac95cc5d792939c587180edb427
3
+ size 142328
features/keypoints/eat_010_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:21bb09d23324bab3842704c545746ccfb31c2f600051c9e6df162f753006a908
3
+ size 142328
features/keypoints/eat_011_aug_shift_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4ccc18cd1ce4a245782aacf4a8537c5184da8d71325ee66f8e36870072eebe5a
3
+ size 108128
features/keypoints/eat_011_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b7639d1bf8e8078ac29e9b902e6f9e40608f76c6c4d5093aef8a8fde681fa26
3
+ size 108128
features/keypoints/eat_012_aug_shift_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7d19dd3b2f32489c26647d159b8a0f0cc7ae70316d388112a754194a052b7b02
3
+ size 117128
features/keypoints/eat_012_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0fe0b5ba4eeb44c1bfac57150d90ba1a48209f57fb4594cab85a0dd1e7a9bb73
3
+ size 117128
features/keypoints/eat_013_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:55abe1d417e117214ffc21d904e0cb5c16744532c2be5d372b01a2ca4f09f105
3
+ size 135128
features/keypoints/eat_014_aug_shift_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:97d11a52f16f85c2b1410a48b94fb125e8cacc2670f9e2bba433505076c07c82
3
+ size 19928
features/keypoints/eat_014_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0d1a52d5ca452c9457a9bdeef7548e3ae4ea0e961d53ea9fb3ee76ed8720150f
3
+ size 19928
features/keypoints/eat_015_aug_flip_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dbe6de310dfa313a97d4dc02b8251ef884ec2309cb0666b3de79d581f95ac227
3
+ size 104528
features/keypoints/eat_015_aug_shift_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:51640f93d4f85ddf9dcef70f9923874171cccc3f7b02fc974e9da0e325fc8369
3
+ size 104528
features/keypoints/eat_015_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c54181425bfa7e9fa456e784045b8c395421190cd4abd4b3fed0b7aca215dbfc
3
+ size 104528
features/keypoints/eat_016_aug_flip_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bdae046313081cc3f535fd6a3c6786261035598ed7a5be9dfbfbbcce2bc79e8e
3
+ size 73928
features/keypoints/eat_016_aug_shift_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ea797ff8dc868c1dcc08b795f4a63960aa8498bbd11853ea5c6dff466b2c9260
3
+ size 73928
features/keypoints/eat_016_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:da9e5fde63665c21e7322becfca0cfa87252101b3c4685472a5e18ff37b30c43
3
+ size 73928
features/keypoints/eat_017_aug_rotate_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:68a8efce216a1f8939e8fd7e35e2d197518401e9b8c7c2cb644ff48780ebe709
3
+ size 55928
features/keypoints/eat_017_aug_shift_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b5fe26446d611efba6c7ffa86106edfe40c75642b7b4e990413e24868301a29b
3
+ size 55928
features/keypoints/eat_017_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:56720e82bb30bc073cd258ccfcfa1bdad9d5c9e0cf9c1f95594d61978e26c51c
3
+ size 55928
features/keypoints/eat_018_keypoints.npy ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0e8f2343955cf7741e6fcae8a45fc9c448b76963c4cca43394753d78bcf54832
3
+ size 149528