From e7a06e2b8f7bbbb50e04414ee45051a220a79bfd Mon Sep 17 00:00:00 2001 From: egg Date: Mon, 22 Dec 2025 08:44:04 +0800 Subject: [PATCH] chore: Archive all pending OpenSpec proposals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../design.md | 129 ++++++++++++++++ .../proposal.md | 85 ++++++++++ .../specs/middleware/spec.md | 122 +++++++++++++++ .../tasks.md | 45 ++++++ .../design.md | 145 ++++++++++++++++++ .../proposal.md | 45 ++++++ .../specs/audio-device-management/spec.md | 131 ++++++++++++++++ .../tasks.md | 125 +++++++++++++++ .../design.md | 115 ++++++++++++++ .../proposal.md | 28 ++++ .../specs/embedded-backend/spec.md | 89 +++++++++++ .../specs/transcription/spec.md | 40 +++++ .../tasks.md | 39 +++++ .../proposal.md | 18 +++ .../specs/embedded-backend/spec.md | 57 +++++++ .../tasks.md | 36 +++++ .../specs/audio-device-management/spec.md | 133 ++++++++++++++++ openspec/specs/embedded-backend/spec.md | 130 ++++++++++++++++ openspec/specs/transcription/spec.md | 39 +++++ 19 files changed, 1551 insertions(+) create mode 100644 openspec/changes/archive/2025-12-14-extract-environment-variables/design.md create mode 100644 openspec/changes/archive/2025-12-14-extract-environment-variables/proposal.md create mode 100644 openspec/changes/archive/2025-12-14-extract-environment-variables/specs/middleware/spec.md create mode 100644 openspec/changes/archive/2025-12-14-extract-environment-variables/tasks.md create mode 100644 openspec/changes/archive/2025-12-22-add-audio-device-selector/design.md create mode 100644 openspec/changes/archive/2025-12-22-add-audio-device-selector/proposal.md create mode 100644 openspec/changes/archive/2025-12-22-add-audio-device-selector/specs/audio-device-management/spec.md create mode 100644 openspec/changes/archive/2025-12-22-add-audio-device-selector/tasks.md create mode 100644 openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/design.md create mode 100644 openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/proposal.md create mode 100644 openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/specs/embedded-backend/spec.md create mode 100644 openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/specs/transcription/spec.md create mode 100644 openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/tasks.md create mode 100644 openspec/changes/archive/2025-12-22-add-flexible-deployment-options/proposal.md create mode 100644 openspec/changes/archive/2025-12-22-add-flexible-deployment-options/specs/embedded-backend/spec.md create mode 100644 openspec/changes/archive/2025-12-22-add-flexible-deployment-options/tasks.md create mode 100644 openspec/specs/audio-device-management/spec.md create mode 100644 openspec/specs/embedded-backend/spec.md diff --git a/openspec/changes/archive/2025-12-14-extract-environment-variables/design.md b/openspec/changes/archive/2025-12-14-extract-environment-variables/design.md new file mode 100644 index 0000000..683bddd --- /dev/null +++ b/openspec/changes/archive/2025-12-14-extract-environment-variables/design.md @@ -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 diff --git a/openspec/changes/archive/2025-12-14-extract-environment-variables/proposal.md b/openspec/changes/archive/2025-12-14-extract-environment-variables/proposal.md new file mode 100644 index 0000000..b038d45 --- /dev/null +++ b/openspec/changes/archive/2025-12-14-extract-environment-variables/proposal.md @@ -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://<伺服器>:/api` +2. 執行打包命令 +3. 分發 EXE 給使用者 diff --git a/openspec/changes/archive/2025-12-14-extract-environment-variables/specs/middleware/spec.md b/openspec/changes/archive/2025-12-14-extract-environment-variables/specs/middleware/spec.md new file mode 100644 index 0000000..a84f0af --- /dev/null +++ b/openspec/changes/archive/2025-12-14-extract-environment-variables/specs/middleware/spec.md @@ -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 diff --git a/openspec/changes/archive/2025-12-14-extract-environment-variables/tasks.md b/openspec/changes/archive/2025-12-14-extract-environment-variables/tasks.md new file mode 100644 index 0000000..4fa9072 --- /dev/null +++ b/openspec/changes/archive/2025-12-14-extract-environment-variables/tasks.md @@ -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 diff --git a/openspec/changes/archive/2025-12-22-add-audio-device-selector/design.md b/openspec/changes/archive/2025-12-22-add-audio-device-selector/design.md new file mode 100644 index 0000000..efba9dd --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-audio-device-selector/design.md @@ -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 diff --git a/openspec/changes/archive/2025-12-22-add-audio-device-selector/proposal.md b/openspec/changes/archive/2025-12-22-add-audio-device-selector/proposal.md new file mode 100644 index 0000000..a208ac3 --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-audio-device-selector/proposal.md @@ -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: 前端開發團隊 diff --git a/openspec/changes/archive/2025-12-22-add-audio-device-selector/specs/audio-device-management/spec.md b/openspec/changes/archive/2025-12-22-add-audio-device-selector/specs/audio-device-management/spec.md new file mode 100644 index 0000000..8ef45db --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-audio-device-selector/specs/audio-device-management/spec.md @@ -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 diff --git a/openspec/changes/archive/2025-12-22-add-audio-device-selector/tasks.md b/openspec/changes/archive/2025-12-22-add-audio-device-selector/tasks.md new file mode 100644 index 0000000..5940a32 --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-audio-device-selector/tasks.md @@ -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 diff --git a/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/design.md b/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/design.md new file mode 100644 index 0000000..b0769d2 --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/design.md @@ -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? diff --git a/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/proposal.md b/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/proposal.md new file mode 100644 index 0000000..b741a5a --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/proposal.md @@ -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 diff --git a/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/specs/embedded-backend/spec.md b/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/specs/embedded-backend/spec.md new file mode 100644 index 0000000..995ac16 --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/specs/embedded-backend/spec.md @@ -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 diff --git a/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/specs/transcription/spec.md b/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/specs/transcription/spec.md new file mode 100644 index 0000000..daa263c --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/specs/transcription/spec.md @@ -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": "", "progress": 0, "total_mb": }` + +#### Scenario: Emit download progress +- **WHEN** download progress updates +- **THEN** sidecar SHALL emit JSON: `{"status": "downloading_model", "progress": , "downloaded_mb": , "total_mb": }` +- **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": ""}` +- **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 diff --git a/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/tasks.md b/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/tasks.md new file mode 100644 index 0000000..59f392f --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-embedded-backend-packaging/tasks.md @@ -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 diff --git a/openspec/changes/archive/2025-12-22-add-flexible-deployment-options/proposal.md b/openspec/changes/archive/2025-12-22-add-flexible-deployment-options/proposal.md new file mode 100644 index 0000000..45639c6 --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-flexible-deployment-options/proposal.md @@ -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 diff --git a/openspec/changes/archive/2025-12-22-add-flexible-deployment-options/specs/embedded-backend/spec.md b/openspec/changes/archive/2025-12-22-add-flexible-deployment-options/specs/embedded-backend/spec.md new file mode 100644 index 0000000..6bbb11b --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-flexible-deployment-options/specs/embedded-backend/spec.md @@ -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) diff --git a/openspec/changes/archive/2025-12-22-add-flexible-deployment-options/tasks.md b/openspec/changes/archive/2025-12-22-add-flexible-deployment-options/tasks.md new file mode 100644 index 0000000..c97634f --- /dev/null +++ b/openspec/changes/archive/2025-12-22-add-flexible-deployment-options/tasks.md @@ -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 diff --git a/openspec/specs/audio-device-management/spec.md b/openspec/specs/audio-device-management/spec.md new file mode 100644 index 0000000..3668437 --- /dev/null +++ b/openspec/specs/audio-device-management/spec.md @@ -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 + diff --git a/openspec/specs/embedded-backend/spec.md b/openspec/specs/embedded-backend/spec.md new file mode 100644 index 0000000..de31530 --- /dev/null +++ b/openspec/specs/embedded-backend/spec.md @@ -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 + diff --git a/openspec/specs/transcription/spec.md b/openspec/specs/transcription/spec.md index 58ed996..47e320b 100644 --- a/openspec/specs/transcription/spec.md +++ b/openspec/specs/transcription/spec.md @@ -175,3 +175,42 @@ The system SHALL support both real-time local transcription and file-based cloud - **WHEN** transcription completes from either source - **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": "", "progress": 0, "total_mb": }` + +#### Scenario: Emit download progress +- **WHEN** download progress updates +- **THEN** sidecar SHALL emit JSON: `{"status": "downloading_model", "progress": , "downloaded_mb": , "total_mb": }` +- **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": ""}` +- **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 +