新增 Tauri 桌面應用

This commit is contained in:
Minidoracat 2025-06-15 11:34:34 +08:00
parent f2ad48c3a5
commit e961a2c1c8
34 changed files with 2493 additions and 5 deletions

152
.github/workflows/build-desktop.yml vendored Normal file
View File

@ -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

View File

@ -16,6 +16,15 @@ on:
description: 'Custom version number (e.g., 2.5.0) - overrides version_type if provided' description: 'Custom version number (e.g., 2.5.0) - overrides version_type if provided'
required: false required: false
type: string type: string
include_desktop:
description: '是否包含桌面應用二進制文件'
required: true
default: true
type: boolean
desktop_build_run_id:
description: '桌面應用構建的 Run ID可選留空使用最新的成功構建'
required: false
type: string
jobs: jobs:
release: release:
@ -38,6 +47,9 @@ jobs:
- name: Set up Python - name: Set up Python
run: uv python install run: uv python install
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install dependencies - name: Install dependencies
run: | run: |
uv sync --dev uv sync --dev
@ -275,6 +287,101 @@ jobs:
- Auto-generated release from workflow" - Auto-generated release from workflow"
git tag "v${{ steps.bump_version.outputs.new }}" 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 - name: Build package
run: uv build run: uv build
@ -311,10 +418,32 @@ jobs:
echo "📦 Published to PyPI: https://pypi.org/project/mcp-feedback-enhanced/" 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 "🚀 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 "📝 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 ""
echo "✅ Next steps:" echo "✅ Next steps:"
echo " - Check the release on GitHub" echo " - Check the release on GitHub"
echo " - Verify the package on PyPI" echo " - Verify the package on PyPI"
echo " - Test installation with: uvx mcp-feedback-enhanced@v${{ steps.bump_version.outputs.new }}" 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 ""
echo "📋 Note: Make sure CHANGELOG files are updated for future releases" echo "📋 Note: Make sure CHANGELOG files are updated for future releases"

26
.gitignore vendored
View File

@ -63,3 +63,29 @@ ui_settings.json
.env .env
.env.local .env.local
.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

View File

@ -3,7 +3,7 @@
# Compatible with Windows PowerShell and Unix systems # Compatible with Windows PowerShell and Unix systems
# 兼容 Windows PowerShell 和 Unix 系統 # 兼容 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 help: ## Show this help message
@ -36,6 +36,13 @@ help: ## Show this help message
@echo " bump-major Bump major version" @echo " bump-major Bump major version"
@echo " ci Simulate CI pipeline locally" @echo " ci Simulate CI pipeline locally"
@echo " quick-check Quick check with auto-fix" @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 install: ## Install the package
@ -142,3 +149,35 @@ quick-check: lint-fix format type-check ## Quick check with auto-fix (recommende
# Windows PowerShell 專用命令 # Windows PowerShell 專用命令
ps-clean: ## PowerShell version of clean (Windows) 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 } }" 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!"

244
docs/DESKTOP_BUILD.md Normal file
View File

@ -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 架構
桌面應用會自動檢測運行平台並選擇對應的二進制文件。

122
docs/WORKFLOWS.md Normal file
View File

@ -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 版本發佈

View File

@ -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"]
}
}
}

View File

@ -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"]
}
}
}

View File

@ -45,12 +45,20 @@ mcp-feedback-enhanced = "mcp_feedback_enhanced.__main__:main"
interactive-feedback-mcp = "mcp_feedback_enhanced.__main__:main" interactive-feedback-mcp = "mcp_feedback_enhanced.__main__:main"
[build-system] [build-system]
requires = ["hatchling"] requires = [
"hatchling",
"maturin>=1.8.7",
"setuptools-rust>=1.11.1"
]
build-backend = "hatchling.build" build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel] [tool.hatch.build.targets.wheel]
packages = ["src/mcp_feedback_enhanced"] 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] [tool.uv]
dev-dependencies = [ dev-dependencies = [
"bump2version>=1.0.1", "bump2version>=1.0.1",
@ -61,6 +69,9 @@ dev-dependencies = [
"ruff>=0.11.0", "ruff>=0.11.0",
"mypy>=1.16.0", "mypy>=1.16.0",
"pre-commit>=4.0.0", "pre-commit>=4.0.0",
"maturin>=1.8.7",
"setuptools-rust>=1.11.1",
"pillow>=11.2.1",
] ]
# ===== Ruff 配置 ===== # ===== Ruff 配置 =====

437
scripts/build_desktop.py Normal file
View File

@ -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()

60
src-tauri/Cargo.toml Normal file
View File

@ -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 <minidora0702@gmail.com>"]
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"

3
src-tauri/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

102
src-tauri/generate_icons.py Normal file
View File

@ -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}")

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
src-tauri/icons/256x256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

