Spaces:
Sleeping
Sleeping
| import sys | |
| import os | |
| from werkzeug.serving import run_simple | |
| sys.path.append(os.path.dirname(os.path.abspath(__file__))) | |
| from dotenv import load_dotenv | |
| load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), ".env")) | |
| from flask import Flask, send_from_directory | |
| from flask_cors import CORS | |
| from constants import Constants | |
| from api.api_blueprint import api_blueprint | |
| from models.base import db # 保留原本 import(即使現在沒用到) | |
| from models.combined_labels import CombinedLabels # 同上 | |
| import logging | |
| # --- 1. 確保 sessions 目錄有合理預設值 --- | |
| SESSION_DIR = Constants.SESSIONS_DIR_NAME or "/app/sessions" | |
| Constants.SESSIONS_DIR_NAME = SESSION_DIR | |
| def create_session_dir(): | |
| path = Constants.SESSIONS_DIR_NAME | |
| if not os.path.isdir(path): | |
| os.makedirs(path, exist_ok=True) | |
| def create_app(): | |
| create_session_dir() | |
| # --- 2. 靜態檔:dist/ --- | |
| # static_folder 指向 Vite build 出來的 dist | |
| # static_url_path 設成 "",讓 /assets/... 之類的路徑正確 | |
| app = Flask(__name__, static_folder="dist", static_url_path="") | |
| # --- 3. API 一律掛在 /api --- | |
| app.register_blueprint(api_blueprint, url_prefix="/api") | |
| # --- 4. 簡單 health check --- | |
| def ping(): | |
| return {"status": "ok"} | |
| # --- 5. React / Vite SPA:所有非靜態路徑 fallback 到 index.html --- | |
| def serve_spa(path: str): | |
| """ | |
| 1. 如果 dist 裡真的有這個檔案,就當成靜態檔案回傳 | |
| 2. 如果沒有,就回 dist/index.html,交給 React Router 處理 | |
| 例如 /case/PanTS_00009391 這種 route | |
| """ | |
| # 先嘗試當成靜態檔 | |
| if path: | |
| full_path = os.path.join(app.static_folder, path) | |
| if os.path.isfile(full_path): | |
| return send_from_directory(app.static_folder, path) | |
| # 找不到對應實體檔案 → 交給前端路由 | |
| return send_from_directory(app.static_folder, "index.html") | |
| # --- 6. Log 過濾:不要一直印 /api/progress --- | |
| class FilterProgressRequests(logging.Filter): | |
| def filter(self, record): | |
| return "/api/progress/" not in record.getMessage() | |
| logging.getLogger("werkzeug").addFilter(FilterProgressRequests()) | |
| # --- 7. CORS --- | |
| CORS(app) | |
| # --- 8. SharedArrayBuffer / WebGL 相關 header --- | |
| def add_security_headers(response): | |
| response.headers["Cross-Origin-Opener-Policy"] = "same-origin" | |
| response.headers["Cross-Origin-Embedder-Policy"] = "require-corp" | |
| return response | |
| return app | |
| # 給 gunicorn 用的 WSGI app | |
| app = create_app() | |
| # --- 9. 本機開發(可選) --- | |
| def find_watch_files(): | |
| watch_dirs = ["api", "models", "services"] | |
| base_path = os.path.dirname(__file__) | |
| all_files = [] | |
| for d in watch_dirs: | |
| dir_path = os.path.join(base_path, d) | |
| for root, _, files in os.walk(dir_path): | |
| for f in files: | |
| if f.endswith(".py"): | |
| all_files.append(os.path.join(root, f)) | |
| return all_files | |
| if __name__ == "__main__": | |
| use_ssl = os.environ.get("USE_SSL", "false").lower() == "true" | |
| ssl_context = ( | |
| ("../certs/localhost-cert.pem", "../certs/localhost-key.pem") if use_ssl else None | |
| ) | |
| run_simple( | |
| hostname="0.0.0.0", | |
| port=5001, | |
| application=app, | |
| use_debugger=True, | |
| use_reloader=True, | |
| extra_files=find_watch_files(), | |
| ssl_context=ssl_context, | |
| ) | |