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:
egg
2025-12-11 19:45:53 +08:00
parent a4a2fc3ae7
commit e790f48967
16 changed files with 1139 additions and 122 deletions

386
start.sh Executable file
View 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 "$@"