feat: Excel template export with meeting number auto-generation
- Add meeting_number field (M-YYYYMMDD-XX format) with auto-generation - Refactor Excel export to use cell coordinates instead of placeholders - Export files saved to backend/record/ directory with meeting number filename - Add database migration for meeting_number column - Add start.sh script for managing frontend/backend/sidecar services - Update OpenSpec documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
386
start.sh
Executable file
386
start.sh
Executable file
@@ -0,0 +1,386 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Meeting Assistant - Startup Script
|
||||
# 啟動前端、後端及 Sidecar 服務
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# 顏色定義
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 專案路徑
|
||||
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BACKEND_DIR="$PROJECT_DIR/backend"
|
||||
CLIENT_DIR="$PROJECT_DIR/client"
|
||||
SIDECAR_DIR="$PROJECT_DIR/sidecar"
|
||||
|
||||
# Port 設定
|
||||
BACKEND_PORT=8000
|
||||
|
||||
# PID 檔案
|
||||
PID_FILE="$PROJECT_DIR/.running_pids"
|
||||
|
||||
# 函數:印出訊息
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[OK]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 函數:檢查 port 是否被佔用
|
||||
check_port() {
|
||||
local port=$1
|
||||
if lsof -i :$port > /dev/null 2>&1; then
|
||||
return 0 # port 被佔用
|
||||
else
|
||||
return 1 # port 可用
|
||||
fi
|
||||
}
|
||||
|
||||
# 函數:釋放 port
|
||||
release_port() {
|
||||
local port=$1
|
||||
if check_port $port; then
|
||||
log_warn "Port $port 被佔用,嘗試釋放..."
|
||||
local pid=$(lsof -t -i :$port 2>/dev/null)
|
||||
if [ -n "$pid" ]; then
|
||||
kill -9 $pid 2>/dev/null || true
|
||||
sleep 1
|
||||
log_success "Port $port 已釋放"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# 函數:檢查環境
|
||||
check_environment() {
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Meeting Assistant 環境檢查"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
local all_ok=true
|
||||
|
||||
# 檢查 Python
|
||||
log_info "檢查 Python..."
|
||||
if command -v python3 &> /dev/null; then
|
||||
local py_version=$(python3 --version 2>&1)
|
||||
log_success "Python: $py_version"
|
||||
else
|
||||
log_error "Python3 未安裝"
|
||||
all_ok=false
|
||||
fi
|
||||
|
||||
# 檢查 Node.js
|
||||
log_info "檢查 Node.js..."
|
||||
if command -v node &> /dev/null; then
|
||||
local node_version=$(node --version)
|
||||
log_success "Node.js: $node_version"
|
||||
else
|
||||
log_error "Node.js 未安裝"
|
||||
all_ok=false
|
||||
fi
|
||||
|
||||
# 檢查 npm
|
||||
log_info "檢查 npm..."
|
||||
if command -v npm &> /dev/null; then
|
||||
local npm_version=$(npm --version)
|
||||
log_success "npm: $npm_version"
|
||||
else
|
||||
log_error "npm 未安裝"
|
||||
all_ok=false
|
||||
fi
|
||||
|
||||
# 檢查後端虛擬環境
|
||||
log_info "檢查後端虛擬環境..."
|
||||
if [ -d "$BACKEND_DIR/venv" ]; then
|
||||
log_success "後端虛擬環境: 存在"
|
||||
else
|
||||
log_error "後端虛擬環境不存在,請執行: cd backend && python3 -m venv venv"
|
||||
all_ok=false
|
||||
fi
|
||||
|
||||
# 檢查 Sidecar 虛擬環境
|
||||
log_info "檢查 Sidecar 虛擬環境..."
|
||||
if [ -d "$SIDECAR_DIR/venv" ]; then
|
||||
log_success "Sidecar 虛擬環境: 存在"
|
||||
else
|
||||
log_warn "Sidecar 虛擬環境不存在(語音轉寫功能將無法使用)"
|
||||
fi
|
||||
|
||||
# 檢查前端依賴
|
||||
log_info "檢查前端依賴..."
|
||||
if [ -d "$CLIENT_DIR/node_modules" ]; then
|
||||
log_success "前端依賴: 已安裝"
|
||||
else
|
||||
log_error "前端依賴未安裝,請執行: cd client && npm install"
|
||||
all_ok=false
|
||||
fi
|
||||
|
||||
# 檢查後端 .env
|
||||
log_info "檢查後端環境變數..."
|
||||
if [ -f "$BACKEND_DIR/.env" ]; then
|
||||
log_success "後端 .env: 存在"
|
||||
else
|
||||
log_warn "後端 .env 不存在,請複製 .env.example 並設定"
|
||||
fi
|
||||
|
||||
# 檢查 Port 狀態
|
||||
log_info "檢查 Port $BACKEND_PORT..."
|
||||
if check_port $BACKEND_PORT; then
|
||||
log_warn "Port $BACKEND_PORT 已被佔用"
|
||||
else
|
||||
log_success "Port $BACKEND_PORT: 可用"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ "$all_ok" = true ]; then
|
||||
log_success "環境檢查通過!"
|
||||
return 0
|
||||
else
|
||||
log_error "環境檢查失敗,請修正上述問題"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 函數:啟動後端
|
||||
start_backend() {
|
||||
log_info "啟動後端服務..."
|
||||
|
||||
# 釋放 port
|
||||
release_port $BACKEND_PORT
|
||||
|
||||
cd "$BACKEND_DIR"
|
||||
source venv/bin/activate
|
||||
|
||||
# 背景啟動 uvicorn
|
||||
nohup uvicorn app.main:app --host 0.0.0.0 --port $BACKEND_PORT --reload > "$PROJECT_DIR/backend.log" 2>&1 &
|
||||
local backend_pid=$!
|
||||
echo "BACKEND_PID=$backend_pid" >> "$PID_FILE"
|
||||
|
||||
# 等待啟動
|
||||
sleep 2
|
||||
|
||||
if check_port $BACKEND_PORT; then
|
||||
log_success "後端服務已啟動 (PID: $backend_pid, Port: $BACKEND_PORT)"
|
||||
else
|
||||
log_error "後端服務啟動失敗,請檢查 backend.log"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 函數:啟動前端
|
||||
start_frontend() {
|
||||
log_info "啟動前端應用..."
|
||||
|
||||
cd "$CLIENT_DIR"
|
||||
|
||||
# 背景啟動 Electron(它會自動管理 Sidecar)
|
||||
nohup npm start > "$PROJECT_DIR/frontend.log" 2>&1 &
|
||||
local frontend_pid=$!
|
||||
echo "FRONTEND_PID=$frontend_pid" >> "$PID_FILE"
|
||||
|
||||
sleep 2
|
||||
log_success "前端應用已啟動 (PID: $frontend_pid)"
|
||||
log_info "Sidecar 語音識別引擎將由 Electron 自動管理"
|
||||
}
|
||||
|
||||
# 函數:停止所有服務
|
||||
stop_all() {
|
||||
echo ""
|
||||
log_info "停止所有服務..."
|
||||
|
||||
# 讀取 PID 檔案並終止程序
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
while IFS='=' read -r key pid; do
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
log_info "停止 $key (PID: $pid)..."
|
||||
kill -TERM "$pid" 2>/dev/null || true
|
||||
fi
|
||||
done < "$PID_FILE"
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
|
||||
# 釋放 port
|
||||
release_port $BACKEND_PORT
|
||||
|
||||
# 清理可能殘留的程序
|
||||
pkill -f "uvicorn app.main:app" 2>/dev/null || true
|
||||
pkill -f "electron ." 2>/dev/null || true
|
||||
pkill -f "transcriber.py" 2>/dev/null || true
|
||||
|
||||
sleep 1
|
||||
log_success "所有服務已停止"
|
||||
}
|
||||
|
||||
# 函數:顯示狀態
|
||||
show_status() {
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Meeting Assistant 服務狀態"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# 檢查後端
|
||||
if check_port $BACKEND_PORT; then
|
||||
local backend_pid=$(lsof -t -i :$BACKEND_PORT 2>/dev/null | head -1)
|
||||
log_success "後端服務: 運行中 (PID: $backend_pid, Port: $BACKEND_PORT)"
|
||||
else
|
||||
log_warn "後端服務: 未運行"
|
||||
fi
|
||||
|
||||
# 檢查 Electron
|
||||
local electron_pid=$(pgrep -f "electron ." 2>/dev/null | head -1)
|
||||
if [ -n "$electron_pid" ]; then
|
||||
log_success "前端應用: 運行中 (PID: $electron_pid)"
|
||||
else
|
||||
log_warn "前端應用: 未運行"
|
||||
fi
|
||||
|
||||
# 檢查 Sidecar
|
||||
local sidecar_pid=$(pgrep -f "transcriber.py" 2>/dev/null | head -1)
|
||||
if [ -n "$sidecar_pid" ]; then
|
||||
log_success "Sidecar: 運行中 (PID: $sidecar_pid)"
|
||||
else
|
||||
log_warn "Sidecar: 未運行(將在前端需要時自動啟動)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 函數:顯示幫助
|
||||
show_help() {
|
||||
echo ""
|
||||
echo "Meeting Assistant 啟動腳本"
|
||||
echo ""
|
||||
echo "用法: $0 [命令]"
|
||||
echo ""
|
||||
echo "命令:"
|
||||
echo " start 啟動所有服務(後端 + 前端)"
|
||||
echo " stop 停止所有服務並釋放 port"
|
||||
echo " restart 重啟所有服務"
|
||||
echo " status 顯示服務狀態"
|
||||
echo " check 檢查環境設定"
|
||||
echo " backend 僅啟動後端服務"
|
||||
echo " frontend 僅啟動前端應用"
|
||||
echo " logs 顯示日誌"
|
||||
echo " help 顯示此幫助訊息"
|
||||
echo ""
|
||||
echo "範例:"
|
||||
echo " $0 start # 啟動所有服務"
|
||||
echo " $0 stop # 停止所有服務"
|
||||
echo " $0 status # 查看狀態"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 函數:顯示日誌
|
||||
show_logs() {
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Meeting Assistant 日誌"
|
||||
echo "=========================================="
|
||||
|
||||
if [ -f "$PROJECT_DIR/backend.log" ]; then
|
||||
echo ""
|
||||
echo "--- 後端日誌 (最後 20 行) ---"
|
||||
tail -20 "$PROJECT_DIR/backend.log"
|
||||
fi
|
||||
|
||||
if [ -f "$PROJECT_DIR/frontend.log" ]; then
|
||||
echo ""
|
||||
echo "--- 前端日誌 (最後 20 行) ---"
|
||||
tail -20 "$PROJECT_DIR/frontend.log"
|
||||
fi
|
||||
}
|
||||
|
||||
# 主程式
|
||||
main() {
|
||||
local command=${1:-help}
|
||||
|
||||
case $command in
|
||||
start)
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Meeting Assistant 啟動中..."
|
||||
echo "=========================================="
|
||||
|
||||
# 清理舊的 PID 檔案
|
||||
rm -f "$PID_FILE"
|
||||
|
||||
if ! check_environment; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
start_backend
|
||||
start_frontend
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
log_success "Meeting Assistant 已啟動!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo " 後端 API: http://localhost:$BACKEND_PORT"
|
||||
echo " API 文件: http://localhost:$BACKEND_PORT/docs"
|
||||
echo ""
|
||||
echo " 停止服務: $0 stop"
|
||||
echo " 查看狀態: $0 status"
|
||||
echo " 查看日誌: $0 logs"
|
||||
echo ""
|
||||
;;
|
||||
stop)
|
||||
stop_all
|
||||
;;
|
||||
restart)
|
||||
stop_all
|
||||
sleep 2
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
show_status
|
||||
;;
|
||||
check)
|
||||
check_environment
|
||||
;;
|
||||
backend)
|
||||
release_port $BACKEND_PORT
|
||||
rm -f "$PID_FILE"
|
||||
start_backend
|
||||
;;
|
||||
frontend)
|
||||
start_frontend
|
||||
;;
|
||||
logs)
|
||||
show_logs
|
||||
;;
|
||||
help|--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
log_error "未知命令: $command"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 捕捉中斷信號
|
||||
trap 'echo ""; log_info "收到中斷信號,停止服務..."; stop_all; exit 0' INT TERM
|
||||
|
||||
# 執行主程式
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user