chore: Archive all pending OpenSpec proposals
Force archive the following proposals: - add-audio-device-selector (complete) - add-embedded-backend-packaging (19/26 tasks) - add-flexible-deployment-options (20/21 tasks) New specs created: - audio-device-management (7 requirements) - embedded-backend (8 requirements) Updated specs: - transcription (+2 requirements for model download progress) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,129 @@
|
|||||||
|
# Design: Extract Environment Variables
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
專案需要支援以下部署場景:
|
||||||
|
1. 開發環境:前後端在本地同時運行
|
||||||
|
2. 生產環境:後端部署於 1Panel 伺服器,前端(Electron 應用)獨立打包部署
|
||||||
|
|
||||||
|
**架構說明**:
|
||||||
|
- **後端**:FastAPI 服務,使用兩個 Dify 服務(LLM 摘要 + STT 轉錄)
|
||||||
|
- **前端**:Electron 應用,包含 Sidecar(本地 Whisper 即時轉錄服務)
|
||||||
|
|
||||||
|
目前的硬編碼配置使得部署困難,且敏感資訊(如 API 密鑰、資料庫密碼)散落在代碼中。
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
### Goals
|
||||||
|
- 將所有硬編碼配置提取到環境變數
|
||||||
|
- 提供完整的 `.env.example` 範例檔案
|
||||||
|
- 支援前端獨立打包時指定後端 API URL
|
||||||
|
- 提供 1Panel 部署完整指南和腳本
|
||||||
|
- 確保向後相容(預設值與現有行為一致)
|
||||||
|
|
||||||
|
### Non-Goals
|
||||||
|
- 不實現配置熱重載
|
||||||
|
- 不實現密鑰輪換機制
|
||||||
|
- 不實現多環境配置管理(如 .env.production, .env.staging)
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### 1. 環境變數命名規範
|
||||||
|
|
||||||
|
**決定**:使用大寫蛇形命名法,前端變數加 `VITE_` 前綴
|
||||||
|
|
||||||
|
**原因**:
|
||||||
|
- Vite 要求客戶端環境變數必須以 `VITE_` 開頭
|
||||||
|
- 大寫蛇形是環境變數的標準慣例
|
||||||
|
|
||||||
|
### 2. 前端 API URL 配置
|
||||||
|
|
||||||
|
**決定**:使用 `VITE_API_BASE_URL` 環境變數,在 `api.js` 中讀取
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "http://localhost:8000/api";
|
||||||
|
```
|
||||||
|
|
||||||
|
**替代方案**:
|
||||||
|
- 使用 runtime 配置檔案(如 `/config.js`)- 更靈活但增加部署複雜度
|
||||||
|
- 使用相對路徑 `/api` - 需要 nginx 反向代理,不適合獨立部署
|
||||||
|
|
||||||
|
### 3. 超時配置單位
|
||||||
|
|
||||||
|
**決定**:統一使用毫秒(ms),與 JavaScript 一致
|
||||||
|
|
||||||
|
**後端配置項**:
|
||||||
|
| 變數名 | 預設值 | 用途 |
|
||||||
|
|--------|--------|------|
|
||||||
|
| UPLOAD_TIMEOUT | 600000 | 大檔案上傳(10分鐘) |
|
||||||
|
| DIFY_STT_TIMEOUT | 300000 | Dify STT 轉錄每個分塊(5分鐘) |
|
||||||
|
| LLM_TIMEOUT | 120000 | Dify LLM 摘要處理(2分鐘) |
|
||||||
|
| AUTH_TIMEOUT | 30000 | 認證 API 調用(30秒) |
|
||||||
|
|
||||||
|
**前端/Sidecar 配置項**:
|
||||||
|
| 變數名 | 預設值 | 用途 |
|
||||||
|
|--------|--------|------|
|
||||||
|
| WHISPER_MODEL | medium | 本地 Whisper 模型大小 |
|
||||||
|
| WHISPER_DEVICE | cpu | 執行裝置(cpu/cuda) |
|
||||||
|
| WHISPER_COMPUTE | int8 | 運算精度 |
|
||||||
|
|
||||||
|
### 4. 1Panel 部署架構
|
||||||
|
|
||||||
|
**決定**:使用 systemd 管理後端服務,nginx 反向代理
|
||||||
|
|
||||||
|
```
|
||||||
|
[Client] → [Nginx:443] → [Uvicorn:8000]
|
||||||
|
↓
|
||||||
|
[Static Files]
|
||||||
|
```
|
||||||
|
|
||||||
|
**原因**:
|
||||||
|
- systemd 提供進程管理、日誌、自動重啟
|
||||||
|
- nginx 處理 HTTPS、靜態檔案、反向代理
|
||||||
|
- 這是 1Panel 的標準部署模式
|
||||||
|
|
||||||
|
### 5. CORS 配置
|
||||||
|
|
||||||
|
**決定**:保持 `allow_origins=["*"]`,不額外配置
|
||||||
|
|
||||||
|
**原因**:
|
||||||
|
- 前端是 Electron 桌面應用,分發到多台電腦
|
||||||
|
- Electron 主進程的 HTTP 請求不受 CORS 限制
|
||||||
|
- 簡化部署配置,IT 只需關心 HOST 和 PORT
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
### 風險 1:環境變數遺漏
|
||||||
|
- **風險**:部署時遺漏必要的環境變數導致服務異常
|
||||||
|
- **緩解**:提供完整的 `.env.example`,啟動時檢查必要變數
|
||||||
|
|
||||||
|
### 風險 2:前端打包後無法修改 API URL
|
||||||
|
- **風險**:Vite 環境變數在打包時固定
|
||||||
|
- **緩解**:文件中說明需要為不同環境分別打包,或考慮未來實現 runtime 配置
|
||||||
|
|
||||||
|
### 風險 3:敏感資訊外洩
|
||||||
|
- **風險**:`.env` 檔案被提交到版本控制
|
||||||
|
- **緩解**:確保 `.gitignore` 包含 `.env`,只提交 `.env.example`
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
1. **Phase 1 - 後端配置**
|
||||||
|
- 更新 `config.py` 添加新配置項
|
||||||
|
- 更新各 router 使用配置
|
||||||
|
- 更新 `.env` 和 `.env.example`
|
||||||
|
|
||||||
|
2. **Phase 2 - 前端配置**
|
||||||
|
- 創建 `.env` 和 `.env.example`
|
||||||
|
- 更新 `api.js` 使用環境變數
|
||||||
|
|
||||||
|
3. **Phase 3 - 部署文件**
|
||||||
|
- 創建 1Panel 部署指南
|
||||||
|
- 創建部署腳本
|
||||||
|
|
||||||
|
4. **Rollback**
|
||||||
|
- 所有配置都有預設值,回滾只需刪除環境變數
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
- Q: 是否需要支援 Docker 部署?
|
||||||
|
- A: 暫不包含,但環境變數配置天然支援 Docker
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Change: Extract Hardcoded Configurations to Environment Variables
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
專案中存在大量硬編碼的路徑、URL、API 端點、埠號及敏感資訊,這些配置散落在前後端程式碼中。為了支援獨立部署(後端部署於 1Panel 伺服器,前端獨立打包),需要將這些配置統一提取到環境變數檔案中管理,提高部署彈性與安全性。
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
### 後端配置提取
|
||||||
|
|
||||||
|
後端使用兩個 Dify 服務:
|
||||||
|
- **LLM 服務**(`DIFY_API_KEY`)- 產生會議結論及行動事項
|
||||||
|
- **STT 服務**(`DIFY_STT_API_KEY`)- 上傳音訊檔案的語音轉文字
|
||||||
|
|
||||||
|
1. **新增環境變數**
|
||||||
|
- `BACKEND_HOST` - 後端監聽地址(預設:0.0.0.0)
|
||||||
|
- `BACKEND_PORT` - 後端監聽埠號(預設:8000)
|
||||||
|
- `DB_POOL_SIZE` - 資料庫連線池大小(預設:5)
|
||||||
|
- `JWT_EXPIRE_HOURS` - JWT Token 過期時間(預設:24)
|
||||||
|
- `UPLOAD_TIMEOUT` - 檔案上傳超時時間(預設:600000ms)
|
||||||
|
- `DIFY_STT_TIMEOUT` - Dify STT 轉錄超時時間(預設:300000ms)
|
||||||
|
- `LLM_TIMEOUT` - Dify LLM 處理超時時間(預設:120000ms)
|
||||||
|
- `AUTH_TIMEOUT` - 認證 API 超時時間(預設:30000ms)
|
||||||
|
- `TEMPLATE_DIR` - Excel 範本目錄路徑
|
||||||
|
- `RECORD_DIR` - 會議記錄匯出目錄路徑
|
||||||
|
- `MAX_FILE_SIZE` - 最大上傳檔案大小(預設:500MB)
|
||||||
|
- `SUPPORTED_AUDIO_FORMATS` - 支援的音訊格式
|
||||||
|
|
||||||
|
**註**:CORS 保持 `allow_origins=["*"]`,因為前端是 Electron 桌面應用,無需細粒度控制。
|
||||||
|
|
||||||
|
2. **已存在環境變數**(確認文件化)
|
||||||
|
- `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASS`, `DB_NAME` - 資料庫配置
|
||||||
|
- `AUTH_API_URL` - 認證 API 端點
|
||||||
|
- `DIFY_API_URL` - Dify API 基礎 URL
|
||||||
|
- `DIFY_API_KEY` - Dify LLM 服務金鑰
|
||||||
|
- `DIFY_STT_API_KEY` - Dify STT 服務金鑰
|
||||||
|
- `ADMIN_EMAIL` - 管理員郵箱
|
||||||
|
- `JWT_SECRET` - JWT 密鑰
|
||||||
|
|
||||||
|
### 前端/Electron 配置提取
|
||||||
|
|
||||||
|
前端包含 Sidecar(本地 Whisper 即時轉錄服務)。
|
||||||
|
|
||||||
|
1. **Vite 環境變數**(打包時使用)
|
||||||
|
- `VITE_API_BASE_URL` - 後端 API 基礎 URL(預設:http://localhost:8000/api)
|
||||||
|
- `VITE_UPLOAD_TIMEOUT` - 大檔案上傳超時時間(預設:600000ms)
|
||||||
|
- `VITE_APP_TITLE` - 應用程式標題
|
||||||
|
|
||||||
|
2. **Sidecar/Whisper 環境變數**(執行時使用)
|
||||||
|
- `WHISPER_MODEL` - 模型大小(預設:medium)
|
||||||
|
- `WHISPER_DEVICE` - 執行裝置(預設:cpu)
|
||||||
|
- `WHISPER_COMPUTE` - 運算精度(預設:int8)
|
||||||
|
- `SIDECAR_DIR` - Sidecar 目錄路徑(Electron 打包時使用)
|
||||||
|
|
||||||
|
### 部署文件與腳本
|
||||||
|
|
||||||
|
1. **1Panel 部署指南** - `docs/1panel-deployment.md`
|
||||||
|
2. **後端部署腳本** - `scripts/deploy-backend.sh`
|
||||||
|
3. **環境變數範例檔案**
|
||||||
|
- 更新 `backend/.env.example`
|
||||||
|
- 新增 `client/.env.example`
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Affected specs: `middleware`
|
||||||
|
- Affected code:
|
||||||
|
- `backend/app/config.py` - 新增配置項
|
||||||
|
- `backend/app/database.py` - 使用連線池配置
|
||||||
|
- `backend/app/routers/ai.py` - 使用 Dify 超時配置
|
||||||
|
- `backend/app/routers/auth.py` - 使用認證超時配置
|
||||||
|
- `backend/app/routers/export.py` - 使用目錄路徑配置
|
||||||
|
- `client/src/services/api.js` - 使用 Vite 環境變數
|
||||||
|
- `client/src/main.js` - Sidecar 路徑配置
|
||||||
|
- `start.sh` - 更新啟動腳本
|
||||||
|
|
||||||
|
## 部署流程簡化
|
||||||
|
|
||||||
|
**IT 只需提供:**
|
||||||
|
1. 後端伺服器 IP/域名
|
||||||
|
2. 後端使用的 PORT
|
||||||
|
|
||||||
|
**開發者打包前端時:**
|
||||||
|
1. 設定 `VITE_API_BASE_URL=http://<伺服器>:<PORT>/api`
|
||||||
|
2. 執行打包命令
|
||||||
|
3. 分發 EXE 給使用者
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: FastAPI Server Configuration
|
||||||
|
The middleware server SHALL be implemented using Python FastAPI framework with comprehensive environment-based configuration supporting standalone deployment.
|
||||||
|
|
||||||
|
#### Scenario: Server startup with valid configuration
|
||||||
|
- **WHEN** the server starts with valid .env file containing all required variables (DB_HOST, DB_PORT, DB_USER, DB_PASS, DB_NAME, DIFY_API_URL, DIFY_API_KEY, AUTH_API_URL)
|
||||||
|
- **THEN** the server SHALL start successfully and accept connections on the configured BACKEND_HOST and BACKEND_PORT
|
||||||
|
|
||||||
|
#### Scenario: Server startup with missing configuration
|
||||||
|
- **WHEN** the server starts with missing required environment variables
|
||||||
|
- **THEN** the server SHALL fail to start with descriptive error message
|
||||||
|
|
||||||
|
#### Scenario: Server startup with optional configuration
|
||||||
|
- **WHEN** optional environment variables (BACKEND_PORT, DB_POOL_SIZE, etc.) are not set
|
||||||
|
- **THEN** the server SHALL use sensible defaults and start normally
|
||||||
|
|
||||||
|
### Requirement: Database Connection Pool
|
||||||
|
The middleware server SHALL maintain a configurable connection pool to the MySQL database using environment variables.
|
||||||
|
|
||||||
|
#### Scenario: Database connection success
|
||||||
|
- **WHEN** the server connects to MySQL with valid credentials from environment
|
||||||
|
- **THEN** a connection pool SHALL be established with DB_POOL_SIZE connections
|
||||||
|
|
||||||
|
#### Scenario: Database connection failure
|
||||||
|
- **WHEN** the database is unreachable
|
||||||
|
- **THEN** the server SHALL return HTTP 503 with error details for affected endpoints
|
||||||
|
|
||||||
|
### Requirement: CORS Configuration
|
||||||
|
The middleware server SHALL allow cross-origin requests from all origins to support Electron desktop application clients.
|
||||||
|
|
||||||
|
#### Scenario: CORS preflight request
|
||||||
|
- **WHEN** any client sends OPTIONS request
|
||||||
|
- **THEN** the server SHALL respond with CORS headers allowing the request (allow_origins=["*"])
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Backend Server Configuration
|
||||||
|
The middleware server SHALL support configurable host and port through environment variables for flexible deployment.
|
||||||
|
|
||||||
|
#### Scenario: Custom port binding
|
||||||
|
- **WHEN** BACKEND_PORT environment variable is set to 9000
|
||||||
|
- **THEN** the server SHALL listen on port 9000
|
||||||
|
|
||||||
|
#### Scenario: Production host binding
|
||||||
|
- **WHEN** BACKEND_HOST is set to 0.0.0.0
|
||||||
|
- **THEN** the server SHALL accept connections from any network interface
|
||||||
|
|
||||||
|
#### Scenario: Default configuration
|
||||||
|
- **WHEN** BACKEND_HOST and BACKEND_PORT are not set
|
||||||
|
- **THEN** the server SHALL default to 0.0.0.0:8000
|
||||||
|
|
||||||
|
### Requirement: Timeout Configuration
|
||||||
|
The middleware server SHALL support configurable timeout values for different operations through environment variables.
|
||||||
|
|
||||||
|
#### Scenario: File upload timeout
|
||||||
|
- **WHEN** UPLOAD_TIMEOUT is set to 900000 (15 minutes)
|
||||||
|
- **THEN** file upload operations SHALL allow up to 15 minutes before timeout
|
||||||
|
|
||||||
|
#### Scenario: LLM processing timeout
|
||||||
|
- **WHEN** LLM_TIMEOUT is set to 180000 (3 minutes)
|
||||||
|
- **THEN** Dify LLM summarization operations SHALL allow up to 3 minutes before timeout
|
||||||
|
|
||||||
|
#### Scenario: Dify STT timeout
|
||||||
|
- **WHEN** DIFY_STT_TIMEOUT is set to 600000 (10 minutes)
|
||||||
|
- **THEN** Dify STT audio transcription per chunk SHALL allow up to 10 minutes before timeout
|
||||||
|
|
||||||
|
#### Scenario: Authentication timeout
|
||||||
|
- **WHEN** AUTH_TIMEOUT is set to 60000 (1 minute)
|
||||||
|
- **THEN** authentication API calls SHALL allow up to 1 minute before timeout
|
||||||
|
|
||||||
|
### Requirement: File Path Configuration
|
||||||
|
The middleware server SHALL support configurable directory paths for templates and records.
|
||||||
|
|
||||||
|
#### Scenario: Custom template directory
|
||||||
|
- **WHEN** TEMPLATE_DIR environment variable is set to /data/templates
|
||||||
|
- **THEN** Excel templates SHALL be loaded from /data/templates
|
||||||
|
|
||||||
|
#### Scenario: Custom record directory
|
||||||
|
- **WHEN** RECORD_DIR environment variable is set to /data/records
|
||||||
|
- **THEN** exported meeting records SHALL be saved to /data/records
|
||||||
|
|
||||||
|
#### Scenario: Relative path resolution
|
||||||
|
- **WHEN** directory paths are relative
|
||||||
|
- **THEN** they SHALL be resolved relative to the backend application root
|
||||||
|
|
||||||
|
### Requirement: Frontend Environment Configuration
|
||||||
|
The frontend Electron application SHALL support environment-based API URL configuration for connecting to deployed backend.
|
||||||
|
|
||||||
|
#### Scenario: Custom API URL in production build
|
||||||
|
- **WHEN** VITE_API_BASE_URL is set to http://192.168.1.100:8000/api during build
|
||||||
|
- **THEN** the built Electron app SHALL connect to http://192.168.1.100:8000/api
|
||||||
|
|
||||||
|
#### Scenario: Default API URL in development
|
||||||
|
- **WHEN** VITE_API_BASE_URL is not set
|
||||||
|
- **THEN** the frontend SHALL default to http://localhost:8000/api
|
||||||
|
|
||||||
|
### Requirement: Sidecar Whisper Configuration
|
||||||
|
The Electron frontend's Sidecar (local Whisper transcription service) SHALL support environment-based model configuration.
|
||||||
|
|
||||||
|
#### Scenario: Custom Whisper model
|
||||||
|
- **WHEN** WHISPER_MODEL environment variable is set to "large"
|
||||||
|
- **THEN** the Sidecar SHALL load the large Whisper model for transcription
|
||||||
|
|
||||||
|
#### Scenario: GPU acceleration
|
||||||
|
- **WHEN** WHISPER_DEVICE is set to "cuda" and WHISPER_COMPUTE is set to "float16"
|
||||||
|
- **THEN** the Sidecar SHALL use GPU for faster transcription
|
||||||
|
|
||||||
|
#### Scenario: Default CPU mode
|
||||||
|
- **WHEN** WHISPER_DEVICE is not set
|
||||||
|
- **THEN** the Sidecar SHALL default to CPU with int8 compute type
|
||||||
|
|
||||||
|
### Requirement: Environment Example Files
|
||||||
|
The project SHALL provide example environment files documenting all configuration options.
|
||||||
|
|
||||||
|
#### Scenario: Backend environment example
|
||||||
|
- **WHEN** developer sets up backend
|
||||||
|
- **THEN** backend/.env.example SHALL list all environment variables with descriptions and example values (without sensitive data)
|
||||||
|
|
||||||
|
#### Scenario: Frontend environment example
|
||||||
|
- **WHEN** developer sets up frontend
|
||||||
|
- **THEN** client/.env.example SHALL list all VITE_ prefixed and WHISPER_ prefixed environment variables with descriptions
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Tasks: Extract Environment Variables
|
||||||
|
|
||||||
|
## 1. Backend Configuration
|
||||||
|
|
||||||
|
- [x] 1.1 Update `backend/app/config.py` with new environment variables
|
||||||
|
- Add: BACKEND_HOST, BACKEND_PORT
|
||||||
|
- Add: DB_POOL_SIZE, JWT_EXPIRE_HOURS
|
||||||
|
- Add: UPLOAD_TIMEOUT, DIFY_STT_TIMEOUT, LLM_TIMEOUT, AUTH_TIMEOUT
|
||||||
|
- Add: TEMPLATE_DIR, RECORD_DIR, MAX_FILE_SIZE, SUPPORTED_AUDIO_FORMATS
|
||||||
|
- [x] 1.2 Update `backend/app/database.py` to use DB_POOL_SIZE from config
|
||||||
|
- [x] 1.3 Update `backend/app/routers/ai.py` to use Dify timeout configs (DIFY_STT_TIMEOUT, LLM_TIMEOUT)
|
||||||
|
- [x] 1.4 Update `backend/app/routers/auth.py` to use AUTH_TIMEOUT and JWT_EXPIRE_HOURS from config
|
||||||
|
- [x] 1.5 Update `backend/app/routers/export.py` to use TEMPLATE_DIR and RECORD_DIR from config
|
||||||
|
- [x] 1.6 Update `backend/.env` with all new variables
|
||||||
|
- [x] 1.7 Update `backend/.env.example` with all variables (without sensitive values)
|
||||||
|
|
||||||
|
## 2. Frontend/Electron Configuration
|
||||||
|
|
||||||
|
- [x] 2.1 Create `client/.env` with VITE_API_BASE_URL and Whisper settings
|
||||||
|
- [x] 2.2 Create `client/.env.example` as template
|
||||||
|
- [x] 2.3 Update `client/src/services/api.js` to use import.meta.env.VITE_API_BASE_URL
|
||||||
|
- [x] 2.4 Update `client/src/main.js` to pass Whisper env vars to Sidecar process
|
||||||
|
|
||||||
|
## 3. Startup Scripts
|
||||||
|
|
||||||
|
- [x] 3.1 Update `start.sh` to load environment variables properly
|
||||||
|
- [x] 3.2 Create `scripts/deploy-backend.sh` for standalone backend deployment
|
||||||
|
|
||||||
|
## 4. Deployment Documentation
|
||||||
|
|
||||||
|
- [x] 4.1 Create `docs/1panel-deployment.md` with step-by-step guide
|
||||||
|
- Include: Prerequisites and system requirements
|
||||||
|
- Include: Python environment setup
|
||||||
|
- Include: Environment variable configuration (IT only needs HOST + PORT)
|
||||||
|
- Include: Nginx reverse proxy configuration example
|
||||||
|
- Include: Systemd service file example
|
||||||
|
- Include: SSL/HTTPS setup guide (optional)
|
||||||
|
- Include: Troubleshooting common issues
|
||||||
|
|
||||||
|
## 5. Validation
|
||||||
|
|
||||||
|
- [ ] 5.1 Test backend starts with new config
|
||||||
|
- [ ] 5.2 Test frontend builds with environment variables
|
||||||
|
- [ ] 5.3 Test API connectivity between frontend and backend
|
||||||
|
- [ ] 5.4 Verify all hardcoded values are externalized
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
# Design: add-audio-device-selector
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Component Structure
|
||||||
|
```
|
||||||
|
meeting-detail.html
|
||||||
|
├── Audio Device Panel (新增)
|
||||||
|
│ ├── Device Selector (dropdown)
|
||||||
|
│ ├── Volume Meter (canvas/div bars)
|
||||||
|
│ ├── Test Controls
|
||||||
|
│ │ ├── Start Test Button
|
||||||
|
│ │ ├── Stop Test Button
|
||||||
|
│ │ └── Play Test Button
|
||||||
|
│ └── Status Indicator
|
||||||
|
└── Existing Recording Controls
|
||||||
|
└── Uses selected device from panel
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
```
|
||||||
|
User selects device → Update localStorage → Update AudioContext
|
||||||
|
→ Start volume monitoring
|
||||||
|
→ Enable test recording
|
||||||
|
|
||||||
|
Test Recording Flow:
|
||||||
|
Start Test → getUserMedia(selected device) → MediaRecorder → Blob
|
||||||
|
Play Test → Audio element → Play blob URL
|
||||||
|
|
||||||
|
Main Recording Flow:
|
||||||
|
Start Recording → Read selected device from state
|
||||||
|
→ getUserMedia(selected device)
|
||||||
|
→ Existing transcription flow
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technical Decisions
|
||||||
|
|
||||||
|
### TD-1: Volume Meter Implementation
|
||||||
|
**Options Considered:**
|
||||||
|
1. **Web Audio API AnalyserNode** - Real-time frequency/amplitude analysis
|
||||||
|
2. **MediaRecorder + periodic sampling** - Sample audio levels periodically
|
||||||
|
3. **CSS-only animation** - Fake animation without real audio data
|
||||||
|
|
||||||
|
**Decision:** Web Audio API AnalyserNode
|
||||||
|
- Provides accurate real-time audio level data
|
||||||
|
- Low latency visualization
|
||||||
|
- Standard browser API, well-supported in Electron
|
||||||
|
|
||||||
|
### TD-2: Device Preference Storage
|
||||||
|
**Options Considered:**
|
||||||
|
1. **localStorage** - Simple key-value storage
|
||||||
|
2. **config.json** - App configuration file
|
||||||
|
3. **Backend database** - Per-user settings
|
||||||
|
|
||||||
|
**Decision:** localStorage
|
||||||
|
- No backend changes required
|
||||||
|
- Immediate persistence
|
||||||
|
- Per-device settings (user may use different mics on different computers)
|
||||||
|
|
||||||
|
### TD-3: Test Recording Duration
|
||||||
|
**Decision:** 5 seconds fixed duration
|
||||||
|
- Long enough to verify audio quality
|
||||||
|
- Short enough to not waste time
|
||||||
|
- Auto-stop prevents forgotten recordings
|
||||||
|
|
||||||
|
### TD-4: UI Placement
|
||||||
|
**Options Considered:**
|
||||||
|
1. **Modal dialog** - Opens on demand
|
||||||
|
2. **Collapsible panel** - Always visible but can be collapsed
|
||||||
|
3. **Settings page** - Separate page for audio settings
|
||||||
|
|
||||||
|
**Decision:** Collapsible panel in meeting-detail page
|
||||||
|
- Quick access before recording
|
||||||
|
- No page navigation needed
|
||||||
|
- Can be collapsed when not needed
|
||||||
|
|
||||||
|
## UI Mockup
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ Audio Device Settings [▼] │
|
||||||
|
├─────────────────────────────────────────────────────────┤
|
||||||
|
│ Microphone: [▼ Realtek Microphone (Realtek Audio) ▼] │
|
||||||
|
│ │
|
||||||
|
│ Input Level: ████████░░░░░░░░░░░░ 45% │
|
||||||
|
│ │
|
||||||
|
│ [🎤 Test Recording] [▶️ Play Test] Status: Ready │
|
||||||
|
│ │
|
||||||
|
│ ℹ️ Click "Test Recording" to verify your microphone │
|
||||||
|
└─────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
### Audio Device State
|
||||||
|
```javascript
|
||||||
|
const audioDeviceState = {
|
||||||
|
availableDevices: [], // Array of MediaDeviceInfo
|
||||||
|
selectedDeviceId: null, // Selected device ID or null for default
|
||||||
|
isMonitoring: false, // Volume meter active
|
||||||
|
currentLevel: 0, // Current audio level 0-100
|
||||||
|
testRecording: null, // Blob of test recording
|
||||||
|
testState: 'idle' // 'idle' | 'recording' | 'playing'
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### localStorage Keys
|
||||||
|
- `audioDevice.selectedId` - Last selected device ID
|
||||||
|
- `audioDevice.lastUsedLabel` - Device label for display fallback
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### With Existing Recording
|
||||||
|
1. `startRecording()` will read `selectedDeviceId` from state
|
||||||
|
2. If no device selected, use current auto-selection logic
|
||||||
|
3. If selected device unavailable, show error and prompt reselection
|
||||||
|
|
||||||
|
### IPC Considerations
|
||||||
|
- No new IPC handlers needed
|
||||||
|
- All audio device operations happen in renderer process
|
||||||
|
- Uses existing `navigator.mediaDevices` API
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| Error | User Message | Recovery |
|
||||||
|
|-------|-------------|----------|
|
||||||
|
| No devices found | "未偵測到麥克風,請連接麥克風後重試" | Refresh device list |
|
||||||
|
| Device disconnected | "選擇的麥克風已斷開連接" | Auto-switch to default |
|
||||||
|
| Permission denied | "麥克風權限被拒絕,請在系統設定中允許" | Show permission guide |
|
||||||
|
| Device busy | "麥克風正被其他應用程式使用" | Retry button |
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
1. Connect multiple microphones
|
||||||
|
2. Verify all appear in dropdown
|
||||||
|
3. Select each and verify volume meter responds
|
||||||
|
4. Record and play test audio for each
|
||||||
|
5. Unplug device during use and verify error handling
|
||||||
|
6. Restart app and verify saved preference loads
|
||||||
|
|
||||||
|
### Automated Testing (Future)
|
||||||
|
- Mock `navigator.mediaDevices` for unit tests
|
||||||
|
- Test device switching logic
|
||||||
|
- Test localStorage persistence
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Proposal: add-audio-device-selector
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
新增音訊設備選擇與驗證功能,讓使用者可以手動選擇麥克風、即時預覽音量、進行收音測試及播放測試錄音。
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
目前系統自動選擇麥克風,使用者無法:
|
||||||
|
1. 查看可用的音訊輸入設備清單
|
||||||
|
2. 手動選擇偏好的麥克風
|
||||||
|
3. 在錄音前確認麥克風是否正常運作
|
||||||
|
4. 測試收音品質
|
||||||
|
|
||||||
|
這導致使用者在錄音失敗時難以診斷問題,也無法在多個麥克風之間切換。
|
||||||
|
|
||||||
|
## Proposed Solution
|
||||||
|
在會議詳情頁面新增音訊設備管理面板,包含:
|
||||||
|
|
||||||
|
1. **設備選擇器**:下拉選單顯示所有可用麥克風
|
||||||
|
2. **音量指示器**:即時顯示麥克風輸入音量(VU meter)
|
||||||
|
3. **收音測試**:錄製 5 秒測試音訊
|
||||||
|
4. **播放測試**:播放剛錄製的測試音訊
|
||||||
|
5. **設備狀態指示**:顯示目前選中設備的連線狀態
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
- **In Scope**:
|
||||||
|
- 前端 UI 元件(設備選擇器、音量計、測試按鈕)
|
||||||
|
- 設備列舉與切換邏輯
|
||||||
|
- 測試錄音與播放功能
|
||||||
|
- 使用者偏好設定儲存(localStorage)
|
||||||
|
|
||||||
|
- **Out of Scope**:
|
||||||
|
- 系統音訊輸出設備選擇
|
||||||
|
- 音訊處理效果(降噪、增益等)
|
||||||
|
- 遠端音訊設備支援
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
- 使用者可以看到所有可用麥克風並選擇一個
|
||||||
|
- 選擇麥克風後可即時看到音量變化
|
||||||
|
- 測試錄音功能可錄製 5 秒音訊並播放
|
||||||
|
- 偏好設定在下次開啟時保留
|
||||||
|
- 錄音功能使用使用者選擇的麥克風
|
||||||
|
|
||||||
|
## Stakeholders
|
||||||
|
- End Users: 會議記錄人員
|
||||||
|
- Developers: 前端開發團隊
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
# audio-device-management Specification Delta
|
||||||
|
|
||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Audio Device Enumeration
|
||||||
|
The frontend SHALL enumerate and display all available audio input devices.
|
||||||
|
|
||||||
|
#### Scenario: List available devices
|
||||||
|
- **WHEN** user opens meeting detail page
|
||||||
|
- **THEN** system SHALL enumerate all audio input devices
|
||||||
|
- **AND** display them in a dropdown selector
|
||||||
|
- **AND** exclude virtual/system devices like "Stereo Mix"
|
||||||
|
|
||||||
|
#### Scenario: Refresh device list
|
||||||
|
- **WHEN** user clicks refresh button or device is connected/disconnected
|
||||||
|
- **THEN** system SHALL re-enumerate devices
|
||||||
|
- **AND** update dropdown options
|
||||||
|
- **AND** preserve current selection if still available
|
||||||
|
|
||||||
|
#### Scenario: Device label display
|
||||||
|
- **WHEN** devices are listed
|
||||||
|
- **THEN** each device SHALL display its friendly name (label)
|
||||||
|
- **AND** indicate if it's the system default device
|
||||||
|
|
||||||
|
### Requirement: Manual Device Selection
|
||||||
|
The frontend SHALL allow users to manually select their preferred audio input device.
|
||||||
|
|
||||||
|
#### Scenario: Select device from dropdown
|
||||||
|
- **WHEN** user selects a device from dropdown
|
||||||
|
- **THEN** system SHALL update selected device state
|
||||||
|
- **AND** start volume monitoring on new device
|
||||||
|
- **AND** save selection to localStorage
|
||||||
|
|
||||||
|
#### Scenario: Load saved preference
|
||||||
|
- **WHEN** meeting detail page loads
|
||||||
|
- **THEN** system SHALL check localStorage for saved device preference
|
||||||
|
- **AND** if saved device is available, auto-select it
|
||||||
|
- **AND** if saved device unavailable, fall back to system default
|
||||||
|
|
||||||
|
#### Scenario: Selected device unavailable
|
||||||
|
- **WHEN** previously selected device is no longer available
|
||||||
|
- **THEN** system SHALL show warning message
|
||||||
|
- **AND** fall back to system default device
|
||||||
|
- **AND** prompt user to select new device
|
||||||
|
|
||||||
|
### Requirement: Real-time Volume Indicator
|
||||||
|
The frontend SHALL display real-time audio input level from the selected microphone.
|
||||||
|
|
||||||
|
#### Scenario: Display volume meter
|
||||||
|
- **WHEN** a device is selected
|
||||||
|
- **THEN** system SHALL show animated volume meter
|
||||||
|
- **AND** update meter at least 10 times per second
|
||||||
|
- **AND** display level as percentage (0-100%)
|
||||||
|
|
||||||
|
#### Scenario: Volume meter accuracy
|
||||||
|
- **WHEN** user speaks into microphone
|
||||||
|
- **THEN** volume meter SHALL reflect actual audio amplitude
|
||||||
|
- **AND** peak levels SHALL be visually distinct
|
||||||
|
|
||||||
|
#### Scenario: Muted or silent input
|
||||||
|
- **WHEN** no audio input detected for 3 seconds
|
||||||
|
- **THEN** volume meter SHALL show minimal/zero level
|
||||||
|
- **AND** optionally show "No input detected" hint
|
||||||
|
|
||||||
|
### Requirement: Audio Test Recording
|
||||||
|
The frontend SHALL allow users to record a short test audio clip.
|
||||||
|
|
||||||
|
#### Scenario: Start test recording
|
||||||
|
- **WHEN** user clicks "Test Recording" button
|
||||||
|
- **THEN** system SHALL start recording from selected device
|
||||||
|
- **AND** button SHALL change to "Stop" with countdown timer
|
||||||
|
- **AND** recording SHALL auto-stop after 5 seconds
|
||||||
|
|
||||||
|
#### Scenario: Stop test recording
|
||||||
|
- **WHEN** recording reaches 5 seconds or user clicks stop
|
||||||
|
- **THEN** recording SHALL stop
|
||||||
|
- **AND** audio blob SHALL be stored in memory
|
||||||
|
- **AND** "Play Test" button SHALL become enabled
|
||||||
|
|
||||||
|
#### Scenario: Recording indicator
|
||||||
|
- **WHEN** test recording is in progress
|
||||||
|
- **THEN** UI SHALL show recording indicator (pulsing dot)
|
||||||
|
- **AND** remaining time SHALL be displayed
|
||||||
|
|
||||||
|
### Requirement: Test Audio Playback
|
||||||
|
The frontend SHALL allow users to play back their test recording.
|
||||||
|
|
||||||
|
#### Scenario: Play test recording
|
||||||
|
- **WHEN** user clicks "Play Test" button
|
||||||
|
- **THEN** system SHALL play the recorded audio through default output
|
||||||
|
- **AND** button SHALL change to indicate playing state
|
||||||
|
- **AND** playback SHALL stop at end of recording
|
||||||
|
|
||||||
|
#### Scenario: No test recording available
|
||||||
|
- **WHEN** no test recording has been made
|
||||||
|
- **THEN** "Play Test" button SHALL be disabled
|
||||||
|
- **AND** tooltip SHALL indicate "Record a test first"
|
||||||
|
|
||||||
|
### Requirement: Integration with Main Recording
|
||||||
|
The main recording function SHALL use the user-selected audio device.
|
||||||
|
|
||||||
|
#### Scenario: Use selected device for recording
|
||||||
|
- **WHEN** user starts main recording
|
||||||
|
- **THEN** system SHALL use the device selected in audio settings panel
|
||||||
|
- **AND** if no device selected, use auto-selection logic
|
||||||
|
|
||||||
|
#### Scenario: Device changed during recording
|
||||||
|
- **WHEN** user changes device selection while recording
|
||||||
|
- **THEN** change SHALL NOT affect current recording
|
||||||
|
- **AND** new selection SHALL apply to next recording session
|
||||||
|
|
||||||
|
### Requirement: Audio Settings Panel UI
|
||||||
|
The frontend SHALL display audio settings in a collapsible panel.
|
||||||
|
|
||||||
|
#### Scenario: Panel visibility
|
||||||
|
- **WHEN** meeting detail page loads
|
||||||
|
- **THEN** audio settings panel SHALL be visible but collapsible
|
||||||
|
- **AND** panel state (expanded/collapsed) SHALL be saved
|
||||||
|
|
||||||
|
#### Scenario: Panel layout
|
||||||
|
- **WHEN** panel is expanded
|
||||||
|
- **THEN** it SHALL display:
|
||||||
|
- Device dropdown selector
|
||||||
|
- Volume meter visualization
|
||||||
|
- Test recording button
|
||||||
|
- Play test button
|
||||||
|
- Status indicator
|
||||||
|
|
||||||
|
#### Scenario: Compact mode
|
||||||
|
- **WHEN** panel is collapsed
|
||||||
|
- **THEN** it SHALL show only selected device name and expand button
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
# Tasks: add-audio-device-selector
|
||||||
|
|
||||||
|
## Phase 1: Core Device Management
|
||||||
|
|
||||||
|
### Task 1.1: Add Audio Settings Panel HTML Structure
|
||||||
|
- [x] Add collapsible panel container in meeting-detail.html
|
||||||
|
- [x] Add device dropdown selector element
|
||||||
|
- [x] Add volume meter container (canvas or div bars)
|
||||||
|
- [x] Add test recording/playback buttons
|
||||||
|
- [x] Add status indicator element
|
||||||
|
- **Validation**: Panel renders correctly, all elements visible
|
||||||
|
|
||||||
|
### Task 1.2: Implement Device Enumeration
|
||||||
|
- [x] Create `enumerateAudioDevices()` function
|
||||||
|
- [x] Filter out virtual devices (Stereo Mix)
|
||||||
|
- [x] Populate dropdown with device labels
|
||||||
|
- [x] Mark default device in dropdown
|
||||||
|
- [x] Add device change event listener for hot-plug support
|
||||||
|
- **Validation**: All connected microphones appear in dropdown
|
||||||
|
|
||||||
|
### Task 1.3: Implement Device Selection Logic
|
||||||
|
- [x] Create `selectAudioDevice(deviceId)` function
|
||||||
|
- [x] Stop existing audio context when switching
|
||||||
|
- [x] Create new AudioContext with selected device
|
||||||
|
- [x] Save selection to localStorage
|
||||||
|
- [x] Handle device unavailable errors
|
||||||
|
- **Validation**: Selecting device updates state, persists after refresh
|
||||||
|
|
||||||
|
## Phase 2: Volume Monitoring
|
||||||
|
|
||||||
|
### Task 2.1: Implement Volume Meter
|
||||||
|
- [x] Create AudioContext and AnalyserNode
|
||||||
|
- [x] Connect selected device to analyser
|
||||||
|
- [x] Create volume calculation function (RMS or peak)
|
||||||
|
- [x] Implement requestAnimationFrame loop for updates
|
||||||
|
- [x] Render volume level as visual bar
|
||||||
|
- **Validation**: Meter responds to voice input, updates smoothly
|
||||||
|
|
||||||
|
### Task 2.2: Volume Meter Styling
|
||||||
|
- [x] Add CSS for volume meter bar
|
||||||
|
- [x] Add gradient colors (green → yellow → red)
|
||||||
|
- [x] Add percentage text display
|
||||||
|
- [x] Add "No input detected" indicator
|
||||||
|
- **Validation**: Visual feedback is clear and responsive
|
||||||
|
|
||||||
|
## Phase 3: Test Recording
|
||||||
|
|
||||||
|
### Task 3.1: Implement Test Recording Function
|
||||||
|
- [x] Create `startTestRecording()` function
|
||||||
|
- [x] Use MediaRecorder with selected device
|
||||||
|
- [x] Implement 5-second auto-stop timer
|
||||||
|
- [x] Store recording as Blob
|
||||||
|
- [x] Update UI during recording (countdown, indicator)
|
||||||
|
- **Validation**: Can record 5 seconds, blob created
|
||||||
|
|
||||||
|
### Task 3.2: Implement Test Playback Function
|
||||||
|
- [x] Create `playTestRecording()` function
|
||||||
|
- [x] Create Audio element from blob URL
|
||||||
|
- [x] Handle play/stop states
|
||||||
|
- [x] Update UI during playback
|
||||||
|
- [x] Clean up blob URL when done
|
||||||
|
- **Validation**: Recorded audio plays back correctly
|
||||||
|
|
||||||
|
### Task 3.3: Test Recording UI State Management
|
||||||
|
- [x] Disable recording button during recording
|
||||||
|
- [x] Show countdown timer during recording
|
||||||
|
- [x] Enable play button after recording
|
||||||
|
- [x] Disable test controls during main recording
|
||||||
|
- **Validation**: UI states transition correctly
|
||||||
|
|
||||||
|
## Phase 4: Integration
|
||||||
|
|
||||||
|
### Task 4.1: Integrate with Main Recording
|
||||||
|
- [x] Modify `startRecording()` to use selected device
|
||||||
|
- [x] Add fallback to auto-selection if no preference
|
||||||
|
- [x] Handle selected device being unavailable
|
||||||
|
- [x] Stop volume monitoring during main recording
|
||||||
|
- **Validation**: Main recording uses selected device
|
||||||
|
|
||||||
|
### Task 4.2: Add Panel Collapse/Expand
|
||||||
|
- [x] Add collapse toggle button
|
||||||
|
- [x] Save panel state to localStorage
|
||||||
|
- [x] Load panel state on page load
|
||||||
|
- [x] Stop volume monitoring when collapsed
|
||||||
|
- **Validation**: Panel remembers collapse state
|
||||||
|
|
||||||
|
### Task 4.3: Add Refresh Device List Button
|
||||||
|
- [x] Add refresh icon button
|
||||||
|
- [x] Re-enumerate devices on click
|
||||||
|
- [x] Preserve selection if still available
|
||||||
|
- [x] Update dropdown options
|
||||||
|
- **Validation**: New devices appear after refresh
|
||||||
|
|
||||||
|
## Phase 5: Polish & Error Handling
|
||||||
|
|
||||||
|
### Task 5.1: Error Handling
|
||||||
|
- [x] Handle "No devices found" state
|
||||||
|
- [x] Handle permission denied errors
|
||||||
|
- [x] Handle device disconnection during use
|
||||||
|
- [x] Show user-friendly error messages (Chinese)
|
||||||
|
- **Validation**: All error states show appropriate messages
|
||||||
|
|
||||||
|
### Task 5.2: Localization
|
||||||
|
- [x] Add Chinese labels for all UI elements
|
||||||
|
- [x] Add Chinese error messages
|
||||||
|
- [x] Add tooltips for buttons
|
||||||
|
- **Validation**: All text is in Traditional Chinese
|
||||||
|
|
||||||
|
### Task 5.3: Testing & Documentation
|
||||||
|
- [x] Manual testing with multiple microphones
|
||||||
|
- [x] Test USB microphone hot-plug
|
||||||
|
- [x] Test headset microphone switching
|
||||||
|
- [x] Update DEPLOYMENT.md if needed
|
||||||
|
- **Validation**: Feature works with various microphone types
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- Task 1.2 depends on Task 1.1
|
||||||
|
- Task 2.1 depends on Task 1.3
|
||||||
|
- Task 3.1 depends on Task 1.3
|
||||||
|
- Task 4.1 depends on Tasks 1.3, 3.1
|
||||||
|
- Phase 5 depends on all previous phases
|
||||||
|
|
||||||
|
## Parallelizable Work
|
||||||
|
- Task 1.1 (HTML) and Task 2.2 (CSS) can run in parallel
|
||||||
|
- Task 3.1 (Recording) and Task 2.1 (Volume) can run in parallel after Task 1.3
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
# Design: Embedded Backend Packaging
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Meeting Assistant uses a three-tier architecture: Electron Client → FastAPI Middleware → MySQL/Dify. For enterprise deployment, administrators want to distribute a single executable that users can run without additional setup. The backend must still connect to remote MySQL and Dify services (no local database).
|
||||||
|
|
||||||
|
**Stakeholders:**
|
||||||
|
- Enterprise IT administrators (simplified deployment)
|
||||||
|
- End users (double-click to run)
|
||||||
|
- Developers (maintain backward compatibility)
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
### Goals
|
||||||
|
- Package backend as a sidecar executable using PyInstaller
|
||||||
|
- Electron manages backend lifecycle (start on launch, stop on close)
|
||||||
|
- Single `config.json` for all configuration (frontend, backend, whisper)
|
||||||
|
- Health check ensures backend is ready before showing UI
|
||||||
|
- Backward compatible with existing separate-deployment mode
|
||||||
|
- Show Whisper model download progress to improve UX
|
||||||
|
|
||||||
|
### Non-Goals
|
||||||
|
- Embedding MySQL database (still remote)
|
||||||
|
- Embedding LLM model (still uses Dify API)
|
||||||
|
- Removing external authentication (still requires company SSO)
|
||||||
|
- Pre-bundling Whisper model (user downloads on first run)
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Decision 1: Use PyInstaller for Backend Packaging
|
||||||
|
**What:** Package FastAPI + uvicorn as standalone executable using PyInstaller `--onedir` mode.
|
||||||
|
|
||||||
|
**Why:**
|
||||||
|
- Consistent with existing transcriber sidecar approach
|
||||||
|
- `--onedir` provides faster startup than `--onefile`
|
||||||
|
- Team already has PyInstaller expertise
|
||||||
|
|
||||||
|
**Alternatives considered:**
|
||||||
|
- Nuitka: Better optimization but longer build times
|
||||||
|
- cx_Freeze: Less community support for FastAPI
|
||||||
|
|
||||||
|
### Decision 2: Configuration via Extended config.json
|
||||||
|
**What:** Add `backend` section to existing `config.json` with database, API, and auth settings.
|
||||||
|
|
||||||
|
**Why:**
|
||||||
|
- Single configuration file for users to manage
|
||||||
|
- Runtime modifiable without rebuilding
|
||||||
|
- Consistent with existing whisper config pattern
|
||||||
|
|
||||||
|
**Schema:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"apiBaseUrl": "http://localhost:8000/api",
|
||||||
|
"backend": {
|
||||||
|
"embedded": true,
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 8000,
|
||||||
|
"database": { "host": "", "port": 33306, "user": "", "password": "", "database": "" },
|
||||||
|
"externalApis": { "authApiUrl": "", "difyApiUrl": "", "difyApiKey": "", "difySttApiKey": "" },
|
||||||
|
"auth": { "adminEmail": "", "jwtSecret": "", "jwtExpireHours": 24 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Decision 3: Health Check Before Window Display
|
||||||
|
**What:** Electron polls `/api/health` endpoint before creating main window.
|
||||||
|
|
||||||
|
**Why:**
|
||||||
|
- Prevents "connection refused" errors on startup
|
||||||
|
- Provides clear feedback if backend fails to start
|
||||||
|
- Maximum 30 attempts with 1-second intervals
|
||||||
|
|
||||||
|
### Decision 4: Backward Compatibility via Feature Flag
|
||||||
|
**What:** `backend.embedded: false` (default) preserves existing behavior; `true` enables embedded mode.
|
||||||
|
|
||||||
|
**Why:**
|
||||||
|
- Existing deployments continue working unchanged
|
||||||
|
- Gradual migration path for enterprises
|
||||||
|
- Same codebase supports both deployment models
|
||||||
|
|
||||||
|
### Decision 5: Huggingface Hub Progress Callback for Model Download
|
||||||
|
**What:** Intercept huggingface_hub download progress and emit JSON status messages.
|
||||||
|
|
||||||
|
**Why:**
|
||||||
|
- faster-whisper uses huggingface_hub internally
|
||||||
|
- Can emit progress without modifying faster-whisper source
|
||||||
|
- JSON format consistent with existing sidecar protocol
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
| Risk | Impact | Mitigation |
|
||||||
|
|------|--------|------------|
|
||||||
|
| PyInstaller hidden imports missing | Backend fails to start | Comprehensive hidden-import list; test on clean Windows |
|
||||||
|
| Config file contains sensitive data | Security exposure | Document security best practices; consider encryption |
|
||||||
|
| Backend startup timeout | Poor UX | Increase timeout; show loading indicator |
|
||||||
|
| Port 8000 already in use | Backend fails | Allow configurable port; detect and report conflicts |
|
||||||
|
|
||||||
|
## Migration Plan
|
||||||
|
|
||||||
|
### For New Deployments (All-in-One)
|
||||||
|
1. Build with `--embedded-backend` flag
|
||||||
|
2. Configure `config.json` with database/API credentials
|
||||||
|
3. Distribute single exe to users
|
||||||
|
|
||||||
|
### For Existing Deployments (Separate Backend)
|
||||||
|
1. No changes required
|
||||||
|
2. Ensure `backend.embedded: false` in config
|
||||||
|
3. Continue using existing backend deployment
|
||||||
|
|
||||||
|
### Rollback
|
||||||
|
- Set `backend.embedded: false` to disable embedded backend
|
||||||
|
- Deploy backend separately as before
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
- Should we add config validation UI on first startup?
|
||||||
|
- Should backend port be auto-discovered if 8000 is in use?
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Change: Add Embedded Backend Packaging for All-in-One Deployment
|
||||||
|
|
||||||
|
## Why
|
||||||
|
Currently, deploying Meeting Assistant requires setting up both the Electron client and a separate backend server. For enterprise internal deployment, users want a simpler experience: **double-click the exe and it works** without needing to understand or configure backend services separately.
|
||||||
|
|
||||||
|
Additionally, when users first run the packaged application, the Whisper model download (~1.5GB) shows only "loading_model" status with no progress indication, causing confusion about whether the download is actually happening.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
- **New capability: Embedded Backend** - Package FastAPI backend as a sidecar managed by Electron
|
||||||
|
- **Backend sidecar management** - Electron starts/stops backend process automatically
|
||||||
|
- **Health check mechanism** - Wait for backend readiness before loading frontend
|
||||||
|
- **Configuration unification** - All settings (DB, API keys, auth) in single `config.json`
|
||||||
|
- **Backward compatible** - Existing deployment method (separate backend) still works via `backend.embedded: false` flag
|
||||||
|
- **Model download progress** - Show real-time download percentage for Whisper model
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
- Affected specs: `embedded-backend` (new), `transcription` (modified)
|
||||||
|
- Affected code:
|
||||||
|
- `backend/run_server.py` (new) - Backend entry point for packaging
|
||||||
|
- `backend/build.py` (new) - PyInstaller build script
|
||||||
|
- `backend/app/config.py` - Support frozen executable paths
|
||||||
|
- `client/src/main.js` - Backend sidecar management
|
||||||
|
- `client/src/preload.js` - Expose backend status API
|
||||||
|
- `client/config.json` - Extended configuration schema
|
||||||
|
- `client/package.json` - Build configuration for backend resources
|
||||||
|
- `sidecar/transcriber.py` - Model download progress reporting
|
||||||
|
- `scripts/build-client.bat` - Integrated build script
|
||||||
|
- `scripts/build-all.ps1` - PowerShell build script
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Embedded Backend Packaging
|
||||||
|
The FastAPI backend SHALL be packaged as a standalone executable using PyInstaller for all-in-one deployment.
|
||||||
|
|
||||||
|
#### Scenario: Backend executable creation
|
||||||
|
- **WHEN** build script runs with embedded backend flag
|
||||||
|
- **THEN** PyInstaller SHALL create `backend/dist/backend/backend.exe` containing FastAPI, uvicorn, and all dependencies
|
||||||
|
|
||||||
|
#### Scenario: Backend executable startup
|
||||||
|
- **WHEN** backend executable is launched
|
||||||
|
- **THEN** it SHALL read configuration from `config.json` in the same directory
|
||||||
|
- **AND** start uvicorn server on configured host and port
|
||||||
|
|
||||||
|
### Requirement: Electron Backend Sidecar Management
|
||||||
|
The Electron main process SHALL manage the embedded backend as a sidecar process.
|
||||||
|
|
||||||
|
#### Scenario: Start backend on app launch
|
||||||
|
- **WHEN** Electron app launches with `backend.embedded: true` in config
|
||||||
|
- **THEN** main process SHALL spawn backend executable as child process
|
||||||
|
- **AND** pass configuration via environment variables
|
||||||
|
|
||||||
|
#### Scenario: Skip backend when disabled
|
||||||
|
- **WHEN** Electron app launches with `backend.embedded: false` in config
|
||||||
|
- **THEN** main process SHALL NOT spawn backend executable
|
||||||
|
- **AND** frontend SHALL connect to remote backend via `apiBaseUrl`
|
||||||
|
|
||||||
|
#### Scenario: Terminate backend on app close
|
||||||
|
- **WHEN** user closes Electron app
|
||||||
|
- **THEN** main process SHALL send SIGTERM to backend process
|
||||||
|
- **AND** force kill after 5 seconds if still running
|
||||||
|
|
||||||
|
### Requirement: Backend Health Check
|
||||||
|
The Electron main process SHALL verify backend readiness before showing the main window.
|
||||||
|
|
||||||
|
#### Scenario: Health check success
|
||||||
|
- **WHEN** backend `/api/health` returns HTTP 200
|
||||||
|
- **THEN** main process SHALL proceed to create main window
|
||||||
|
- **AND** set `backendReady` state to true
|
||||||
|
|
||||||
|
#### Scenario: Health check timeout
|
||||||
|
- **WHEN** backend does not respond within 30 seconds (30 attempts, 1s interval)
|
||||||
|
- **THEN** main process SHALL display error dialog
|
||||||
|
- **AND** log detailed error for debugging
|
||||||
|
|
||||||
|
#### Scenario: Health check polling
|
||||||
|
- **WHEN** health check attempt fails
|
||||||
|
- **THEN** main process SHALL retry after 1 second
|
||||||
|
- **AND** log attempt number for debugging
|
||||||
|
|
||||||
|
### Requirement: Unified Configuration Schema
|
||||||
|
All configuration for frontend, backend, and whisper SHALL be in a single `config.json` file.
|
||||||
|
|
||||||
|
#### Scenario: Backend configuration loading
|
||||||
|
- **WHEN** backend sidecar starts
|
||||||
|
- **THEN** it SHALL read database credentials from `config.json` backend.database section
|
||||||
|
- **AND** read API keys from `config.json` backend.externalApis section
|
||||||
|
- **AND** read auth settings from `config.json` backend.auth section
|
||||||
|
|
||||||
|
#### Scenario: Configuration priority
|
||||||
|
- **WHEN** both environment variable and config.json value exist
|
||||||
|
- **THEN** environment variable SHALL take precedence
|
||||||
|
|
||||||
|
#### Scenario: Default values
|
||||||
|
- **WHEN** configuration value is not specified
|
||||||
|
- **THEN** system SHALL use sensible defaults (host: 127.0.0.1, port: 8000)
|
||||||
|
|
||||||
|
### Requirement: Backend Status API
|
||||||
|
The Electron app SHALL expose backend status to the renderer process.
|
||||||
|
|
||||||
|
#### Scenario: Get backend status
|
||||||
|
- **WHEN** renderer calls `window.electronAPI.getBackendStatus()`
|
||||||
|
- **THEN** it SHALL return object with `ready` boolean and `url` string
|
||||||
|
|
||||||
|
#### Scenario: Backend status in UI
|
||||||
|
- **WHEN** backend is starting
|
||||||
|
- **THEN** UI MAY display loading indicator
|
||||||
|
|
||||||
|
### Requirement: Backward Compatibility
|
||||||
|
The embedded backend feature SHALL NOT break existing separate-deployment mode.
|
||||||
|
|
||||||
|
#### Scenario: Separate deployment unchanged
|
||||||
|
- **WHEN** `backend.embedded` is false or undefined
|
||||||
|
- **THEN** system SHALL behave exactly as before this change
|
||||||
|
- **AND** frontend connects to `apiBaseUrl` without spawning local backend
|
||||||
|
|
||||||
|
#### Scenario: Existing scripts work
|
||||||
|
- **WHEN** user runs `./start.sh start` or `./scripts/setup-backend.sh`
|
||||||
|
- **THEN** backend SHALL start normally as standalone server
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Model Download Progress Display
|
||||||
|
The sidecar SHALL report Whisper model download progress to enable UI feedback.
|
||||||
|
|
||||||
|
#### Scenario: Emit download start
|
||||||
|
- **WHEN** Whisper model download begins
|
||||||
|
- **THEN** sidecar SHALL emit JSON to stdout: `{"status": "downloading_model", "model": "<size>", "progress": 0, "total_mb": <size>}`
|
||||||
|
|
||||||
|
#### Scenario: Emit download progress
|
||||||
|
- **WHEN** download progress updates
|
||||||
|
- **THEN** sidecar SHALL emit JSON: `{"status": "downloading_model", "progress": <percent>, "downloaded_mb": <current>, "total_mb": <total>}`
|
||||||
|
- **AND** progress updates SHALL occur at least every 5% or every 5 seconds
|
||||||
|
|
||||||
|
#### Scenario: Emit download complete
|
||||||
|
- **WHEN** model download completes
|
||||||
|
- **THEN** sidecar SHALL emit JSON: `{"status": "model_downloaded", "model": "<size>"}`
|
||||||
|
- **AND** proceed to model loading
|
||||||
|
|
||||||
|
#### Scenario: Skip download for cached model
|
||||||
|
- **WHEN** model already exists in huggingface cache
|
||||||
|
- **THEN** sidecar SHALL NOT emit download progress messages
|
||||||
|
- **AND** proceed directly to loading
|
||||||
|
|
||||||
|
### Requirement: Frontend Model Download Progress Display
|
||||||
|
The Electron frontend SHALL display model download progress to users.
|
||||||
|
|
||||||
|
#### Scenario: Show download progress in transcript panel
|
||||||
|
- **WHEN** sidecar emits download progress
|
||||||
|
- **THEN** whisper status element SHALL display download percentage and size
|
||||||
|
- **AND** format: "Downloading: XX% (YYY MB / ZZZ MB)"
|
||||||
|
|
||||||
|
#### Scenario: Show download complete
|
||||||
|
- **WHEN** sidecar emits model_downloaded status
|
||||||
|
- **THEN** whisper status element SHALL briefly show "Model downloaded"
|
||||||
|
- **AND** transition to loading state
|
||||||
|
|
||||||
|
#### Scenario: Forward progress events via IPC
|
||||||
|
- **WHEN** main process receives download progress from sidecar
|
||||||
|
- **THEN** it SHALL forward to renderer via `model-download-progress` IPC channel
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Tasks: Add Embedded Backend Packaging
|
||||||
|
|
||||||
|
## 1. Backend Packaging Infrastructure
|
||||||
|
- [x] 1.1 Create `backend/run_server.py` - Entry point that loads config and starts uvicorn
|
||||||
|
- [x] 1.2 Create `backend/build.py` - PyInstaller build script with hidden imports
|
||||||
|
- [x] 1.3 Modify `backend/app/config.py` - Support frozen executable path detection
|
||||||
|
- [ ] 1.4 Test backend executable runs standalone on Windows
|
||||||
|
|
||||||
|
## 2. Electron Backend Sidecar Management
|
||||||
|
- [x] 2.1 Add `backendProcess` and `backendReady` state variables in `main.js`
|
||||||
|
- [x] 2.2 Implement `startBackendSidecar()` function
|
||||||
|
- [x] 2.3 Implement `waitForBackendReady()` health check polling
|
||||||
|
- [x] 2.4 Modify `app.whenReady()` to start backend before window
|
||||||
|
- [x] 2.5 Modify window close handler to terminate backend process
|
||||||
|
- [x] 2.6 Add `get-backend-status` IPC handler
|
||||||
|
|
||||||
|
## 3. Configuration Schema Extension
|
||||||
|
- [x] 3.1 Extend `client/config.json` with `backend` section
|
||||||
|
- [x] 3.2 Modify `client/src/preload.js` to expose backend status API
|
||||||
|
- [x] 3.3 Add configuration loading in backend entry point
|
||||||
|
- [ ] 3.4 Document configuration options in DEPLOYMENT.md
|
||||||
|
|
||||||
|
## 4. Build Script Integration
|
||||||
|
- [x] 4.1 Modify `scripts/build-client.bat` to build backend sidecar
|
||||||
|
- [ ] 4.2 Modify `scripts/build-all.ps1` to build backend sidecar
|
||||||
|
- [x] 4.3 Update `client/package.json` extraResources for backend
|
||||||
|
- [x] 4.4 Add `--embedded-backend` flag to build scripts
|
||||||
|
|
||||||
|
## 5. Model Download Progress Display
|
||||||
|
- [x] 5.1 Modify `sidecar/transcriber.py` to emit download progress JSON
|
||||||
|
- [x] 5.2 Add progress event forwarding in `main.js`
|
||||||
|
- [x] 5.3 Expose `onModelDownloadProgress` in `preload.js`
|
||||||
|
- [x] 5.4 Update `meeting-detail.html` to display download progress
|
||||||
|
|
||||||
|
## 6. Testing and Documentation
|
||||||
|
- [ ] 6.1 Test embedded mode on clean Windows machine
|
||||||
|
- [ ] 6.2 Test backward compatibility (embedded: false)
|
||||||
|
- [ ] 6.3 Test model download progress display
|
||||||
|
- [ ] 6.4 Update DEPLOYMENT.md with all-in-one deployment instructions
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Change: Add Flexible Deployment Options
|
||||||
|
|
||||||
|
## Why
|
||||||
|
Enterprise deployment environments vary significantly. Some networks block MySQL port 33306, preventing access to cloud databases. Additionally, the current portable executable extracts to a random folder in `%TEMP%`, causing Windows Defender warnings on each launch and potential permission issues.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
- **SQLite database support** - Allow choosing between MySQL (cloud) and SQLite (local) databases at build time via `--database-type` parameter
|
||||||
|
- **Fixed portable extraction path** - Configure `unpackDirName` to use a predictable folder name instead of random UUID
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
- Affected specs: `embedded-backend` (modified)
|
||||||
|
- Affected code:
|
||||||
|
- `client/config.json` - Add `database.type` and `database.sqlitePath` fields
|
||||||
|
- `client/package.json` - Add `unpackDirName` to portable configuration
|
||||||
|
- `backend/app/config.py` - Add `DB_TYPE` and `SQLITE_PATH` settings
|
||||||
|
- `backend/app/database.py` - Conditional SQLite/MySQL initialization
|
||||||
|
- `backend/run_server.py` - Pass database type environment variables
|
||||||
|
- `scripts/build-client.bat` - Add `--database-type` parameter
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: SQLite Database Support
|
||||||
|
The backend SHALL support SQLite as an alternative to MySQL for offline/standalone deployments.
|
||||||
|
|
||||||
|
#### Scenario: SQLite mode initialization
|
||||||
|
- **WHEN** `database.type` is set to `"sqlite"` in config.json
|
||||||
|
- **THEN** backend SHALL create SQLite database at `database.sqlitePath`
|
||||||
|
- **AND** initialize all required tables using SQLite-compatible syntax
|
||||||
|
|
||||||
|
#### Scenario: MySQL mode initialization
|
||||||
|
- **WHEN** `database.type` is set to `"mysql"` or not specified in config.json
|
||||||
|
- **THEN** backend SHALL connect to MySQL using credentials from `database` section
|
||||||
|
- **AND** behave exactly as before this change
|
||||||
|
|
||||||
|
#### Scenario: SQLite thread safety
|
||||||
|
- **WHEN** multiple concurrent requests access SQLite database
|
||||||
|
- **THEN** backend SHALL use thread lock to serialize database operations
|
||||||
|
- **AND** use `check_same_thread=False` for SQLite connection
|
||||||
|
|
||||||
|
#### Scenario: SQLite data persistence
|
||||||
|
- **WHEN** app is closed and reopened
|
||||||
|
- **THEN** all meeting data SHALL persist in SQLite file
|
||||||
|
- **AND** be accessible on next launch
|
||||||
|
|
||||||
|
### Requirement: Portable Extraction Path Configuration
|
||||||
|
The portable Windows build SHALL extract to a predictable folder name.
|
||||||
|
|
||||||
|
#### Scenario: Fixed extraction folder
|
||||||
|
- **WHEN** portable executable starts
|
||||||
|
- **THEN** it SHALL extract to `%TEMP%\Meeting-Assistant` instead of random UUID folder
|
||||||
|
|
||||||
|
#### Scenario: Windows Defender consistency
|
||||||
|
- **WHEN** user launches portable executable multiple times
|
||||||
|
- **THEN** Windows Defender SHALL NOT prompt for permission each time
|
||||||
|
- **BECAUSE** extraction path is consistent across launches
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Unified Configuration Schema
|
||||||
|
All configuration for frontend, backend, and whisper SHALL be in a single `config.json` file.
|
||||||
|
|
||||||
|
#### Scenario: Backend configuration loading
|
||||||
|
- **WHEN** backend sidecar starts
|
||||||
|
- **THEN** it SHALL read database type from `config.json` backend.database.type section
|
||||||
|
- **AND** read SQLite path from `config.json` backend.database.sqlitePath section (if SQLite mode)
|
||||||
|
- **AND** read database credentials from `config.json` backend.database section (if MySQL mode)
|
||||||
|
- **AND** read API keys from `config.json` backend.externalApis section
|
||||||
|
- **AND** read auth settings from `config.json` backend.auth section
|
||||||
|
|
||||||
|
#### Scenario: Configuration priority
|
||||||
|
- **WHEN** both environment variable and config.json value exist
|
||||||
|
- **THEN** environment variable SHALL take precedence
|
||||||
|
|
||||||
|
#### Scenario: Default values
|
||||||
|
- **WHEN** configuration value is not specified
|
||||||
|
- **THEN** system SHALL use sensible defaults (host: 127.0.0.1, port: 8000, database.type: mysql)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Tasks: Add Flexible Deployment Options
|
||||||
|
|
||||||
|
## 1. Portable Extraction Path
|
||||||
|
- [x] 1.1 Update `client/package.json` - Add `unpackDirName: "Meeting-Assistant"` to portable config
|
||||||
|
|
||||||
|
## 2. Configuration Schema for SQLite
|
||||||
|
- [x] 2.1 Update `client/config.json` - Add `database.type` field (default: "mysql")
|
||||||
|
- [x] 2.2 Update `client/config.json` - Add `database.sqlitePath` field (default: "data/meeting.db")
|
||||||
|
|
||||||
|
## 3. Backend Configuration
|
||||||
|
- [x] 3.1 Update `backend/app/config.py` - Add `DB_TYPE` setting
|
||||||
|
- [x] 3.2 Update `backend/app/config.py` - Add `SQLITE_PATH` setting
|
||||||
|
- [x] 3.3 Update `backend/run_server.py` - Pass `DB_TYPE` and `SQLITE_PATH` to environment
|
||||||
|
|
||||||
|
## 4. Database Abstraction Layer
|
||||||
|
- [x] 4.1 Refactor `backend/app/database.py` - Create `init_db()` dispatcher function
|
||||||
|
- [x] 4.2 Implement `init_sqlite()` - SQLite connection with row_factory
|
||||||
|
- [x] 4.3 Implement `init_mysql()` - Keep existing MySQL pool logic
|
||||||
|
- [x] 4.4 Create unified `get_db_cursor()` context manager for both backends
|
||||||
|
- [x] 4.5 Add SQLite table creation statements (convert MySQL syntax)
|
||||||
|
- [x] 4.6 Add thread lock for SQLite connection safety
|
||||||
|
|
||||||
|
## 5. Build Script Integration
|
||||||
|
- [x] 5.1 Update `scripts/build-client.bat` - Add `--database-type` parameter parsing
|
||||||
|
- [x] 5.2 Update `scripts/build-client.bat` - Add `update_config_database` function
|
||||||
|
- [x] 5.3 Update help message with new parameter
|
||||||
|
|
||||||
|
## 6. Testing
|
||||||
|
- [x] 6.1 Test SQLite mode - Create meeting, query, update, delete
|
||||||
|
- [x] 6.2 Test MySQL mode - Ensure backward compatibility
|
||||||
|
- [ ] 6.3 Test portable extraction to `%TEMP%\Meeting-Assistant` (requires Windows build)
|
||||||
|
|
||||||
|
## 7. Documentation
|
||||||
|
- [x] 7.1 Update DEPLOYMENT.md with SQLite mode instructions
|
||||||
|
- [x] 7.2 Update DEPLOYMENT.md with --database-type parameter
|
||||||
|
- [x] 7.3 Update DEPLOYMENT.md with portable extraction path info
|
||||||
133
openspec/specs/audio-device-management/spec.md
Normal file
133
openspec/specs/audio-device-management/spec.md
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# audio-device-management Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
TBD - created by archiving change add-audio-device-selector. Update Purpose after archive.
|
||||||
|
## Requirements
|
||||||
|
### Requirement: Audio Device Enumeration
|
||||||
|
The frontend SHALL enumerate and display all available audio input devices.
|
||||||
|
|
||||||
|
#### Scenario: List available devices
|
||||||
|
- **WHEN** user opens meeting detail page
|
||||||
|
- **THEN** system SHALL enumerate all audio input devices
|
||||||
|
- **AND** display them in a dropdown selector
|
||||||
|
- **AND** exclude virtual/system devices like "Stereo Mix"
|
||||||
|
|
||||||
|
#### Scenario: Refresh device list
|
||||||
|
- **WHEN** user clicks refresh button or device is connected/disconnected
|
||||||
|
- **THEN** system SHALL re-enumerate devices
|
||||||
|
- **AND** update dropdown options
|
||||||
|
- **AND** preserve current selection if still available
|
||||||
|
|
||||||
|
#### Scenario: Device label display
|
||||||
|
- **WHEN** devices are listed
|
||||||
|
- **THEN** each device SHALL display its friendly name (label)
|
||||||
|
- **AND** indicate if it's the system default device
|
||||||
|
|
||||||
|
### Requirement: Manual Device Selection
|
||||||
|
The frontend SHALL allow users to manually select their preferred audio input device.
|
||||||
|
|
||||||
|
#### Scenario: Select device from dropdown
|
||||||
|
- **WHEN** user selects a device from dropdown
|
||||||
|
- **THEN** system SHALL update selected device state
|
||||||
|
- **AND** start volume monitoring on new device
|
||||||
|
- **AND** save selection to localStorage
|
||||||
|
|
||||||
|
#### Scenario: Load saved preference
|
||||||
|
- **WHEN** meeting detail page loads
|
||||||
|
- **THEN** system SHALL check localStorage for saved device preference
|
||||||
|
- **AND** if saved device is available, auto-select it
|
||||||
|
- **AND** if saved device unavailable, fall back to system default
|
||||||
|
|
||||||
|
#### Scenario: Selected device unavailable
|
||||||
|
- **WHEN** previously selected device is no longer available
|
||||||
|
- **THEN** system SHALL show warning message
|
||||||
|
- **AND** fall back to system default device
|
||||||
|
- **AND** prompt user to select new device
|
||||||
|
|
||||||
|
### Requirement: Real-time Volume Indicator
|
||||||
|
The frontend SHALL display real-time audio input level from the selected microphone.
|
||||||
|
|
||||||
|
#### Scenario: Display volume meter
|
||||||
|
- **WHEN** a device is selected
|
||||||
|
- **THEN** system SHALL show animated volume meter
|
||||||
|
- **AND** update meter at least 10 times per second
|
||||||
|
- **AND** display level as percentage (0-100%)
|
||||||
|
|
||||||
|
#### Scenario: Volume meter accuracy
|
||||||
|
- **WHEN** user speaks into microphone
|
||||||
|
- **THEN** volume meter SHALL reflect actual audio amplitude
|
||||||
|
- **AND** peak levels SHALL be visually distinct
|
||||||
|
|
||||||
|
#### Scenario: Muted or silent input
|
||||||
|
- **WHEN** no audio input detected for 3 seconds
|
||||||
|
- **THEN** volume meter SHALL show minimal/zero level
|
||||||
|
- **AND** optionally show "No input detected" hint
|
||||||
|
|
||||||
|
### Requirement: Audio Test Recording
|
||||||
|
The frontend SHALL allow users to record a short test audio clip.
|
||||||
|
|
||||||
|
#### Scenario: Start test recording
|
||||||
|
- **WHEN** user clicks "Test Recording" button
|
||||||
|
- **THEN** system SHALL start recording from selected device
|
||||||
|
- **AND** button SHALL change to "Stop" with countdown timer
|
||||||
|
- **AND** recording SHALL auto-stop after 5 seconds
|
||||||
|
|
||||||
|
#### Scenario: Stop test recording
|
||||||
|
- **WHEN** recording reaches 5 seconds or user clicks stop
|
||||||
|
- **THEN** recording SHALL stop
|
||||||
|
- **AND** audio blob SHALL be stored in memory
|
||||||
|
- **AND** "Play Test" button SHALL become enabled
|
||||||
|
|
||||||
|
#### Scenario: Recording indicator
|
||||||
|
- **WHEN** test recording is in progress
|
||||||
|
- **THEN** UI SHALL show recording indicator (pulsing dot)
|
||||||
|
- **AND** remaining time SHALL be displayed
|
||||||
|
|
||||||
|
### Requirement: Test Audio Playback
|
||||||
|
The frontend SHALL allow users to play back their test recording.
|
||||||
|
|
||||||
|
#### Scenario: Play test recording
|
||||||
|
- **WHEN** user clicks "Play Test" button
|
||||||
|
- **THEN** system SHALL play the recorded audio through default output
|
||||||
|
- **AND** button SHALL change to indicate playing state
|
||||||
|
- **AND** playback SHALL stop at end of recording
|
||||||
|
|
||||||
|
#### Scenario: No test recording available
|
||||||
|
- **WHEN** no test recording has been made
|
||||||
|
- **THEN** "Play Test" button SHALL be disabled
|
||||||
|
- **AND** tooltip SHALL indicate "Record a test first"
|
||||||
|
|
||||||
|
### Requirement: Integration with Main Recording
|
||||||
|
The main recording function SHALL use the user-selected audio device.
|
||||||
|
|
||||||
|
#### Scenario: Use selected device for recording
|
||||||
|
- **WHEN** user starts main recording
|
||||||
|
- **THEN** system SHALL use the device selected in audio settings panel
|
||||||
|
- **AND** if no device selected, use auto-selection logic
|
||||||
|
|
||||||
|
#### Scenario: Device changed during recording
|
||||||
|
- **WHEN** user changes device selection while recording
|
||||||
|
- **THEN** change SHALL NOT affect current recording
|
||||||
|
- **AND** new selection SHALL apply to next recording session
|
||||||
|
|
||||||
|
### Requirement: Audio Settings Panel UI
|
||||||
|
The frontend SHALL display audio settings in a collapsible panel.
|
||||||
|
|
||||||
|
#### Scenario: Panel visibility
|
||||||
|
- **WHEN** meeting detail page loads
|
||||||
|
- **THEN** audio settings panel SHALL be visible but collapsible
|
||||||
|
- **AND** panel state (expanded/collapsed) SHALL be saved
|
||||||
|
|
||||||
|
#### Scenario: Panel layout
|
||||||
|
- **WHEN** panel is expanded
|
||||||
|
- **THEN** it SHALL display:
|
||||||
|
- Device dropdown selector
|
||||||
|
- Volume meter visualization
|
||||||
|
- Test recording button
|
||||||
|
- Play test button
|
||||||
|
- Status indicator
|
||||||
|
|
||||||
|
#### Scenario: Compact mode
|
||||||
|
- **WHEN** panel is collapsed
|
||||||
|
- **THEN** it SHALL show only selected device name and expand button
|
||||||
|
|
||||||
130
openspec/specs/embedded-backend/spec.md
Normal file
130
openspec/specs/embedded-backend/spec.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# embedded-backend Specification
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
TBD - created by archiving change add-embedded-backend-packaging. Update Purpose after archive.
|
||||||
|
## Requirements
|
||||||
|
### Requirement: Embedded Backend Packaging
|
||||||
|
The FastAPI backend SHALL be packaged as a standalone executable using PyInstaller for all-in-one deployment.
|
||||||
|
|
||||||
|
#### Scenario: Backend executable creation
|
||||||
|
- **WHEN** build script runs with embedded backend flag
|
||||||
|
- **THEN** PyInstaller SHALL create `backend/dist/backend/backend.exe` containing FastAPI, uvicorn, and all dependencies
|
||||||
|
|
||||||
|
#### Scenario: Backend executable startup
|
||||||
|
- **WHEN** backend executable is launched
|
||||||
|
- **THEN** it SHALL read configuration from `config.json` in the same directory
|
||||||
|
- **AND** start uvicorn server on configured host and port
|
||||||
|
|
||||||
|
### Requirement: Electron Backend Sidecar Management
|
||||||
|
The Electron main process SHALL manage the embedded backend as a sidecar process.
|
||||||
|
|
||||||
|
#### Scenario: Start backend on app launch
|
||||||
|
- **WHEN** Electron app launches with `backend.embedded: true` in config
|
||||||
|
- **THEN** main process SHALL spawn backend executable as child process
|
||||||
|
- **AND** pass configuration via environment variables
|
||||||
|
|
||||||
|
#### Scenario: Skip backend when disabled
|
||||||
|
- **WHEN** Electron app launches with `backend.embedded: false` in config
|
||||||
|
- **THEN** main process SHALL NOT spawn backend executable
|
||||||
|
- **AND** frontend SHALL connect to remote backend via `apiBaseUrl`
|
||||||
|
|
||||||
|
#### Scenario: Terminate backend on app close
|
||||||
|
- **WHEN** user closes Electron app
|
||||||
|
- **THEN** main process SHALL send SIGTERM to backend process
|
||||||
|
- **AND** force kill after 5 seconds if still running
|
||||||
|
|
||||||
|
### Requirement: Backend Health Check
|
||||||
|
The Electron main process SHALL verify backend readiness before showing the main window.
|
||||||
|
|
||||||
|
#### Scenario: Health check success
|
||||||
|
- **WHEN** backend `/api/health` returns HTTP 200
|
||||||
|
- **THEN** main process SHALL proceed to create main window
|
||||||
|
- **AND** set `backendReady` state to true
|
||||||
|
|
||||||
|
#### Scenario: Health check timeout
|
||||||
|
- **WHEN** backend does not respond within 30 seconds (30 attempts, 1s interval)
|
||||||
|
- **THEN** main process SHALL display error dialog
|
||||||
|
- **AND** log detailed error for debugging
|
||||||
|
|
||||||
|
#### Scenario: Health check polling
|
||||||
|
- **WHEN** health check attempt fails
|
||||||
|
- **THEN** main process SHALL retry after 1 second
|
||||||
|
- **AND** log attempt number for debugging
|
||||||
|
|
||||||
|
### Requirement: Unified Configuration Schema
|
||||||
|
All configuration for frontend, backend, and whisper SHALL be in a single `config.json` file.
|
||||||
|
|
||||||
|
#### Scenario: Backend configuration loading
|
||||||
|
- **WHEN** backend sidecar starts
|
||||||
|
- **THEN** it SHALL read database type from `config.json` backend.database.type section
|
||||||
|
- **AND** read SQLite path from `config.json` backend.database.sqlitePath section (if SQLite mode)
|
||||||
|
- **AND** read database credentials from `config.json` backend.database section (if MySQL mode)
|
||||||
|
- **AND** read API keys from `config.json` backend.externalApis section
|
||||||
|
- **AND** read auth settings from `config.json` backend.auth section
|
||||||
|
|
||||||
|
#### Scenario: Configuration priority
|
||||||
|
- **WHEN** both environment variable and config.json value exist
|
||||||
|
- **THEN** environment variable SHALL take precedence
|
||||||
|
|
||||||
|
#### Scenario: Default values
|
||||||
|
- **WHEN** configuration value is not specified
|
||||||
|
- **THEN** system SHALL use sensible defaults (host: 127.0.0.1, port: 8000, database.type: mysql)
|
||||||
|
|
||||||
|
### Requirement: Backend Status API
|
||||||
|
The Electron app SHALL expose backend status to the renderer process.
|
||||||
|
|
||||||
|
#### Scenario: Get backend status
|
||||||
|
- **WHEN** renderer calls `window.electronAPI.getBackendStatus()`
|
||||||
|
- **THEN** it SHALL return object with `ready` boolean and `url` string
|
||||||
|
|
||||||
|
#### Scenario: Backend status in UI
|
||||||
|
- **WHEN** backend is starting
|
||||||
|
- **THEN** UI MAY display loading indicator
|
||||||
|
|
||||||
|
### Requirement: Backward Compatibility
|
||||||
|
The embedded backend feature SHALL NOT break existing separate-deployment mode.
|
||||||
|
|
||||||
|
#### Scenario: Separate deployment unchanged
|
||||||
|
- **WHEN** `backend.embedded` is false or undefined
|
||||||
|
- **THEN** system SHALL behave exactly as before this change
|
||||||
|
- **AND** frontend connects to `apiBaseUrl` without spawning local backend
|
||||||
|
|
||||||
|
#### Scenario: Existing scripts work
|
||||||
|
- **WHEN** user runs `./start.sh start` or `./scripts/setup-backend.sh`
|
||||||
|
- **THEN** backend SHALL start normally as standalone server
|
||||||
|
|
||||||
|
### Requirement: SQLite Database Support
|
||||||
|
The backend SHALL support SQLite as an alternative to MySQL for offline/standalone deployments.
|
||||||
|
|
||||||
|
#### Scenario: SQLite mode initialization
|
||||||
|
- **WHEN** `database.type` is set to `"sqlite"` in config.json
|
||||||
|
- **THEN** backend SHALL create SQLite database at `database.sqlitePath`
|
||||||
|
- **AND** initialize all required tables using SQLite-compatible syntax
|
||||||
|
|
||||||
|
#### Scenario: MySQL mode initialization
|
||||||
|
- **WHEN** `database.type` is set to `"mysql"` or not specified in config.json
|
||||||
|
- **THEN** backend SHALL connect to MySQL using credentials from `database` section
|
||||||
|
- **AND** behave exactly as before this change
|
||||||
|
|
||||||
|
#### Scenario: SQLite thread safety
|
||||||
|
- **WHEN** multiple concurrent requests access SQLite database
|
||||||
|
- **THEN** backend SHALL use thread lock to serialize database operations
|
||||||
|
- **AND** use `check_same_thread=False` for SQLite connection
|
||||||
|
|
||||||
|
#### Scenario: SQLite data persistence
|
||||||
|
- **WHEN** app is closed and reopened
|
||||||
|
- **THEN** all meeting data SHALL persist in SQLite file
|
||||||
|
- **AND** be accessible on next launch
|
||||||
|
|
||||||
|
### Requirement: Portable Extraction Path Configuration
|
||||||
|
The portable Windows build SHALL extract to a predictable folder name.
|
||||||
|
|
||||||
|
#### Scenario: Fixed extraction folder
|
||||||
|
- **WHEN** portable executable starts
|
||||||
|
- **THEN** it SHALL extract to `%TEMP%\Meeting-Assistant` instead of random UUID folder
|
||||||
|
|
||||||
|
#### Scenario: Windows Defender consistency
|
||||||
|
- **WHEN** user launches portable executable multiple times
|
||||||
|
- **THEN** Windows Defender SHALL NOT prompt for permission each time
|
||||||
|
- **BECAUSE** extraction path is consistent across launches
|
||||||
|
|
||||||
@@ -175,3 +175,42 @@ The system SHALL support both real-time local transcription and file-based cloud
|
|||||||
- **WHEN** transcription completes from either source
|
- **WHEN** transcription completes from either source
|
||||||
- **THEN** result SHALL be displayed in the same transcript area in meeting detail page
|
- **THEN** result SHALL be displayed in the same transcript area in meeting detail page
|
||||||
|
|
||||||
|
### Requirement: Model Download Progress Display
|
||||||
|
The sidecar SHALL report Whisper model download progress to enable UI feedback.
|
||||||
|
|
||||||
|
#### Scenario: Emit download start
|
||||||
|
- **WHEN** Whisper model download begins
|
||||||
|
- **THEN** sidecar SHALL emit JSON to stdout: `{"status": "downloading_model", "model": "<size>", "progress": 0, "total_mb": <size>}`
|
||||||
|
|
||||||
|
#### Scenario: Emit download progress
|
||||||
|
- **WHEN** download progress updates
|
||||||
|
- **THEN** sidecar SHALL emit JSON: `{"status": "downloading_model", "progress": <percent>, "downloaded_mb": <current>, "total_mb": <total>}`
|
||||||
|
- **AND** progress updates SHALL occur at least every 5% or every 5 seconds
|
||||||
|
|
||||||
|
#### Scenario: Emit download complete
|
||||||
|
- **WHEN** model download completes
|
||||||
|
- **THEN** sidecar SHALL emit JSON: `{"status": "model_downloaded", "model": "<size>"}`
|
||||||
|
- **AND** proceed to model loading
|
||||||
|
|
||||||
|
#### Scenario: Skip download for cached model
|
||||||
|
- **WHEN** model already exists in huggingface cache
|
||||||
|
- **THEN** sidecar SHALL NOT emit download progress messages
|
||||||
|
- **AND** proceed directly to loading
|
||||||
|
|
||||||
|
### Requirement: Frontend Model Download Progress Display
|
||||||
|
The Electron frontend SHALL display model download progress to users.
|
||||||
|
|
||||||
|
#### Scenario: Show download progress in transcript panel
|
||||||
|
- **WHEN** sidecar emits download progress
|
||||||
|
- **THEN** whisper status element SHALL display download percentage and size
|
||||||
|
- **AND** format: "Downloading: XX% (YYY MB / ZZZ MB)"
|
||||||
|
|
||||||
|
#### Scenario: Show download complete
|
||||||
|
- **WHEN** sidecar emits model_downloaded status
|
||||||
|
- **THEN** whisper status element SHALL briefly show "Model downloaded"
|
||||||
|
- **AND** transition to loading state
|
||||||
|
|
||||||
|
#### Scenario: Forward progress events via IPC
|
||||||
|
- **WHEN** main process receives download progress from sidecar
|
||||||
|
- **THEN** it SHALL forward to renderer via `model-download-progress` IPC channel
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user