39
src-tauri/pyproject.toml Normal file
View File

@ -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"

View File

@ -0,0 +1,30 @@
#!/usr/bin/env python3
"""
MCP Feedback Enhanced Desktop Application
=========================================
基於 Tauri 的桌面應用程式包裝器 MCP Feedback Enhanced 提供原生桌面體驗
主要功能
- 原生桌面應用程式界面
- 整合現有的 Web UI 功能
- 跨平台支援WindowsmacOSLinux
- 無需瀏覽器的獨立運行環境
作者: 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",
]

View File

@ -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()

132
src-tauri/src/lib.rs Normal file
View File

@ -0,0 +1,132 @@
use pyo3::prelude::*;
use tauri::{Builder, Context, Manager};
use std::sync::Mutex;
// 全局狀態管理
static APP_STATE: Mutex<Option<tauri::AppHandle>> = 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<tauri::Wry> {
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::<AppState>();
{
// 這裡可以設置初始狀態
}
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<AppState>) -> String {
state.web_url.clone()
}
/// 設置 Web URL
#[tauri::command]
fn set_web_url(url: String, _state: tauri::State<AppState>) {
// 注意:這裡需要使用內部可變性,但 tauri::State 不支援
// 實際實現中可能需要使用 Mutex 或其他同步原語
println!("設置 Web URL: {}", url);
}
/// 檢查是否為桌面模式
#[tauri::command]
fn is_desktop_mode(state: tauri::State<AppState>) -> bool {
state.desktop_mode
}
/// 設置桌面模式
#[tauri::command]
fn set_desktop_mode(enabled: bool, _state: tauri::State<AppState>) {
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<String> {
// 返回序列化的上下文信息
// 實際實現中,這裡應該返回可以被 Python 使用的上下文
Ok("tauri_context".to_string())
}
/// 創建 Tauri 構建器的工廠函數
#[pyfunction]
fn builder_factory() -> PyResult<String> {
// 返回序列化的構建器信息
// 實際實現中,這裡應該返回可以被 Python 使用的構建器
Ok("tauri_builder".to_string())
}
/// 運行 Tauri 應用程式
#[pyfunction]
fn run_app(web_url: String) -> PyResult<i32> {
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),
}
}
}

79
src-tauri/src/main.rs Normal file
View File

@ -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<Option<tauri::AppHandle>> = 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<AppState>) -> String {
state.web_url.clone()
}
/// 設置 Web URL
#[tauri::command]
fn set_web_url(url: String, _state: tauri::State<AppState>) {
println!("設置 Web URL: {}", url);
}
/// 檢查是否為桌面模式
#[tauri::command]
fn is_desktop_mode(state: tauri::State<AppState>) -> bool {
state.desktop_mode
}
/// 設置桌面模式
#[tauri::command]
fn set_desktop_mode(enabled: bool, _state: tauri::State<AppState>) {
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 應用程式時發生錯誤");
}

70
src-tauri/tauri.conf.json Normal file
View File

@ -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
}
}
}

View File

@ -47,6 +47,9 @@ def main():
test_parser.add_argument( test_parser.add_argument(
"--web", action="store_true", help="測試 Web UI (自動持續運行)" "--web", action="store_true", help="測試 Web UI (自動持續運行)"
) )
test_parser.add_argument(
"--desktop", action="store_true", help="啟動桌面應用程式模式"
)
test_parser.add_argument( test_parser.add_argument(
"--timeout", type=int, default=60, help="測試超時時間 (秒)" "--timeout", type=int, default=60, help="測試超時時間 (秒)"
) )
@ -100,10 +103,16 @@ def run_tests(args):
success = test_web_ui_simple() success = test_web_ui_simple()
if not success: if not success:
sys.exit(1) sys.exit(1)
elif args.desktop:
print("🖥️ 啟動桌面應用程式...")
success = test_desktop_app()
if not success:
sys.exit(1)
else: else:
print("❌ 測試功能已簡化") print("❌ 測試功能已簡化")
print("💡 可用的測試選項:") print("💡 可用的測試選項:")
print(" --web 測試 Web UI") print(" --web 測試 Web UI")
print(" --desktop 啟動桌面應用程式")
print("💡 對於開發者:使用 'uv run pytest' 執行完整測試") print("💡 對於開發者:使用 'uv run pytest' 執行完整測試")
sys.exit(1) sys.exit(1)
@ -185,6 +194,117 @@ def test_web_ui_simple():
os.environ.pop("MCP_WEB_PORT", None) 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): async def wait_for_process(process):
"""等待進程結束""" """等待進程結束"""
try: try:

View File

@ -0,0 +1 @@
"桌面應用程式二進制檔案"

View File

@ -603,6 +603,14 @@ def main():
# 檢查是否啟用調試模式 # 檢查是否啟用調試模式
debug_enabled = os.getenv("MCP_DEBUG", "").lower() in ("true", "1", "yes", "on") 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: if debug_enabled:
debug_log("🚀 啟動互動式回饋收集 MCP 服務器") debug_log("🚀 啟動互動式回饋收集 MCP 服務器")
debug_log(f" 服務器名稱: {SERVER_NAME}") debug_log(f" 服務器名稱: {SERVER_NAME}")
@ -611,6 +619,7 @@ def main():
debug_log(f" 編碼初始化: {'成功' if _encoding_initialized else '失敗'}") debug_log(f" 編碼初始化: {'成功' if _encoding_initialized else '失敗'}")
debug_log(f" 遠端環境: {is_remote_environment()}") debug_log(f" 遠端環境: {is_remote_environment()}")
debug_log(f" WSL 環境: {is_wsl_environment()}") debug_log(f" WSL 環境: {is_wsl_environment()}")
debug_log(f" 桌面模式: {'啟用' if desktop_mode else '禁用'}")
debug_log(" 介面類型: Web UI") debug_log(" 介面類型: Web UI")
debug_log(" 等待來自 AI 助手的調用...") debug_log(" 等待來自 AI 助手的調用...")
debug_log("準備啟動 MCP 伺服器...") debug_log("準備啟動 MCP 伺服器...")

View File

@ -115,6 +115,7 @@ class WebUIManager:
self.server_thread: threading.Thread | None = None self.server_thread: threading.Thread | None = None
self.server_process = None self.server_process = None
self.desktop_app_instance: Any = None # 桌面應用實例引用
# 初始化標記,用於追蹤異步初始化狀態 # 初始化標記,用於追蹤異步初始化狀態
self._initialization_complete = False self._initialization_complete = False
@ -563,10 +564,15 @@ class WebUIManager:
"""智能開啟瀏覽器 - 檢測是否已有活躍標籤頁 """智能開啟瀏覽器 - 檢測是否已有活躍標籤頁
Returns: Returns:
bool: True 表示檢測到活躍標籤頁False 表示開啟了新視窗 bool: True 表示檢測到活躍標籤頁或桌面模式False 表示開啟了新視窗
""" """
try: try:
# 檢查是否為桌面模式
if os.environ.get("MCP_DESKTOP_MODE", "").lower() == "true":
debug_log("檢測到桌面模式,跳過瀏覽器開啟")
return True
# 檢查是否有活躍標籤頁 # 檢查是否有活躍標籤頁
has_active_tabs = await self._check_active_tabs() has_active_tabs = await self._check_active_tabs()
@ -585,6 +591,83 @@ class WebUIManager:
self.open_browser(url) self.open_browser(url)
return False 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): async def notify_session_update(self, session):
"""向活躍標籤頁發送會話更新通知""" """向活躍標籤頁發送會話更新通知"""
try: try:
@ -1012,9 +1095,19 @@ async def launch_web_feedback_ui(
if manager.server_thread is None or not manager.server_thread.is_alive(): if manager.server_thread is None or not manager.server_thread.is_alive():
manager.start_server() manager.start_server()
# 使用根路徑 URL 並智能開啟瀏覽器 # 檢查是否為桌面模式
desktop_mode = os.environ.get("MCP_DESKTOP_MODE", "").lower() == "true"
# 使用根路徑 URL
feedback_url = manager.get_server_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}") debug_log(f"[DEBUG] 服務器地址: {feedback_url}")

View File

@ -421,6 +421,23 @@ class WebFeedbackSession:
"status": self.status.value, "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: except Exception as e:
debug_log(f"發送反饋確認失敗: {e}") debug_log(f"發送反饋確認失敗: {e}")

View File

@ -0,0 +1,2 @@
# 這是一個佔位符文件,實際的 favicon 需要是二進制格式
# 在實際部署中,應該使用真正的 .ico 文件

View File

@ -0,0 +1,158 @@
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MCP Feedback Enhanced - 桌面版</title>
<style>
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
color: white;
}
.loading-container {
text-align: center;
padding: 2rem;
}
.logo {
font-size: 2.5rem;
font-weight: bold;
margin-bottom: 1rem;
opacity: 0;
animation: fadeIn 1s ease-in-out forwards;
}
.subtitle {
font-size: 1.2rem;
margin-bottom: 2rem;
opacity: 0;
animation: fadeIn 1s ease-in-out 0.5s forwards;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid rgba(255, 255, 255, 0.3);
border-top: 4px solid white;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
opacity: 0;
animation: fadeIn 1s ease-in-out 1s forwards, spin 1s linear 1s infinite;
}
.status {
font-size: 1rem;
opacity: 0.8;
animation: fadeIn 1s ease-in-out 1.5s forwards;
}
@keyframes fadeIn {
to { opacity: 1; }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
color: #ff6b6b;
background: rgba(255, 107, 107, 0.1);
padding: 1rem;
border-radius: 8px;
margin-top: 1rem;
display: none;
}
</style>
</head>
<body>
<div class="loading-container">
<div class="logo">🖥️ MCP Feedback Enhanced</div>
<div class="subtitle">桌面版正在啟動...</div>
<div class="spinner"></div>
<div class="status" id="status">正在連接到後端服務...</div>
<div class="error" id="error"></div>
</div>
<script>
// 檢測是否在 Tauri 環境中
const isTauri = window.__TAURI__ !== undefined;
// 後端服務 URL
const backendUrl = 'http://127.0.0.1:8765';
// 狀態更新函數
function updateStatus(message) {
document.getElementById('status').textContent = message;
}
// 錯誤顯示函數
function showError(message) {
const errorDiv = document.getElementById('error');
errorDiv.textContent = message;
errorDiv.style.display = 'block';
}
// 檢查後端服務是否可用
async function checkBackendService() {
try {
updateStatus('正在檢查後端服務...');
const response = await fetch(backendUrl + '/health', {
method: 'GET',
timeout: 5000
});
if (response.ok) {
updateStatus('後端服務已就緒,正在載入界面...');
// 延遲一下再重定向,讓用戶看到狀態
setTimeout(() => {
window.location.href = backendUrl;
}, 1000);
} else {
throw new Error(`後端服務回應錯誤: ${response.status}`);
}
} catch (error) {
console.error('檢查後端服務失敗:', error);
updateStatus('正在重試連接...');
// 如果是網路錯誤,直接嘗試重定向
if (error.name === 'TypeError' || error.message.includes('fetch')) {
setTimeout(() => {
window.location.href = backendUrl;
}, 2000);
} else {
showError('無法連接到後端服務,請確保服務正在運行');
}
}
}
// 如果不是 Tauri 環境,直接重定向
if (!isTauri) {
updateStatus('正在重定向到 Web 界面...');
setTimeout(() => {
window.location.href = backendUrl;
}, 1000);
} else {
// Tauri 環境中,檢查後端服務
setTimeout(checkBackendService, 1000);
}
// 如果 5 秒後還沒有重定向,強制重定向
setTimeout(() => {
if (window.location.href.includes('index.html')) {
updateStatus('強制重定向到後端服務...');
window.location.href = backendUrl;
}
}, 5000);
</script>
</body>
</html>

View File

@ -561,6 +561,10 @@
console.log('🔄 收到會話更新訊息:', data.session_info); console.log('🔄 收到會話更新訊息:', data.session_info);
this.handleSessionUpdated(data); this.handleSessionUpdated(data);
break; break;
case 'desktop_close_request':
console.log('🖥️ 收到桌面關閉請求');
this.handleDesktopCloseRequest(data);
break;
} }
}; };
@ -596,6 +600,34 @@
console.log('反饋已提交,頁面保持開啟狀態'); 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();
}
};
/** /**
* 處理會話更新 * 處理會話更新
*/ */

View File

@ -48,6 +48,18 @@ def is_wsl_environment() -> bool:
return False 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: def open_browser_in_wsl(url: str) -> None:
""" """
WSL 環境中開啟 Windows 瀏覽器 WSL 環境中開啟 Windows 瀏覽器
@ -117,6 +129,11 @@ def smart_browser_open(url: str) -> None:
Args: Args:
url: 要開啟的 URL url: 要開啟的 URL
""" """
# 檢查是否為桌面模式
if is_desktop_mode():
debug_log("檢測到桌面模式,跳過瀏覽器開啟")
return
if is_wsl_environment(): if is_wsl_environment():
debug_log("檢測到 WSL 環境,使用 WSL 專用瀏覽器啟動方式") debug_log("檢測到 WSL 環境,使用 WSL 專用瀏覽器啟動方式")
open_browser_in_wsl(url) open_browser_in_wsl(url)