v9.5: 實作標籤完全不重疊算法
- 新增 _calculate_lane_conflicts_v2() 分開返回標籤重疊和線穿框分數 - 修改泳道選擇算法,優先選擇無標籤重疊的泳道 - 兩階段搜尋:優先側別無可用泳道則嘗試另一側 - 增強日誌輸出,顯示標籤範圍和詳細衝突分數 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
157
app.py
Normal file
157
app.py
Normal file
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
TimeLine Designer - PyWebview 主程式
|
||||
|
||||
本程式整合 FastAPI 後端與 HTML 前端,提供桌面應用介面。
|
||||
|
||||
Author: AI Agent
|
||||
Version: 1.0.0
|
||||
DocID: SDD-APP-001
|
||||
Rationale: 實現 SDD.md 定義的 PyWebview Host 架構
|
||||
"""
|
||||
|
||||
import webview
|
||||
import threading
|
||||
import uvicorn
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 設定日誌
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TimelineDesignerApp:
|
||||
"""
|
||||
TimeLine Designer 應用程式類別
|
||||
|
||||
負責啟動 FastAPI 後端與 PyWebview 前端。
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.api_port = 8000
|
||||
self.api_host = "127.0.0.1"
|
||||
self.frontend_path = self._get_frontend_path()
|
||||
|
||||
def _get_frontend_path(self) -> str:
|
||||
"""
|
||||
取得前端 HTML 檔案路徑
|
||||
|
||||
Returns:
|
||||
前端 index.html 的絕對路徑
|
||||
"""
|
||||
# 開發模式:從專案目錄載入
|
||||
dev_path = Path(__file__).parent / "frontend" / "static" / "index.html"
|
||||
if dev_path.exists():
|
||||
return str(dev_path.absolute())
|
||||
|
||||
# 打包模式:從執行檔旁邊載入
|
||||
bundle_path = Path(sys.executable).parent / "frontend" / "static" / "index.html"
|
||||
if bundle_path.exists():
|
||||
return str(bundle_path.absolute())
|
||||
|
||||
# 找不到前端檔案
|
||||
logger.error("找不到前端 HTML 檔案")
|
||||
raise FileNotFoundError("Frontend index.html not found")
|
||||
|
||||
def start_api_server(self):
|
||||
"""
|
||||
啟動 FastAPI 後端伺服器
|
||||
|
||||
在獨立執行緒中運行,避免阻塞主程式。
|
||||
"""
|
||||
try:
|
||||
from backend.main import app
|
||||
|
||||
logger.info(f"正在啟動 API 伺服器於 http://{self.api_host}:{self.api_port}")
|
||||
|
||||
# 設定 uvicorn
|
||||
config = uvicorn.Config(
|
||||
app,
|
||||
host=self.api_host,
|
||||
port=self.api_port,
|
||||
log_level="info"
|
||||
)
|
||||
server = uvicorn.Server(config)
|
||||
server.run()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"API 伺服器啟動失敗: {str(e)}")
|
||||
raise
|
||||
|
||||
def start_gui(self):
|
||||
"""
|
||||
啟動 PyWebview GUI
|
||||
|
||||
在主執行緒中運行。
|
||||
"""
|
||||
try:
|
||||
logger.info("正在啟動 GUI 視窗")
|
||||
|
||||
# 建立視窗
|
||||
window = webview.create_window(
|
||||
title='TimeLine Designer',
|
||||
url=self.frontend_path,
|
||||
width=1400,
|
||||
height=900,
|
||||
resizable=True,
|
||||
fullscreen=False,
|
||||
min_size=(1024, 768),
|
||||
)
|
||||
|
||||
logger.info("GUI 視窗已建立")
|
||||
|
||||
# 啟動 webview(這會阻塞直到視窗關閉)
|
||||
webview.start(debug=True)
|
||||
|
||||
logger.info("GUI 視窗已關閉")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"GUI 啟動失敗: {str(e)}")
|
||||
raise
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
執行應用程式
|
||||
|
||||
啟動順序:
|
||||
1. 在背景執行緒啟動 FastAPI 伺服器
|
||||
2. 在主執行緒啟動 PyWebview GUI
|
||||
"""
|
||||
logger.info("=== TimeLine Designer 啟動中 ===")
|
||||
|
||||
# 在背景執行緒啟動 API 伺服器
|
||||
api_thread = threading.Thread(target=self.start_api_server, daemon=True)
|
||||
api_thread.start()
|
||||
|
||||
# 等待 API 伺服器啟動
|
||||
import time
|
||||
logger.info("等待 API 伺服器啟動...")
|
||||
time.sleep(2)
|
||||
|
||||
# 在主執行緒啟動 GUI
|
||||
self.start_gui()
|
||||
|
||||
logger.info("=== TimeLine Designer 已關閉 ===")
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
應用程式入口點
|
||||
"""
|
||||
try:
|
||||
app = TimelineDesignerApp()
|
||||
app.run()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("使用者中斷程式")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
logger.error(f"應用程式錯誤: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user