diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 5b243e9..e1c7529 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -19,44 +19,44 @@ jobs:
permissions:
contents: write
id-token: write
-
+
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
-
+
- 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: Configure Git
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
-
+
- name: Commit dependency changes if any
run: |
if [ -n "$(git status --porcelain)" ]; then
git add .
git commit -m "📦 Update dependencies" || true
fi
-
+
- name: Get current version
id: current_version
run: |
CURRENT_VERSION=$(grep '^version =' pyproject.toml | cut -d'"' -f2)
echo "current=$CURRENT_VERSION" >> $GITHUB_OUTPUT
echo "Current version: $CURRENT_VERSION"
-
+
- name: Bump version
id: bump_version
run: |
@@ -64,12 +64,12 @@ jobs:
NEW_VERSION=$(grep '^version =' pyproject.toml | cut -d'"' -f2)
echo "new=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "New version: $NEW_VERSION"
-
+
- name: Update __init__.py version
run: |
NEW_VERSION="${{ steps.bump_version.outputs.new }}"
sed -i "s/__version__ = \".*\"/__version__ = \"$NEW_VERSION\"/" src/mcp_feedback_enhanced/__init__.py
-
+
- name: Extract Release Highlights
id: extract_highlights
run: |
@@ -112,7 +112,7 @@ jobs:
echo "- 🚀 New features and improvements" > highlights.txt
echo "- 🐛 Bug fixes and optimizations" >> highlights.txt
fi
-
+
- name: Generate Release Body
id: release_body
run: |
@@ -173,7 +173,7 @@ jobs:
echo "**Release automatically generated from CHANGELOG system** 🤖" >> release_body.md
echo "Release body generated successfully"
-
+
- name: Verify CHANGELOG Files
run: |
NEW_VERSION="v${{ steps.bump_version.outputs.new }}"
@@ -227,7 +227,7 @@ jobs:
else
echo "✅ All CHANGELOG files verified successfully"
fi
-
+
- name: Commit version bump
run: |
git add .
@@ -236,24 +236,24 @@ jobs:
- Updated version to ${{ steps.bump_version.outputs.new }}
- Auto-generated release from simplified workflow"
git tag "v${{ steps.bump_version.outputs.new }}"
-
+
- name: Build package
run: uv build
-
+
- name: Check package
run: uv run twine check dist/*
-
+
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
-
+
- name: Push changes and tags
run: |
git push origin main
git push origin "v${{ steps.bump_version.outputs.new }}"
-
+
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
@@ -265,7 +265,7 @@ jobs:
generate_release_notes: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
+
- name: Summary
run: |
echo "🎉 Release v${{ steps.bump_version.outputs.new }} completed successfully!"
@@ -279,4 +279,4 @@ jobs:
echo " - Verify the package on PyPI"
echo " - Test installation with: uvx mcp-feedback-enhanced@v${{ steps.bump_version.outputs.new }}"
echo ""
- echo "📋 Note: Make sure CHANGELOG files are updated for future releases"
\ No newline at end of file
+ echo "📋 Note: Make sure CHANGELOG files are updated for future releases"
diff --git a/.gitignore b/.gitignore
index e0226ea..94dab5f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,4 +68,4 @@ test_*.py
# User configuration files
ui_settings.json
-.config/
\ No newline at end of file
+.config/
diff --git a/LICENSE b/LICENSE
index aa85224..18001f2 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2,7 +2,7 @@ MIT License
Copyright (c) 2024 Fábio Ferreira
-Portions of this software are modifications and enhancements
+Portions of this software are modifications and enhancements
Copyright (c) 2024 Minidoracat
Permission is hereby granted, free of charge, to any person obtaining a copy
diff --git a/RELEASE_NOTES/CHANGELOG.en.md b/RELEASE_NOTES/CHANGELOG.en.md
index e01efa1..e8f9c8a 100644
--- a/RELEASE_NOTES/CHANGELOG.en.md
+++ b/RELEASE_NOTES/CHANGELOG.en.md
@@ -182,4 +182,4 @@ This version focuses on improving system stability and user experience, particul
---
-**Full Project Info:** [GitHub - mcp-feedback-enhanced](https://github.com/Minidoracat/mcp-feedback-enhanced)
\ No newline at end of file
+**Full Project Info:** [GitHub - mcp-feedback-enhanced](https://github.com/Minidoracat/mcp-feedback-enhanced)
diff --git a/RELEASE_NOTES/CHANGELOG.zh-CN.md b/RELEASE_NOTES/CHANGELOG.zh-CN.md
index 3381487..c0ae4a0 100644
--- a/RELEASE_NOTES/CHANGELOG.zh-CN.md
+++ b/RELEASE_NOTES/CHANGELOG.zh-CN.md
@@ -182,4 +182,4 @@
---
-**完整项目信息:** [GitHub - mcp-feedback-enhanced](https://github.com/Minidoracat/mcp-feedback-enhanced)
\ No newline at end of file
+**完整项目信息:** [GitHub - mcp-feedback-enhanced](https://github.com/Minidoracat/mcp-feedback-enhanced)
diff --git a/RELEASE_NOTES/CHANGELOG.zh-TW.md b/RELEASE_NOTES/CHANGELOG.zh-TW.md
index e196e39..fbbfff0 100644
--- a/RELEASE_NOTES/CHANGELOG.zh-TW.md
+++ b/RELEASE_NOTES/CHANGELOG.zh-TW.md
@@ -182,4 +182,4 @@
---
-**完整專案資訊:** [GitHub - mcp-feedback-enhanced](https://github.com/Minidoracat/mcp-feedback-enhanced)
\ No newline at end of file
+**完整專案資訊:** [GitHub - mcp-feedback-enhanced](https://github.com/Minidoracat/mcp-feedback-enhanced)
diff --git a/RELEASE_NOTES/README.md b/RELEASE_NOTES/README.md
index 3b23d01..9fc50f9 100644
--- a/RELEASE_NOTES/README.md
+++ b/RELEASE_NOTES/README.md
@@ -63,4 +63,4 @@ GitHub Actions 工作流程會自動執行:
- **簡潔描述**: 保持項目符號簡潔但具描述性
- **問題引用**: 在適當的地方包含問題引用(例如 `fixes #10`)
- **平行結構**: 在所有語言中保持平行結構
-- **時間順序**: 在 CHANGELOG 檔案中將最新版本放在頂部
\ No newline at end of file
+- **時間順序**: 在 CHANGELOG 檔案中將最新版本放在頂部
diff --git a/RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md b/RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md
index bece0c7..b2c4be0 100644
--- a/RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md
+++ b/RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md
@@ -14,7 +14,7 @@ This project now uses a simplified release workflow that no longer requires crea
Before releasing, manually update these three files:
- `RELEASE_NOTES/CHANGELOG.en.md`
-- `RELEASE_NOTES/CHANGELOG.zh-TW.md`
+- `RELEASE_NOTES/CHANGELOG.zh-TW.md`
- `RELEASE_NOTES/CHANGELOG.zh-CN.md`
### 2. CHANGELOG 格式要求 / CHANGELOG Format Requirements
diff --git a/RELEASE_NOTES/template.md b/RELEASE_NOTES/template.md
index 5500046..fc68480 100644
--- a/RELEASE_NOTES/template.md
+++ b/RELEASE_NOTES/template.md
@@ -21,4 +21,4 @@
---
**說明**: 此模板應該適應每種語言(CHANGELOG.en.md, CHANGELOG.zh-TW.md, CHANGELOG.zh-CN.md)
-**注意**: 版本發佈文件不包含安裝與相關連結部分,這些內容已移至各語言的完整 CHANGELOG 文件中
\ No newline at end of file
+**注意**: 版本發佈文件不包含安裝與相關連結部分,這些內容已移至各語言的完整 CHANGELOG 文件中
diff --git a/debug_websocket.html b/debug_websocket.html
index 04f30ae..c20bb62 100644
--- a/debug_websocket.html
+++ b/debug_websocket.html
@@ -59,24 +59,24 @@
🔧 WebSocket 診斷工具
-
+
準備開始診斷...
-
+
-
+
-
+
等待操作...
@@ -84,43 +84,43 @@
let websocket = null;
let logElement = document.getElementById('log');
let statusElement = document.getElementById('status');
-
+
function log(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
logElement.textContent += `[${timestamp}] ${message}\n`;
logElement.scrollTop = logElement.scrollHeight;
-
+
// 更新狀態
statusElement.textContent = message;
statusElement.className = `status ${type}`;
}
-
+
function clearLog() {
logElement.textContent = '';
log('日誌已清除');
}
-
+
function testConnection() {
const url = document.getElementById('wsUrl').value;
-
+
if (websocket) {
log('關閉現有連接...', 'warning');
websocket.close();
websocket = null;
}
-
+
log(`嘗試連接到: ${url}`, 'info');
-
+
try {
websocket = new WebSocket(url);
-
+
websocket.onopen = function(event) {
log('✅ WebSocket 連接成功!', 'success');
};
-
+
websocket.onmessage = function(event) {
log(`📨 收到消息: ${event.data}`, 'success');
-
+
try {
const data = JSON.parse(event.data);
log(`📋 解析後的數據: ${JSON.stringify(data, null, 2)}`, 'info');
@@ -128,36 +128,36 @@
log(`⚠️ JSON 解析失敗: ${e.message}`, 'warning');
}
};
-
+
websocket.onclose = function(event) {
log(`🔌 連接已關閉 - Code: ${event.code}, Reason: ${event.reason}`, 'warning');
websocket = null;
};
-
+
websocket.onerror = function(error) {
log(`❌ WebSocket 錯誤: ${error}`, 'error');
console.error('WebSocket error:', error);
};
-
+
} catch (error) {
log(`❌ 連接失敗: ${error.message}`, 'error');
}
}
-
+
function sendMessage() {
const messageInput = document.getElementById('messageInput');
const message = messageInput.value.trim();
-
+
if (!message) {
log('⚠️ 請輸入要發送的消息', 'warning');
return;
}
-
+
if (!websocket || websocket.readyState !== WebSocket.OPEN) {
log('❌ WebSocket 未連接', 'error');
return;
}
-
+
try {
websocket.send(message);
log(`📤 已發送: ${message}`, 'info');
@@ -166,13 +166,13 @@
log(`❌ 發送失敗: ${error.message}`, 'error');
}
}
-
+
// 頁面加載時自動測試
window.onload = function() {
log('🚀 WebSocket 診斷工具已載入');
log('💡 點擊 "測試連接" 開始診斷');
};
-
+
// Enter 鍵發送消息
document.getElementById('messageInput').addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
diff --git a/docs/architecture/component-details.md b/docs/architecture/component-details.md
index 94e10f8..f21c4e1 100644
--- a/docs/architecture/component-details.md
+++ b/docs/architecture/component-details.md
@@ -13,25 +13,25 @@ graph TB
TOOL[interactive_feedback
核心工具]
I18N[i18n.py
國際化支援]
end
-
+
subgraph "第二層:Web UI 管理層"
MANAGER[WebUIManager
單例管理器]
SESSION[WebFeedbackSession
會話模型]
RESULT[FeedbackResult
結果模型]
end
-
+
subgraph "第三層:Web 服務層"
MAIN[main.py
FastAPI 應用]
ROUTES[main_routes.py
路由處理]
WS[WebSocket
實時通信]
end
-
+
subgraph "第四層:前端交互層"
HTML[feedback.html
主頁面]
JS[app.js
交互邏輯]
CSS[樣式文件]
end
-
+
subgraph "工具層"
BROWSER[browser.py
瀏覽器控制]
NETWORK[network.py
網路工具]
@@ -39,14 +39,14 @@ graph TB
CLEANUP[session_cleanup_manager.py
清理管理]
COMPRESS[compression_*.py
壓縮工具]
end
-
+
SERVER --> MANAGER
TOOL --> SESSION
MANAGER --> MAIN
SESSION --> ROUTES
ROUTES --> HTML
HTML --> JS
-
+
BROWSER --> MANAGER
NETWORK --> MAIN
PORT --> MAIN
@@ -63,7 +63,7 @@ class MCPServer:
def __init__(self):
self.app = FastMCP("mcp-feedback-enhanced")
self.setup_tools()
-
+
@self.app.tool()
async def interactive_feedback(
project_directory: str,
@@ -128,12 +128,12 @@ stateDiagram-v2
FEEDBACK_PROCESSING --> FEEDBACK_SUBMITTED: 處理完成
FEEDBACK_SUBMITTED --> WAITING: 新會話更新
FEEDBACK_SUBMITTED --> [*]: 會話結束
-
+
note right of WAITING
等待用戶輸入
顯示 AI 摘要
end note
-
+
note right of FEEDBACK_PROCESSING
處理回饋數據
圖片壓縮等
@@ -155,7 +155,7 @@ class FastAPIApp:
self.setup_middleware()
self.setup_routes()
self.setup_websocket()
-
+
def setup_middleware(self):
# CORS 設定
# 靜態文件服務
@@ -176,13 +176,13 @@ graph LR
FEEDBACK[GET /feedback]
STATIC[靜態資源]
end
-
+
subgraph "WebSocket 路由"
WS[/ws]
MSG[訊息處理]
BROADCAST[廣播機制]
end
-
+
GET --> FEEDBACK
FEEDBACK --> STATIC
WS --> MSG
@@ -220,15 +220,15 @@ class FeedbackApp {
this.currentSession = null;
this.feedbackState = 'WAITING';
}
-
+
// WebSocket 管理
initWebSocket() { /* ... */ }
handleWebSocketMessage(data) { /* ... */ }
-
+
// 用戶交互
submitFeedback() { /* ... */ }
handleImageUpload() { /* ... */ }
-
+
// UI 更新
updateSessionDisplay() { /* ... */ }
updateFeedbackState() { /* ... */ }
diff --git a/docs/architecture/deployment-guide.md b/docs/architecture/deployment-guide.md
index ba4e11b..54e5098 100644
--- a/docs/architecture/deployment-guide.md
+++ b/docs/architecture/deployment-guide.md
@@ -13,7 +13,7 @@ graph TB
LOCAL_BROWSER[本地瀏覽器]
LOCAL --> LOCAL_BROWSER
end
-
+
subgraph "SSH 遠程環境"
REMOTE[遠程服務器]
SSH_TUNNEL[SSH 隧道]
@@ -21,13 +21,13 @@ graph TB
REMOTE --> SSH_TUNNEL
SSH_TUNNEL --> LOCAL_CLIENT
end
-
+
subgraph "WSL 環境"
WSL[WSL 子系統]
WIN_BROWSER[Windows 瀏覽器]
WSL --> WIN_BROWSER
end
-
+
subgraph "容器化部署"
DOCKER[Docker 容器]
PORT_MAP[埠映射]
@@ -97,11 +97,11 @@ flowchart TD
SSH -->|否| WSL{WSL 環境?}
WSL -->|是| WSL_CONFIG[WSL 配置]
WSL -->|否| LOCAL_CONFIG[本地配置]
-
+
SSH_CONFIG --> TUNNEL[建立 SSH 隧道]
WSL_CONFIG --> WSL_BROWSER[WSL 瀏覽器開啟]
LOCAL_CONFIG --> LOCAL_BROWSER[本地瀏覽器開啟]
-
+
TUNNEL --> SUCCESS[部署成功]
WSL_BROWSER --> SUCCESS
LOCAL_BROWSER --> SUCCESS
diff --git a/docs/architecture/interaction-flows.md b/docs/architecture/interaction-flows.md
index da9c8c3..69178d5 100644
--- a/docs/architecture/interaction-flows.md
+++ b/docs/architecture/interaction-flows.md
@@ -16,7 +16,7 @@ sequenceDiagram
participant WS as WebSocket
participant UI as Web UI
participant User as 用戶
-
+
Note over AI,User: 第一次調用流程
AI->>MCP: interactive_feedback(summary, timeout)
MCP->>WM: launch_web_feedback_ui()
@@ -26,14 +26,14 @@ sequenceDiagram
User->>UI: 訪問回饋頁面
UI->>WS: 建立 WebSocket 連接
WS->>UI: connection_established
-
+
Note over AI,User: 用戶回饋流程
User->>UI: 填寫回饋內容
UI->>WS: submit_feedback
WS->>WM: 處理回饋數據
WM->>MCP: 設置回饋完成
MCP->>AI: 返回回饋結果
-
+
Note over AI,User: 第二次調用流程
AI->>MCP: interactive_feedback(new_summary, timeout)
MCP->>WM: 更新現有會話
@@ -98,7 +98,7 @@ async def create_session(self, summary: str, project_dir: str):
old_websockets = []
if self.current_session:
old_websockets = list(self.current_session.websockets)
-
+
# 創建新會話
session_id = str(uuid.uuid4())
self.current_session = WebFeedbackSession(
@@ -106,11 +106,11 @@ async def create_session(self, summary: str, project_dir: str):
summary=summary,
project_directory=project_dir
)
-
+
# 繼承 WebSocket 連接
for ws in old_websockets:
self.current_session.add_websocket(ws)
-
+
# 標記需要發送會話更新
self._pending_session_update = True
```
@@ -123,13 +123,13 @@ sequenceDiagram
participant UI as Web UI
participant WS as WebSocket
participant Session as 會話管理
-
+
Browser->>UI: 訪問 /feedback
UI->>WS: 建立 WebSocket 連接
WS->>Session: 註冊連接
Session->>WS: connection_established
WS->>UI: 發送連接確認
-
+
alt 有待處理的會話更新
Session->>WS: session_updated
WS->>UI: 會話更新訊息
@@ -153,13 +153,13 @@ stateDiagram-v2
AIProcessing --> SecondCall: AI 再次調用
SecondCall --> SessionUpdated: 會話更新
SessionUpdated --> UserFeedback: 等待新回饋
-
+
note right of SessionActive
Web 服務器持續運行
瀏覽器標籤頁保持開啟
WebSocket 連接維持
end note
-
+
note right of SessionUpdated
無需重新開啟瀏覽器
局部更新頁面內容
@@ -199,19 +199,19 @@ flowchart TD
function handleSessionUpdated(data) {
// 顯示會話更新通知
showNotification('會話已更新', 'info');
-
+
// 重置回饋狀態
feedbackState = 'FEEDBACK_WAITING';
-
+
// 局部更新 AI 摘要
updateAISummary(data.summary);
-
+
// 清空回饋表單
clearFeedbackForm();
-
+
// 更新會話 ID
currentSessionId = data.session_id;
-
+
// 保持 WebSocket 連接不變
// 無需重新建立連接
}
@@ -229,7 +229,7 @@ graph LR
FR[feedback_received
回饋確認]
ST[status_update
狀態更新]
end
-
+
subgraph "客戶端 → 服務器"
SF[submit_feedback
提交回饋]
HB[heartbeat
心跳檢測]
@@ -246,7 +246,7 @@ stateDiagram-v2
FEEDBACK_PROCESSING --> FEEDBACK_SUBMITTED: 處理完成
FEEDBACK_SUBMITTED --> WAITING: 新會話更新
FEEDBACK_SUBMITTED --> [*]: 會話結束
-
+
WAITING --> ERROR: 連接錯誤
FEEDBACK_PROCESSING --> ERROR: 處理錯誤
ERROR --> WAITING: 錯誤恢復
@@ -260,7 +260,7 @@ stateDiagram-v2
// WebSocket 重連機制
function handleWebSocketClose() {
console.log('WebSocket 連接已關閉,嘗試重連...');
-
+
setTimeout(() => {
initWebSocket();
}, 3000); // 3秒後重連
diff --git a/docs/architecture/system-overview.md b/docs/architecture/system-overview.md
index 5f08d67..891a3e9 100644
--- a/docs/architecture/system-overview.md
+++ b/docs/architecture/system-overview.md
@@ -11,40 +11,40 @@ graph TB
subgraph "AI 助手環境"
AI[AI 助手
Claude/GPT等]
end
-
+
subgraph "MCP Feedback Enhanced"
subgraph "MCP 服務層"
MCP[MCP Server
server.py]
TOOL[interactive_feedback
工具]
end
-
+
subgraph "Web UI 管理層"
WM[WebUIManager
單例模式]
SESSION[WebFeedbackSession
會話管理]
end
-
+
subgraph "Web 服務層"
API[FastAPI
HTTP/WebSocket]
ROUTES[路由處理
main_routes.py]
end
-
+
subgraph "前端交互層"
UI[Web UI
HTML/JS]
WS[WebSocket
實時通信]
end
-
+
subgraph "工具層"
ENV[環境檢測]
BROWSER[智能瀏覽器開啟]
RESOURCE[資源管理]
end
end
-
+
subgraph "用戶環境"
USER[用戶瀏覽器]
FILES[專案文件]
end
-
+
AI -->|調用 MCP 工具| MCP
MCP --> TOOL
TOOL --> WM
@@ -54,11 +54,11 @@ graph TB
ROUTES --> UI
UI --> WS
WS --> USER
-
+
ENV --> MCP
BROWSER --> USER
RESOURCE --> SESSION
-
+
USER -->|回饋提交| WS
FILES -->|專案內容| TOOL
```
@@ -74,7 +74,7 @@ stateDiagram-v2
SessionUpdated --> ActiveSession: 會話切換完成
ActiveSession --> Cleanup: 超時或手動清理
Cleanup --> NoSession: 資源釋放
-
+
note right of ActiveSession
只維護一個活躍會話
提升性能和用戶體驗
@@ -97,7 +97,7 @@ flowchart TD
REMOTE -->|否| WSL{WSL 環境?}
WSL -->|是| WSLOPEN[WSL 瀏覽器開啟]
WSL -->|否| FALLBACK[回退模式]
-
+
DIRECT --> SUCCESS[成功啟動]
TUNNEL --> SUCCESS
WSLOPEN --> SUCCESS
@@ -148,7 +148,7 @@ sequenceDiagram
participant WM as WebUIManager
participant UI as Web UI
participant User as 用戶
-
+
AI->>MCP: interactive_feedback()
MCP->>WM: 創建/更新會話
WM->>UI: 啟動 Web 服務
@@ -168,7 +168,7 @@ graph LR
D --> E[會話無縫更新]
E --> F[用戶再次回饋]
F --> G[持續循環...]
-
+
style D fill:#e1f5fe
style E fill:#e8f5e8
```
diff --git a/docs/en/cache-management.md b/docs/en/cache-management.md
index aef9112..f6c6bcd 100644
--- a/docs/en/cache-management.md
+++ b/docs/en/cache-management.md
@@ -57,7 +57,7 @@ python scripts/cleanup_cache.py --force
# Windows
taskkill /f /im uvx.exe
taskkill /f /im python.exe /fi "WINDOWTITLE eq *mcp-feedback-enhanced*"
-
+
# Then execute cleanup
uv cache clean
```
diff --git a/docs/zh-CN/cache-management.md b/docs/zh-CN/cache-management.md
index c91b2f3..440f33c 100644
--- a/docs/zh-CN/cache-management.md
+++ b/docs/zh-CN/cache-management.md
@@ -57,7 +57,7 @@ python scripts/cleanup_cache.py --force
# Windows
taskkill /f /im uvx.exe
taskkill /f /im python.exe /fi "WINDOWTITLE eq *mcp-feedback-enhanced*"
-
+
# 然后执行清理
uv cache clean
```
diff --git a/docs/zh-TW/cache-management.md b/docs/zh-TW/cache-management.md
index 79ee85a..a895b28 100644
--- a/docs/zh-TW/cache-management.md
+++ b/docs/zh-TW/cache-management.md
@@ -57,7 +57,7 @@ python scripts/cleanup_cache.py --force
# Windows
taskkill /f /im uvx.exe
taskkill /f /im python.exe /fi "WINDOWTITLE eq *mcp-feedback-enhanced*"
-
+
# 然後執行清理
uv cache clean
```
diff --git a/pyproject.toml b/pyproject.toml
index 388e221..25eb28f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -160,6 +160,18 @@ ignore = [
"S110", # 允許 try-except-pass(暫時)
"E712", # 允許布林比較(暫時)
"E722", # 允許裸露 except(暫時)
+ "ARG001", # 允許未使用函數參數(暫時)
+ "ARG002", # 允許未使用方法參數(暫時)
+ "PLW0603", # 允許使用 global 語句(暫時)
+ "RUF012", # 允許可變類別屬性(暫時)
+ "RUF006", # 允許未儲存 asyncio.create_task 返回值(暫時)
+ "PLR0915", # 允許函數語句過多(暫時)
+ "SIM110", # 允許使用 for 迴圈而非 any()(暫時)
+ "A002", # 允許遮蔽內建函數名稱(暫時)
+ "S104", # 允許綁定所有介面(暫時)
+ "RUF013", # 允許隱式 Optional(暫時)
+ "SIM108", # 允許 if-else 而非三元運算子(暫時)
+ "S602", # 允許 subprocess shell=True(暫時)
]
# 每個檔案的最大複雜度
diff --git a/pytest.ini b/pytest.ini
index 9d093fd..26eb7c6 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -8,7 +8,7 @@ testpaths = tests
minversion = 6.0
# 添加選項
-addopts =
+addopts =
--strict-markers
--strict-config
--disable-warnings
diff --git a/scripts/cleanup_cache.py b/scripts/cleanup_cache.py
index 5ed318a..8b91afb 100644
--- a/scripts/cleanup_cache.py
+++ b/scripts/cleanup_cache.py
@@ -19,27 +19,26 @@ UV Cache 清理腳本
- 支援 Windows/macOS/Linux 跨平台
"""
-import subprocess
-import sys
import argparse
-import shutil
-from pathlib import Path
import os
+import subprocess
+from pathlib import Path
+
def get_cache_dir():
"""取得 uv cache 目錄"""
# Windows 預設路徑
- if os.name == 'nt':
+ if os.name == "nt":
return Path.home() / "AppData" / "Local" / "uv"
# macOS/Linux 預設路徑
- else:
- return Path.home() / ".cache" / "uv"
+ return Path.home() / ".cache" / "uv"
+
def get_cache_size(cache_dir):
"""計算 cache 目錄大小"""
if not cache_dir.exists():
return 0
-
+
total_size = 0
for dirpath, dirnames, filenames in os.walk(cache_dir):
for filename in filenames:
@@ -50,25 +49,24 @@ def get_cache_size(cache_dir):
pass
return total_size
+
def format_size(size_bytes):
"""格式化檔案大小顯示"""
if size_bytes == 0:
return "0 B"
-
- for unit in ['B', 'KB', 'MB', 'GB']:
+
+ for unit in ["B", "KB", "MB", "GB"]:
if size_bytes < 1024.0:
return f"{size_bytes:.1f} {unit}"
size_bytes /= 1024.0
return f"{size_bytes:.1f} TB"
+
def run_uv_command(command, check=True):
"""執行 uv 命令"""
try:
result = subprocess.run(
- ["uv"] + command,
- capture_output=True,
- text=True,
- check=check
+ ["uv"] + command, capture_output=True, text=True, check=check
)
return result
except subprocess.CalledProcessError as e:
@@ -79,25 +77,26 @@ def run_uv_command(command, check=True):
print("❌ 找不到 uv 命令,請確認 uv 已正確安裝")
return None
+
def show_cache_info():
"""顯示 cache 資訊"""
print("🔍 UV Cache 資訊")
print("=" * 50)
-
+
cache_dir = get_cache_dir()
print(f"Cache 目錄: {cache_dir}")
-
+
if cache_dir.exists():
cache_size = get_cache_size(cache_dir)
print(f"Cache 大小: {format_size(cache_size)}")
-
+
# 顯示子目錄大小
subdirs = []
for subdir in cache_dir.iterdir():
if subdir.is_dir():
subdir_size = get_cache_size(subdir)
subdirs.append((subdir.name, subdir_size))
-
+
if subdirs:
print("\n📁 子目錄大小:")
subdirs.sort(key=lambda x: x[1], reverse=True)
@@ -106,6 +105,7 @@ def show_cache_info():
else:
print("Cache 目錄不存在")
+
def clean_cache_selective(cache_dir, dry_run=False):
"""選擇性清理 cache,跳過正在使用的檔案"""
cleaned_count = 0
@@ -117,7 +117,7 @@ def clean_cache_selective(cache_dir, dry_run=False):
# 遍歷 cache 目錄
for root, dirs, files in os.walk(cache_dir):
# 跳過一些可能正在使用的目錄
- if any(skip_dir in root for skip_dir in ['Scripts', 'Lib', 'pyvenv.cfg']):
+ if any(skip_dir in root for skip_dir in ["Scripts", "Lib", "pyvenv.cfg"]):
continue
for file in files:
@@ -128,19 +128,22 @@ def clean_cache_selective(cache_dir, dry_run=False):
total_saved += file_size
cleaned_count += 1
if cleaned_count <= 10: # 只顯示前10個
- print(f" 將清理: {file_path.relative_to(cache_dir)} ({format_size(file_size)})")
+ print(
+ f" 將清理: {file_path.relative_to(cache_dir)} ({format_size(file_size)})"
+ )
else:
file_size = file_path.stat().st_size
file_path.unlink()
total_saved += file_size
cleaned_count += 1
- except (OSError, PermissionError, FileNotFoundError) as e:
+ except (OSError, PermissionError, FileNotFoundError):
skipped_count += 1
if not dry_run and skipped_count <= 5: # 只顯示前5個錯誤
print(f" ⚠️ 跳過: {file_path.name} (正在使用中)")
return cleaned_count, skipped_count, total_saved
+
def clean_cache(dry_run=False):
"""清理 cache"""
action = "預覽" if dry_run else "執行"
@@ -164,8 +167,10 @@ def clean_cache(dry_run=False):
print(result.stdout)
else:
print(" 使用自定義掃描...")
- cleaned_count, skipped_count, total_saved = clean_cache_selective(cache_dir, dry_run=True)
- print(f"\n📊 預覽結果:")
+ cleaned_count, skipped_count, total_saved = clean_cache_selective(
+ cache_dir, dry_run=True
+ )
+ print("\n📊 預覽結果:")
print(f" 可清理檔案: {cleaned_count}")
print(f" 預計節省: {format_size(total_saved)}")
else:
@@ -177,9 +182,11 @@ def clean_cache(dry_run=False):
print("✅ 標準 Cache 清理完成")
else:
print("⚠️ 標準清理失敗,使用選擇性清理...")
- cleaned_count, skipped_count, total_saved = clean_cache_selective(cache_dir, dry_run=False)
+ cleaned_count, skipped_count, total_saved = clean_cache_selective(
+ cache_dir, dry_run=False
+ )
- print(f"\n📊 清理結果:")
+ print("\n📊 清理結果:")
print(f" 已清理檔案: {cleaned_count}")
print(f" 跳過檔案: {skipped_count}")
print(f" 節省空間: {format_size(total_saved)}")
@@ -192,13 +199,14 @@ def clean_cache(dry_run=False):
if cache_dir.exists():
after_size = get_cache_size(cache_dir)
saved_size = before_size - after_size
- print(f"\n📈 總體效果:")
+ print("\n📈 總體效果:")
print(f" 清理前: {format_size(before_size)}")
print(f" 清理後: {format_size(after_size)}")
print(f" 實際節省: {format_size(saved_size)}")
else:
print(f" 節省空間: {format_size(before_size)}")
+
def force_clean_cache():
"""強制清理 cache(關閉相關程序後)"""
print("🔥 強制清理模式")
@@ -206,7 +214,7 @@ def force_clean_cache():
print("⚠️ 警告:此模式會嘗試關閉可能使用 cache 的程序")
confirm = input("確定要繼續嗎?(y/N): ")
- if confirm.lower() != 'y':
+ if confirm.lower() != "y":
print("❌ 已取消")
return
@@ -222,22 +230,28 @@ def force_clean_cache():
print("\n🔍 檢查相關程序...")
try:
import psutil
+
killed_processes = []
- for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
+ for proc in psutil.process_iter(["pid", "name", "cmdline"]):
try:
- if proc.info['name'] and any(name in proc.info['name'].lower()
- for name in ['uvx', 'uv.exe', 'python.exe']):
- cmdline = ' '.join(proc.info['cmdline'] or [])
- if 'mcp-feedback-enhanced' in cmdline or 'uvx' in cmdline:
- print(f" 終止程序: {proc.info['name']} (PID: {proc.info['pid']})")
+ if proc.info["name"] and any(
+ name in proc.info["name"].lower()
+ for name in ["uvx", "uv.exe", "python.exe"]
+ ):
+ cmdline = " ".join(proc.info["cmdline"] or [])
+ if "mcp-feedback-enhanced" in cmdline or "uvx" in cmdline:
+ print(
+ f" 終止程序: {proc.info['name']} (PID: {proc.info['pid']})"
+ )
proc.terminate()
- killed_processes.append(proc.info['pid'])
+ killed_processes.append(proc.info["pid"])
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
if killed_processes:
print(f" 已終止 {len(killed_processes)} 個程序")
import time
+
time.sleep(2) # 等待程序完全關閉
else:
print(" 未發現相關程序")
@@ -252,22 +266,29 @@ def force_clean_cache():
print("✅ 強制清理成功")
else:
print("⚠️ 標準清理仍然失敗,使用檔案級清理...")
- cleaned_count, skipped_count, total_saved = clean_cache_selective(cache_dir, dry_run=False)
+ cleaned_count, skipped_count, total_saved = clean_cache_selective(
+ cache_dir, dry_run=False
+ )
print(f" 清理檔案: {cleaned_count}, 跳過: {skipped_count}")
# 顯示結果
after_size = get_cache_size(cache_dir)
saved_size = before_size - after_size
- print(f"\n📈 清理結果:")
+ print("\n📈 清理結果:")
print(f" 節省空間: {format_size(saved_size)}")
+
def main():
parser = argparse.ArgumentParser(description="UV Cache 清理工具")
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--size", action="store_true", help="顯示 cache 大小資訊")
- group.add_argument("--dry-run", action="store_true", help="預覽清理內容(不實際清理)")
+ group.add_argument(
+ "--dry-run", action="store_true", help="預覽清理內容(不實際清理)"
+ )
group.add_argument("--clean", action="store_true", help="執行 cache 清理")
- group.add_argument("--force", action="store_true", help="強制清理(會嘗試關閉相關程序)")
+ group.add_argument(
+ "--force", action="store_true", help="強制清理(會嘗試關閉相關程序)"
+ )
args = parser.parse_args()
@@ -280,5 +301,6 @@ def main():
elif args.force:
force_clean_cache()
+
if __name__ == "__main__":
main()
diff --git a/scripts/release.py b/scripts/release.py
index cdc4b11..a41dba8 100644
--- a/scripts/release.py
+++ b/scripts/release.py
@@ -3,24 +3,28 @@
本地發布腳本
用法:
python scripts/release.py patch # 2.0.0 -> 2.0.1
- python scripts/release.py minor # 2.0.0 -> 2.1.0
+ python scripts/release.py minor # 2.0.0 -> 2.1.0
python scripts/release.py major # 2.0.0 -> 3.0.0
"""
+import re
import subprocess
import sys
-import re
from pathlib import Path
+
def run_cmd(cmd, check=True):
"""執行命令並返回結果"""
print(f"🔨 執行: {cmd}")
- result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
+ result = subprocess.run(
+ cmd, shell=True, capture_output=True, text=True, check=False
+ )
if check and result.returncode != 0:
print(f"❌ 錯誤: {result.stderr}")
sys.exit(1)
return result
+
def get_current_version():
"""從 pyproject.toml 獲取當前版本"""
pyproject_path = Path("pyproject.toml")
@@ -30,73 +34,76 @@ def get_current_version():
return match.group(1)
raise ValueError("無法找到版本號")
+
def bump_version(version_type):
"""更新版本號"""
- if version_type not in ['patch', 'minor', 'major']:
+ if version_type not in ["patch", "minor", "major"]:
print("❌ 版本類型必須是: patch, minor, major")
sys.exit(1)
-
+
current = get_current_version()
print(f"📦 當前版本: {current}")
-
+
# 使用 bump2version with allow-dirty
run_cmd(f"uv run bump2version --allow-dirty {version_type}")
-
+
new_version = get_current_version()
print(f"🎉 新版本: {new_version}")
-
+
return current, new_version
+
def main():
if len(sys.argv) != 2:
print(__doc__)
sys.exit(1)
-
+
version_type = sys.argv[1]
-
+
print("🚀 開始發布流程...")
-
+
# 檢查 Git 狀態(僅提示,不阻止)
result = run_cmd("git status --porcelain", check=False)
if result.stdout.strip():
print("⚠️ 有未提交的變更:")
print(result.stdout)
print("💡 將繼續執行(使用 --allow-dirty 模式)")
-
+
# 更新版本
old_version, new_version = bump_version(version_type)
-
+
# 建置套件
print("📦 建置套件...")
run_cmd("uv build")
-
+
# 檢查套件
print("🔍 檢查套件...")
run_cmd("uv run twine check dist/*")
-
+
# 提交所有變更(包括版本更新)
print("💾 提交版本更新...")
run_cmd("git add .")
run_cmd(f'git commit -m "🔖 Release v{new_version}"')
run_cmd(f'git tag "v{new_version}"')
-
+
# 詢問是否發布
print(f"\n✅ 準備發布版本 {old_version} -> {new_version}")
choice = input("是否發布到 PyPI? (y/N): ")
-
- if choice.lower() == 'y':
+
+ if choice.lower() == "y":
print("🚀 發布到 PyPI...")
run_cmd("uv run twine upload dist/*")
-
+
print("📤 推送到 GitHub...")
run_cmd("git push origin main")
run_cmd(f'git push origin "v{new_version}"')
-
+
print(f"🎉 發布完成!版本 v{new_version} 已上線")
- print(f"📦 安裝命令: uvx mcp-feedback-enhanced")
+ print("📦 安裝命令: uvx mcp-feedback-enhanced")
else:
print("⏸️ 發布已取消,版本已更新但未發布")
print("💡 您可以稍後手動發布: uv run twine upload dist/*")
+
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/src/mcp_feedback_enhanced/__init__.py b/src/mcp_feedback_enhanced/__init__.py
index 4a3f0eb..4a84c2e 100644
--- a/src/mcp_feedback_enhanced/__init__.py
+++ b/src/mcp_feedback_enhanced/__init__.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
"""
MCP Interactive Feedback Enhanced
==================================
@@ -27,24 +26,27 @@ import os
from .server import main as run_server
# 導入新的 Web UI 模組
-from .web import WebUIManager, launch_web_feedback_ui, get_web_ui_manager, stop_web_ui
+from .web import WebUIManager, get_web_ui_manager, launch_web_feedback_ui, stop_web_ui
+
# 保持向後兼容性
feedback_ui = None
# 主要導出介面
__all__ = [
- "run_server",
- "feedback_ui",
"WebUIManager",
- "launch_web_feedback_ui",
- "get_web_ui_manager",
- "stop_web_ui",
- "__version__",
"__author__",
+ "__version__",
+ "feedback_ui",
+ "get_web_ui_manager",
+ "launch_web_feedback_ui",
+ "run_server",
+ "stop_web_ui",
]
+
def main():
"""主要入口點,用於 uvx 執行"""
from .__main__ import main as cli_main
- return cli_main()
\ No newline at end of file
+
+ return cli_main()
diff --git a/src/mcp_feedback_enhanced/__main__.py b/src/mcp_feedback_enhanced/__main__.py
index 37d3dbf..53aa636 100644
--- a/src/mcp_feedback_enhanced/__main__.py
+++ b/src/mcp_feedback_enhanced/__main__.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
"""
MCP Interactive Feedback Enhanced - 主程式入口
==============================================
@@ -11,15 +10,18 @@ MCP Interactive Feedback Enhanced - 主程式入口
python -m mcp_feedback_enhanced test # 執行測試
"""
-import sys
import argparse
-import os
import asyncio
+import os
+import sys
import warnings
+
# 抑制 Windows 上的 asyncio ResourceWarning
-if sys.platform == 'win32':
- warnings.filterwarnings("ignore", category=ResourceWarning, message=".*unclosed transport.*")
+if sys.platform == "win32":
+ warnings.filterwarnings(
+ "ignore", category=ResourceWarning, message=".*unclosed transport.*"
+ )
warnings.filterwarnings("ignore", category=ResourceWarning, message=".*unclosed.*")
# 設置 asyncio 事件循環策略以減少警告
@@ -28,55 +30,67 @@ if sys.platform == 'win32':
except AttributeError:
pass
+
def main():
"""主程式入口點"""
parser = argparse.ArgumentParser(
description="MCP Feedback Enhanced Enhanced - 互動式回饋收集 MCP 伺服器"
)
-
- subparsers = parser.add_subparsers(dest='command', help='可用命令')
-
+
+ subparsers = parser.add_subparsers(dest="command", help="可用命令")
+
# 伺服器命令(預設)
- server_parser = subparsers.add_parser('server', help='啟動 MCP 伺服器(預設)')
-
+ server_parser = subparsers.add_parser("server", help="啟動 MCP 伺服器(預設)")
+
# 測試命令
- test_parser = subparsers.add_parser('test', help='執行測試')
- test_parser.add_argument('--web', action='store_true', help='測試 Web UI (自動持續運行)')
- test_parser.add_argument('--desktop', action='store_true', help='測試桌面應用 (啟動 Electron 應用)')
- test_parser.add_argument('--full', action='store_true', help='完整整合測試 (Web + 桌面)')
- test_parser.add_argument('--electron-only', action='store_true', help='僅測試 Electron 環境')
- test_parser.add_argument('--timeout', type=int, default=60, help='測試超時時間 (秒)')
-
+ test_parser = subparsers.add_parser("test", help="執行測試")
+ test_parser.add_argument(
+ "--web", action="store_true", help="測試 Web UI (自動持續運行)"
+ )
+ test_parser.add_argument(
+ "--desktop", action="store_true", help="測試桌面應用 (啟動 Electron 應用)"
+ )
+ test_parser.add_argument(
+ "--full", action="store_true", help="完整整合測試 (Web + 桌面)"
+ )
+ test_parser.add_argument(
+ "--electron-only", action="store_true", help="僅測試 Electron 環境"
+ )
+ test_parser.add_argument(
+ "--timeout", type=int, default=60, help="測試超時時間 (秒)"
+ )
+
# 版本命令
- version_parser = subparsers.add_parser('version', help='顯示版本資訊')
-
+ version_parser = subparsers.add_parser("version", help="顯示版本資訊")
+
args = parser.parse_args()
-
- if args.command == 'test':
+
+ if args.command == "test":
run_tests(args)
- elif args.command == 'version':
+ elif args.command == "version":
show_version()
- elif args.command == 'server':
- run_server()
- elif args.command is None:
+ elif args.command == "server" or args.command is None:
run_server()
else:
# 不應該到達這裡
parser.print_help()
sys.exit(1)
+
def run_server():
"""啟動 MCP 伺服器"""
from .server import main as server_main
+
return server_main()
+
def run_tests(args):
"""執行測試"""
# 啟用調試模式以顯示測試過程
os.environ["MCP_DEBUG"] = "true"
# 在 Windows 上抑制 asyncio 警告
- if sys.platform == 'win32':
+ if sys.platform == "win32":
os.environ["PYTHONWARNINGS"] = "ignore::ResourceWarning"
if args.web:
@@ -113,20 +127,18 @@ def run_tests(args):
def test_web_ui_simple():
"""簡單的 Web UI 測試"""
try:
- from .web.main import WebUIManager
import tempfile
import time
import webbrowser
+ from .web.main import WebUIManager
+
print("🔧 創建 Web UI 管理器...")
manager = WebUIManager(host="127.0.0.1", port=8765) # 使用固定端口
print("🔧 創建測試會話...")
with tempfile.TemporaryDirectory() as temp_dir:
- session_id = manager.create_session(
- temp_dir,
- "Web UI 測試 - 驗證基本功能"
- )
+ session_id = manager.create_session(temp_dir, "Web UI 測試 - 驗證基本功能")
if session_id:
print("✅ 會話創建成功")
@@ -170,6 +182,7 @@ def test_web_ui_simple():
except Exception as e:
print(f"❌ Web UI 測試失敗: {e}")
import traceback
+
traceback.print_exc()
return False
@@ -181,6 +194,7 @@ def test_desktop_app():
# 檢查桌面環境可用性
from .desktop import is_desktop_available
+
if not is_desktop_available():
print("❌ 桌面環境不可用")
print("💡 請確保 Node.js 已安裝且不在遠程環境中")
@@ -189,10 +203,9 @@ def test_desktop_app():
print("✅ 桌面環境檢查通過")
# 設置桌面模式
- os.environ['MCP_FEEDBACK_MODE'] = 'desktop'
+ os.environ["MCP_FEEDBACK_MODE"] = "desktop"
print("🔧 創建 Electron 管理器...")
- from .desktop.electron_manager import ElectronManager
import asyncio
async def run_desktop_test():
@@ -208,7 +221,7 @@ def test_desktop_app():
result = await launch_desktop_app(
os.getcwd(),
"桌面應用測試 - 驗證 Electron 整合功能",
- 300 # 5分鐘超時
+ 300, # 5分鐘超時
)
print("✅ 桌面應用測試完成")
@@ -224,6 +237,7 @@ def test_desktop_app():
except Exception as e:
print(f"❌ 桌面應用測試失敗: {e}")
import traceback
+
traceback.print_exc()
return False
@@ -236,11 +250,11 @@ async def wait_for_process(process):
# 確保管道正確關閉
try:
- if hasattr(process, 'stdout') and process.stdout:
+ if hasattr(process, "stdout") and process.stdout:
process.stdout.close()
- if hasattr(process, 'stderr') and process.stderr:
+ if hasattr(process, "stderr") and process.stderr:
process.stderr.close()
- if hasattr(process, 'stdin') and process.stdin:
+ if hasattr(process, "stdin") and process.stdin:
process.stdin.close()
except Exception as close_error:
print(f"關閉進程管道時出錯: {close_error}")
@@ -256,9 +270,15 @@ def test_electron_environment():
# 檢查 Node.js
import subprocess
+
try:
- result = subprocess.run(['node', '--version'],
- capture_output=True, text=True, timeout=10)
+ result = subprocess.run(
+ ["node", "--version"],
+ capture_output=True,
+ text=True,
+ timeout=10,
+ check=False,
+ )
if result.returncode == 0:
print(f"✅ Node.js 版本: {result.stdout.strip()}")
else:
@@ -270,6 +290,7 @@ def test_electron_environment():
# 檢查桌面模組
from .desktop import is_desktop_available
+
if is_desktop_available():
print("✅ 桌面環境可用")
else:
@@ -278,6 +299,7 @@ def test_electron_environment():
# 檢查 Electron 管理器
from .desktop.electron_manager import ElectronManager
+
manager = ElectronManager()
if manager.is_electron_available():
@@ -288,7 +310,7 @@ def test_electron_environment():
# 檢查文件結構
desktop_dir = manager.desktop_dir
- required_files = ['main.js', 'preload.js', 'package.json']
+ required_files = ["main.js", "preload.js", "package.json"]
for file_name in required_files:
file_path = desktop_dir / file_name
@@ -324,20 +346,24 @@ def test_full_integration():
test_cases = [("auto", "auto"), ("web", "web"), ("desktop", "desktop")]
for env_value, expected in test_cases:
- os.environ['MCP_FEEDBACK_MODE'] = env_value
+ os.environ["MCP_FEEDBACK_MODE"] = env_value
# 重新導入以獲取新的環境變數值
import sys
- if 'mcp_feedback_enhanced.server' in sys.modules:
- del sys.modules['mcp_feedback_enhanced.server']
+
+ if "mcp_feedback_enhanced.server" in sys.modules:
+ del sys.modules["mcp_feedback_enhanced.server"]
from .server import get_feedback_mode
+
actual = get_feedback_mode().value
if actual == expected:
print(f" ✅ MCP_FEEDBACK_MODE='{env_value}' → {actual}")
else:
- print(f" ❌ MCP_FEEDBACK_MODE='{env_value}' → {actual} (期望: {expected})")
+ print(
+ f" ❌ MCP_FEEDBACK_MODE='{env_value}' → {actual} (期望: {expected})"
+ )
return False
# 2. Electron 環境測試
@@ -348,9 +374,10 @@ def test_full_integration():
# 3. Web UI 基本功能測試
print("\n📋 3. 測試 Web UI 基本功能...")
- from .web.main import WebUIManager
import tempfile
+ from .web.main import WebUIManager
+
with tempfile.TemporaryDirectory() as temp_dir:
manager = WebUIManager(host="127.0.0.1", port=8766) # 使用不同端口避免衝突
session_id = manager.create_session(temp_dir, "整合測試會話")
@@ -363,7 +390,7 @@ def test_full_integration():
# 4. 桌面模式檢測測試
print("\n📋 4. 測試桌面模式檢測...")
- os.environ['MCP_FEEDBACK_MODE'] = 'desktop'
+ os.environ["MCP_FEEDBACK_MODE"] = "desktop"
manager = WebUIManager()
if manager.should_use_desktop_mode():
@@ -379,16 +406,19 @@ def test_full_integration():
except Exception as e:
print(f"❌ 完整整合測試失敗: {e}")
import traceback
+
traceback.print_exc()
return False
def show_version():
"""顯示版本資訊"""
- from . import __version__, __author__
+ from . import __author__, __version__
+
print(f"MCP Feedback Enhanced Enhanced v{__version__}")
print(f"作者: {__author__}")
print("GitHub: https://github.com/Minidoracat/mcp-feedback-enhanced")
+
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/src/mcp_feedback_enhanced/debug.py b/src/mcp_feedback_enhanced/debug.py
index c120784..e390adb 100644
--- a/src/mcp_feedback_enhanced/debug.py
+++ b/src/mcp_feedback_enhanced/debug.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
"""
統一調試日誌模組
================
@@ -29,26 +28,26 @@ from typing import Any
def debug_log(message: Any, prefix: str = "DEBUG") -> None:
"""
輸出調試訊息到標準錯誤,避免污染標準輸出
-
+
Args:
message: 要輸出的調試信息
prefix: 調試信息的前綴標識,默認為 "DEBUG"
"""
# 只在啟用調試模式時才輸出,避免干擾 MCP 通信
- if not os.getenv("MCP_DEBUG", "").lower() in ("true", "1", "yes", "on"):
+ if os.getenv("MCP_DEBUG", "").lower() not in ("true", "1", "yes", "on"):
return
-
+
try:
# 確保消息是字符串類型
if not isinstance(message, str):
message = str(message)
-
+
# 安全地輸出到 stderr,處理編碼問題
try:
print(f"[{prefix}] {message}", file=sys.stderr, flush=True)
except UnicodeEncodeError:
# 如果遇到編碼問題,使用 ASCII 安全模式
- safe_message = message.encode('ascii', errors='replace').decode('ascii')
+ safe_message = message.encode("ascii", errors="replace").decode("ascii")
print(f"[{prefix}] {safe_message}", file=sys.stderr, flush=True)
except Exception:
# 最後的備用方案:靜默失敗,不影響主程序
@@ -77,4 +76,4 @@ def is_debug_enabled() -> bool:
def set_debug_mode(enabled: bool) -> None:
"""設置調試模式(用於測試)"""
- os.environ["MCP_DEBUG"] = "true" if enabled else "false"
\ No newline at end of file
+ os.environ["MCP_DEBUG"] = "true" if enabled else "false"
diff --git a/src/mcp_feedback_enhanced/desktop/__init__.py b/src/mcp_feedback_enhanced/desktop/__init__.py
index 18a7b31..65b928c 100644
--- a/src/mcp_feedback_enhanced/desktop/__init__.py
+++ b/src/mcp_feedback_enhanced/desktop/__init__.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
"""
桌面應用模組
===========
@@ -26,30 +25,35 @@ from ..debug import web_debug_log as debug_log
def is_desktop_available() -> bool:
"""
檢測桌面環境是否可用
-
+
Returns:
bool: True 表示桌面環境可用
"""
try:
# 檢查是否有 Node.js 環境
import subprocess
- result = subprocess.run(['node', '--version'],
- capture_output=True,
- text=True,
- timeout=5)
+
+ result = subprocess.run(
+ ["node", "--version"],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ check=False,
+ )
if result.returncode != 0:
debug_log("Node.js 不可用,桌面模式不可用")
return False
-
+
# 檢查是否為遠程環境
from ..server import is_remote_environment
+
if is_remote_environment():
debug_log("檢測到遠程環境,桌面模式不適用")
return False
-
+
debug_log("桌面環境檢測通過")
return True
-
+
except (subprocess.TimeoutExpired, FileNotFoundError, ImportError) as e:
debug_log(f"桌面環境檢測失敗: {e}")
return False
@@ -75,10 +79,12 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
try:
# 創建 Electron 管理器
from .electron_manager import ElectronManager
+
manager = ElectronManager()
# 首先啟動 Web 服務器(桌面應用需要載入 Web UI)
from ..web import get_web_ui_manager
+
web_manager = get_web_ui_manager()
# 創建會話
@@ -95,6 +101,7 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
# 等待 Web 服務器完全啟動
import time
+
debug_log("等待 Web 服務器啟動...")
time.sleep(5) # 增加等待時間
@@ -102,7 +109,7 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
if web_manager.server_thread and web_manager.server_thread.is_alive():
debug_log(f"✅ Web 服務器成功啟動在端口: {web_manager.port}")
else:
- raise RuntimeError(f"Web 服務器啟動失敗")
+ raise RuntimeError("Web 服務器啟動失敗")
# 設置 Web 服務器端口
manager.set_web_server_port(web_manager.port)
@@ -117,52 +124,49 @@ async def launch_desktop_app(project_dir: str, summary: str, timeout: int) -> di
result = await session.wait_for_feedback(timeout)
debug_log("收到桌面應用用戶回饋")
return result
- else:
- debug_log("桌面應用啟動失敗,回退到 Web 模式")
- # 回退到 Web 模式
- from ..web import launch_web_feedback_ui
- return await launch_web_feedback_ui(project_dir, summary, timeout)
+ debug_log("桌面應用啟動失敗,回退到 Web 模式")
+ # 回退到 Web 模式
+ from ..web import launch_web_feedback_ui
+
+ return await launch_web_feedback_ui(project_dir, summary, timeout)
except Exception as e:
debug_log(f"桌面應用啟動過程中出錯: {e}")
debug_log("回退到 Web 模式")
# 回退到 Web 模式
from ..web import launch_web_feedback_ui
+
return await launch_web_feedback_ui(project_dir, summary, timeout)
class ElectronManager:
"""Electron 管理器 - 預留接口"""
-
+
def __init__(self):
"""初始化 Electron 管理器"""
self.electron_process = None
self.web_server_port = None
debug_log("ElectronManager 初始化(預留實現)")
-
+
async def launch_desktop_app(self, summary: str, project_dir: str) -> bool:
"""
啟動桌面應用
-
+
Args:
summary: AI 工作摘要
project_dir: 專案目錄
-
+
Returns:
bool: 啟動是否成功
"""
debug_log("桌面應用啟動功能尚未實現")
debug_log("此功能將在階段 2 中實現")
return False
-
+
def is_available(self) -> bool:
"""檢查桌面管理器是否可用"""
return is_desktop_available()
# 主要導出介面
-__all__ = [
- "is_desktop_available",
- "launch_desktop_app",
- "ElectronManager"
-]
+__all__ = ["ElectronManager", "is_desktop_available", "launch_desktop_app"]
diff --git a/src/mcp_feedback_enhanced/desktop/electron_manager.py b/src/mcp_feedback_enhanced/desktop/electron_manager.py
index a03b30e..420ae8b 100644
--- a/src/mcp_feedback_enhanced/desktop/electron_manager.py
+++ b/src/mcp_feedback_enhanced/desktop/electron_manager.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
"""
Electron 管理器
==============
@@ -16,11 +15,9 @@ Electron 管理器
版本: 2.3.0
"""
-import subprocess
import asyncio
-import os
+import subprocess
from pathlib import Path
-from typing import Optional
from ..debug import web_debug_log as debug_log
from ..utils.error_handler import ErrorHandler, ErrorType
@@ -28,16 +25,16 @@ from ..utils.error_handler import ErrorHandler, ErrorType
class ElectronManager:
"""Electron 進程管理器"""
-
+
def __init__(self):
"""初始化 Electron 管理器"""
- self.electron_process: Optional[subprocess.Popen] = None
+ self.electron_process: subprocess.Popen | None = None
self.desktop_dir = Path(__file__).parent
- self.web_server_port: Optional[int] = None
-
+ self.web_server_port: int | None = None
+
debug_log("ElectronManager 初始化完成")
debug_log(f"桌面模組目錄: {self.desktop_dir}")
-
+
async def launch_desktop_app(self, summary: str, project_dir: str) -> bool:
"""
啟動 Electron 桌面應用
@@ -64,50 +61,52 @@ class ElectronManager:
if success:
debug_log("Electron 桌面應用啟動成功")
return True
- else:
- debug_log("Electron 桌面應用啟動失敗")
- return False
+ debug_log("Electron 桌面應用啟動失敗")
+ return False
except Exception as e:
error_id = ErrorHandler.log_error_with_context(
e,
context={"operation": "桌面應用啟動", "project_dir": project_dir},
- error_type=ErrorType.SYSTEM
+ error_type=ErrorType.SYSTEM,
)
debug_log(f"桌面應用啟動異常 [錯誤ID: {error_id}]: {e}")
return False
-
+
def set_web_server_port(self, port: int):
"""設置 Web 服務器端口"""
self.web_server_port = port
debug_log(f"設置 Web 服務器端口: {port}")
-
+
def is_electron_available(self) -> bool:
"""檢查 Electron 是否可用"""
try:
# 檢查 Node.js
- result = subprocess.run(['node', '--version'],
- capture_output=True,
- text=True,
- timeout=5)
+ result = subprocess.run(
+ ["node", "--version"],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ check=False,
+ )
if result.returncode != 0:
debug_log("Node.js 不可用")
return False
-
+
debug_log(f"Node.js 版本: {result.stdout.strip()}")
-
+
# 檢查 package.json 是否存在
package_json = self.desktop_dir / "package.json"
if not package_json.exists():
debug_log("package.json 不存在,需要在階段 2 中創建")
return False
-
+
return True
-
+
except Exception as e:
debug_log(f"Electron 可用性檢查失敗: {e}")
return False
-
+
async def ensure_dependencies(self) -> bool:
"""確保依賴已安裝"""
debug_log("檢查 Electron 依賴...")
@@ -137,13 +136,11 @@ class ElectronManager:
except Exception as e:
error_id = ErrorHandler.log_error_with_context(
- e,
- context={"operation": "依賴檢查"},
- error_type=ErrorType.DEPENDENCY
+ e, context={"operation": "依賴檢查"}, error_type=ErrorType.DEPENDENCY
)
debug_log(f"依賴檢查失敗 [錯誤ID: {error_id}]: {e}")
return False
-
+
def cleanup(self):
"""清理資源"""
if self.electron_process:
@@ -166,21 +163,30 @@ class ElectronManager:
# 關閉管道以避免 ResourceWarning
try:
# 對於 asyncio 子進程,需要特殊處理
- if hasattr(self.electron_process, 'stdout') and self.electron_process.stdout:
- if hasattr(self.electron_process.stdout, 'close'):
+ if (
+ hasattr(self.electron_process, "stdout")
+ and self.electron_process.stdout
+ ):
+ if hasattr(self.electron_process.stdout, "close"):
self.electron_process.stdout.close()
- if hasattr(self.electron_process, 'stderr') and self.electron_process.stderr:
- if hasattr(self.electron_process.stderr, 'close'):
+ if (
+ hasattr(self.electron_process, "stderr")
+ and self.electron_process.stderr
+ ):
+ if hasattr(self.electron_process.stderr, "close"):
self.electron_process.stderr.close()
- if hasattr(self.electron_process, 'stdin') and self.electron_process.stdin:
- if hasattr(self.electron_process.stdin, 'close'):
+ if (
+ hasattr(self.electron_process, "stdin")
+ and self.electron_process.stdin
+ ):
+ if hasattr(self.electron_process.stdin, "close"):
self.electron_process.stdin.close()
except Exception:
# 忽略管道關閉錯誤,這些通常是無害的
pass
self.electron_process = None
-
+
async def _create_package_json(self):
"""創建 package.json 文件"""
package_config = {
@@ -188,21 +194,15 @@ class ElectronManager:
"version": "2.3.0",
"description": "MCP Feedback Enhanced Desktop Application",
"main": "main.js",
- "scripts": {
- "start": "electron .",
- "dev": "electron . --dev"
- },
- "dependencies": {
- "electron": "^28.0.0"
- },
- "devDependencies": {
- "electron-builder": "^24.0.0"
- }
+ "scripts": {"start": "electron .", "dev": "electron . --dev"},
+ "dependencies": {"electron": "^28.0.0"},
+ "devDependencies": {"electron-builder": "^24.0.0"},
}
package_json_path = self.desktop_dir / "package.json"
- with open(package_json_path, 'w', encoding='utf-8') as f:
+ with open(package_json_path, "w", encoding="utf-8") as f:
import json
+
json.dump(package_config, f, indent=2, ensure_ascii=False)
debug_log(f"已創建 package.json: {package_json_path}")
@@ -213,12 +213,12 @@ class ElectronManager:
try:
# 使用 npm install
- install_cmd = ['npm', 'install']
+ install_cmd = ["npm", "install"]
process = await asyncio.create_subprocess_exec(
*install_cmd,
cwd=self.desktop_dir,
stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE
+ stderr=asyncio.subprocess.PIPE,
)
_, stderr = await process.communicate()
@@ -226,9 +226,8 @@ class ElectronManager:
if process.returncode == 0:
debug_log("Node.js 依賴安裝成功")
return True
- else:
- debug_log(f"依賴安裝失敗: {stderr.decode()}")
- return False
+ debug_log(f"依賴安裝失敗: {stderr.decode()}")
+ return False
except Exception as e:
debug_log(f"依賴安裝過程中出錯: {e}")
@@ -241,21 +240,29 @@ class ElectronManager:
try:
# 構建 Electron 命令 - 使用本地安裝的 electron
import platform
+
if platform.system() == "Windows":
- electron_path = self.desktop_dir / "node_modules" / ".bin" / "electron.cmd"
+ electron_path = (
+ self.desktop_dir / "node_modules" / ".bin" / "electron.cmd"
+ )
else:
electron_path = self.desktop_dir / "node_modules" / ".bin" / "electron"
if electron_path.exists():
electron_cmd = [
- str(electron_path), '.',
- '--port', str(self.web_server_port or 8765)
+ str(electron_path),
+ ".",
+ "--port",
+ str(self.web_server_port or 8765),
]
else:
# 回退到 npx
electron_cmd = [
- 'npx', 'electron', '.',
- '--port', str(self.web_server_port or 8765)
+ "npx",
+ "electron",
+ ".",
+ "--port",
+ str(self.web_server_port or 8765),
]
debug_log(f"使用 Electron 命令: {' '.join(electron_cmd)}")
@@ -265,7 +272,7 @@ class ElectronManager:
*electron_cmd,
cwd=self.desktop_dir,
stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE
+ stderr=asyncio.subprocess.PIPE,
)
debug_log(f"Electron 進程已啟動,PID: {self.electron_process.pid}")
@@ -277,16 +284,17 @@ class ElectronManager:
if self.electron_process.returncode is None:
debug_log("Electron 進程運行正常")
return True
- else:
- debug_log(f"Electron 進程異常退出,返回碼: {self.electron_process.returncode}")
- # 讀取錯誤輸出
- try:
- _, stderr = await self.electron_process.communicate()
- if stderr:
- debug_log(f"Electron 錯誤輸出: {stderr.decode()}")
- except Exception as e:
- debug_log(f"讀取 Electron 錯誤輸出失敗: {e}")
- return False
+ debug_log(
+ f"Electron 進程異常退出,返回碼: {self.electron_process.returncode}"
+ )
+ # 讀取錯誤輸出
+ try:
+ _, stderr = await self.electron_process.communicate()
+ if stderr:
+ debug_log(f"Electron 錯誤輸出: {stderr.decode()}")
+ except Exception as e:
+ debug_log(f"讀取 Electron 錯誤輸出失敗: {e}")
+ return False
except Exception as e:
debug_log(f"啟動 Electron 進程失敗: {e}")
@@ -301,9 +309,9 @@ class ElectronManager:
async def create_electron_manager() -> ElectronManager:
"""創建 Electron 管理器實例"""
manager = ElectronManager()
-
+
# 檢查可用性
if not manager.is_electron_available():
debug_log("Electron 環境不可用,建議使用 Web 模式")
-
+
return manager
diff --git a/src/mcp_feedback_enhanced/desktop/main.js b/src/mcp_feedback_enhanced/desktop/main.js
index d8e8847..6c42bd4 100644
--- a/src/mcp_feedback_enhanced/desktop/main.js
+++ b/src/mcp_feedback_enhanced/desktop/main.js
@@ -2,16 +2,16 @@
/**
* Electron 主進程
* ===============
- *
+ *
* 此文件是 MCP Feedback Enhanced 桌面應用的主進程入口點。
* 負責創建和管理 BrowserWindow,以及與現有 Web UI 的整合。
- *
+ *
* 主要功能:
* - 創建和管理應用視窗
* - 載入本地 Web 服務器內容
* - 處理應用生命週期事件
* - 提供桌面應用特有的功能
- *
+ *
* 作者: Augment Agent
* 版本: 2.3.0
*/
@@ -42,7 +42,7 @@ class ElectronApp {
this.mainWindow = null;
this.webServerPort = APP_CONFIG.defaultPort;
this.isDevMode = process.argv.includes('--dev');
-
+
this.setupEventHandlers();
this.parseCommandLineArgs();
}
@@ -53,7 +53,7 @@ class ElectronApp {
parseCommandLineArgs() {
const args = process.argv;
const portIndex = args.indexOf('--port');
-
+
if (portIndex !== -1 && portIndex + 1 < args.length) {
const port = parseInt(args[portIndex + 1]);
if (!isNaN(port) && port > 0 && port < 65536) {
@@ -197,7 +197,7 @@ class ElectronApp {
連接錯誤 - ${APP_CONFIG.name}