diff --git a/.github/workflows/build-desktop.yml b/.github/workflows/build-desktop.yml new file mode 100644 index 0000000..74b77f8 --- /dev/null +++ b/.github/workflows/build-desktop.yml @@ -0,0 +1,152 @@ +name: Build Desktop Applications + +on: + workflow_dispatch: # 手動觸發 + inputs: + platforms: + description: '選擇要構建的平台' + required: true + default: 'all' + type: choice + options: + - all + - windows + - macos + - linux + upload_artifacts: + description: '是否上傳構建產物' + required: true + default: true + type: boolean + + push: + paths: + - 'src-tauri/**' # 桌面應用代碼變更時自動觸發 + - 'scripts/build_desktop.py' + branches: + - main + + pull_request: + paths: + - 'src-tauri/**' + - 'scripts/build_desktop.py' + +env: + CARGO_TERM_COLOR: always + +jobs: + # 多平台桌面應用構建 + build-desktop: + strategy: + fail-fast: false # 允許部分平台失敗 + matrix: + include: + - os: windows-latest + target: x86_64-pc-windows-msvc + binary: mcp-feedback-enhanced-desktop.exe + name: windows + enabled: ${{ github.event.inputs.platforms == 'all' || github.event.inputs.platforms == 'windows' || github.event.inputs.platforms == '' }} + - os: macos-latest + target: x86_64-apple-darwin + binary: mcp-feedback-enhanced-desktop + name: macos-intel + enabled: ${{ github.event.inputs.platforms == 'all' || github.event.inputs.platforms == 'macos' || github.event.inputs.platforms == '' }} + - os: macos-latest + target: aarch64-apple-darwin + binary: mcp-feedback-enhanced-desktop + name: macos-arm64 + enabled: ${{ github.event.inputs.platforms == 'all' || github.event.inputs.platforms == 'macos' || github.event.inputs.platforms == '' }} + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + binary: mcp-feedback-enhanced-desktop + name: linux + enabled: ${{ github.event.inputs.platforms == 'all' || github.event.inputs.platforms == 'linux' || github.event.inputs.platforms == '' }} + + runs-on: ${{ matrix.os }} + if: ${{ matrix.enabled != 'false' }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: "latest" + + - name: Set up Python + run: uv python install + + - name: Install dependencies + run: uv sync --dev + + - name: Cache Rust dependencies + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + src-tauri/target + key: ${{ runner.os }}-cargo-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-${{ matrix.target }}- + ${{ runner.os }}-cargo- + + - name: Build desktop application for ${{ matrix.name }} + run: | + cd src-tauri + cargo build --release --target ${{ matrix.target }} --bin mcp-feedback-enhanced-desktop + + - name: Verify build output + run: | + echo "🔍 檢查構建產物..." + if [ "${{ matrix.os }}" = "windows-latest" ]; then + ls -la "src-tauri/target/${{ matrix.target }}/release/${{ matrix.binary }}" || echo "❌ Windows 二進制文件不存在" + else + ls -la "src-tauri/target/${{ matrix.target }}/release/${{ matrix.binary }}" || echo "❌ ${{ matrix.name }} 二進制文件不存在" + fi + shell: bash + + - name: Upload desktop binary + if: ${{ github.event.inputs.upload_artifacts != 'false' }} + uses: actions/upload-artifact@v4 + with: + name: desktop-${{ matrix.name }} + path: src-tauri/target/${{ matrix.target }}/release/${{ matrix.binary }} + retention-days: 30 # 保留 30 天 + compression-level: 6 + + # 構建摘要 + build-summary: + needs: build-desktop + runs-on: ubuntu-latest + if: always() + + steps: + - name: Generate build summary + run: | + echo "## 🖥️ 桌面應用構建摘要" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 構建結果" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # 檢查各平台構建狀態 + if [ "${{ needs.build-desktop.result }}" = "success" ]; then + echo "✅ 所有平台構建成功" >> $GITHUB_STEP_SUMMARY + elif [ "${{ needs.build-desktop.result }}" = "failure" ]; then + echo "❌ 部分平台構建失敗" >> $GITHUB_STEP_SUMMARY + else + echo "⚠️ 構建狀態: ${{ needs.build-desktop.result }}" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### 下一步" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- 構建產物已上傳到 GitHub Artifacts" >> $GITHUB_STEP_SUMMARY + echo "- 可以在發佈流程中使用這些二進制文件" >> $GITHUB_STEP_SUMMARY + echo "- 如需重新構建,請手動觸發此工作流程" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c5cf2f6..3a148cc 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,6 +16,15 @@ on: description: 'Custom version number (e.g., 2.5.0) - overrides version_type if provided' required: false type: string + include_desktop: + description: '是否包含桌面應用二進制文件' + required: true + default: true + type: boolean + desktop_build_run_id: + description: '桌面應用構建的 Run ID(可選,留空使用最新的成功構建)' + required: false + type: string jobs: release: @@ -38,6 +47,9 @@ jobs: - name: Set up Python run: uv python install + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + - name: Install dependencies run: | uv sync --dev @@ -275,6 +287,101 @@ jobs: - Auto-generated release from workflow" git tag "v${{ steps.bump_version.outputs.new }}" + - name: Download desktop binaries from latest build + if: ${{ github.event.inputs.include_desktop == 'true' }} + uses: dawidd6/action-download-artifact@v6 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + workflow: build-desktop.yml + run_id: ${{ github.event.inputs.desktop_build_run_id }} + path: desktop-artifacts + if_no_artifact_found: warn + + - name: Prepare multi-platform desktop binaries + if: ${{ github.event.inputs.include_desktop == 'true' }} + run: | + # 檢查是否有桌面應用構建產物 + if [ ! -d "desktop-artifacts" ] || [ -z "$(ls -A desktop-artifacts 2>/dev/null)" ]; then + echo "⚠️ 警告:沒有找到桌面應用構建產物" + echo "💡 提示:請先運行 'Build Desktop Applications' 工作流程" + echo "🔧 或者設置 include_desktop 為 false 來跳過桌面應用" + exit 1 + fi + + # 創建桌面應用目錄 + mkdir -p src/mcp_feedback_enhanced/desktop_release + + # 複製所有平台的二進制文件並重命名 + echo "📦 準備多平台桌面應用二進制文件..." + + # 檢查並複製各平台文件 + if [ -f "desktop-artifacts/desktop-windows/mcp-feedback-enhanced-desktop.exe" ]; then + cp desktop-artifacts/desktop-windows/mcp-feedback-enhanced-desktop.exe src/mcp_feedback_enhanced/desktop_release/ + echo "✅ Windows 二進制文件已複製" + else + echo "⚠️ Windows 二進制文件不存在" + fi + + if [ -f "desktop-artifacts/desktop-macos-intel/mcp-feedback-enhanced-desktop" ]; then + cp desktop-artifacts/desktop-macos-intel/mcp-feedback-enhanced-desktop src/mcp_feedback_enhanced/desktop_release/mcp-feedback-enhanced-desktop-macos-intel + echo "✅ macOS Intel 二進制文件已複製" + else + echo "⚠️ macOS Intel 二進制文件不存在" + fi + + if [ -f "desktop-artifacts/desktop-macos-arm64/mcp-feedback-enhanced-desktop" ]; then + cp desktop-artifacts/desktop-macos-arm64/mcp-feedback-enhanced-desktop src/mcp_feedback_enhanced/desktop_release/mcp-feedback-enhanced-desktop-macos-arm64 + echo "✅ macOS ARM64 二進制文件已複製" + else + echo "⚠️ macOS ARM64 二進制文件不存在" + fi + + if [ -f "desktop-artifacts/desktop-linux/mcp-feedback-enhanced-desktop" ]; then + cp desktop-artifacts/desktop-linux/mcp-feedback-enhanced-desktop src/mcp_feedback_enhanced/desktop_release/mcp-feedback-enhanced-desktop-linux + echo "✅ Linux 二進制文件已複製" + else + echo "⚠️ Linux 二進制文件不存在" + fi + + # 設置執行權限 + chmod +x src/mcp_feedback_enhanced/desktop_release/mcp-feedback-enhanced-desktop-* 2>/dev/null || true + + # 創建 __init__.py + echo '"""桌面應用程式二進制檔案"""' > src/mcp_feedback_enhanced/desktop_release/__init__.py + + # 顯示文件列表 + echo "📦 最終的桌面應用二進制文件:" + ls -la src/mcp_feedback_enhanced/desktop_release/ + + - name: Build desktop applications locally (fallback) + if: ${{ github.event.inputs.include_desktop == 'true' }} + run: | + # 如果沒有預構建的桌面應用,嘗試本地構建 + if [ ! -d "src/mcp_feedback_enhanced/desktop_release" ] || [ -z "$(ls -A src/mcp_feedback_enhanced/desktop_release 2>/dev/null)" ]; then + echo "🔧 沒有找到預構建的桌面應用,嘗試本地構建..." + echo "⚠️ 注意:本地構建可能只支援當前平台" + + # 安裝 Rust(如果還沒安裝) + if ! command -v cargo &> /dev/null; then + echo "📦 安裝 Rust..." + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source ~/.cargo/env + fi + + # 運行本地構建腳本 + python scripts/build_desktop.py --release + + echo "✅ 本地桌面應用構建完成" + else + echo "✅ 使用預構建的桌面應用" + fi + + - name: Skip desktop applications + if: ${{ github.event.inputs.include_desktop != 'true' }} + run: | + echo "⏭️ 跳過桌面應用,僅發佈 Web 版本" + echo "💡 用戶將只能使用 Web 模式,無法使用桌面模式" + - name: Build package run: uv build @@ -311,10 +418,32 @@ jobs: echo "📦 Published to PyPI: https://pypi.org/project/mcp-feedback-enhanced/" echo "🚀 GitHub Release: https://github.com/Minidoracat/mcp-feedback-enhanced/releases/tag/v${{ steps.bump_version.outputs.new }}" echo "📝 Release notes generated from CHANGELOG files" + echo "" + + # 顯示桌面應用狀態 + if [ "${{ github.event.inputs.include_desktop }}" = "true" ]; then + echo "🖥️ 桌面應用狀態:" + if [ -d "src/mcp_feedback_enhanced/desktop_release" ] && [ -n "$(ls -A src/mcp_feedback_enhanced/desktop_release 2>/dev/null)" ]; then + echo " ✅ 桌面應用已包含在發佈中" + echo " 📱 支援的平台:" + [ -f "src/mcp_feedback_enhanced/desktop_release/mcp-feedback-enhanced-desktop.exe" ] && echo " - Windows x64" + [ -f "src/mcp_feedback_enhanced/desktop_release/mcp-feedback-enhanced-desktop-macos-intel" ] && echo " - macOS Intel" + [ -f "src/mcp_feedback_enhanced/desktop_release/mcp-feedback-enhanced-desktop-macos-arm64" ] && echo " - macOS Apple Silicon" + [ -f "src/mcp_feedback_enhanced/desktop_release/mcp-feedback-enhanced-desktop-linux" ] && echo " - Linux x64" + else + echo " ⚠️ 桌面應用未包含(可能構建失敗)" + fi + else + echo "🖥️ 桌面應用狀態:⏭️ 已跳過(僅 Web 版本)" + fi + echo "" echo "✅ Next steps:" echo " - Check the release on GitHub" echo " - Verify the package on PyPI" echo " - Test installation with: uvx mcp-feedback-enhanced@v${{ steps.bump_version.outputs.new }}" + if [ "${{ github.event.inputs.include_desktop }}" = "true" ]; then + echo " - Test desktop mode with: uvx mcp-feedback-enhanced@v${{ steps.bump_version.outputs.new }} test --desktop" + fi echo "" echo "📋 Note: Make sure CHANGELOG files are updated for future releases" diff --git a/.gitignore b/.gitignore index c753a01..49c7367 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,29 @@ ui_settings.json .env .env.local .env.*.local + +# Rust/Tauri build artifacts +src-tauri/target/ +src-tauri/Cargo.lock +src-tauri/WixTools/ +src-tauri/gen/ + +# Desktop application binaries (these should be built and copied by build script) +src/mcp_feedback_enhanced/desktop_release/* +src/mcp_feedback_enhanced/desktop_app/ +src/mcp_feedback_enhanced/desktop/mcp-feedback-enhanced-desktop + + + +# Tauri bundle outputs +src-tauri/target/bundle/ +src-tauri/target/release/bundle/ + +# Node.js (if using Tauri with frontend framework) +node_modules/ +package-lock.json +yarn.lock + +# Temporary build files +*.tmp +*.temp diff --git a/Makefile b/Makefile index 182b683..39b1d2c 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # Compatible with Windows PowerShell and Unix systems # 兼容 Windows PowerShell 和 Unix 系統 -.PHONY: help install install-dev install-hooks lint format type-check test clean pre-commit-run pre-commit-all update-deps +.PHONY: help install install-dev install-hooks lint format type-check test clean pre-commit-run pre-commit-all update-deps check-rust build-desktop build-desktop-release test-desktop clean-desktop build-all test-all # 預設目標 - 顯示幫助訊息 help: ## Show this help message @@ -36,6 +36,13 @@ help: ## Show this help message @echo " bump-major Bump major version" @echo " ci Simulate CI pipeline locally" @echo " quick-check Quick check with auto-fix" + @echo "" + @echo "Desktop Application Commands:" + @echo " build-desktop Build desktop application (debug)" + @echo " build-desktop-release Build desktop application (release)" + @echo " test-desktop Test desktop application" + @echo " clean-desktop Clean desktop build artifacts" + @echo " check-rust Check Rust development environment" # 安裝相關命令 install: ## Install the package @@ -142,3 +149,35 @@ quick-check: lint-fix format type-check ## Quick check with auto-fix (recommende # Windows PowerShell 專用命令 ps-clean: ## PowerShell version of clean (Windows) powershell -Command "Get-ChildItem -Path . -Recurse -Name '__pycache__' | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue; Get-ChildItem -Path . -Recurse -Name '*.pyc' | Remove-Item -Force -ErrorAction SilentlyContinue; @('.mypy_cache', '.ruff_cache', '.pytest_cache', 'htmlcov', 'dist', 'build') | ForEach-Object { if (Test-Path $$_) { Remove-Item $$_ -Recurse -Force } }" + +# 桌面應用程式相關命令 +check-rust: ## Check Rust development environment + @echo "🔍 Checking Rust environment..." + @rustc --version || (echo "❌ Rust not installed. Please visit https://rustup.rs/" && exit 1) + @cargo --version || (echo "❌ Cargo not installed" && exit 1) + @cargo install --list | grep tauri-cli || (echo "⚠️ Tauri CLI not installed, installing..." && cargo install tauri-cli) + @echo "✅ Rust environment check completed" + +build-desktop: ## Build desktop application (debug mode) + @echo "🔨 Building desktop application (debug)..." + uv run python scripts/build_desktop.py + +build-desktop-release: ## Build desktop application (release mode) + @echo "🚀 Building desktop application (release)..." + uv run python scripts/build_desktop.py --release + +test-desktop: build-desktop ## Test desktop application + @echo "🖥️ Testing desktop application..." + uv run python -m mcp_feedback_enhanced test --desktop + +clean-desktop: ## Clean desktop build artifacts + @echo "🧹 Cleaning desktop build artifacts..." + uv run python scripts/build_desktop.py --clean + +# 完整構建流程(包含桌面應用程式) +build-all: clean build-desktop-release build ## Build complete package with desktop app + @echo "🎉 Complete build finished!" + +# 測試所有功能 +test-all: test test-desktop ## Run all tests including desktop + @echo "✅ All tests completed!" diff --git a/docs/DESKTOP_BUILD.md b/docs/DESKTOP_BUILD.md new file mode 100644 index 0000000..269ba27 --- /dev/null +++ b/docs/DESKTOP_BUILD.md @@ -0,0 +1,244 @@ +# 桌面應用程式構建指南 + +本文檔說明如何構建 MCP Feedback Enhanced 的桌面應用程式。 + +## 先決條件 + +### 必需工具 + +1. **Python 3.8+** + ```bash + python --version + ``` + +2. **Rust 工具鏈** + ```bash + # 安裝 Rust (如果尚未安裝) + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + + # 驗證安裝 + rustc --version + cargo --version + ``` + +3. **Tauri CLI** (自動安裝) + ```bash + cargo install tauri-cli + ``` + +### 開發依賴 + +```bash +# 安裝 Python 開發依賴 +uv sync --dev + +# 或使用 pip +pip install -e ".[dev]" +``` + +## 構建方法 + +### 方法 1: 使用 Makefile (推薦) + +```bash +# 構建 Debug 版本 +make build-desktop + +# 構建 Release 版本 +make build-desktop-release + +# 構建並測試 +make test-desktop + +# 清理構建產物 +make clean-desktop + +# 完整構建流程 (包含 PyPI 包) +make build-all +``` + +### 方法 2: 直接使用 Python 腳本 + +```bash +# 構建 Debug 版本 +python scripts/build_desktop.py + +# 構建 Release 版本 +python scripts/build_desktop.py --release + +# 清理構建產物 +python scripts/build_desktop.py --clean + +# 查看幫助 +python scripts/build_desktop.py --help +``` + +## 構建產物 + +構建完成後,產物將位於: + +``` +src/mcp_feedback_enhanced/ +├── desktop_release/ # 發佈用二進制文件 +│ ├── __init__.py +│ ├── mcp-feedback-enhanced-desktop.exe # Windows +│ ├── mcp-feedback-enhanced-desktop-macos-intel # macOS Intel +│ ├── mcp-feedback-enhanced-desktop-macos-arm64 # macOS Apple Silicon +│ └── mcp-feedback-enhanced-desktop-linux # Linux +├── desktop_app/ # 發佈用 Python 模組 +│ ├── __init__.py +│ └── desktop_app.py +└── ... + +src-tauri/python/mcp_feedback_enhanced_desktop/ # 開發環境模組 +├── __init__.py +├── desktop_app.py +└── ... +``` + +### 多平台支援 + +構建腳本會自動構建以下平台的二進制文件: + +- **Windows**: `mcp-feedback-enhanced-desktop.exe` +- **macOS Intel**: `mcp-feedback-enhanced-desktop-macos-intel` +- **macOS Apple Silicon**: `mcp-feedback-enhanced-desktop-macos-arm64` +- **Linux**: `mcp-feedback-enhanced-desktop-linux` + +桌面應用會根據運行平台自動選擇對應的二進制文件。 + +## 測試桌面應用程式 + +```bash +# 方法 1: 直接測試 +python -m mcp_feedback_enhanced test --desktop + +# 方法 2: 使用 Makefile +make test-desktop +``` + +## 跨平台注意事項 + +### Windows +- 桌面應用程式不會顯示額外的 CMD 視窗 +- 二進制檔案: `mcp-feedback-enhanced-desktop.exe` + +### Linux/macOS +- 二進制檔案: `mcp-feedback-enhanced-desktop` +- 自動設置執行權限 + +## 故障排除 + +### 常見問題 + +1. **Rust 未安裝** + ``` + ❌ Rust 未安裝,請訪問 https://rustup.rs/ + ``` + 解決方案: 安裝 Rust 工具鏈 + +2. **Tauri CLI 未安裝** + ``` + ⚠️ Tauri CLI 未安裝,正在安裝... + ``` + 解決方案: 腳本會自動安裝,或手動執行 `cargo install tauri-cli` + +3. **構建失敗** + ``` + ❌ 構建失敗 + ``` + 解決方案: 檢查 Rust 環境,清理後重新構建 + ```bash + make clean-desktop + make build-desktop + ``` + +### 檢查環境 + +```bash +# 檢查 Rust 環境 +make check-rust + +# 檢查所有依賴 +make dev-setup +``` + +### 跨平台編譯要求 + +構建腳本會自動安裝以下 Rust targets: + +```bash +# 這些 targets 會自動安裝,無需手動執行 +rustup target add x86_64-pc-windows-msvc # Windows +rustup target add x86_64-apple-darwin # macOS Intel +rustup target add aarch64-apple-darwin # macOS Apple Silicon +rustup target add x86_64-unknown-linux-gnu # Linux +``` + +**注意**: +- 本地構建通常只能成功構建當前平台的二進制文件 +- 跨平台編譯需要複雜的工具鏈配置(C 編譯器、系統庫等) +- **完整的多平台支援在 GitHub Actions CI 中實現** +- 發佈到 PyPI 時會自動包含所有平台的二進制文件 + +## 自動化構建 + +### CI/CD 多平台構建 + +GitHub Actions 會在各自的原生平台上構建桌面應用: + +```yaml +# 多平台構建策略 +strategy: + matrix: + include: + - os: windows-latest # Windows 原生構建 + target: x86_64-pc-windows-msvc + - os: macos-latest # macOS 原生構建 + target: x86_64-apple-darwin + - os: ubuntu-latest # Linux 原生構建 + target: x86_64-unknown-linux-gnu +``` + +這確保了: +- ✅ 每個平台在其原生環境中構建 +- ✅ 避免跨平台編譯的複雜性 +- ✅ 最終 PyPI 包包含所有平台的二進制文件 + +### 本地自動化 + +```bash +# 完整的開發工作流程 +make dev-setup # 初始化環境 +make quick-check # 程式碼檢查 +make build-all # 構建所有組件 +make test-all # 測試所有功能 +``` + +## 發布流程 + +1. **構建發布版本** + ```bash + make build-desktop-release + ``` + +2. **構建 PyPI 包** + ```bash + make build-all + ``` + +3. **驗證包內容** + ```bash + # 檢查桌面應用程式是否包含在包中 + tar -tf dist/*.tar.gz | grep desktop + ``` + +桌面應用程式現在已完全集成到 PyPI 包中,包含所有主要平台的二進制文件,用戶安裝後可直接使用 `--desktop` 選項啟動。 + +### 平台兼容性 + +- **Windows**: 支援 x64 架構 +- **macOS**: 支援 Intel 和 Apple Silicon (M1/M2) 架構 +- **Linux**: 支援 x64 架構 + +桌面應用會自動檢測運行平台並選擇對應的二進制文件。 diff --git a/docs/WORKFLOWS.md b/docs/WORKFLOWS.md new file mode 100644 index 0000000..06e155f --- /dev/null +++ b/docs/WORKFLOWS.md @@ -0,0 +1,122 @@ +# GitHub Actions 工作流程說明 + +本項目使用雙工作流程架構來優化構建和發佈流程。 + +## 🏗️ 工作流程架構 + +### 1. 桌面應用構建工作流程 (build-desktop.yml) + +**用途**: 專門負責構建多平台桌面應用二進制文件 + +**觸發條件**: +- 手動觸發 (workflow_dispatch) +- 桌面應用代碼變更時自動觸發 (`src-tauri/**`, `scripts/build_desktop.py`) +- Pull Request 中的桌面應用變更 + +**功能**: +- 在各自原生平台上構建桌面應用 +- 支援選擇性平台構建 +- 上傳構建產物到 GitHub Artifacts (保留 30 天) +- 提供詳細的構建摘要 + +**支援平台**: +- Windows x64 (`windows-latest`) +- macOS Intel (`macos-latest` + `x86_64-apple-darwin`) +- macOS Apple Silicon (`macos-latest` + `aarch64-apple-darwin`) +- Linux x64 (`ubuntu-latest`) + +### 2. 發佈工作流程 (publish.yml) + +**用途**: 負責版本管理和 PyPI 發佈 + +**觸發條件**: +- 手動觸發 (workflow_dispatch) + +**功能**: +- 自動或手動版本號管理 +- 可選擇是否包含桌面應用 +- 從最新的桌面應用構建下載二進制文件 +- 發佈到 PyPI +- 創建 GitHub Release + +## 🚀 使用方式 + +### 開發桌面應用時 + +1. **修改桌面應用代碼** (`src-tauri/` 目錄) +2. **自動觸發構建** - 推送到 main 分支會自動觸發桌面應用構建 +3. **手動觸發構建** (可選) - 在 GitHub Actions 頁面手動運行 "Build Desktop Applications" + +### 發佈新版本時 + +1. **確保桌面應用已構建** - 檢查最新的 "Build Desktop Applications" 工作流程是否成功 +2. **手動觸發發佈** - 在 GitHub Actions 頁面運行 "Auto Release to PyPI" +3. **選擇發佈選項**: + - `version_type`: patch/minor/major (或使用 custom_version) + - `include_desktop`: 是否包含桌面應用 (預設: true) + - `desktop_build_run_id`: 指定特定的構建 ID (可選) + +## 📋 最佳實踐 + +### 桌面應用構建 + +```bash +# 本地測試桌面應用構建 +python scripts/build_desktop.py --release + +# 檢查構建產物 +ls -la src/mcp_feedback_enhanced/desktop_release/ +ls -la src/mcp_feedback_enhanced/desktop_app/ +``` + +### 發佈流程 + +1. **準備發佈**: + - 更新 CHANGELOG 文件 + - 確保桌面應用構建成功 + - 測試本地功能 + +2. **執行發佈**: + - 手動觸發 "Auto Release to PyPI" 工作流程 + - 選擇適當的版本類型 + - 確認包含桌面應用 (如果需要) + +3. **發佈後驗證**: + - 檢查 PyPI 上的新版本 + - 測試安裝: `uvx mcp-feedback-enhanced@latest` + - 測試桌面模式: `uvx mcp-feedback-enhanced@latest test --desktop` + +## 🔧 故障排除 + +### 桌面應用構建失敗 + +1. **檢查構建日誌** - 查看 GitHub Actions 中的詳細錯誤信息 +2. **平台特定問題**: + - macOS: 可能缺少 Xcode 命令行工具 + - Linux: 可能缺少系統依賴 (GTK, Cairo 等) + - Windows: 通常構建成功 + +3. **本地測試** - 在對應平台上運行本地構建腳本 + +### 發佈時桌面應用缺失 + +1. **檢查構建狀態** - 確保最新的桌面應用構建成功 +2. **手動指定構建** - 使用 `desktop_build_run_id` 參數指定特定的成功構建 +3. **跳過桌面應用** - 設置 `include_desktop: false` 僅發佈 Web 版本 + +## 📊 工作流程優勢 + +### 效率提升 +- **分離關注點**: 構建和發佈獨立進行 +- **避免重複構建**: 不是每次發佈都需要重新構建桌面應用 +- **快速發佈**: 發佈流程更快速,特別是僅修改 Python 代碼時 + +### 靈活性 +- **選擇性構建**: 可以只構建特定平台 +- **選擇性發佈**: 可以選擇是否包含桌面應用 +- **版本控制**: 可以使用不同的桌面應用構建版本 + +### 可靠性 +- **原生構建**: 每個平台在其原生環境中構建 +- **構建緩存**: 利用 GitHub Actions 緩存加速構建 +- **錯誤隔離**: 桌面應用構建失敗不會影響 Web 版本發佈 diff --git a/examples/mcp-config-desktop.json b/examples/mcp-config-desktop.json new file mode 100644 index 0000000..46a46cc --- /dev/null +++ b/examples/mcp-config-desktop.json @@ -0,0 +1,15 @@ +{ + "mcpServers": { + "mcp-feedback-enhanced": { + "command": "uvx", + "args": ["mcp-feedback-enhanced@latest"], + "timeout": 600, + "env": { + "MCP_DESKTOP_MODE": "true", + "MCP_WEB_PORT": "8765", + "MCP_DEBUG": "false" + }, + "autoApprove": ["interactive_feedback"] + } + } +} diff --git a/examples/mcp-config-web.json b/examples/mcp-config-web.json new file mode 100644 index 0000000..675f8cd --- /dev/null +++ b/examples/mcp-config-web.json @@ -0,0 +1,15 @@ +{ + "mcpServers": { + "mcp-feedback-enhanced": { + "command": "uvx", + "args": ["mcp-feedback-enhanced@latest"], + "timeout": 600, + "env": { + "MCP_DESKTOP_MODE": "false", + "MCP_WEB_PORT": "8765", + "MCP_DEBUG": "false" + }, + "autoApprove": ["interactive_feedback"] + } + } +} diff --git a/pyproject.toml b/pyproject.toml index f71740f..c585834 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,12 +45,20 @@ mcp-feedback-enhanced = "mcp_feedback_enhanced.__main__:main" interactive-feedback-mcp = "mcp_feedback_enhanced.__main__:main" [build-system] -requires = ["hatchling"] +requires = [ + "hatchling", + "maturin>=1.8.7", + "setuptools-rust>=1.11.1" +] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/mcp_feedback_enhanced"] +# 包含桌面應用程式二進制檔案 +[tool.hatch.build.targets.wheel.force-include] +"src/mcp_feedback_enhanced/desktop_release" = "mcp_feedback_enhanced/desktop_release" + [tool.uv] dev-dependencies = [ "bump2version>=1.0.1", @@ -61,6 +69,9 @@ dev-dependencies = [ "ruff>=0.11.0", "mypy>=1.16.0", "pre-commit>=4.0.0", + "maturin>=1.8.7", + "setuptools-rust>=1.11.1", + "pillow>=11.2.1", ] # ===== Ruff 配置 ===== diff --git a/scripts/build_desktop.py b/scripts/build_desktop.py new file mode 100644 index 0000000..306c278 --- /dev/null +++ b/scripts/build_desktop.py @@ -0,0 +1,437 @@ +#!/usr/bin/env python3 +""" +桌面應用程式構建腳本 + +此腳本負責構建 Tauri 桌面應用程式和 Python 擴展模組, +確保在 PyPI 發布時包含預編譯的二進制檔案。 + +使用方法: + python scripts/build_desktop.py [--release] [--clean] +""" + +import argparse +import os +import shutil +import subprocess +import sys +from pathlib import Path + + +def run_command( + cmd: list[str], cwd: str = None, check: bool = True, show_info: bool = True +) -> subprocess.CompletedProcess: + """執行命令並返回結果""" + if show_info: + print(f"🔧 執行命令: {' '.join(cmd)}") + if cwd: + print(f"📁 工作目錄: {cwd}") + + result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, check=False) + + # 處理標準輸出 + if result.stdout and show_info: + print("📤 輸出:") + print(result.stdout.strip()) + + # 智能處理標準錯誤 - 區分信息和真正的錯誤 + if result.stderr: + stderr_lines = result.stderr.strip().split("\n") + info_lines = [] + error_lines = [] + + for line in stderr_lines: + stripped_line = line.strip() + if not stripped_line: + continue + # 識別信息性消息和正常編譯輸出 + if ( + stripped_line.startswith("info:") + or "is up to date" in stripped_line + or "downloading component" in stripped_line + or "installing component" in stripped_line + or stripped_line.startswith("Compiling") + or stripped_line.startswith("Finished") + or stripped_line.startswith("Building") + or "target(s) in" in stripped_line + ): + info_lines.append(stripped_line) + else: + error_lines.append(stripped_line) + + # 顯示信息性消息 + if info_lines and show_info: + print("ℹ️ 信息:") + for line in info_lines: + print(f" {line}") + + # 顯示真正的錯誤 + if error_lines: + print("❌ 錯誤:") + for line in error_lines: + print(f" {line}") + + if check and result.returncode != 0: + raise subprocess.CalledProcessError(result.returncode, cmd) + + return result + + +def check_rust_environment(): + """檢查 Rust 開發環境""" + print("🔍 檢查 Rust 開發環境...") + + try: + result = run_command(["rustc", "--version"]) + print(f"✅ Rust 編譯器: {result.stdout.strip()}") + except (subprocess.CalledProcessError, FileNotFoundError): + print("❌ 未找到 Rust 編譯器") + print("💡 請安裝 Rust: https://rustup.rs/") + return False + + try: + result = run_command(["cargo", "--version"]) + print(f"✅ Cargo: {result.stdout.strip()}") + except (subprocess.CalledProcessError, FileNotFoundError): + print("❌ 未找到 Cargo") + return False + + try: + result = run_command(["cargo", "install", "--list"]) + if "tauri-cli" in result.stdout: + print("✅ Tauri CLI 已安裝") + else: + print("⚠️ Tauri CLI 未安裝,嘗試安裝...") + run_command(["cargo", "install", "tauri-cli"]) + print("✅ Tauri CLI 安裝完成") + except subprocess.CalledProcessError: + print("❌ 無法安裝 Tauri CLI") + return False + + return True + + +def install_rust_targets(): + """安裝跨平台編譯所需的 Rust targets""" + print("🎯 安裝跨平台編譯 targets...") + + # 定義需要的 targets + targets = [ + ("x86_64-pc-windows-msvc", "Windows x64"), + ("x86_64-apple-darwin", "macOS Intel"), + ("aarch64-apple-darwin", "macOS Apple Silicon"), + ("x86_64-unknown-linux-gnu", "Linux x64"), + ] + + installed_count = 0 + updated_count = 0 + + for target, description in targets: + print(f"📦 檢查 target: {target} ({description})") + try: + result = run_command( + ["rustup", "target", "add", target], check=False, show_info=False + ) + + if result.returncode == 0: + # 檢查是否是新安裝還是已存在 + if "is up to date" in result.stderr: + print(f"✅ {description} - 已是最新版本") + updated_count += 1 + elif "installing component" in result.stderr: + print(f"🆕 {description} - 新安裝完成") + installed_count += 1 + else: + print(f"✅ {description} - 安裝成功") + installed_count += 1 + else: + print(f"⚠️ {description} - 安裝失敗") + if result.stderr: + print(f" 錯誤: {result.stderr.strip()}") + except Exception as e: + print(f"⚠️ 安裝 {description} 時發生錯誤: {e}") + + print( + f"✅ Rust targets 檢查完成 (新安裝: {installed_count}, 已存在: {updated_count})" + ) + + +def clean_build_artifacts(project_root: Path): + """清理構建產物""" + print("🧹 清理構建產物...") + + # 清理 Rust 構建產物 + rust_target = project_root / "src-tauri" / "target" + if rust_target.exists(): + print(f"清理 Rust target 目錄: {rust_target}") + shutil.rmtree(rust_target) + + # 清理 Python 構建產物 + python_build_dirs = [ + project_root / "build", + project_root / "dist", + project_root / "*.egg-info", + ] + + for build_dir in python_build_dirs: + if build_dir.exists(): + print(f"清理 Python 構建目錄: {build_dir}") + if build_dir.is_dir(): + shutil.rmtree(build_dir) + else: + build_dir.unlink() + + +def build_rust_extension(project_root: Path, release: bool = True): + """構建 Rust 擴展模組""" + print("🔨 構建 Rust 擴展模組...") + + src_tauri = project_root / "src-tauri" + if not src_tauri.exists(): + raise FileNotFoundError(f"src-tauri 目錄不存在: {src_tauri}") + + # 構建 Rust 庫 + build_cmd = ["cargo", "build"] + if release: + build_cmd.append("--release") + + run_command(build_cmd, cwd=str(src_tauri)) + print("✅ Rust 擴展模組構建完成") + + +def build_tauri_app_multiplatform(project_root: Path, release: bool = True): + """構建多平台 Tauri 桌面應用程式""" + print("🖥️ 構建多平台 Tauri 桌面應用程式...") + + src_tauri = project_root / "src-tauri" + + # 定義目標平台 + targets = [ + ("x86_64-pc-windows-msvc", "mcp-feedback-enhanced-desktop.exe"), + ("x86_64-apple-darwin", "mcp-feedback-enhanced-desktop"), + ("aarch64-apple-darwin", "mcp-feedback-enhanced-desktop"), + ("x86_64-unknown-linux-gnu", "mcp-feedback-enhanced-desktop"), + ] + + successful_builds = [] + + # 平台描述映射 + platform_descriptions = { + "x86_64-pc-windows-msvc": "Windows x64", + "x86_64-apple-darwin": "macOS Intel", + "aarch64-apple-darwin": "macOS Apple Silicon", + "x86_64-unknown-linux-gnu": "Linux x64", + } + + for target, binary_name in targets: + description = platform_descriptions.get(target, target) + print(f"🔨 構建 {description} ({target})...") + + # 構建命令 + build_cmd = [ + "cargo", + "build", + "--bin", + "mcp-feedback-enhanced-desktop", + "--target", + target, + ] + if release: + build_cmd.append("--release") + + try: + run_command(build_cmd, cwd=str(src_tauri), show_info=False) + successful_builds.append((target, binary_name)) + print(f"✅ {description} 構建成功") + except subprocess.CalledProcessError as e: + print(f"⚠️ {description} 構建失敗") + print("💡 可能缺少該平台的編譯工具鏈或依賴") + # 顯示具體錯誤信息 + if hasattr(e, "stderr") and e.stderr: + print(f" 錯誤詳情: {e.stderr.strip()}") + except Exception as e: + print(f"❌ {description} 構建時發生未預期錯誤: {e}") + + if successful_builds: + print(f"✅ 成功構建 {len(successful_builds)} 個平台") + + # 如果只構建了當前平台,給出提示 + if len(successful_builds) == 1: + print("") + print("💡 注意:只成功構建了當前平台的二進制文件") + print(" 其他平台的構建失敗通常是因為缺少跨平台編譯工具鏈") + print(" 完整的多平台支援將在 GitHub Actions CI 中完成") + print(" 發佈到 PyPI 時會包含所有平台的二進制文件") + + return successful_builds + print("❌ 所有平台構建都失敗了") + return [] + + +def copy_multiplatform_artifacts( + project_root: Path, successful_builds: list, release: bool = True +): + """複製多平台構建產物到適當位置""" + print("📦 複製多平台構建產物...") + + src_tauri = project_root / "src-tauri" + build_type = "release" if release else "debug" + + # 創建目標目錄 + desktop_dir = project_root / "src" / "mcp_feedback_enhanced" / "desktop_release" + desktop_dir.mkdir(parents=True, exist_ok=True) + + # 定義平台到文件名的映射 + platform_mapping = { + "x86_64-pc-windows-msvc": "mcp-feedback-enhanced-desktop.exe", + "x86_64-apple-darwin": "mcp-feedback-enhanced-desktop-macos-intel", + "aarch64-apple-darwin": "mcp-feedback-enhanced-desktop-macos-arm64", + "x86_64-unknown-linux-gnu": "mcp-feedback-enhanced-desktop-linux", + } + + copied_files = [] + + for target, original_binary_name in successful_builds: + # 源文件路徑 + src_file = src_tauri / "target" / target / build_type / original_binary_name + + # 目標文件名 + dst_filename = platform_mapping.get(target, original_binary_name) + dst_file = desktop_dir / dst_filename + + if src_file.exists(): + shutil.copy2(src_file, dst_file) + # 設置執行權限(非 Windows) + # 0o755 權限是必要的,因為這些是可執行的二進制檔案 + if not dst_filename.endswith(".exe"): + os.chmod(dst_file, 0o755) # noqa: S103 + copied_files.append(dst_filename) + print(f"✅ 複製 {target} 二進制檔案: {src_file} -> {dst_file}") + else: + print(f"⚠️ 找不到 {target} 的二進制檔案: {src_file}") + + if not copied_files: + print("⚠️ 沒有找到可複製的二進制檔案") + return False + + # 創建 __init__.py 文件,讓 desktop 目錄成為 Python 包 + desktop_init = desktop_dir / "__init__.py" + if not desktop_init.exists(): + desktop_init.write_text('"""桌面應用程式二進制檔案"""', encoding="utf-8") + print(f"✅ 創建 __init__.py: {desktop_init}") + + print(f"✅ 成功複製 {len(copied_files)} 個平台的二進制檔案") + return True + + +def copy_desktop_python_module(project_root: Path): + """複製桌面應用 Python 模組到發佈位置""" + print("📦 複製桌面應用 Python 模組...") + + # 源路徑和目標路徑 + python_src = project_root / "src-tauri" / "python" / "mcp_feedback_enhanced_desktop" + python_dst = project_root / "src" / "mcp_feedback_enhanced" / "desktop_app" + + if not python_src.exists(): + print(f"⚠️ 源模組不存在: {python_src}") + return False + + # 如果目標目錄存在,先刪除 + if python_dst.exists(): + shutil.rmtree(python_dst) + print(f"🗑️ 清理舊的模組目錄: {python_dst}") + + # 複製模組 + shutil.copytree(python_src, python_dst) + print(f"✅ 複製桌面應用模組: {python_src} -> {python_dst}") + + return True + + +def main(): + """主函數""" + parser = argparse.ArgumentParser( + description="構建 MCP Feedback Enhanced 桌面應用程式", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +範例: + python scripts/build_desktop.py # 構建 Debug 版本 + python scripts/build_desktop.py --release # 構建 Release 版本 + python scripts/build_desktop.py --clean # 清理構建產物 + +構建完成後,可以使用以下命令測試: + python -m mcp_feedback_enhanced test --desktop + +或使用 Makefile: + make build-desktop # 構建 Debug 版本 + make build-desktop-release # 構建 Release 版本 + make test-desktop # 構建並測試 + """, + ) + parser.add_argument( + "--release", action="store_true", help="構建發布版本 (預設為 Debug)" + ) + parser.add_argument("--clean", action="store_true", help="清理構建產物") + + args = parser.parse_args() + + # 獲取專案根目錄 + project_root = Path(__file__).parent.parent.resolve() + print(f"專案根目錄: {project_root}") + + try: + # 清理構建產物(如果需要) + if args.clean: + clean_build_artifacts(project_root) + + # 檢查 Rust 環境 + if not check_rust_environment(): + sys.exit(1) + + # 安裝跨平台編譯 targets + install_rust_targets() + + # 構建 Rust 擴展 + build_rust_extension(project_root, args.release) + + # 構建多平台 Tauri 應用程式 + successful_builds = build_tauri_app_multiplatform(project_root, args.release) + + if not successful_builds: + print("❌ 沒有成功構建任何平台") + sys.exit(1) + + # 複製多平台構建產物 + if not copy_multiplatform_artifacts( + project_root, successful_builds, args.release + ): + print("⚠️ 構建產物複製失敗,但 Rust 編譯成功") + return + + # 複製桌面應用 Python 模組 + if not copy_desktop_python_module(project_root): + print("⚠️ 桌面應用模組複製失敗") + return + + print("🎉 多平台桌面應用程式構建完成!") + print("") + print("📍 構建產物位置:") + print(" 多平台二進制檔案: src/mcp_feedback_enhanced/desktop_release/") + print(" 桌面應用模組: src/mcp_feedback_enhanced/desktop_app/") + print(" 開發環境模組: src-tauri/python/mcp_feedback_enhanced_desktop/") + print("") + print("🌍 支援的平台:") + for target, _ in successful_builds: + print(f" ✅ {target}") + print("") + print("🚀 下一步:") + print(" 測試桌面應用程式: python -m mcp_feedback_enhanced test --desktop") + print(" 或使用 Makefile: make test-desktop") + print(" 構建發布包: make build-all") + + except Exception as e: + print(f"❌ 構建失敗: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..b485bd5 --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "mcp-feedback-enhanced-desktop" +version = "2.4.3" +edition = "2021" +rust-version = "1.70" +description = "Desktop application for MCP Feedback Enhanced using Tauri" +authors = ["Minidoracat "] +license = "MIT" + +# 設置 crate 類型為 cdylib,用於 Python 擴展 +[lib] +name = "mcp_feedback_enhanced_desktop_lib" +crate-type = ["cdylib"] + +# 二進制目標 +[[bin]] +name = "mcp-feedback-enhanced-desktop" +path = "src/main.rs" + +[dependencies] +# Tauri 核心依賴 +tauri = { version = "2.2", features = ["custom-protocol"] } +tauri-plugin-shell = "2.2" + +# PyO3 用於 Python 綁定 +pyo3 = { version = "0.22", features = ["extension-module"] } + +# 序列化支援 +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +# 異步運行時 +tokio = { version = "1.0", features = ["full"] } + +# 日誌記錄 +log = "0.4" +env_logger = "0.11" + +[build-dependencies] +tauri-build = { version = "2.2", features = [] } + +# 開發配置文件 +[profile.dev] +incremental = true +debug = true + +# 發布配置文件 +[profile.release] +codegen-units = 1 +lto = true +opt-level = "s" +panic = "abort" +strip = true + +# 專用於打包的配置文件 +[profile.bundle-dev] +inherits = "dev" + +[profile.bundle-release] +inherits = "release" diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/generate_icons.py b/src-tauri/generate_icons.py new file mode 100644 index 0000000..cd2af8f --- /dev/null +++ b/src-tauri/generate_icons.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +""" +生成基本的應用程式圖標 + +這個腳本會生成 Tauri 應用程式所需的基本圖標文件。 +在實際部署中,應該使用專業的圖標設計。 +""" + +import os + +from PIL import Image, ImageDraw + + +def create_simple_icon(size, output_path): + """創建簡單的圖標""" + # 創建圖像 + img = Image.new("RGBA", (size, size), (0, 0, 0, 0)) + draw = ImageDraw.Draw(img) + + # 繪製簡單的圖標 - 一個帶邊框的圓形 + margin = size // 8 + draw.ellipse( + [margin, margin, size - margin, size - margin], + fill=(52, 152, 219, 255), + outline=(41, 128, 185, 255), + width=2, + ) + + # 在中心繪製 "MCP" 文字 + try: + # 嘗試使用系統字體 + from PIL import ImageFont + + font_size = size // 4 + try: + font = ImageFont.truetype("arial.ttf", font_size) + except: + font = ImageFont.load_default() + except ImportError: + font = None + + text = "MCP" + if font: + bbox = draw.textbbox((0, 0), text, font=font) + text_width = bbox[2] - bbox[0] + text_height = bbox[3] - bbox[1] + else: + text_width = len(text) * (size // 8) + text_height = size // 6 + + text_x = (size - text_width) // 2 + text_y = (size - text_height) // 2 + + draw.text((text_x, text_y), text, fill=(255, 255, 255, 255), font=font) + + # 保存圖像 + img.save(output_path) + print(f"已生成圖標: {output_path}") + + +def main(): + """主函數""" + icons_dir = "icons" + os.makedirs(icons_dir, exist_ok=True) + + # 生成不同尺寸的 PNG 圖標 + sizes = [32, 128, 256] + for size in sizes: + if size == 128: + # 生成普通和 2x 版本 + create_simple_icon(size, f"{icons_dir}/{size}x{size}.png") + create_simple_icon(size * 2, f"{icons_dir}/{size}x{size}@2x.png") + else: + create_simple_icon(size, f"{icons_dir}/{size}x{size}.png") + + # 為 Windows 創建 ICO 文件 + try: + img_256 = Image.open(f"{icons_dir}/256x256.png") + img_256.save( + f"{icons_dir}/icon.ico", + format="ICO", + sizes=[(256, 256), (128, 128), (64, 64), (32, 32), (16, 16)], + ) + print(f"已生成 Windows 圖標: {icons_dir}/icon.ico") + except Exception as e: + print(f"生成 ICO 文件失敗: {e}") + + # 為 macOS 創建 ICNS 文件(需要額外工具) + print("注意:macOS ICNS 文件需要使用專門的工具生成") + print("可以使用在線工具或 iconutil 命令將 PNG 轉換為 ICNS") + + print("圖標生成完成!") + + +if __name__ == "__main__": + try: + main() + except ImportError: + print("錯誤:需要安裝 Pillow 庫") + print("請運行:pip install Pillow") + except Exception as e: + print(f"生成圖標時發生錯誤: {e}") diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 0000000..fc9a86d Binary files /dev/null and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..5db972b Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/256x256.png b/src-tauri/icons/256x256.png new file mode 100644 index 0000000..5db972b Binary files /dev/null and b/src-tauri/icons/256x256.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 0000000..382c39c Binary files /dev/null and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 0000000..e7ddd88 Binary files /dev/null and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000..e7ddd88 Binary files /dev/null and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/pyproject.toml b/src-tauri/pyproject.toml new file mode 100644 index 0000000..5ddd3d6 --- /dev/null +++ b/src-tauri/pyproject.toml @@ -0,0 +1,39 @@ +[project] +name = "mcp-feedback-enhanced-desktop" +version = "2.4.3" +description = "Desktop application extension for MCP Feedback Enhanced" +requires-python = ">=3.11" +dependencies = [ + "mcp-feedback-enhanced>=2.4.3" +] + +[project.entry-points.pytauri] +ext_mod = "mcp_feedback_enhanced_desktop.ext_mod" + +[build-system] +requires = [ + "setuptools>=61", + "setuptools-rust>=1.11.1", + "maturin>=1.8.7" +] +build-backend = "setuptools.build_meta" + +# Maturin 配置 +[tool.maturin] +# Python 源碼目錄 +python-source = "python" +# 模組名稱 +module-name = "mcp_feedback_enhanced_desktop.ext_mod" +# 必要的功能特性 +features = ["pyo3/extension-module", "tauri/custom-protocol"] +# 使用 Git 作為 sdist 生成器 +sdist-generator = "git" +# 包含前端資源 +include = [ + { path = "../src/mcp_feedback_enhanced/web/static/**/*", format = "sdist" } +] + +# 支援 Python 穩定 ABI +[tool.maturin.abi3] +enabled = true +minimum = "3.11" diff --git a/src-tauri/python/mcp_feedback_enhanced_desktop/__init__.py b/src-tauri/python/mcp_feedback_enhanced_desktop/__init__.py new file mode 100644 index 0000000..01a63df --- /dev/null +++ b/src-tauri/python/mcp_feedback_enhanced_desktop/__init__.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +""" +MCP Feedback Enhanced Desktop Application +========================================= + +基於 Tauri 的桌面應用程式包裝器,為 MCP Feedback Enhanced 提供原生桌面體驗。 + +主要功能: +- 原生桌面應用程式界面 +- 整合現有的 Web UI 功能 +- 跨平台支援(Windows、macOS、Linux) +- 無需瀏覽器的獨立運行環境 + +作者: Minidoracat +版本: 2.4.3 +""" + +__version__ = "2.4.3" +__author__ = "Minidoracat" +__email__ = "minidora0702@gmail.com" + +from .desktop_app import DesktopApp, launch_desktop_app + + +__all__ = [ + "DesktopApp", + "__author__", + "__version__", + "launch_desktop_app", +] diff --git a/src-tauri/python/mcp_feedback_enhanced_desktop/desktop_app.py b/src-tauri/python/mcp_feedback_enhanced_desktop/desktop_app.py new file mode 100644 index 0000000..4f67472 --- /dev/null +++ b/src-tauri/python/mcp_feedback_enhanced_desktop/desktop_app.py @@ -0,0 +1,334 @@ +#!/usr/bin/env python3 +""" +桌面應用程式主要模組 + +此模組提供桌面應用程式的核心功能,包括: +- 桌面模式檢測 +- Tauri 應用程式啟動 +- 與現有 Web UI 的整合 +""" + +import asyncio +import os +import sys +import time + + +# 導入現有的 MCP Feedback Enhanced 模組 +try: + from mcp_feedback_enhanced.debug import server_debug_log as debug_log + from mcp_feedback_enhanced.web.main import WebUIManager, get_web_ui_manager +except ImportError as e: + print(f"無法導入 MCP Feedback Enhanced 模組: {e}") + sys.exit(1) + + +class DesktopApp: + """桌面應用程式管理器""" + + def __init__(self): + self.web_manager: WebUIManager | None = None + self.desktop_mode = False + self.app_handle = None + + def set_desktop_mode(self, enabled: bool = True): + """設置桌面模式""" + self.desktop_mode = enabled + if enabled: + # 設置環境變數,防止開啟瀏覽器 + os.environ["MCP_DESKTOP_MODE"] = "true" + debug_log("桌面模式已啟用,將禁止開啟瀏覽器") + else: + os.environ.pop("MCP_DESKTOP_MODE", None) + debug_log("桌面模式已禁用") + + def is_desktop_mode(self) -> bool: + """檢查是否為桌面模式""" + return ( + self.desktop_mode + or os.environ.get("MCP_DESKTOP_MODE", "").lower() == "true" + ) + + async def start_web_backend(self) -> str: + """啟動 Web 後端服務""" + debug_log("啟動 Web 後端服務...") + + # 獲取 Web UI 管理器 + self.web_manager = get_web_ui_manager() + + # 設置桌面模式,禁止自動開啟瀏覽器 + self.set_desktop_mode(True) + + # 啟動服務器 + if ( + self.web_manager.server_thread is None + or not self.web_manager.server_thread.is_alive() + ): + self.web_manager.start_server() + + # 等待服務器啟動 + max_wait = 10 # 最多等待 10 秒 + wait_count = 0 + while wait_count < max_wait: + if ( + self.web_manager.server_thread + and self.web_manager.server_thread.is_alive() + ): + break + await asyncio.sleep(0.5) + wait_count += 0.5 + + if not ( + self.web_manager.server_thread and self.web_manager.server_thread.is_alive() + ): + raise RuntimeError("Web 服務器啟動失敗") + + server_url = self.web_manager.get_server_url() + debug_log(f"Web 後端服務已啟動: {server_url}") + return server_url + + def create_test_session(self): + """創建測試會話""" + if not self.web_manager: + raise RuntimeError("Web 管理器未初始化") + + import tempfile + + with tempfile.TemporaryDirectory() as temp_dir: + session_id = self.web_manager.create_session( + temp_dir, "桌面應用程式測試 - 驗證 Tauri 整合功能" + ) + debug_log(f"測試會話已創建: {session_id}") + return session_id + + async def launch_tauri_app(self, server_url: str): + """啟動 Tauri 桌面應用程式""" + debug_log("正在啟動 Tauri 桌面視窗...") + + import os + import subprocess + from pathlib import Path + + # 找到 Tauri 可執行檔案 + # 首先嘗試從打包後的位置找(PyPI 安裝後的位置) + try: + from mcp_feedback_enhanced.desktop_release import __file__ as desktop_init + + desktop_dir = Path(desktop_init).parent + + # 根據平台選擇對應的二進制文件 + import platform + + system = platform.system().lower() + machine = platform.machine().lower() + + # 定義平台到二進制文件的映射 + if system == "windows": + tauri_exe = desktop_dir / "mcp-feedback-enhanced-desktop.exe" + elif system == "darwin": # macOS + # 檢測 Apple Silicon 或 Intel + if machine in ["arm64", "aarch64"]: + tauri_exe = ( + desktop_dir / "mcp-feedback-enhanced-desktop-macos-arm64" + ) + else: + tauri_exe = ( + desktop_dir / "mcp-feedback-enhanced-desktop-macos-intel" + ) + elif system == "linux": + tauri_exe = desktop_dir / "mcp-feedback-enhanced-desktop-linux" + else: + # 回退到通用名稱 + tauri_exe = desktop_dir / "mcp-feedback-enhanced-desktop" + + if tauri_exe.exists(): + debug_log(f"找到打包後的 Tauri 可執行檔案: {tauri_exe}") + else: + # 嘗試回退選項 + fallback_files = [ + desktop_dir / "mcp-feedback-enhanced-desktop.exe", + desktop_dir / "mcp-feedback-enhanced-desktop-macos-intel", + desktop_dir / "mcp-feedback-enhanced-desktop-macos-arm64", + desktop_dir / "mcp-feedback-enhanced-desktop-linux", + desktop_dir / "mcp-feedback-enhanced-desktop", + ] + + for fallback in fallback_files: + if fallback.exists(): + tauri_exe = fallback + debug_log(f"使用回退的可執行檔案: {tauri_exe}") + break + else: + raise FileNotFoundError( + f"找不到任何可執行檔案,檢查的路徑: {tauri_exe}" + ) + + except (ImportError, FileNotFoundError): + # 回退到開發環境路徑 + debug_log("未找到打包後的可執行檔案,嘗試開發環境路徑...") + project_root = Path(__file__).parent.parent.parent.parent + tauri_exe = ( + project_root + / "src-tauri" + / "target" + / "debug" + / "mcp-feedback-enhanced-desktop.exe" + ) + + if not tauri_exe.exists(): + # 嘗試其他可能的路徑 + tauri_exe = ( + project_root + / "src-tauri" + / "target" + / "debug" + / "mcp-feedback-enhanced-desktop" + ) + + if not tauri_exe.exists(): + # 嘗試 release 版本 + tauri_exe = ( + project_root + / "src-tauri" + / "target" + / "release" + / "mcp-feedback-enhanced-desktop.exe" + ) + if not tauri_exe.exists(): + tauri_exe = ( + project_root + / "src-tauri" + / "target" + / "release" + / "mcp-feedback-enhanced-desktop" + ) + + if not tauri_exe.exists(): + raise FileNotFoundError( + "找不到 Tauri 可執行檔案,已嘗試的路徑包括開發和發布目錄" + ) from None + + debug_log(f"找到 Tauri 可執行檔案: {tauri_exe}") + + # 設置環境變數 + env = os.environ.copy() + env["MCP_DESKTOP_MODE"] = "true" + env["MCP_WEB_URL"] = server_url + + # 啟動 Tauri 應用程式 + try: + # Windows 下隱藏控制台視窗 + creation_flags = 0 + if os.name == "nt": + creation_flags = subprocess.CREATE_NO_WINDOW + + self.app_handle = subprocess.Popen( + [str(tauri_exe)], + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + creationflags=creation_flags, + ) + debug_log("Tauri 桌面應用程式已啟動") + + # 等待一下確保應用程式啟動 + await asyncio.sleep(2) + + except Exception as e: + debug_log(f"啟動 Tauri 應用程式失敗: {e}") + raise + + def stop(self): + """停止桌面應用程式""" + debug_log("正在停止桌面應用程式...") + + # 停止 Tauri 應用程式 + if self.app_handle: + try: + self.app_handle.terminate() + self.app_handle.wait(timeout=5) + debug_log("Tauri 應用程式已停止") + except Exception as e: + debug_log(f"停止 Tauri 應用程式時發生錯誤: {e}") + try: + self.app_handle.kill() + except: + pass + finally: + self.app_handle = None + + if self.web_manager: + # 注意:不停止 Web 服務器,保持持久性 + debug_log("Web 服務器保持運行狀態") + + # 注意:不清除桌面模式設置,保持 MCP_DESKTOP_MODE 環境變數 + # 這樣下次 MCP 調用時仍然會啟動桌面應用程式 + # self.set_desktop_mode(False) # 註釋掉這行 + debug_log("桌面應用程式已停止") + + +async def launch_desktop_app(test_mode: bool = False) -> DesktopApp: + """啟動桌面應用程式 + + Args: + test_mode: 是否為測試模式,測試模式下會創建測試會話 + """ + debug_log("正在啟動桌面應用程式...") + + app = DesktopApp() + + try: + # 啟動 Web 後端 + server_url = await app.start_web_backend() + + if test_mode: + # 測試模式:創建測試會話 + debug_log("測試模式:創建測試會話") + app.create_test_session() + else: + # MCP 調用模式:使用現有會話 + debug_log("MCP 調用模式:使用現有 MCP 會話,不創建新的測試會話") + + # 啟動 Tauri 桌面應用程式 + await app.launch_tauri_app(server_url) + + debug_log(f"桌面應用程式已啟動,後端服務: {server_url}") + return app + + except Exception as e: + debug_log(f"桌面應用程式啟動失敗: {e}") + app.stop() + raise + + +def run_desktop_app(): + """同步方式運行桌面應用程式""" + try: + # 設置事件循環策略(Windows) + if sys.platform == "win32": + asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + + # 運行應用程式 + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + app = loop.run_until_complete(launch_desktop_app()) + + # 保持應用程式運行 + debug_log("桌面應用程式正在運行,按 Ctrl+C 停止...") + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + debug_log("收到停止信號...") + finally: + app.stop() + loop.close() + + except Exception as e: + print(f"桌面應用程式運行失敗: {e}") + sys.exit(1) + + +if __name__ == "__main__": + run_desktop_app() diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 0000000..2b55b9f --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,132 @@ +use pyo3::prelude::*; +use tauri::{Builder, Context, Manager}; +use std::sync::Mutex; + +// 全局狀態管理 +static APP_STATE: Mutex> = Mutex::new(None); + +/// Tauri 應用程式狀態 +#[derive(Default)] +struct AppState { + web_url: String, + desktop_mode: bool, +} + +/// 生成 Tauri 上下文 +pub fn tauri_generate_context() -> Context { + tauri::generate_context!() +} + +/// 創建 Tauri 應用程式構建器 +pub fn create_tauri_builder() -> Builder { + Builder::default() + .plugin(tauri_plugin_shell::init()) + .manage(AppState::default()) + .setup(|app| { + // 儲存應用程式句柄到全局狀態 + { + let mut state = APP_STATE.lock().unwrap(); + *state = Some(app.handle().clone()); + } + + // 設置應用程式狀態 + let _app_state = app.state::(); + { + // 這裡可以設置初始狀態 + } + + println!("Tauri 應用程式已初始化"); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + get_web_url, + set_web_url, + is_desktop_mode, + set_desktop_mode + ]) +} + +/// 獲取 Web URL +#[tauri::command] +fn get_web_url(state: tauri::State) -> String { + state.web_url.clone() +} + +/// 設置 Web URL +#[tauri::command] +fn set_web_url(url: String, _state: tauri::State) { + // 注意:這裡需要使用內部可變性,但 tauri::State 不支援 + // 實際實現中可能需要使用 Mutex 或其他同步原語 + println!("設置 Web URL: {}", url); +} + +/// 檢查是否為桌面模式 +#[tauri::command] +fn is_desktop_mode(state: tauri::State) -> bool { + state.desktop_mode +} + +/// 設置桌面模式 +#[tauri::command] +fn set_desktop_mode(enabled: bool, _state: tauri::State) { + println!("設置桌面模式: {}", enabled); +} + +/// PyO3 模組定義 +#[pymodule] +#[pyo3(name = "ext_mod")] +pub mod ext_mod { + use super::*; + + #[pymodule_init] + fn init(module: &Bound<'_, PyModule>) -> PyResult<()> { + // 註冊 context_factory 函數 + module.add_function(wrap_pyfunction!(context_factory, module)?)?; + + // 註冊 builder_factory 函數 + module.add_function(wrap_pyfunction!(builder_factory, module)?)?; + + // 註冊 run_app 函數 + module.add_function(wrap_pyfunction!(run_app, module)?)?; + + Ok(()) + } + + /// 創建 Tauri 上下文的工廠函數 + #[pyfunction] + fn context_factory() -> PyResult { + // 返回序列化的上下文信息 + // 實際實現中,這裡應該返回可以被 Python 使用的上下文 + Ok("tauri_context".to_string()) + } + + /// 創建 Tauri 構建器的工廠函數 + #[pyfunction] + fn builder_factory() -> PyResult { + // 返回序列化的構建器信息 + // 實際實現中,這裡應該返回可以被 Python 使用的構建器 + Ok("tauri_builder".to_string()) + } + + /// 運行 Tauri 應用程式 + #[pyfunction] + fn run_app(web_url: String) -> PyResult { + println!("正在啟動 Tauri 應用程式,Web URL: {}", web_url); + + // 創建並運行 Tauri 應用程式 + let _builder = create_tauri_builder(); + let _context = tauri_generate_context(); + + // 在實際實現中,這裡需要處理異步運行 + // 目前返回成功狀態 + match std::thread::spawn(move || { + // 這裡應該運行 Tauri 應用程式 + // builder.run(context) + println!("Tauri 應用程式線程已啟動"); + 0 + }).join() { + Ok(code) => Ok(code), + Err(_) => Ok(1), + } + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..6a85df1 --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,79 @@ +// Prevents additional console window on Windows in both debug and release, DO NOT REMOVE!! +#![cfg_attr(target_os = "windows", windows_subsystem = "windows")] + +use tauri::{Builder, Manager}; +use std::sync::Mutex; + +// 全局狀態管理 +static APP_STATE: Mutex> = Mutex::new(None); + +/// Tauri 應用程式狀態 +#[derive(Default)] +struct AppState { + web_url: String, + desktop_mode: bool, +} + +/// 獲取 Web URL +#[tauri::command] +fn get_web_url(state: tauri::State) -> String { + state.web_url.clone() +} + +/// 設置 Web URL +#[tauri::command] +fn set_web_url(url: String, _state: tauri::State) { + println!("設置 Web URL: {}", url); +} + +/// 檢查是否為桌面模式 +#[tauri::command] +fn is_desktop_mode(state: tauri::State) -> bool { + state.desktop_mode +} + +/// 設置桌面模式 +#[tauri::command] +fn set_desktop_mode(enabled: bool, _state: tauri::State) { + println!("設置桌面模式: {}", enabled); +} + +fn main() { + // 初始化日誌 + env_logger::init(); + + println!("正在啟動 MCP Feedback Enhanced 桌面應用程式..."); + + // 創建 Tauri 應用程式 + Builder::default() + .plugin(tauri_plugin_shell::init()) + .manage(AppState::default()) + .setup(|app| { + // 儲存應用程式句柄到全局狀態 + { + let mut state = APP_STATE.lock().unwrap(); + *state = Some(app.handle().clone()); + } + + // 檢查是否有 MCP_WEB_URL 環境變數 + if let Ok(web_url) = std::env::var("MCP_WEB_URL") { + println!("檢測到 Web URL: {}", web_url); + + // 獲取主視窗並導航到 Web URL + if let Some(window) = app.get_webview_window("main") { + let _ = window.navigate(web_url.parse().unwrap()); + } + } + + println!("Tauri 應用程式已初始化"); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + get_web_url, + set_web_url, + is_desktop_mode, + set_desktop_mode + ]) + .run(tauri::generate_context!()) + .expect("運行 Tauri 應用程式時發生錯誤"); +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 0000000..6fbd9d4 --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,70 @@ +{ + "productName": "MCP Feedback Enhanced", + "version": "2.4.3", + "identifier": "com.minidoracat.mcp-feedback-enhanced", + "build": { + "frontendDist": "../src/mcp_feedback_enhanced/web/static", + "devUrl": "http://127.0.0.1:8765", + "beforeDevCommand": "", + "beforeBuildCommand": "" + }, + "app": { + "withGlobalTauri": true, + "windows": [ + { + "title": "MCP Feedback Enhanced", + "width": 1200, + "height": 800, + "minWidth": 800, + "minHeight": 600, + "resizable": true, + "fullscreen": false, + "decorations": true, + "alwaysOnTop": false, + "skipTaskbar": false, + "center": true, + "url": "index.html" + } + ], + "security": { + "csp": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' ws: wss: http: https:; font-src 'self' data:;" + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "resources": [], + "externalBin": [], + "copyright": "Copyright © 2024 Minidoracat", + "category": "DeveloperTool", + "shortDescription": "Enhanced MCP server for interactive user feedback", + "longDescription": "An enhanced MCP server that provides interactive user feedback functionality for AI-assisted development, featuring Web UI with intelligent environment detection.", + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + }, + "macOS": { + "frameworks": [], + "minimumSystemVersion": "10.13", + "exceptionDomain": "" + }, + "linux": { + "deb": { + "depends": [] + } + } + }, + "plugins": { + "shell": { + "open": true + } + } +} diff --git a/src/mcp_feedback_enhanced/__main__.py b/src/mcp_feedback_enhanced/__main__.py index 27f33be..4a2cff4 100644 --- a/src/mcp_feedback_enhanced/__main__.py +++ b/src/mcp_feedback_enhanced/__main__.py @@ -47,6 +47,9 @@ def main(): test_parser.add_argument( "--web", action="store_true", help="測試 Web UI (自動持續運行)" ) + test_parser.add_argument( + "--desktop", action="store_true", help="啟動桌面應用程式模式" + ) test_parser.add_argument( "--timeout", type=int, default=60, help="測試超時時間 (秒)" ) @@ -100,10 +103,16 @@ def run_tests(args): success = test_web_ui_simple() if not success: sys.exit(1) + elif args.desktop: + print("🖥️ 啟動桌面應用程式...") + success = test_desktop_app() + if not success: + sys.exit(1) else: print("❌ 測試功能已簡化") print("💡 可用的測試選項:") print(" --web 測試 Web UI") + print(" --desktop 啟動桌面應用程式") print("💡 對於開發者:使用 'uv run pytest' 執行完整測試") sys.exit(1) @@ -185,6 +194,117 @@ def test_web_ui_simple(): os.environ.pop("MCP_WEB_PORT", None) +def test_desktop_app(): + """測試桌面應用程式""" + try: + print("🔧 檢查桌面應用程式依賴...") + + # 檢查是否有 Tauri 桌面模組 + try: + import os + import sys + + # 嘗試導入桌面應用程式模組 + def import_desktop_app(): + # 首先嘗試從發佈包位置導入 + try: + from .desktop_app import launch_desktop_app as desktop_func + + print("✅ 找到發佈包中的桌面應用程式模組") + return desktop_func + except ImportError: + pass + + # 回退到開發環境路徑 + tauri_python_path = os.path.join( + os.path.dirname(__file__), "..", "..", "src-tauri", "python" + ) + if os.path.exists(tauri_python_path): + sys.path.insert(0, tauri_python_path) + print(f"✅ 找到 Tauri Python 模組路徑: {tauri_python_path}") + try: + from mcp_feedback_enhanced_desktop import ( # type: ignore + launch_desktop_app as dev_func, + ) + + return dev_func + except ImportError: + print("❌ 無法從開發環境路徑導入桌面應用程式模組") + return None + else: + print(f"⚠️ Tauri Python 模組路徑不存在: {tauri_python_path}") + print("💡 請確保已正確建立 PyTauri 專案結構") + return None + + launch_desktop_app_func = import_desktop_app() + if launch_desktop_app_func is None: + return False + + print("✅ 桌面應用程式模組導入成功") + + except ImportError as e: + print(f"❌ 無法導入桌面應用程式模組: {e}") + print( + "💡 請確保已執行 'make build-desktop' 或 'python scripts/build_desktop.py'" + ) + return False + + print("🚀 啟動桌面應用程式...") + + # 設置桌面模式環境變數 + os.environ["MCP_DESKTOP_MODE"] = "true" + + # 使用 asyncio 啟動桌面應用程式 + if sys.platform == "win32": + asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy()) + + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + try: + # 使用 WebUIManager 來管理桌面應用實例 + from .web.main import get_web_ui_manager + + manager = get_web_ui_manager() + + # 啟動桌面應用並保存實例到 manager + app = loop.run_until_complete(launch_desktop_app_func(test_mode=True)) + manager.desktop_app_instance = app + + print("✅ 桌面應用程式啟動成功") + print("💡 桌面應用程式正在運行,按 Ctrl+C 停止...") + + # 保持應用程式運行 + try: + while True: + import time + + time.sleep(1) + except KeyboardInterrupt: + print("\n🛑 停止桌面應用程式...") + app.stop() + return True + + except Exception as e: + print(f"❌ 桌面應用程式啟動失敗: {e}") + import traceback + + traceback.print_exc() + return False + finally: + loop.close() + + except Exception as e: + print(f"❌ 桌面應用程式測試失敗: {e}") + import traceback + + traceback.print_exc() + return False + finally: + # 清理環境變數 + os.environ.pop("MCP_DESKTOP_MODE", None) + + async def wait_for_process(process): """等待進程結束""" try: diff --git a/src/mcp_feedback_enhanced/desktop_release/__init__.py b/src/mcp_feedback_enhanced/desktop_release/__init__.py new file mode 100644 index 0000000..ee2044b --- /dev/null +++ b/src/mcp_feedback_enhanced/desktop_release/__init__.py @@ -0,0 +1 @@ +"桌面應用程式二進制檔案" diff --git a/src/mcp_feedback_enhanced/server.py b/src/mcp_feedback_enhanced/server.py index a989cb6..dd8af1d 100644 --- a/src/mcp_feedback_enhanced/server.py +++ b/src/mcp_feedback_enhanced/server.py @@ -603,6 +603,14 @@ def main(): # 檢查是否啟用調試模式 debug_enabled = os.getenv("MCP_DEBUG", "").lower() in ("true", "1", "yes", "on") + # 檢查是否啟用桌面模式 + desktop_mode = os.getenv("MCP_DESKTOP_MODE", "").lower() in ( + "true", + "1", + "yes", + "on", + ) + if debug_enabled: debug_log("🚀 啟動互動式回饋收集 MCP 服務器") debug_log(f" 服務器名稱: {SERVER_NAME}") @@ -611,6 +619,7 @@ def main(): debug_log(f" 編碼初始化: {'成功' if _encoding_initialized else '失敗'}") debug_log(f" 遠端環境: {is_remote_environment()}") debug_log(f" WSL 環境: {is_wsl_environment()}") + debug_log(f" 桌面模式: {'啟用' if desktop_mode else '禁用'}") debug_log(" 介面類型: Web UI") debug_log(" 等待來自 AI 助手的調用...") debug_log("準備啟動 MCP 伺服器...") diff --git a/src/mcp_feedback_enhanced/web/main.py b/src/mcp_feedback_enhanced/web/main.py index f41ac0f..43ad4a0 100644 --- a/src/mcp_feedback_enhanced/web/main.py +++ b/src/mcp_feedback_enhanced/web/main.py @@ -115,6 +115,7 @@ class WebUIManager: self.server_thread: threading.Thread | None = None self.server_process = None + self.desktop_app_instance: Any = None # 桌面應用實例引用 # 初始化標記,用於追蹤異步初始化狀態 self._initialization_complete = False @@ -563,10 +564,15 @@ class WebUIManager: """智能開啟瀏覽器 - 檢測是否已有活躍標籤頁 Returns: - bool: True 表示檢測到活躍標籤頁,False 表示開啟了新視窗 + bool: True 表示檢測到活躍標籤頁或桌面模式,False 表示開啟了新視窗 """ try: + # 檢查是否為桌面模式 + if os.environ.get("MCP_DESKTOP_MODE", "").lower() == "true": + debug_log("檢測到桌面模式,跳過瀏覽器開啟") + return True + # 檢查是否有活躍標籤頁 has_active_tabs = await self._check_active_tabs() @@ -585,6 +591,83 @@ class WebUIManager: self.open_browser(url) return False + async def launch_desktop_app(self, url: str) -> bool: + """ + 啟動桌面應用程式 + + Args: + url: Web 服務 URL + + Returns: + bool: True 表示成功啟動桌面應用程式 + """ + try: + # 嘗試導入桌面應用程式模組 + def import_desktop_app(): + # 首先嘗試從發佈包位置導入 + try: + from mcp_feedback_enhanced.desktop_app import ( + launch_desktop_app as desktop_func, + ) + + debug_log("使用發佈包中的桌面應用程式模組") + return desktop_func + except ImportError: + pass + + # 回退到開發環境路徑 + import sys + + project_root = os.path.dirname( + os.path.dirname(os.path.dirname(__file__)) + ) + desktop_module_path = os.path.join(project_root, "src-tauri", "python") + if desktop_module_path not in sys.path: + sys.path.insert(0, desktop_module_path) + try: + from mcp_feedback_enhanced_desktop import ( # type: ignore + launch_desktop_app as dev_func, + ) + + debug_log("使用開發環境桌面應用程式模組") + return dev_func + except ImportError: + debug_log("無法從開發環境路徑導入桌面應用程式模組") + raise + + launch_desktop_app_func = import_desktop_app() + + # 啟動桌面應用程式 + desktop_app = await launch_desktop_app_func() + # 保存桌面應用實例引用,以便後續控制 + self.desktop_app_instance = desktop_app + debug_log("桌面應用程式啟動成功") + return True + + except ImportError as e: + debug_log(f"無法導入桌面應用程式模組: {e}") + debug_log("回退到瀏覽器模式...") + self.open_browser(url) + return False + except Exception as e: + debug_log(f"桌面應用程式啟動失敗: {e}") + debug_log("回退到瀏覽器模式...") + self.open_browser(url) + return False + + def close_desktop_app(self): + """關閉桌面應用程式""" + if self.desktop_app_instance: + try: + debug_log("正在關閉桌面應用程式...") + self.desktop_app_instance.stop() + self.desktop_app_instance = None + debug_log("桌面應用程式已關閉") + except Exception as e: + debug_log(f"關閉桌面應用程式失敗: {e}") + else: + debug_log("沒有活躍的桌面應用程式實例") + async def notify_session_update(self, session): """向活躍標籤頁發送會話更新通知""" try: @@ -1012,9 +1095,19 @@ async def launch_web_feedback_ui( if manager.server_thread is None or not manager.server_thread.is_alive(): manager.start_server() - # 使用根路徑 URL 並智能開啟瀏覽器 + # 檢查是否為桌面模式 + desktop_mode = os.environ.get("MCP_DESKTOP_MODE", "").lower() == "true" + + # 使用根路徑 URL feedback_url = manager.get_server_url() # 直接使用根路徑 - has_active_tabs = await manager.smart_open_browser(feedback_url) + + if desktop_mode: + # 桌面模式:啟動桌面應用程式 + debug_log("檢測到桌面模式,啟動桌面應用程式...") + has_active_tabs = await manager.launch_desktop_app(feedback_url) + else: + # Web 模式:智能開啟瀏覽器 + has_active_tabs = await manager.smart_open_browser(feedback_url) debug_log(f"[DEBUG] 服務器地址: {feedback_url}") diff --git a/src/mcp_feedback_enhanced/web/models/feedback_session.py b/src/mcp_feedback_enhanced/web/models/feedback_session.py index f70aa8e..7aa915b 100644 --- a/src/mcp_feedback_enhanced/web/models/feedback_session.py +++ b/src/mcp_feedback_enhanced/web/models/feedback_session.py @@ -421,6 +421,23 @@ class WebFeedbackSession: "status": self.status.value, } ) + + # 檢查是否為桌面模式,如果是則立即關閉桌面應用程式 + import os + + if os.environ.get("MCP_DESKTOP_MODE", "").lower() == "true": + debug_log("桌面模式:反饋提交後立即關閉桌面應用程式") + + # 立即關閉桌面應用程式,無延遲 + try: + from ..main import get_web_ui_manager + + manager = get_web_ui_manager() + manager.close_desktop_app() + debug_log("桌面應用程式立即關閉成功") + except Exception as close_error: + debug_log(f"立即關閉桌面應用程式失敗: {close_error}") + except Exception as e: debug_log(f"發送反饋確認失敗: {e}") diff --git a/src/mcp_feedback_enhanced/web/static/favicon.ico b/src/mcp_feedback_enhanced/web/static/favicon.ico new file mode 100644 index 0000000..9a0fc00 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/static/favicon.ico @@ -0,0 +1,2 @@ +# 這是一個佔位符文件,實際的 favicon 需要是二進制格式 +# 在實際部署中,應該使用真正的 .ico 文件 diff --git a/src/mcp_feedback_enhanced/web/static/index.html b/src/mcp_feedback_enhanced/web/static/index.html new file mode 100644 index 0000000..29c6f5a --- /dev/null +++ b/src/mcp_feedback_enhanced/web/static/index.html @@ -0,0 +1,158 @@ + + + + + + MCP Feedback Enhanced - 桌面版 + + + +
+ +
桌面版正在啟動...
+
+
正在連接到後端服務...
+
+
+ + + + diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index caadb01..d5f56e5 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -561,6 +561,10 @@ console.log('🔄 收到會話更新訊息:', data.session_info); this.handleSessionUpdated(data); break; + case 'desktop_close_request': + console.log('🖥️ 收到桌面關閉請求'); + this.handleDesktopCloseRequest(data); + break; } }; @@ -596,6 +600,34 @@ console.log('反饋已提交,頁面保持開啟狀態'); }; + /** + * 處理桌面關閉請求 + */ + FeedbackApp.prototype.handleDesktopCloseRequest = function(data) { + console.log('🖥️ 處理桌面關閉請求:', data.message); + + // 顯示關閉訊息 + const closeMessage = data.message || '正在關閉桌面應用程式...'; + window.MCPFeedback.Utils.showMessage(closeMessage, window.MCPFeedback.Utils.CONSTANTS.MESSAGE_INFO); + + // 檢查是否在 Tauri 環境中 + if (window.__TAURI__) { + console.log('🖥️ 檢測到 Tauri 環境,關閉桌面視窗'); + try { + // 使用 Tauri API 關閉視窗 + window.__TAURI__.window.getCurrent().close(); + } catch (error) { + console.error('關閉 Tauri 視窗失敗:', error); + // 備用方案:關閉瀏覽器視窗 + window.close(); + } + } else { + console.log('🖥️ 非 Tauri 環境,嘗試關閉瀏覽器視窗'); + // 在瀏覽器環境中嘗試關閉視窗 + window.close(); + } + }; + /** * 處理會話更新 */ diff --git a/src/mcp_feedback_enhanced/web/utils/browser.py b/src/mcp_feedback_enhanced/web/utils/browser.py index eb9b36f..66846ed 100644 --- a/src/mcp_feedback_enhanced/web/utils/browser.py +++ b/src/mcp_feedback_enhanced/web/utils/browser.py @@ -48,6 +48,18 @@ def is_wsl_environment() -> bool: return False +def is_desktop_mode() -> bool: + """ + 檢測是否為桌面模式 + + 當設置了 MCP_DESKTOP_MODE 環境變數時,禁止開啟瀏覽器 + + Returns: + bool: True 表示桌面模式,False 表示 Web 模式 + """ + return os.environ.get("MCP_DESKTOP_MODE", "").lower() == "true" + + def open_browser_in_wsl(url: str) -> None: """ 在 WSL 環境中開啟 Windows 瀏覽器 @@ -117,6 +129,11 @@ def smart_browser_open(url: str) -> None: Args: url: 要開啟的 URL """ + # 檢查是否為桌面模式 + if is_desktop_mode(): + debug_log("檢測到桌面模式,跳過瀏覽器開啟") + return + if is_wsl_environment(): debug_log("檢測到 WSL 環境,使用 WSL 專用瀏覽器啟動方式") open_browser_in_wsl(url)