@echo off chcp 65001 >nul REM Meeting Assistant Client - Windows 打包腳本 REM 將 Electron 應用與 Python Sidecar 打包成免安裝 exe setlocal enabledelayedexpansion REM 顏色設定 (Windows 10+) set "GREEN=[92m" set "YELLOW=[93m" set "RED=[91m" set "BLUE=[94m" set "CYAN=[96m" set "NC=[0m" REM 專案路徑 set "SCRIPT_DIR=%~dp0" set "PROJECT_DIR=%SCRIPT_DIR%.." set "CLIENT_DIR=%PROJECT_DIR%\client" set "SIDECAR_DIR=%PROJECT_DIR%\sidecar" set "BACKEND_DIR=%PROJECT_DIR%\backend" set "BUILD_DIR=%PROJECT_DIR%\build" REM 預設配置 set "SKIP_SIDECAR=false" set "SKIP_BACKEND=true" set "EMBEDDED_BACKEND=false" set "CLEAN_BUILD=false" set "API_URL=" set "DATABASE_TYPE=" set "BUILD_TARGET=nsis" REM 解析參數 set "COMMAND=help" :parse_args if "%~1"=="" goto :main if /i "%~1"=="build" (set "COMMAND=build" & shift & goto :parse_args) if /i "%~1"=="sidecar" (set "COMMAND=sidecar" & shift & goto :parse_args) if /i "%~1"=="electron" (set "COMMAND=electron" & shift & goto :parse_args) if /i "%~1"=="clean" (set "COMMAND=clean" & shift & goto :parse_args) if /i "%~1"=="help" (set "COMMAND=help" & shift & goto :parse_args) if /i "%~1"=="--skip-sidecar" (set "SKIP_SIDECAR=true" & shift & goto :parse_args) if /i "%~1"=="--skip-backend" (set "SKIP_BACKEND=true" & shift & goto :parse_args) if /i "%~1"=="--embedded-backend" (set "EMBEDDED_BACKEND=true" & set "SKIP_BACKEND=false" & shift & goto :parse_args) if /i "%~1"=="--clean" (set "CLEAN_BUILD=true" & shift & goto :parse_args) if /i "%~1"=="--api-url" (set "API_URL=%~2" & shift & shift & goto :parse_args) if /i "%~1"=="--database-type" (set "DATABASE_TYPE=%~2" & shift & shift & goto :parse_args) if /i "%~1"=="--target" (set "BUILD_TARGET=%~2" & shift & shift & goto :parse_args) echo %RED%[ERROR]%NC% 未知參數: %~1 goto :show_help :main if "%COMMAND%"=="help" goto :show_help if "%COMMAND%"=="build" goto :do_build if "%COMMAND%"=="sidecar" goto :do_sidecar if "%COMMAND%"=="electron" goto :do_electron if "%COMMAND%"=="clean" goto :do_clean goto :show_help :show_banner echo. echo %CYAN%========================================== echo Meeting Assistant Client Builder echo 打包 Electron + Sidecar 為免安裝執行檔 echo ==========================================%NC% echo. goto :eof :check_environment echo %BLUE%[STEP]%NC% 檢查建置環境... REM 檢查 Node.js where node >nul 2>&1 if %errorlevel% equ 0 ( for /f "tokens=*" %%i in ('node --version') do echo %GREEN%[OK]%NC% Node.js: %%i ) else ( echo %RED%[ERROR]%NC% Node.js 未安裝 exit /b 1 ) REM 檢查 npm where npm >nul 2>&1 if %errorlevel% equ 0 ( for /f "tokens=*" %%i in ('npm --version') do echo %GREEN%[OK]%NC% npm: %%i ) else ( echo %RED%[ERROR]%NC% npm 未安裝 exit /b 1 ) REM 檢查 Python where python >nul 2>&1 if %errorlevel% equ 0 ( for /f "tokens=*" %%i in ('python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"') do set "PY_VERSION=%%i" for /f "tokens=*" %%i in ('python -c "import sys; print(sys.version_info.major)"') do set "PY_MAJOR=%%i" for /f "tokens=*" %%i in ('python -c "import sys; print(sys.version_info.minor)"') do set "PY_MINOR=%%i" if !PY_MAJOR! geq 3 if !PY_MINOR! geq 10 ( set "PYTHON_CMD=python" echo %GREEN%[OK]%NC% Python !PY_VERSION! goto :eof ) ) echo %RED%[ERROR]%NC% 需要 Python 3.10 或更高版本 exit /b 1 :update_config if "%API_URL%"=="" goto :eof echo %BLUE%[STEP]%NC% 更新 API URL 設定... set "CONFIG_FILE=%CLIENT_DIR%\config.json" if not exist "%CONFIG_FILE%" ( echo %YELLOW%[WARN]%NC% 找不到 config.json,跳過 API URL 設定 goto :eof ) REM 使用 PowerShell 更新 JSON powershell -Command "$config = Get-Content '%CONFIG_FILE%' -Raw | ConvertFrom-Json; $config.apiBaseUrl = '%API_URL%'; $config | ConvertTo-Json -Depth 10 | Set-Content '%CONFIG_FILE%' -Encoding UTF8" if errorlevel 1 ( echo %RED%[ERROR]%NC% 更新 config.json 失敗 exit /b 1 ) echo %GREEN%[OK]%NC% API URL 已設定為: %API_URL% goto :eof :do_clean echo %BLUE%[STEP]%NC% 清理建置目錄... if exist "%BUILD_DIR%" rmdir /s /q "%BUILD_DIR%" if exist "%CLIENT_DIR%\dist" rmdir /s /q "%CLIENT_DIR%\dist" if exist "%SIDECAR_DIR%\dist" rmdir /s /q "%SIDECAR_DIR%\dist" if exist "%SIDECAR_DIR%\build" rmdir /s /q "%SIDECAR_DIR%\build" if exist "%SIDECAR_DIR%\venv" rmdir /s /q "%SIDECAR_DIR%\venv" if exist "%SIDECAR_DIR%\*.spec" del /q "%SIDECAR_DIR%\*.spec" if exist "%BACKEND_DIR%\dist" rmdir /s /q "%BACKEND_DIR%\dist" if exist "%BACKEND_DIR%\build" rmdir /s /q "%BACKEND_DIR%\build" if exist "%BACKEND_DIR%\venv" rmdir /s /q "%BACKEND_DIR%\venv" if exist "%BACKEND_DIR%\*.spec" del /q "%BACKEND_DIR%\*.spec" echo %GREEN%[OK]%NC% 建置目錄已清理 goto :eof :setup_sidecar_venv echo %BLUE%[STEP]%NC% 設置 Sidecar 建置環境... cd /d "%SIDECAR_DIR%" if not exist "venv" ( echo %BLUE%[INFO]%NC% 創建虛擬環境... %PYTHON_CMD% -m venv venv ) echo %BLUE%[INFO]%NC% 安裝 Sidecar 依賴... call venv\Scripts\activate.bat python -m pip install --upgrade pip -q python -m pip install -r requirements.txt -q echo %BLUE%[INFO]%NC% 安裝 PyInstaller... python -m pip install pyinstaller -q echo %GREEN%[OK]%NC% Sidecar 建置環境就緒 goto :eof :build_sidecar echo %BLUE%[STEP]%NC% 打包 Sidecar (Python → 獨立執行檔)... cd /d "%SIDECAR_DIR%" call venv\Scripts\activate.bat if not exist "dist" mkdir dist echo %BLUE%[INFO]%NC% 執行 PyInstaller... echo %BLUE%[INFO]%NC% 這可能需要幾分鐘... pyinstaller ^ --onedir ^ --name transcriber ^ --distpath dist ^ --workpath build ^ --specpath . ^ --noconfirm ^ --clean ^ --log-level WARN ^ --console ^ --hidden-import=faster_whisper ^ --hidden-import=ctranslate2 ^ --hidden-import=huggingface_hub ^ --hidden-import=tokenizers ^ --hidden-import=onnxruntime ^ --hidden-import=opencc ^ --hidden-import=pydub ^ --hidden-import=numpy ^ --hidden-import=av ^ --collect-data=onnxruntime ^ --collect-data=faster_whisper ^ transcriber.py if exist "dist\transcriber" ( echo %GREEN%[OK]%NC% Sidecar 打包完成: %SIDECAR_DIR%\dist\transcriber ) else ( echo %RED%[ERROR]%NC% Sidecar 打包失敗 exit /b 1 ) goto :eof :setup_backend_venv echo %BLUE%[STEP]%NC% 設置 Backend 建置環境... cd /d "%BACKEND_DIR%" if not exist "venv" ( echo %BLUE%[INFO]%NC% 創建虛擬環境... %PYTHON_CMD% -m venv venv ) echo %BLUE%[INFO]%NC% 安裝 Backend 依賴... call venv\Scripts\activate.bat python -m pip install --upgrade pip -q python -m pip install -r requirements.txt -q echo %BLUE%[INFO]%NC% 安裝 PyInstaller... python -m pip install pyinstaller -q echo %GREEN%[OK]%NC% Backend 建置環境就緒 goto :eof :build_backend echo %BLUE%[STEP]%NC% 打包 Backend (Python → 獨立執行檔)... cd /d "%BACKEND_DIR%" call venv\Scripts\activate.bat if not exist "dist" mkdir dist echo %BLUE%[INFO]%NC% 執行 PyInstaller... echo %BLUE%[INFO]%NC% 這可能需要幾分鐘... pyinstaller ^ --onedir ^ --name backend ^ --distpath dist ^ --workpath build ^ --specpath . ^ --noconfirm ^ --clean ^ --log-level WARN ^ --console ^ --hidden-import=uvicorn ^ --hidden-import=uvicorn.logging ^ --hidden-import=uvicorn.loops ^ --hidden-import=uvicorn.loops.auto ^ --hidden-import=uvicorn.protocols ^ --hidden-import=uvicorn.protocols.http ^ --hidden-import=uvicorn.protocols.http.auto ^ --hidden-import=uvicorn.protocols.websockets ^ --hidden-import=uvicorn.protocols.websockets.auto ^ --hidden-import=uvicorn.lifespan ^ --hidden-import=uvicorn.lifespan.on ^ --hidden-import=uvicorn.lifespan.off ^ --hidden-import=fastapi ^ --hidden-import=starlette ^ --hidden-import=pydantic ^ --hidden-import=pydantic_core ^ --hidden-import=mysql.connector ^ --hidden-import=mysql.connector.pooling ^ --hidden-import=sqlite3 ^ --hidden-import=httpx ^ --hidden-import=httpcore ^ --hidden-import=jose ^ --hidden-import=jose.jwt ^ --hidden-import=cryptography ^ --hidden-import=openpyxl ^ --hidden-import=multipart ^ --hidden-import=python_multipart ^ --hidden-import=dotenv ^ --hidden-import=tzdata ^ --hidden-import=app ^ --hidden-import=app.main ^ --hidden-import=app.config ^ --hidden-import=app.database ^ --hidden-import=app.models ^ --hidden-import=app.models.schemas ^ --hidden-import=app.routers ^ --hidden-import=app.routers.auth ^ --hidden-import=app.routers.meetings ^ --hidden-import=app.routers.ai ^ --hidden-import=app.routers.export ^ --collect-data=pydantic ^ --collect-data=uvicorn ^ run_server.py if exist "dist\backend" ( echo %BLUE%[INFO]%NC% 複製 template 目錄... if exist "template" ( xcopy /s /e /y "template\*" "dist\backend\template\" >nul 2>&1 ) if not exist "dist\backend\record" mkdir "dist\backend\record" echo %GREEN%[OK]%NC% Backend 打包完成: %BACKEND_DIR%\dist\backend ) else ( echo %RED%[ERROR]%NC% Backend 打包失敗 exit /b 1 ) goto :eof :update_config_embedded REM 更新 config.json 以啟用 embedded backend if "%EMBEDDED_BACKEND%"=="false" goto :eof echo %BLUE%[STEP]%NC% 啟用內嵌後端模式... set "CONFIG_FILE=%CLIENT_DIR%\config.json" if not exist "%CONFIG_FILE%" ( echo %YELLOW%[WARN]%NC% 找不到 config.json,跳過內嵌模式設定 goto :eof ) REM 使用 PowerShell 更新 backend.embedded = true (使用 UTF8 without BOM) powershell -Command "$config = Get-Content '%CONFIG_FILE%' -Raw | ConvertFrom-Json; if (-not $config.backend) { $config | Add-Member -NotePropertyName 'backend' -NotePropertyValue @{} }; $config.backend.embedded = $true; $json = $config | ConvertTo-Json -Depth 10; [System.IO.File]::WriteAllText('%CONFIG_FILE%', $json, [System.Text.UTF8Encoding]::new($false))" if errorlevel 1 ( echo %RED%[ERROR]%NC% 更新 config.json embedded 設定失敗 exit /b 1 ) echo %GREEN%[OK]%NC% 已啟用內嵌後端模式 goto :eof :update_config_database REM 更新 config.json 的資料庫類型 if "%DATABASE_TYPE%"=="" goto :eof echo %BLUE%[STEP]%NC% 設定資料庫類型... set "CONFIG_FILE=%CLIENT_DIR%\config.json" if not exist "%CONFIG_FILE%" ( echo %YELLOW%[WARN]%NC% 找不到 config.json,跳過資料庫類型設定 goto :eof ) REM 驗證資料庫類型 if /i not "%DATABASE_TYPE%"=="mysql" if /i not "%DATABASE_TYPE%"=="sqlite" ( echo %RED%[ERROR]%NC% 無效的資料庫類型: %DATABASE_TYPE% echo %BLUE%[INFO]%NC% 有效選項: mysql, sqlite exit /b 1 ) REM 使用 PowerShell 更新 database.type (使用 UTF8 without BOM) if /i "%DATABASE_TYPE%"=="sqlite" ( REM SQLite 模式: 設定 type=sqlite,清空 MySQL 連線資訊 powershell -Command "$config = Get-Content '%CONFIG_FILE%' -Raw | ConvertFrom-Json; $config.backend.database.type = 'sqlite'; $config.backend.database.host = ''; $config.backend.database.user = ''; $config.backend.database.password = ''; $config.backend.database.database = ''; $json = $config | ConvertTo-Json -Depth 10; [System.IO.File]::WriteAllText('%CONFIG_FILE%', $json, [System.Text.UTF8Encoding]::new($false))" echo %GREEN%[OK]%NC% 資料庫類型已設定為: SQLite ^(本地模式^) ) else ( REM MySQL 模式: 僅設定 type=mysql,保留連線資訊 powershell -Command "$config = Get-Content '%CONFIG_FILE%' -Raw | ConvertFrom-Json; $config.backend.database.type = 'mysql'; $json = $config | ConvertTo-Json -Depth 10; [System.IO.File]::WriteAllText('%CONFIG_FILE%', $json, [System.Text.UTF8Encoding]::new($false))" echo %GREEN%[OK]%NC% 資料庫類型已設定為: MySQL ^(雲端模式^) ) if errorlevel 1 ( echo %RED%[ERROR]%NC% 更新 config.json database.type 失敗 exit /b 1 ) goto :eof :setup_client echo %BLUE%[STEP]%NC% 設置前端建置環境... cd /d "%CLIENT_DIR%" REM 總是執行 npm install 確保依賴完整 echo %BLUE%[INFO]%NC% 安裝前端依賴... call npm install if errorlevel 1 ( echo %RED%[ERROR]%NC% npm install 失敗 exit /b 1 ) REM 確認 electron-builder 已安裝 if not exist "node_modules\electron-builder" ( echo %RED%[ERROR]%NC% electron-builder 未安裝 echo %BLUE%[INFO]%NC% 請檢查 package.json 中的 devDependencies exit /b 1 ) if not exist ".env" ( if exist ".env.example" ( copy .env.example .env >nul echo %YELLOW%[WARN]%NC% 已創建 .env 檔案,請確認設定 ) ) echo %GREEN%[OK]%NC% 前端建置環境就緒 goto :eof :build_electron echo %BLUE%[STEP]%NC% 打包 Electron 應用... cd /d "%CLIENT_DIR%" REM 驗證 BUILD_TARGET if /i "%BUILD_TARGET%"=="nsis" goto :valid_target if /i "%BUILD_TARGET%"=="portable" goto :valid_target echo %RED%[ERROR]%NC% 無效的打包目標: %BUILD_TARGET% echo %BLUE%[INFO]%NC% 有效選項: nsis, portable exit /b 1 :valid_target if /i "%BUILD_TARGET%"=="nsis" ( echo %BLUE%[INFO]%NC% 目標平台: Windows NSIS 安裝檔 - 推薦 ) else ( echo %BLUE%[INFO]%NC% 目標平台: Windows Portable echo %YELLOW%[WARN]%NC% 注意: Portable 模式的臨時資料夾會在關閉時清空 echo %YELLOW%[WARN]%NC% SQLite 資料庫已自動儲存到 %%APPDATA%%\Meeting-Assistant ) REM 清理可能損壞的 electron-builder 快取(解決 symlink 問題) set "EB_CACHE=%LOCALAPPDATA%\electron-builder\Cache\winCodeSign" if exist "%EB_CACHE%" ( echo %BLUE%[INFO]%NC% 清理 electron-builder 快取... rmdir /s /q "%EB_CACHE%" 2>nul ) echo %BLUE%[INFO]%NC% 執行 electron-builder... REM 使用 npm run build 或直接執行 node_modules 中的 electron-builder if exist "node_modules\.bin\electron-builder.cmd" ( call "node_modules\.bin\electron-builder.cmd" --win %BUILD_TARGET% ) else ( call npx electron-builder --win %BUILD_TARGET% ) if errorlevel 1 ( echo %RED%[ERROR]%NC% electron-builder 執行失敗 echo %YELLOW%[WARN]%NC% 如果出現 symlink 錯誤,請嘗試以下方案: echo %YELLOW%[WARN]%NC% 1. 以系統管理員身分執行此腳本 echo %YELLOW%[WARN]%NC% 2. 或啟用 Windows 開發人員模式(設定 ^> 更新與安全性 ^> 開發人員專用) exit /b 1 ) if exist "dist" ( echo %GREEN%[OK]%NC% Electron 打包完成 echo %BLUE%[INFO]%NC% 輸出目錄: %CLIENT_DIR%\dist ) else ( echo %RED%[ERROR]%NC% Electron 打包失敗 - dist 目錄不存在 exit /b 1 ) goto :eof :finalize_build echo %BLUE%[STEP]%NC% 整合建置輸出... if not exist "%BUILD_DIR%" mkdir "%BUILD_DIR%" REM 複製 Electron 輸出 if exist "%CLIENT_DIR%\dist" ( xcopy /s /e /y "%CLIENT_DIR%\dist\*" "%BUILD_DIR%\" >nul 2>&1 ) echo. echo %CYAN%========================================== echo 建置完成 echo ==========================================%NC% echo. echo 輸出目錄: %BUILD_DIR% echo. dir /b "%BUILD_DIR%" echo. echo %GREEN%[OK]%NC% 打包完成! echo. if /i "%BUILD_TARGET%"=="nsis" ( echo Windows 使用說明 (NSIS 安裝檔): echo 1. 找到 build\ 中的 *-setup.exe 檔案 echo 2. 執行安裝檔,選擇安裝目錄 echo 3. 安裝後從開始選單或桌面捷徑啟動 echo 4. 資料會持久保存在安裝目錄中 ) else ( echo Windows 使用說明 (Portable): echo 1. 找到 build\ 中的 *-portable.exe 檔案 echo 2. 直接執行,無需安裝 echo 3. 注意: 關閉程式後臨時檔案會清空 echo 4. SQLite 資料庫保存在 %%APPDATA%%\Meeting-Assistant ) echo. goto :eof :do_build call :show_banner call :check_environment if errorlevel 1 exit /b 1 if "%CLEAN_BUILD%"=="true" call :do_clean REM 更新 API URL(如果有指定) call :update_config REM 更新 embedded backend 設定(如果有指定) call :update_config_embedded REM 更新資料庫類型設定(如果有指定) call :update_config_database if "%SKIP_SIDECAR%"=="false" ( call :setup_sidecar_venv call :build_sidecar ) if "%SKIP_BACKEND%"=="false" ( call :setup_backend_venv call :build_backend ) call :setup_client call :build_electron call :finalize_build goto :eof :do_sidecar call :show_banner call :check_environment if errorlevel 1 exit /b 1 if "%CLEAN_BUILD%"=="true" ( if exist "%SIDECAR_DIR%\dist" rmdir /s /q "%SIDECAR_DIR%\dist" if exist "%SIDECAR_DIR%\build" rmdir /s /q "%SIDECAR_DIR%\build" ) call :setup_sidecar_venv call :build_sidecar goto :eof :do_electron call :show_banner call :check_environment if errorlevel 1 exit /b 1 if not exist "%SIDECAR_DIR%\dist\transcriber" ( if "%SKIP_SIDECAR%"=="false" ( echo %YELLOW%[WARN]%NC% Sidecar 尚未打包 echo %BLUE%[INFO]%NC% 請先執行: %~nx0 sidecar echo %BLUE%[INFO]%NC% 或使用 --skip-sidecar 跳過 exit /b 1 ) ) if "%CLEAN_BUILD%"=="true" ( if exist "%CLIENT_DIR%\dist" rmdir /s /q "%CLIENT_DIR%\dist" ) call :setup_client call :build_electron call :finalize_build goto :eof :show_help echo. echo Meeting Assistant Client - Windows 打包腳本 echo. echo 用法: %~nx0 [命令] [選項] echo. echo 命令: echo build 完整建置 (Sidecar + Electron) echo sidecar 僅打包 Sidecar echo electron 僅打包 Electron (需先打包 Sidecar) echo clean 清理建置目錄 echo help 顯示此幫助訊息 echo. echo 選項: echo --api-url URL 後端 API URL (預設: http://localhost:8000/api) echo --skip-sidecar 跳過 Sidecar 打包 echo --skip-backend 跳過 Backend 打包 (預設) echo --embedded-backend 打包內嵌後端 (全包部署模式) echo --database-type TYPE 資料庫類型: mysql (雲端) 或 sqlite (本地) echo --target TARGET 打包目標: nsis (安裝檔, 預設) 或 portable (免安裝) echo --clean 建置前先清理 echo. echo 範例: echo %~nx0 build 完整建置 (NSIS 安裝檔) echo %~nx0 build --embedded-backend 全包部署 (含內嵌後端) echo %~nx0 build --embedded-backend --database-type sqlite 全包部署 + SQLite echo %~nx0 build --target portable 打包為免安裝 Portable echo %~nx0 build --api-url "http://192.168.1.100:8000/api" 指定遠端後端 echo %~nx0 sidecar 僅打包 Sidecar echo %~nx0 electron --skip-sidecar 僅打包 Electron echo. echo 部署模式: echo 分離部署(預設): 前端連接遠端後端,使用 --api-url 指定後端地址 echo 全包部署: 使用 --embedded-backend 將後端打包進 exe,雙擊即可運行 echo. echo 打包目標: echo nsis(預設): 產生安裝檔,安裝後資料持久保存,推薦正式使用 echo portable: 產生免安裝 exe,臨時資料夾會在關閉時清空 echo SQLite 資料庫會自動儲存到 %%APPDATA%%\Meeting-Assistant echo. echo 資料庫模式: echo MySQL(預設): 連接雲端資料庫,需要網路存取 echo SQLite: 本地資料庫,適合離線或防火牆環境,使用 --database-type sqlite echo. echo 注意: echo - 首次打包 Sidecar 需下載 Whisper 模型,可能需要較長時間 echo - 全包部署需要額外約 50MB 空間用於後端 echo - 確保有足夠的磁碟空間 (建議 5GB+) echo. goto :eof :end endlocal