feat: Add build scripts and runtime config support

Backend:
- Add setup-backend.sh/bat for one-click backend setup
- Fix test_auth.py mock settings (JWT_EXPIRE_HOURS)
- Fix test_excel_export.py TEMPLATE_DIR reference

Frontend:
- Add config.json for runtime API URL configuration
- Add init.js and settings.js for config loading
- Update main.js to load config from external file
- Update api.js to use dynamic API_BASE_URL
- Update all pages to initialize config before API calls
- Update package.json with extraResources for config

Build:
- Add build-client.sh/bat for packaging Electron + Sidecar
- Add build-all.ps1 PowerShell script with -ApiUrl parameter
- Add GitHub Actions workflow for Windows builds
- Add scripts/README.md documentation

This allows IT to configure backend URL without rebuilding.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
egg
2025-12-16 20:03:16 +08:00
parent 01aee1fd0d
commit 7d4fc69071
20 changed files with 2454 additions and 32 deletions

304
scripts/build-all.ps1 Normal file
View File

@@ -0,0 +1,304 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Meeting Assistant - Windows 一鍵打包腳本
.DESCRIPTION
在 Windows 環境下一鍵打包 Sidecar + Electron 成免安裝 exe
.EXAMPLE
.\scripts\build-all.ps1
.\scripts\build-all.ps1 -ApiUrl "http://192.168.1.100:8000/api"
.\scripts\build-all.ps1 -SkipSidecar
.\scripts\build-all.ps1 -Clean
#>
param(
[string]$ApiUrl,
[switch]$SkipSidecar,
[switch]$Clean,
[switch]$Help
)
$ErrorActionPreference = "Stop"
# 顏色輸出函數
function Write-Step { param($msg) Write-Host "[STEP] $msg" -ForegroundColor Cyan }
function Write-OK { param($msg) Write-Host "[OK] $msg" -ForegroundColor Green }
function Write-Warn { param($msg) Write-Host "[WARN] $msg" -ForegroundColor Yellow }
function Write-Err { param($msg) Write-Host "[ERROR] $msg" -ForegroundColor Red }
# 路徑設定
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ProjectDir = Split-Path -Parent $ScriptDir
$SidecarDir = Join-Path $ProjectDir "sidecar"
$ClientDir = Join-Path $ProjectDir "client"
$BuildDir = Join-Path $ProjectDir "build"
function Show-Banner {
Write-Host ""
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host " Meeting Assistant - Windows Builder" -ForegroundColor Cyan
Write-Host " 一鍵打包 Electron + Sidecar" -ForegroundColor Cyan
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host ""
}
function Show-Help {
Write-Host @"
Meeting Assistant - Windows
: .\build-all.ps1 []
:
-ApiUrl URL API URL (: http://localhost:8000/api)
-SkipSidecar Sidecar
-Clean
-Help
:
.\build-all.ps1 # (使 localhost)
.\build-all.ps1 -ApiUrl "http://192.168.1.100:8000/api" # URL
.\build-all.ps1 -ApiUrl "https://api.example.com/api" # 使
.\build-all.ps1 -Clean #
.\build-all.ps1 -SkipSidecar # Electron
"@
}
function Update-Config {
param([string]$NewApiUrl)
Write-Step "更新設定檔..."
$configPath = Join-Path $ClientDir "config.json"
if (Test-Path $configPath) {
$config = Get-Content $configPath -Raw | ConvertFrom-Json
if ($NewApiUrl) {
$config.apiBaseUrl = $NewApiUrl
Write-Host " API URL: $NewApiUrl" -ForegroundColor Gray
}
$config | ConvertTo-Json -Depth 10 | Set-Content $configPath -Encoding UTF8
Write-OK "設定檔已更新"
} else {
Write-Warn "找不到 config.json使用預設設定"
}
}
function Test-Prerequisites {
Write-Step "檢查建置環境..."
# 檢查 Python
try {
$pyVersion = python --version 2>&1
Write-OK "Python: $pyVersion"
} catch {
Write-Err "Python 未安裝,請安裝 Python 3.10+"
exit 1
}
# 檢查 Node.js
try {
$nodeVersion = node --version 2>&1
Write-OK "Node.js: $nodeVersion"
} catch {
Write-Err "Node.js 未安裝,請安裝 Node.js 18+"
exit 1
}
# 檢查 npm
try {
$npmVersion = npm --version 2>&1
Write-OK "npm: $npmVersion"
} catch {
Write-Err "npm 未安裝"
exit 1
}
}
function Clear-BuildDirs {
Write-Step "清理建置目錄..."
$dirsToClean = @(
$BuildDir,
(Join-Path $ClientDir "dist"),
(Join-Path $SidecarDir "dist"),
(Join-Path $SidecarDir "build")
)
foreach ($dir in $dirsToClean) {
if (Test-Path $dir) {
Remove-Item -Recurse -Force $dir
Write-Host " 已刪除: $dir" -ForegroundColor Gray
}
}
# 刪除 spec 檔案
Get-ChildItem -Path $SidecarDir -Filter "*.spec" | Remove-Item -Force
Write-OK "清理完成"
}
function Build-Sidecar {
Write-Step "打包 Sidecar (Python → exe)..."
Write-Host " 這可能需要 5-10 分鐘..." -ForegroundColor Gray
Push-Location $SidecarDir
try {
# 建立/啟動虛擬環境
if (-not (Test-Path "venv")) {
Write-Host " 建立虛擬環境..." -ForegroundColor Gray
python -m venv venv
}
# 啟動虛擬環境並安裝依賴
& ".\venv\Scripts\Activate.ps1"
Write-Host " 安裝依賴..." -ForegroundColor Gray
pip install --upgrade pip -q
pip install -r requirements.txt -q
pip install pyinstaller -q
# 建立 dist 目錄
if (-not (Test-Path "dist")) {
New-Item -ItemType Directory -Path "dist" | Out-Null
}
Write-Host " 執行 PyInstaller..." -ForegroundColor Gray
# PyInstaller 打包
pyinstaller `
--onedir `
--name transcriber `
--distpath dist `
--workpath build `
--noconfirm `
--clean `
--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 (Test-Path "dist\transcriber\transcriber.exe") {
Write-OK "Sidecar 打包完成"
} else {
Write-Err "Sidecar 打包失敗"
exit 1
}
}
finally {
Pop-Location
}
}
function Build-Electron {
Write-Step "打包 Electron 應用..."
Push-Location $ClientDir
try {
# 檢查 Sidecar 是否存在
$sidecarExe = Join-Path $SidecarDir "dist\transcriber\transcriber.exe"
if (-not (Test-Path $sidecarExe)) {
Write-Err "找不到 Sidecar: $sidecarExe"
Write-Warn "請先執行完整打包或移除 -SkipSidecar 參數"
exit 1
}
# 安裝依賴
if (-not (Test-Path "node_modules")) {
Write-Host " 安裝 npm 依賴..." -ForegroundColor Gray
npm install
}
# 建立 .env 如果不存在
if (-not (Test-Path ".env") -and (Test-Path ".env.example")) {
Copy-Item ".env.example" ".env"
Write-Warn "已建立 .env請確認設定"
}
Write-Host " 執行 electron-builder..." -ForegroundColor Gray
npm run build -- --win
if (Test-Path "dist\*.exe") {
Write-OK "Electron 打包完成"
} else {
Write-Err "Electron 打包失敗"
exit 1
}
}
finally {
Pop-Location
}
}
function Copy-Output {
Write-Step "整合輸出..."
if (-not (Test-Path $BuildDir)) {
New-Item -ItemType Directory -Path $BuildDir | Out-Null
}
# 複製 exe 檔案
$exeFiles = Get-ChildItem -Path (Join-Path $ClientDir "dist") -Filter "*.exe"
foreach ($file in $exeFiles) {
Copy-Item $file.FullName -Destination $BuildDir
Write-Host " 複製: $($file.Name)" -ForegroundColor Gray
}
Write-Host ""
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host " 打包完成!" -ForegroundColor Green
Write-Host "==========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host " 輸出目錄: $BuildDir" -ForegroundColor White
Write-Host ""
# 列出輸出檔案
Get-ChildItem -Path $BuildDir | ForEach-Object {
$size = [math]::Round($_.Length / 1MB, 2)
Write-Host " $($_.Name) ($size MB)" -ForegroundColor Gray
}
Write-Host ""
Write-Host " 使用說明:" -ForegroundColor Yellow
Write-Host " 直接執行 .exe 檔案即可,無需安裝" -ForegroundColor Gray
Write-Host ""
}
# 主程式
if ($Help) {
Show-Help
exit 0
}
Show-Banner
Test-Prerequisites
if ($Clean) {
Clear-BuildDirs
}
# Update config.json if API URL is specified
if ($ApiUrl) {
Update-Config -NewApiUrl $ApiUrl
}
if (-not $SkipSidecar) {
Build-Sidecar
}
Build-Electron
Copy-Output