diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a789f09..f4e587e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,9 +9,9 @@ on: default: 'patch' type: choice options: - - patch # 2.0.0 -> 2.0.1 (bug fixes) - - minor # 2.0.0 -> 2.1.0 (new features) - - major # 2.0.0 -> 3.0.0 (breaking changes) + - patch # 2.0.0 -> 2.0.1 (bug fixes, security patches, documentation updates) + - minor # 2.0.0 -> 2.1.0 (new features, enhancements, backward-compatible changes) + - major # 2.0.0 -> 3.0.0 (breaking changes, architecture refactoring, API changes) jobs: release: @@ -70,167 +70,156 @@ jobs: NEW_VERSION="${{ steps.bump_version.outputs.new }}" sed -i "s/__version__ = \".*\"/__version__ = \"$NEW_VERSION\"/" src/mcp_feedback_enhanced/__init__.py - - name: Check Release Notes - id: check_notes + - name: Extract Release Highlights + id: extract_highlights run: | NEW_VERSION="v${{ steps.bump_version.outputs.new }}" - RELEASE_DIR="RELEASE_NOTES/${NEW_VERSION}" - - if [ ! -d "$RELEASE_DIR" ]; then - echo "❌ Error: Release notes directory not found at $RELEASE_DIR" - echo "Please create release notes before publishing!" - echo "Required files:" - echo " - $RELEASE_DIR/en.md" - echo " - $RELEASE_DIR/zh-TW.md" - exit 1 + + # Extract highlights from English CHANGELOG + if [ -f "RELEASE_NOTES/CHANGELOG.en.md" ]; then + # Find the section for the new version and extract highlights + awk "/## \[${NEW_VERSION}\]/{flag=1; next} /## \[/{flag=0} flag && /### 🌟 Highlights/{getline; while(getline && !/^###/ && !/^##/) if(/^[^[:space:]]/ || /^- /) print}' RELEASE_NOTES/CHANGELOG.en.md > highlights.txt + + # If no highlights section found, extract from new features + if [ ! -s highlights.txt ]; then + awk "/## \[${NEW_VERSION}\]/{flag=1; next} /## \[/{flag=0} flag && /### ✨ New Features/{getline; while(getline && !/^###/ && !/^##/) if(/^- /) print}' RELEASE_NOTES/CHANGELOG.en.md | head -4 > highlights.txt + fi + + echo "✅ Extracted highlights for $NEW_VERSION" + else + echo "⚠️ CHANGELOG.en.md not found, using default highlights" + echo "- 🚀 New features and improvements" > highlights.txt + echo "- 🐛 Bug fixes and optimizations" >> highlights.txt fi - - if [ ! -f "$RELEASE_DIR/en.md" ]; then - echo "❌ Error: English release notes not found at $RELEASE_DIR/en.md" - exit 1 - fi - - if [ ! -f "$RELEASE_DIR/zh-TW.md" ]; then - echo "❌ Error: Traditional Chinese release notes not found at $RELEASE_DIR/zh-TW.md" - exit 1 - fi - - if [ ! -f "$RELEASE_DIR/zh-CN.md" ]; then - echo "❌ Error: Simplified Chinese release notes not found at $RELEASE_DIR/zh-CN.md" - exit 1 - fi - - echo "✅ Release notes found for $NEW_VERSION" - echo "release_dir=$RELEASE_DIR" >> $GITHUB_OUTPUT - name: Generate Release Body id: release_body run: | NEW_VERSION="v${{ steps.bump_version.outputs.new }}" - RELEASE_DIR="${{ steps.check_notes.outputs.release_dir }}" - - # Create multi-language release body - cat > release_body.md << 'EOF' - ## 🌐 Multi-Language Release Notes - + + # Get release title from English CHANGELOG + RELEASE_TITLE=$(awk "/## \[${NEW_VERSION}\]/{print; exit}" RELEASE_NOTES/CHANGELOG.en.md | sed 's/## \[.*\] - //') + if [ -z "$RELEASE_TITLE" ]; then + RELEASE_TITLE="Latest Release" + fi + + # Create release body with highlights and links + cat > release_body.md << EOF + # Release ${NEW_VERSION} - ${RELEASE_TITLE} + + ## 🌟 Key Highlights + EOF + + # Add highlights + if [ -s highlights.txt ]; then + cat highlights.txt >> release_body.md + else + echo "- 🚀 New features and improvements" >> release_body.md + echo "- 🐛 Bug fixes and optimizations" >> release_body.md + fi + + # Add multi-language links section + cat >> release_body.md << 'EOF' + + ## 🌐 Detailed Release Notes + ### 🇺🇸 English - EOF - - # Add English content - cat "$RELEASE_DIR/en.md" >> release_body.md - - # Add separator - cat >> release_body.md << 'EOF' - - --- - + 📖 **[View Complete English Release Notes](https://github.com/Minidoracat/mcp-feedback-enhanced/blob/main/RELEASE_NOTES/CHANGELOG.en.md)** + ### 🇹🇼 繁體中文 - EOF - - # Add Traditional Chinese content - cat "$RELEASE_DIR/zh-TW.md" >> release_body.md - - # Add separator - cat >> release_body.md << 'EOF' - - --- - + 📖 **[查看完整繁體中文發布說明](https://github.com/Minidoracat/mcp-feedback-enhanced/blob/main/RELEASE_NOTES/CHANGELOG.zh-TW.md)** + ### 🇨🇳 简体中文 - EOF - - # Add Simplified Chinese content - cat "$RELEASE_DIR/zh-CN.md" >> release_body.md - - # Add installation section - cat >> release_body.md << 'EOF' - + 📖 **[查看完整简体中文发布说明](https://github.com/Minidoracat/mcp-feedback-enhanced/blob/main/RELEASE_NOTES/CHANGELOG.zh-CN.md)** + --- - - ## 📦 Installation & Update - + + ## 📦 Quick Installation / 快速安裝 + ```bash - # Quick test latest version - uvx mcp-feedback-enhanced@latest test - - # Update to this specific version + # Latest version / 最新版本 + uvx mcp-feedback-enhanced@latest + + # This specific version / 此特定版本 EOF - - echo "uvx mcp-feedback-enhanced@$NEW_VERSION test" >> release_body.md - + + echo "uvx mcp-feedback-enhanced@${NEW_VERSION}" >> release_body.md + cat >> release_body.md << 'EOF' ``` - + ## 🔗 Links - **Documentation**: [README.md](https://github.com/Minidoracat/mcp-feedback-enhanced/blob/main/README.md) - **Full Changelog**: [CHANGELOG](https://github.com/Minidoracat/mcp-feedback-enhanced/blob/main/RELEASE_NOTES/) - **Issues**: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) - + --- - **Release automatically generated from RELEASE_NOTES system** 🤖 + **Release automatically generated from CHANGELOG system** 🤖 EOF - + echo "Release body generated successfully" - - name: Sync CHANGELOG Files + - name: Verify CHANGELOG Files run: | NEW_VERSION="v${{ steps.bump_version.outputs.new }}" - RELEASE_DIR="${{ steps.check_notes.outputs.release_dir }}" - - # Function to add version to changelog - add_to_changelog() { - local lang_file="$1" - local changelog_file="$2" - local temp_file="changelog_temp.md" - - # Get the header and separator - head -n 5 "$changelog_file" > "$temp_file" - - # Add the new version content - cat "$lang_file" >> "$temp_file" - - # Add separator - echo "" >> "$temp_file" - echo "---" >> "$temp_file" - echo "" >> "$temp_file" - - # Add the rest of the changelog (skip header) - tail -n +7 "$changelog_file" >> "$temp_file" - - # Replace the original file - mv "$temp_file" "$changelog_file" - - echo "✅ Updated $changelog_file" - } - - # Check if CHANGELOG files exist + + # Check if CHANGELOG files exist and contain the new version + echo "🔍 Verifying CHANGELOG files contain version ${NEW_VERSION}..." + + MISSING_FILES="" + if [ -f "RELEASE_NOTES/CHANGELOG.en.md" ]; then - add_to_changelog "$RELEASE_DIR/en.md" "RELEASE_NOTES/CHANGELOG.en.md" + if ! grep -q "\[${NEW_VERSION}\]" "RELEASE_NOTES/CHANGELOG.en.md"; then + echo "⚠️ Warning: ${NEW_VERSION} not found in CHANGELOG.en.md" + MISSING_FILES="${MISSING_FILES} en" + else + echo "✅ Found ${NEW_VERSION} in CHANGELOG.en.md" + fi else - echo "⚠️ CHANGELOG.en.md not found, skipping" + echo "❌ CHANGELOG.en.md not found" + MISSING_FILES="${MISSING_FILES} en" fi - + if [ -f "RELEASE_NOTES/CHANGELOG.zh-TW.md" ]; then - add_to_changelog "$RELEASE_DIR/zh-TW.md" "RELEASE_NOTES/CHANGELOG.zh-TW.md" + if ! grep -q "\[${NEW_VERSION}\]" "RELEASE_NOTES/CHANGELOG.zh-TW.md"; then + echo "⚠️ Warning: ${NEW_VERSION} not found in CHANGELOG.zh-TW.md" + MISSING_FILES="${MISSING_FILES} zh-TW" + else + echo "✅ Found ${NEW_VERSION} in CHANGELOG.zh-TW.md" + fi else - echo "⚠️ CHANGELOG.zh-TW.md not found, skipping" + echo "❌ CHANGELOG.zh-TW.md not found" + MISSING_FILES="${MISSING_FILES} zh-TW" fi - + if [ -f "RELEASE_NOTES/CHANGELOG.zh-CN.md" ]; then - add_to_changelog "$RELEASE_DIR/zh-CN.md" "RELEASE_NOTES/CHANGELOG.zh-CN.md" + if ! grep -q "\[${NEW_VERSION}\]" "RELEASE_NOTES/CHANGELOG.zh-CN.md"; then + echo "⚠️ Warning: ${NEW_VERSION} not found in CHANGELOG.zh-CN.md" + MISSING_FILES="${MISSING_FILES} zh-CN" + else + echo "✅ Found ${NEW_VERSION} in CHANGELOG.zh-CN.md" + fi else - echo "⚠️ CHANGELOG.zh-CN.md not found, skipping" + echo "❌ CHANGELOG.zh-CN.md not found" + MISSING_FILES="${MISSING_FILES} zh-CN" + fi + + if [ -n "$MISSING_FILES" ]; then + echo "" + echo "📝 Note: Please ensure CHANGELOG files are updated with version ${NEW_VERSION}" + echo "Missing or incomplete files:${MISSING_FILES}" + echo "The release will continue, but manual CHANGELOG updates may be needed." + else + echo "✅ All CHANGELOG files verified successfully" fi - - echo "📝 CHANGELOG files synchronized" - - name: Commit version bump and changelog updates + - name: Commit version bump run: | git add . git commit -m "🔖 Release v${{ steps.bump_version.outputs.new }} - Updated version to ${{ steps.bump_version.outputs.new }} - - Synchronized CHANGELOG files with release notes - - Auto-generated from RELEASE_NOTES system" + - Auto-generated release from simplified workflow" git tag "v${{ steps.bump_version.outputs.new }}" - name: Build package @@ -268,9 +257,11 @@ jobs: echo "" 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 "📝 CHANGELOG files have been automatically updated" + echo "📝 Release notes generated from CHANGELOG files" echo "" echo "✅ Next steps:" echo " - Check the release on GitHub" echo " - Verify the package on PyPI" - echo " - Test installation with: uvx mcp-feedback-enhanced@v${{ steps.bump_version.outputs.new }} test" \ No newline at end of file + 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 diff --git a/README.md b/README.md index 2b7795e..dc74269 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ## 🎯 Core Concept -This is an [MCP server](https://modelcontextprotocol.io/) that establishes **feedback-oriented development workflows**, perfectly adapting to local, **SSH remote development environments**, and **WSL (Windows Subsystem for Linux) environments**. By guiding AI to confirm with users rather than making speculative operations, it can consolidate multiple tool calls into a single feedback-oriented request, dramatically reducing platform costs and improving development efficiency. +This is an [MCP server](https://modelcontextprotocol.io/) that establishes **feedback-oriented development workflows**, perfectly adapting to local, **SSH Remote environments** (Cursor SSH Remote, VS Code Remote SSH), and **WSL (Windows Subsystem for Linux) environments**. By guiding AI to confirm with users rather than making speculative operations, it can consolidate multiple tool calls into a single feedback-oriented request, dramatically reducing platform costs and improving development efficiency. **Supported Platforms:** [Cursor](https://www.cursor.com) | [Cline](https://cline.bot) | [Windsurf](https://windsurf.com) | [Augment](https://www.augmentcode.com) | [Trae](https://www.trae.ai) @@ -42,12 +42,19 @@ This is an [MCP server](https://modelcontextprotocol.io/) that establishes **fee - **Smart Detection**: Auto-select based on system language - **Live Switching**: Change language directly within interface -### ✨ WSL Environment Support (v2.2.5 New Feature) +### ✨ WSL Environment Support (v2.2.5) - **Auto Detection**: Intelligently identifies WSL (Windows Subsystem for Linux) environments - **Browser Integration**: Automatically launches Windows browser in WSL environments - **Multiple Launch Methods**: Supports `cmd.exe`, `powershell.exe`, `wslview` and other browser launch methods - **Seamless Experience**: WSL users can directly use Web UI without additional configuration +### 🌐 SSH Remote Environment Support (v2.3.0 New Feature) +- **Smart Detection**: Automatically identifies SSH Remote environments (Cursor SSH Remote, VS Code Remote SSH, etc.) +- **Browser Launch Guidance**: Provides clear solutions when browser cannot launch automatically +- **Port Forwarding Support**: Complete port forwarding setup guidance and troubleshooting +- **MCP Integration Optimization**: Improved integration with MCP system for more stable connection experience +- **Detailed Documentation**: [SSH Remote Environment Usage Guide](docs/en/ssh-remote/browser-launch-issues.md) + ## 🖥️ Interface Preview ### Qt GUI Interface (Refactored Version) @@ -134,6 +141,7 @@ For best results, add these rules to your AI assistant: |----------|---------|--------|---------| | `FORCE_WEB` | Force use Web UI | `true`/`false` | `false` | | `MCP_DEBUG` | Debug mode | `true`/`false` | `false` | +| `MCP_WEB_PORT` | Web UI port | `1024-65535` | `8765` | ### Testing Options ```bash @@ -178,20 +186,38 @@ uvx --with-editable . mcp-feedback-enhanced test --web # Test Web UI (auto co 📋 **Complete Version History:** [RELEASE_NOTES/CHANGELOG.en.md](RELEASE_NOTES/CHANGELOG.en.md) -### Latest Version Highlights (v2.2.5) -- ✨ **WSL Environment Support**: Added comprehensive support for WSL (Windows Subsystem for Linux) environments -- 🌐 **Smart Browser Launching**: Automatically invokes Windows browser in WSL environments with multiple launch methods -- 🎯 **Environment Detection Optimization**: Improved remote environment detection logic, WSL no longer misidentified as remote environment -- 🧪 **Testing Experience Improvement**: Test mode automatically attempts browser launching for better testing experience +### Latest Version Highlights (v2.3.0) +- 🌐 **SSH Remote Environment Support**: Solved Cursor SSH Remote browser launch issues with clear usage guidance +- 🛡️ **Error Message Improvements**: Provides more user-friendly error messages and solution suggestions when errors occur +- 🧹 **Auto-cleanup Features**: Automatically cleans temporary files and expired sessions to keep the system tidy +- 📊 **Memory Monitoring**: Monitors memory usage to prevent system resource shortage +- 🔧 **Connection Stability**: Improved Web UI connection stability and error handling ## 🐛 Common Issues +### 🌐 SSH Remote Environment Issues +**Q: Browser cannot launch in SSH Remote environment** +A: This is normal behavior. SSH Remote environments have no graphical interface, requiring manual opening in local browser. For detailed solutions, see: [SSH Remote Environment Usage Guide](docs/en/ssh-remote/browser-launch-issues.md) + +**Q: Why am I not receiving new MCP feedback?** +A: There might be a WebSocket connection issue. **Solution**: Simply refresh the browser page. + +**Q: Why isn't MCP being called?** +A: Please confirm the MCP tool status shows green light. **Solution**: Toggle the MCP tool on/off repeatedly, wait a few seconds for system reconnection. + +**Q: Augment cannot start MCP** +A: **Solution**: Completely close and restart VS Code or Cursor, then reopen the project. + +### 🔧 General Issues **Q: Getting "Unexpected token 'D'" error** A: Debug output interference. Set `MCP_DEBUG=false` or remove the environment variable. **Q: Chinese character garbled text** A: Fixed in v2.0.3. Update to latest version: `uvx mcp-feedback-enhanced@latest` +**Q: Multi-screen window disappearing or positioning errors** +A: Fixed in v2.1.1. Go to "⚙️ Settings" tab, check "Always show window at primary screen center" to resolve. Especially useful for T-shaped screen arrangements and other complex multi-monitor configurations. + **Q: Image upload fails** A: Check file size (≤1MB) and format (PNG/JPG/GIF/BMP/WebP). @@ -218,21 +244,11 @@ uv cache clean ``` For detailed instructions, see: [Cache Management Guide](docs/en/cache-management.md) -**Q: Gemini Pro 2.5 cannot parse images** -A: Known issue. Gemini Pro 2.5 may not correctly parse uploaded image content. Testing shows Claude-4-Sonnet can properly analyze images. Recommend using Claude models for better image understanding capabilities. - -**Q: Multi-screen window positioning issues** -A: Fixed in v2.1.1. Go to "⚙️ Settings" tab, check "Always show window at primary screen center" to resolve window positioning issues. Especially useful for T-shaped screen arrangements and other complex multi-monitor configurations. - -**Q: Cannot launch browser in WSL environment** -A: v2.2.5 has added WSL environment support. If issues persist: -1. Confirm WSL version (WSL 2 recommended) -2. Check if Windows browser is properly installed -3. Try manual test: run `cmd.exe /c start https://www.google.com` in WSL -4. If `wslu` package is installed, you can also try the `wslview` command - -**Q: WSL environment misidentified as remote environment** -A: v2.2.5 has fixed this issue. WSL environments are now correctly identified and use Web UI with Windows browser launching, instead of being misidentified as remote environments. +**Q: AI models cannot parse images** +A: Various AI models (including Gemini Pro 2.5, Claude, etc.) may have instability in image parsing, sometimes correctly identifying and sometimes unable to parse uploaded image content. This is a known limitation of AI visual understanding technology. Recommendations: +1. Ensure good image quality (high contrast, clear text) +2. Try uploading multiple times, retries usually succeed +3. If parsing continues to fail, try adjusting image size or format ## 🙏 Acknowledgments @@ -245,4 +261,15 @@ If you find this useful, please: - 📱 [Follow the original author](https://x.com/fabiomlferreira) ### Design Inspiration -**sanshao85** - [mcp-feedback-collector](https://github.com/sanshao85/mcp-feedback-collector) \ No newline at end of file +**sanshao85** - [mcp-feedback-collector](https://github.com/sanshao85/mcp-feedback-collector) + +### Community Support +- **Discord:** [https://discord.gg/Gur2V67](https://discord.gg/Gur2V67) +- **Issues:** [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) + +## 📄 License + +MIT License - see [LICENSE](LICENSE) file for details + +--- +**🌟 Welcome to Star and share with more developers!** \ No newline at end of file diff --git a/README.zh-CN.md b/README.zh-CN.md index 20bdc0d..b94e59a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -8,7 +8,7 @@ ## 🎯 核心概念 -这是一个 [MCP 服务器](https://modelcontextprotocol.io/),建立**反馈导向的开发工作流程**,完美适配本地、**SSH 远程开发环境**与 **WSL (Windows Subsystem for Linux) 环境**。通过引导 AI 与用户确认而非进行推测性操作,可将多次工具调用合并为单次反馈导向请求,大幅节省平台成本并提升开发效率。 +这是一个 [MCP 服务器](https://modelcontextprotocol.io/),建立**反馈导向的开发工作流程**,完美适配本地、**SSH Remote 环境**(Cursor SSH Remote、VS Code Remote SSH)与 **WSL (Windows Subsystem for Linux) 环境**。通过引导 AI 与用户确认而非进行推测性操作,可将多次工具调用合并为单次反馈导向请求,大幅节省平台成本并提升开发效率。 **支持平台:** [Cursor](https://www.cursor.com) | [Cline](https://cline.bot) | [Windsurf](https://windsurf.com) | [Augment](https://www.augmentcode.com) | [Trae](https://www.trae.ai) @@ -42,12 +42,19 @@ - **智能检测**:根据系统语言自动选择 - **即时切换**:界面内可直接切换语言 -### ✨ WSL 环境支持(v2.2.5 新功能) +### ✨ WSL 环境支持(v2.2.5) - **自动检测**:智能识别 WSL (Windows Subsystem for Linux) 环境 - **浏览器整合**:WSL 环境下自动启动 Windows 浏览器 - **多种启动方式**:支持 `cmd.exe`、`powershell.exe`、`wslview` 等多种浏览器启动方法 - **无缝体验**:WSL 用户可直接使用 Web UI,无需额外配置 +### 🌐 SSH Remote 环境支持(v2.3.0 新功能) +- **智能检测**:自动识别 SSH Remote 环境(Cursor SSH Remote、VS Code Remote SSH 等) +- **浏览器启动指引**:当无法自动启动浏览器时,提供清晰的解决方案 +- **端口转发支持**:完整的端口转发设置指引和故障排除 +- **MCP 整合优化**:改善与 MCP 系统的整合,提供更稳定的连接体验 +- **详细文档**:[SSH Remote 环境使用指南](docs/zh-CN/ssh-remote/browser-launch-issues.md) + ## 🖥️ 界面预览 ### Qt GUI 界面(重构版) @@ -180,25 +187,43 @@ uvx --with-editable . mcp-feedback-enhanced test --web # 测试 Web UI (自 📋 **完整版本更新记录:** [RELEASE_NOTES/CHANGELOG.zh-CN.md](RELEASE_NOTES/CHANGELOG.zh-CN.md) -### 最新版本亮点(v2.2.5) -- ✨ **WSL 环境支持**: 新增 WSL (Windows Subsystem for Linux) 环境的完整支持 -- 🌐 **智能浏览器启动**: WSL 环境下自动调用 Windows 浏览器,支持多种启动方式 -- 🎯 **环境检测优化**: 改进远程环境检测逻辑,WSL 不再被误判为远程环境 -- 🧪 **测试体验提升**: 测试模式下自动尝试启动浏览器,提供更好的测试体验 +### 最新版本亮点(v2.3.0) +- 🌐 **SSH Remote 环境支持**: 解决 Cursor SSH Remote 无法启动浏览器的问题,提供清晰的使用指引 +- 🛡️ **错误提示改善**: 当发生错误时,提供更友善的错误信息和解决建议 +- 🧹 **自动清理功能**: 自动清理临时文件和过期会话,保持系统整洁 +- 📊 **内存监控**: 监控内存使用情况,防止系统资源不足 +- 🔧 **连接稳定性**: 改善 Web UI 的连接稳定性和错误处理 ## 🐛 常见问题 +### 🌐 SSH Remote 环境问题 +**Q: SSH Remote 环境下浏览器无法启动** +A: 这是正常现象。SSH Remote 环境没有图形界面,需要手动在本地浏览器打开。详细解决方案请参考:[SSH Remote 环境使用指南](docs/zh-CN/ssh-remote/browser-launch-issues.md) + +**Q: 为什么没有接收到 MCP 新的反馈?** +A: 可能是 WebSocket 连接问题。**解决方法**:直接重新刷新浏览器页面。 + +**Q: 为什么没有调用出 MCP?** +A: 请确认 MCP 工具状态为绿灯。**解决方法**:反复开关 MCP 工具,等待几秒让系统重新连接。 + +**Q: Augment 无法启动 MCP** +A: **解决方法**:完全关闭并重新启动 VS Code 或 Cursor,重新打开项目。 + +### 🔧 一般问题 **Q: 出现 "Unexpected token 'D'" 错误** A: 调试输出干扰。设置 `MCP_DEBUG=false` 或移除该环境变量。 **Q: 中文字符乱码** A: 已在 v2.0.3 修复。更新到最新版本:`uvx mcp-feedback-enhanced@latest` +**Q: 多屏幕环境下窗口消失或定位错误** +A: 已在 v2.1.1 修复。进入「⚙️ 设置」标签页,勾选「总是在主屏幕中心显示窗口」即可解决。特别适用于 T 字型屏幕排列等复杂多屏幕配置。 + **Q: 图片上传失败** A: 检查文件大小(≤1MB)和格式(PNG/JPG/GIF/BMP/WebP)。 **Q: Web UI 无法启动** -A: 设置 `FORCE_WEB=true` 或检查火墙设定。 +A: 设置 `FORCE_WEB=true` 或检查防火墙设置。 **Q: UV Cache 占用过多磁盘空间** A: 由于频繁使用 `uvx` 命令,cache 可能会累积到数十 GB。建议定期清理: @@ -220,21 +245,11 @@ uv cache clean ``` 详细说明请参考:[Cache 管理指南](docs/zh-CN/cache-management.md) -**Q: Gemini Pro 2.5 无法解析图片** -A: 已知问题,Gemini Pro 2.5 可能无法正确解析上传的图片内容。实测 Claude-4-Sonnet 可以正常解析图片。建议使用 Claude 模型获得更好的图片理解能力。 - -**Q: 多屏幕视窗定位问题** -A: 已在 v2.1.1 修复。进入「⚙️ 设置」标签页,勾选「总是在主屏幕中心显示窗口」即可解决窗口定位问题。特别适用于 T 字型屏幕排列等复杂多屏幕配置。 - -**Q: WSL 环境下无法启动浏览器** -A: v2.2.5 已新增 WSL 环境支持。如果仍有问题: -1. 确认 WSL 版本(建议使用 WSL 2) -2. 检查 Windows 浏览器是否正常安装 -3. 尝试手动测试:在 WSL 中执行 `cmd.exe /c start https://www.google.com` -4. 如果安装了 `wslu` 套件,也可尝试 `wslview` 命令 - -**Q: WSL 环境被误判为远程环境** -A: v2.2.5 已修复此问题。WSL 环境现在会被正确识别并使用 Web UI 配合 Windows 浏览器启动,而不会被误判为远程环境。 +**Q: AI 模型无法解析图片** +A: 各种 AI 模型(包括 Gemini Pro 2.5、Claude 等)在图片解析上可能存在不稳定性,表现为有时能正确识别、有时无法解析上传的图片内容。这是 AI 视觉理解技术的已知限制。建议: +1. 确保图片质量良好(高对比度、清晰文字) +2. 多尝试几次上传,通常重试可以成功 +3. 如持续无法解析,可尝试调整图片大小或格式 ## 🙏 致谢 diff --git a/README.zh-TW.md b/README.zh-TW.md index 4320f90..0794410 100644 --- a/README.zh-TW.md +++ b/README.zh-TW.md @@ -42,12 +42,19 @@ - **智能偵測**:根據系統語言自動選擇 - **即時切換**:介面內可直接切換語言 -### ✨ WSL 環境支援(v2.2.5 新功能) +### ✨ WSL 環境支援(v2.2.5) - **自動檢測**:智能識別 WSL (Windows Subsystem for Linux) 環境 - **瀏覽器整合**:WSL 環境下自動啟動 Windows 瀏覽器 - **多種啟動方式**:支援 `cmd.exe`、`powershell.exe`、`wslview` 等多種瀏覽器啟動方法 - **無縫體驗**:WSL 用戶可直接使用 Web UI,無需額外配置 +### 🌐 SSH Remote 環境支援(v2.3.0 新功能) +- **智能檢測**:自動識別 SSH Remote 環境(Cursor SSH Remote、VS Code Remote SSH 等) +- **瀏覽器啟動指引**:當無法自動啟動瀏覽器時,提供清晰的解決方案 +- **端口轉發支援**:完整的端口轉發設定指引和故障排除 +- **MCP 整合優化**:改善與 MCP 系統的整合,提供更穩定的連接體驗 +- **詳細文檔**:[SSH Remote 環境使用指南](docs/zh-TW/ssh-remote/browser-launch-issues.md) + ## 🖥️ 介面預覽 ### Qt GUI 介面(重構版) @@ -180,14 +187,29 @@ uvx --with-editable . mcp-feedback-enhanced test --web # 測試 Web UI (自 📋 **完整版本更新記錄:** [RELEASE_NOTES/CHANGELOG.zh-TW.md](RELEASE_NOTES/CHANGELOG.zh-TW.md) -### 最新版本亮點(v2.2.5) -- ✨ **WSL 環境支援**: 新增 WSL (Windows Subsystem for Linux) 環境的完整支援 -- 🌐 **智能瀏覽器啟動**: WSL 環境下自動調用 Windows 瀏覽器,支援多種啟動方式 -- 🎯 **環境檢測優化**: 改進遠端環境檢測邏輯,WSL 不再被誤判為遠端環境 -- 🧪 **測試體驗提升**: 測試模式下自動嘗試啟動瀏覽器,提供更好的測試體驗 +### 最新版本亮點(v2.3.0) +- 🌐 **SSH Remote 環境支援**: 解決 Cursor SSH Remote 無法啟動瀏覽器的問題,提供清晰的使用指引 +- 🛡️ **錯誤提示改善**: 當發生錯誤時,提供更友善的錯誤訊息和解決建議 +- 🧹 **自動清理功能**: 自動清理臨時文件和過期會話,保持系統整潔 +- 📊 **記憶體監控**: 監控記憶體使用情況,防止系統資源不足 +- 🔧 **連線穩定性**: 改善 Web UI 的連線穩定性和錯誤處理 ## 🐛 常見問題 +### 🌐 SSH Remote 環境問題 +**Q: SSH Remote 環境下瀏覽器無法啟動** +A: 這是正常現象。SSH Remote 環境沒有圖形界面,需要手動在本地瀏覽器開啟。詳細解決方案請參考:[SSH Remote 環境使用指南](docs/zh-TW/ssh-remote/browser-launch-issues.md) + +**Q: 為什麼沒有接收到 MCP 新的反饋?** +A: 可能是 WebSocket 連接問題。**解決方法**:直接重新整理瀏覽器頁面。 + +**Q: 為什麼沒有呼叫出 MCP?** +A: 請確認 MCP 工具狀態為綠燈。**解決方法**:反覆開關 MCP 工具,等待幾秒讓系統重新連接。 + +**Q: Augment 無法啟動 MCP** +A: **解決方法**:完全關閉並重新啟動 VS Code 或 Cursor,重新開啟專案。 + +### 🔧 一般問題 **Q: 出現 "Unexpected token 'D'" 錯誤** A: 調試輸出干擾。設置 `MCP_DEBUG=false` 或移除該環境變數。 diff --git a/RELEASE_NOTES/CHANGELOG.en.md b/RELEASE_NOTES/CHANGELOG.en.md index 596f6f3..ae884a4 100644 --- a/RELEASE_NOTES/CHANGELOG.en.md +++ b/RELEASE_NOTES/CHANGELOG.en.md @@ -2,38 +2,32 @@ This document records all version updates for **MCP Feedback Enhanced**. -## [v2.2.5] - WSL Environment Support & Cross-Platform Enhancement -# Release v2.2.5 - WSL Environment Support & Cross-Platform Enhancement +## [v2.3.0] - System Stability & Resource Management Enhancement -## 🌟 Highlights -This version introduces comprehensive support for WSL (Windows Subsystem for Linux) environments, enabling WSL users to seamlessly use this tool with automatic Windows browser launching, significantly improving cross-platform development experience. +### 🌟 Highlights +This version focuses on improving system stability and user experience, particularly solving the browser launch issue in Cursor SSH Remote environments. -## ✨ New Features -- 🐧 **WSL Environment Detection**: Automatically identifies WSL environments and provides specialized support logic -- 🌐 **Smart Browser Launching**: Automatically invokes Windows browser in WSL environments with multiple launch methods -- 🔧 **Cross-Platform Testing Enhancement**: Test functionality integrates WSL detection for improved test coverage +### ✨ New Features +- 🌐 **SSH Remote Environment Support**: Solved Cursor SSH Remote browser launch issues with clear usage guidance +- 🛡️ **Error Message Improvements**: Provides more user-friendly error messages and solution suggestions when errors occur +- 🧹 **Auto-cleanup Features**: Automatically cleans temporary files and expired sessions to keep the system tidy +- 📊 **Memory Monitoring**: Monitors memory usage to prevent system resource shortage -## 🚀 Improvements -- 🎯 **Environment Detection Optimization**: Improved remote environment detection logic, WSL no longer misidentified as remote environment -- 📊 **System Information Enhancement**: System information tool now displays WSL environment status -- 🧪 **Testing Experience Improvement**: Test mode automatically attempts browser launching for better testing experience +### 🚀 Improvements +- 💾 **Resource Management Optimization**: Better system resource management for improved performance +- 🔧 **Enhanced Error Handling**: Provides clearer explanations and solutions when problems occur +- 🌐 **Connection Stability**: Improved Web UI connection stability +- 🖼️ **Image Upload Optimization**: Enhanced stability of image upload functionality -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reports: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Project Homepage: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) +### 🐛 Bug Fixes +- 🌐 **Connection Issues**: Fixed WebSocket connection related problems +- 🔄 **Session Management**: Fixed session state tracking issues +- 🖼️ **Image Processing**: Fixed event handling issues during image upload --- +## [v2.2.5] - WSL Environment Support & Cross-Platform Enhancement + ### ✨ New Features - 🐧 **WSL Environment Detection**: Automatically identifies WSL environments and provides specialized support logic - 🌐 **Smart Browser Launching**: Automatically invokes Windows browser in WSL environments with multiple launch methods diff --git a/RELEASE_NOTES/CHANGELOG.zh-CN.md b/RELEASE_NOTES/CHANGELOG.zh-CN.md index cfc7ea7..9a18a21 100644 --- a/RELEASE_NOTES/CHANGELOG.zh-CN.md +++ b/RELEASE_NOTES/CHANGELOG.zh-CN.md @@ -2,56 +2,27 @@ 本文件记录了 **MCP Feedback Enhanced** 的所有版本更新内容。 -## [v2.2.5] - WSL 环境支持与跨平台增强 -# Release v2.2.5 - WSL 环境支持与跨平台增强 +## [v2.3.0] - 系统稳定性与资源管理增强 -## 🌟 亮点 -本版本新增了 WSL (Windows Subsystem for Linux) 环境的完整支持,让 WSL 用户能够无缝使用本工具并自动启动 Windows 浏览器,大幅提升跨平台开发体验。 - -## ✨ 新功能 -- 🐧 **WSL 环境检测**: 自动识别 WSL 环境,提供专门的支持逻辑 -- 🌐 **智能浏览器启动**: WSL 环境下自动调用 Windows 浏览器,支持多种启动方式 -- 🔧 **跨平台测试增强**: 测试功能整合 WSL 检测,提升测试覆盖率 - -## 🚀 改进功能 -- 🎯 **环境检测优化**: 改进远程环境检测逻辑,WSL 不再被误判为远程环境 -- 📊 **系统信息增强**: 系统信息工具新增 WSL 环境状态显示 -- 🧪 **测试体验提升**: 测试模式下自动尝试启动浏览器,提供更好的测试体验 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 项目首页: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) - ---- +### 🌟 亮点 +本版本专注于提升系统稳定性和使用体验,特别解决了 Cursor SSH Remote 环境下无法启动浏览器的问题。 ### ✨ 新功能 -- 🐧 **WSL 环境检测**: 自动识别 WSL 环境,提供专门的支持逻辑 -- 🌐 **智能浏览器启动**: WSL 环境下自动调用 Windows 浏览器,支持多种启动方式 -- 🔧 **跨平台测试增强**: 测试功能整合 WSL 检测,提升测试覆盖率 +- 🌐 **SSH Remote 环境支持**: 解决 Cursor SSH Remote 无法启动浏览器的问题,提供清晰的使用指引 +- 🛡️ **错误提示改善**: 当发生错误时,提供更友善的错误信息和解决建议 +- 🧹 **自动清理功能**: 自动清理临时文件和过期会话,保持系统整洁 +- 📊 **内存监控**: 监控内存使用情况,防止系统资源不足 ### 🚀 改进功能 -- 🎯 **环境检测优化**: 改进远程环境检测逻辑,WSL 不再被误判为远程环境 -- 📊 **系统信息增强**: 系统信息工具新增 WSL 环境状态显示 -- 🧪 **测试体验提升**: 测试模式下自动尝试启动浏览器,提供更好的测试体验 - ---- - -## [v2.2.4] - GUI 体验优化与问题修复 +- 💾 **资源管理优化**: 更好地管理系统资源,提升运行效率 +- 🔧 **错误处理增强**: 遇到问题时提供更清楚的说明和解决方案 +- 🌐 **连接稳定性**: 改善 Web UI 的连接稳定性 +- 🖼️ **图片上传优化**: 改善图片上传功能的稳定性 ### 🐛 问题修复 -- 🖼️ **图片重复粘贴修复**: 解决 GUI 界面中使用 Ctrl+V 复制粘贴图片时出现重复粘贴的问题 -- 🌐 **语系切换修复**: 修复图片设定区域在语言切换时文字没有正确翻译的问题 -- 📝 **字体可读性改善**: 调整图片设定区域的字体大小,提升文字可读性 +- 🌐 **连接问题**: 修复 WebSocket 连接的相关问题 +- 🔄 **会话管理**: 修复会话状态跟踪的问题 +- 🖼️ **图片处理**: 修复图片上传时的事件处理问题 --- diff --git a/RELEASE_NOTES/CHANGELOG.zh-TW.md b/RELEASE_NOTES/CHANGELOG.zh-TW.md index 8f04371..bfaf78e 100644 --- a/RELEASE_NOTES/CHANGELOG.zh-TW.md +++ b/RELEASE_NOTES/CHANGELOG.zh-TW.md @@ -2,38 +2,32 @@ 本文件記錄了 **MCP Feedback Enhanced** 的所有版本更新內容。 -## [v2.2.5] - WSL 環境支援與跨平台增強 -# Release v2.2.5 - WSL 環境支援與跨平台增強 +## [v2.3.0] - 系統穩定性與資源管理增強 -## 🌟 亮點 -本版本新增了 WSL (Windows Subsystem for Linux) 環境的完整支援,讓 WSL 用戶能夠無縫使用本工具並自動啟動 Windows 瀏覽器,大幅提升跨平台開發體驗。 +### 🌟 亮點 +本版本專注於提升系統穩定性和使用體驗,特別解決了 Cursor SSH Remote 環境下無法啟動瀏覽器的問題。 -## ✨ 新功能 -- 🐧 **WSL 環境檢測**: 自動識別 WSL 環境,提供專門的支援邏輯 -- 🌐 **智能瀏覽器啟動**: WSL 環境下自動調用 Windows 瀏覽器,支援多種啟動方式 -- 🔧 **跨平台測試增強**: 測試功能整合 WSL 檢測,提升測試覆蓋率 +### ✨ 新功能 +- 🌐 **SSH Remote 環境支援**: 解決 Cursor SSH Remote 無法啟動瀏覽器的問題,提供清晰的使用指引 +- 🛡️ **錯誤提示改善**: 當發生錯誤時,提供更友善的錯誤訊息和解決建議 +- 🧹 **自動清理功能**: 自動清理臨時文件和過期會話,保持系統整潔 +- 📊 **記憶體監控**: 監控記憶體使用情況,防止系統資源不足 -## 🚀 改進功能 -- 🎯 **環境檢測優化**: 改進遠端環境檢測邏輯,WSL 不再被誤判為遠端環境 -- 📊 **系統資訊增強**: 系統資訊工具新增 WSL 環境狀態顯示 -- 🧪 **測試體驗提升**: 測試模式下自動嘗試啟動瀏覽器,提供更好的測試體驗 +### 🚀 改進功能 +- 💾 **資源管理優化**: 更好地管理系統資源,提升運行效率 +- 🔧 **錯誤處理增強**: 遇到問題時提供更清楚的說明和解決方案 +- 🌐 **連線穩定性**: 改善 Web UI 的連線穩定性 +- 🖼️ **圖片上傳優化**: 改善圖片上傳功能的穩定性 -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 專案首頁: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) +### 🐛 問題修復 +- 🌐 **連線問題**: 修復 WebSocket 連線的相關問題 +- 🔄 **會話管理**: 修復會話狀態追蹤的問題 +- 🖼️ **圖片處理**: 修復圖片上傳時的事件處理問題 --- +## [v2.2.5] - WSL 環境支援與跨平台增強 + ### ✨ 新功能 - 🐧 **WSL 環境檢測**: 自動識別 WSL 環境,提供專門的支援邏輯 - 🌐 **智能瀏覽器啟動**: WSL 環境下自動調用 Windows 瀏覽器,支援多種啟動方式 diff --git a/RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md b/RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md new file mode 100644 index 0000000..bece0c7 --- /dev/null +++ b/RELEASE_NOTES/SIMPLIFIED_WORKFLOW.md @@ -0,0 +1,147 @@ +# 簡化發布流程 / Simplified Release Workflow + +## 🎯 概述 / Overview + +此專案已採用簡化的發布流程,不再需要建立版本化目錄(如 `v2.3.0/`),而是直接更新 CHANGELOG 文件。 + +This project now uses a simplified release workflow that no longer requires creating versioned directories (like `v2.3.0/`), but instead directly updates CHANGELOG files. + +## 📋 新的發布流程 / New Release Process + +### 1. 更新 CHANGELOG 文件 / Update CHANGELOG Files + +在發布前,請手動更新以下三個文件: +Before releasing, manually update these three files: + +- `RELEASE_NOTES/CHANGELOG.en.md` +- `RELEASE_NOTES/CHANGELOG.zh-TW.md` +- `RELEASE_NOTES/CHANGELOG.zh-CN.md` + +### 2. CHANGELOG 格式要求 / CHANGELOG Format Requirements + +每個新版本應該按照以下格式添加到 CHANGELOG 文件的頂部: +Each new version should be added to the top of CHANGELOG files in this format: + +```markdown +## [v2.3.0] - 版本標題 / Version Title + +### 🌟 亮點 / Highlights +本次發佈的主要特色... + +### ✨ 新功能 / New Features +- 🆕 **功能名稱**: 功能描述 + +### 🐛 錯誤修復 / Bug Fixes +- 🔧 **問題修復**: 修復描述 + +### 🚀 改進功能 / Improvements +- ⚡ **效能優化**: 優化描述 + +--- +``` + +### 3. 執行發布 / Execute Release + +1. 確保所有 CHANGELOG 文件都已更新 + Ensure all CHANGELOG files are updated + +2. 前往 GitHub Actions 頁面 + Go to GitHub Actions page + +3. 執行 "Auto Release to PyPI" workflow + Run "Auto Release to PyPI" workflow + +4. 選擇版本類型(patch/minor/major) + Select version type (patch/minor/major) + +### 📊 版本類型說明 / Version Type Explanation + +選擇適當的版本類型非常重要,請根據變更內容選擇: +Choosing the appropriate version type is important, select based on the changes: + +#### 🔧 Patch (修補版本) +- **用途 / Usage**: 錯誤修復、小幅改進、安全修補 +- **範例 / Example**: `2.3.0 → 2.3.1` +- **適用情況 / When to use**: + - 🐛 修復 bug / Bug fixes + - 🔒 安全性修補 / Security patches + - 📝 文檔更新 / Documentation updates + - 🎨 小幅 UI 調整 / Minor UI tweaks + +#### ✨ Minor (次要版本) +- **用途 / Usage**: 新功能、功能增強、向後相容的變更 +- **範例 / Example**: `2.3.0 → 2.4.0` +- **適用情況 / When to use**: + - 🆕 新增功能 / New features + - 🚀 功能增強 / Feature enhancements + - 🎯 效能改進 / Performance improvements + - 🌐 新的語言支援 / New language support + +#### 🚨 Major (主要版本) +- **用途 / Usage**: 重大變更、不向後相容的修改、架構重構 +- **範例 / Example**: `2.3.0 → 3.0.0` +- **適用情況 / When to use**: + - 💥 破壞性變更 / Breaking changes + - 🏗️ 架構重構 / Architecture refactoring + - 🔄 API 變更 / API changes + - 📦 依賴項重大更新 / Major dependency updates + +#### 🤔 如何選擇 / How to Choose + +**問自己這些問題 / Ask yourself these questions**: + +1. **會破壞現有功能嗎?** / **Will it break existing functionality?** + - 是 / Yes → Major + - 否 / No → 繼續下一個問題 / Continue to next question + +2. **是否新增了功能?** / **Does it add new functionality?** + - 是 / Yes → Minor + - 否 / No → 繼續下一個問題 / Continue to next question + +3. **只是修復或小幅改進?** / **Just fixes or minor improvements?** + - 是 / Yes → Patch + +## 🔄 自動化流程 / Automated Process + +GitHub workflow 將自動: +The GitHub workflow will automatically: + +1. ✅ 版本號碼升級 / Version bump +2. ✅ 從 CHANGELOG 提取亮點 / Extract highlights from CHANGELOG +3. ✅ 生成多語系 GitHub Release / Generate multi-language GitHub Release +4. ✅ 發布到 PyPI / Publish to PyPI +5. ✅ 建立 Git 標籤 / Create Git tags + +## 📦 GitHub Release 格式 / GitHub Release Format + +自動生成的 Release 將包含: +Auto-generated releases will include: + +- 🌟 版本亮點 / Version highlights +- 🌐 多語系 CHANGELOG 連結 / Multi-language CHANGELOG links +- 📦 安裝指令 / Installation commands +- 🔗 相關連結 / Related links + +## ⚠️ 注意事項 / Important Notes + +1. **不再需要版本目錄**:舊的 `RELEASE_NOTES/v2.x.x/` 目錄結構已棄用 + **No more version directories**: Old `RELEASE_NOTES/v2.x.x/` directory structure is deprecated + +2. **手動更新 CHANGELOG**:發布前必須手動更新 CHANGELOG 文件 + **Manual CHANGELOG updates**: CHANGELOG files must be manually updated before release + +3. **格式一致性**:請保持 CHANGELOG 格式的一致性以確保自動提取正常運作 + **Format consistency**: Maintain CHANGELOG format consistency for proper auto-extraction + +## 🗂️ 舊版本目錄清理 / Old Version Directory Cleanup + +現有的版本目錄(`v2.2.1` 到 `v2.2.5`)可以選擇性保留作為歷史記錄,或者清理以簡化專案結構。 + +Existing version directories (`v2.2.1` to `v2.2.5`) can optionally be kept for historical records or cleaned up to simplify project structure. + +## 🚀 優點 / Benefits + +- ✅ 減少維護負擔 / Reduced maintenance burden +- ✅ 單一真實來源 / Single source of truth +- ✅ 簡化的專案結構 / Simplified project structure +- ✅ 自動化的 Release 生成 / Automated release generation diff --git a/RELEASE_NOTES/v2.2.1/en.md b/RELEASE_NOTES/v2.2.1/en.md deleted file mode 100644 index bbaa681..0000000 --- a/RELEASE_NOTES/v2.2.1/en.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.1 - Window Optimization & Unified Settings Interface - -## 🌟 Highlights -This release primarily addresses GUI window size constraints, implements smart window state saving mechanisms, and optimizes the unified settings interface. - -## 🚀 Improvements -- 🖥️ **Window Size Constraint Removal**: Removed GUI main window minimum size limit from 1000×800 to 400×300, allowing users to freely adjust window size for different use cases -- 💾 **Real-time Window State Saving**: Implemented real-time saving mechanism for window size and position changes, with debounce delay to avoid excessive I/O operations -- ⚙️ **Unified Settings Interface Optimization**: Improved GUI settings page configuration saving logic to avoid setting conflicts, ensuring correct window positioning and size settings -- 🎯 **Smart Window Size Saving**: In "Always center display" mode, correctly saves window size (but not position); in "Smart positioning" mode, saves complete window state - -## 🐛 Bug Fixes -- 🔧 **Window Size Constraint**: Fixed GUI window unable to resize to small dimensions issue (fixes #10 part one) -- 🛡️ **Setting Conflicts**: Fixed potential configuration conflicts during settings save operations - -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.1 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reporting: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Issues Addressed: #10 (partially completed) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.1/zh-CN.md b/RELEASE_NOTES/v2.2.1/zh-CN.md deleted file mode 100644 index 793c239..0000000 --- a/RELEASE_NOTES/v2.2.1/zh-CN.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.1 - 窗口优化与统一设置接口 - -## 🌟 亮点 -本版本主要解决了 GUI 窗口大小限制问题,实现了窗口状态的智能保存机制,并优化了设置接口的统一性。 - -## 🚀 改进功能 -- 🖥️ **窗口大小限制解除**: 解除 GUI 主窗口最小大小限制,从 1000×800 降至 400×300,让用户可以自由调整窗口大小以符合不同使用场景 -- 💾 **窗口状态实时保存**: 实现窗口大小与位置的即时保存机制,支持防抖延迟避免过度频繁的 I/O 操作 -- ⚙️ **统一设置接口优化**: 改进 GUI 设置版面的配置保存逻辑,避免设置冲突,确保窗口定位与大小设置的正确性 -- 🎯 **智能窗口大小保存**: 「总是在主屏幕中心显示」模式下正确保存窗口大小(但不保存位置),「智能定位」模式下保存完整的窗口状态 - -## 🐛 问题修复 -- 🔧 **窗口大小限制**: 解决 GUI 窗口无法调整至小尺寸的问题 (fixes #10 第一部分) -- 🛡️ **设置冲突**: 修复设置保存时可能出现的配置冲突问题 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.1 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 解决问题: #10 (部分完成) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.1/zh-TW.md b/RELEASE_NOTES/v2.2.1/zh-TW.md deleted file mode 100644 index aca5a3f..0000000 --- a/RELEASE_NOTES/v2.2.1/zh-TW.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.1 - 視窗優化與統一設定接口 - -## 🌟 亮點 -本版本主要解決了 GUI 視窗大小限制問題,實現了視窗狀態的智能保存機制,並優化了設定接口的統一性。 - -## 🚀 改進功能 -- 🖥️ **視窗大小限制解除**: 解除 GUI 主視窗最小大小限制,從 1000×800 降至 400×300,讓用戶可以自由調整視窗大小以符合不同使用場景 -- 💾 **視窗狀態實時保存**: 實現視窗大小與位置的即時保存機制,支援防抖延遲避免過度頻繁的 I/O 操作 -- ⚙️ **統一設定接口優化**: 改進 GUI 設定版面的配置保存邏輯,避免設定衝突,確保視窗定位與大小設定的正確性 -- 🎯 **智能視窗大小保存**: 「總是在主螢幕中心顯示」模式下正確保存視窗大小(但不保存位置),「智能定位」模式下保存完整的視窗狀態 - -## 🐛 問題修復 -- 🔧 **視窗大小限制**: 解決 GUI 視窗無法調整至小尺寸的問題 (fixes #10 第一部分) -- 🛡️ **設定衝突**: 修復設定保存時可能出現的配置衝突問題 - -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.1 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 解決問題: #10 (部分完成) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.2/en.md b/RELEASE_NOTES/v2.2.2/en.md deleted file mode 100644 index 69a134d..0000000 --- a/RELEASE_NOTES/v2.2.2/en.md +++ /dev/null @@ -1,30 +0,0 @@ -# Release v2.2.2 - Timeout Auto-cleanup Fix - -## 🌟 Highlights -This version fixes a critical resource management issue where GUI/Web UI interfaces were not properly closed when MCP sessions ended due to timeout, causing the interfaces to remain open and unresponsive. - -## 🐛 Bug Fixes -- 🔄 **Timeout Auto-cleanup**: Fixed GUI/Web UI not automatically closing after MCP session timeout (default 600 seconds) -- 🛡️ **Resource Management Optimization**: Improved timeout handling mechanism to ensure proper cleanup and closure of all UI resources on timeout -- ⚡ **Enhanced Timeout Detection**: Strengthened timeout detection logic to correctly handle timeout events in various scenarios -- 🔧 **Interface Response Improvement**: Enhanced Web UI frontend handling of session timeout events - -## 🚀 Technical Improvements -- 📦 **Web Session Management**: Refactored WebFeedbackSession timeout handling logic -- 🎯 **QTimer Integration**: Introduced precise QTimer timeout control mechanism in GUI -- 🌐 **Frontend Communication Optimization**: Improved timeout message communication between Web UI frontend and backend -- 🧹 **Resource Cleanup Mechanism**: Added _cleanup_resources_on_timeout method to ensure thorough cleanup - -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.2 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reporting: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Fixed Issue: #5 (GUI/Web UI timeout cleanup) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.2/zh-CN.md b/RELEASE_NOTES/v2.2.2/zh-CN.md deleted file mode 100644 index 7e8437f..0000000 --- a/RELEASE_NOTES/v2.2.2/zh-CN.md +++ /dev/null @@ -1,30 +0,0 @@ -# Release v2.2.2 - 超时自动清理修复 - -## 🌟 亮点 -本版本修复了一个重要的资源管理问题:当 MCP session 因超时结束时,GUI/Web UI 界面没有正确关闭,导致界面持续显示而无法正常关闭。 - -## 🐛 问题修复 -- 🔄 **超时自动清理**: 修复 GUI/Web UI 在 MCP session timeout (默认 600 秒) 后没有自动关闭的问题 -- 🛡️ **资源管理优化**: 改进超时处理机制,确保在超时时正确清理和关闭所有 UI 资源 -- ⚡ **超时检测增强**: 加强超时检测逻辑,确保在各种情况下都能正确处理超时事件 -- 🔧 **界面响应改进**: 改善 Web UI 前端对 session timeout 事件的处理响应 - -## 🚀 技术改进 -- 📦 **Web Session 管理**: 重构 WebFeedbackSession 的超时处理逻辑 -- 🎯 **QTimer 整合**: 在 GUI 中引入精确的 QTimer 超时控制机制 -- 🌐 **前端通信优化**: 改进 Web UI 前端与后端的超时消息传递 -- 🧹 **资源清理机制**: 新增 _cleanup_resources_on_timeout 方法确保彻底清理 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.2 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 解决问题: #5 (GUI/Web UI timeout cleanup) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.2/zh-TW.md b/RELEASE_NOTES/v2.2.2/zh-TW.md deleted file mode 100644 index c83280b..0000000 --- a/RELEASE_NOTES/v2.2.2/zh-TW.md +++ /dev/null @@ -1,30 +0,0 @@ -# Release v2.2.2 - 超時自動清理修復 - -## 🌟 亮點 -本版本修復了一個重要的資源管理問題:當 MCP session 因超時結束時,GUI/Web UI 介面沒有正確關閉,導致介面持續顯示而無法正常關閉。 - -## 🐛 問題修復 -- 🔄 **超時自動清理**: 修復 GUI/Web UI 在 MCP session timeout (預設 600 秒) 後沒有自動關閉的問題 -- 🛡️ **資源管理優化**: 改進超時處理機制,確保在超時時正確清理和關閉所有 UI 資源 -- ⚡ **超時檢測增強**: 加強超時檢測邏輯,確保在各種情況下都能正確處理超時事件 -- 🔧 **介面回應改進**: 改善 Web UI 前端對 session timeout 事件的處理回應 - -## 🚀 技術改進 -- 📦 **Web Session 管理**: 重構 WebFeedbackSession 的超時處理邏輯 -- 🎯 **QTimer 整合**: 在 GUI 中引入精確的 QTimer 超時控制機制 -- 🌐 **前端通訊優化**: 改進 Web UI 前端與後端的超時訊息傳遞 -- 🧹 **資源清理機制**: 新增 _cleanup_resources_on_timeout 方法確保徹底清理 - -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.2 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 解決問題: #5 (GUI/Web UI timeout cleanup) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.3/en.md b/RELEASE_NOTES/v2.2.3/en.md deleted file mode 100644 index daae09f..0000000 --- a/RELEASE_NOTES/v2.2.3/en.md +++ /dev/null @@ -1,42 +0,0 @@ -# Release v2.2.3 - Timeout Control & Image Settings Enhancement - -## 🌟 Highlights -This version introduces user-controllable timeout settings and flexible image upload configuration options, while improving UV Cache management tools to enhance the overall user experience. - -## ✨ New Features -- ⏰ **User Timeout Control**: Added customizable timeout settings with flexible range from 30 seconds to 2 hours -- ⏱️ **Countdown Timer**: Real-time countdown timer display at the top of the interface for visual time reminders -- 🖼️ **Image Size Limits**: Added image upload size limit settings (unlimited/1MB/3MB/5MB) -- 🔧 **Base64 Compatibility Mode**: Added Base64 detail mode to improve image recognition compatibility with AI models -- 🧹 **UV Cache Management Tool**: Added `cleanup_cache.py` script to help manage and clean UV cache space - -## 🚀 Improvements -- 📚 **Documentation Structure Optimization**: Reorganized documentation directory structure, moved images to `docs/{language}/images/` paths -- 📖 **Cache Management Guide**: Added detailed UV Cache management guide with automated cleanup solutions -- 🎯 **Smart Compatibility Hints**: Automatically display Base64 compatibility mode suggestions when image upload fails -- 🔄 **Settings Sync Mechanism**: Improved image settings synchronization between different interface modes - -## 🐛 Bug Fixes -- 🛡️ **Timeout Handling Optimization**: Improved coordination between user-defined timeout and MCP system timeout -- 🖥️ **Interface Auto-close**: Fixed interface auto-close and resource cleanup logic after timeout -- 📱 **Responsive Layout**: Optimized timeout control component display on small screen devices - -## 🔧 Technical Improvements -- 🎛️ **Timeout Control Architecture**: Implemented separated design for frontend countdown timer and backend timeout handling -- 📊 **Image Processing Optimization**: Improved image upload size checking and format validation mechanisms -- 🗂️ **Settings Persistence**: Enhanced settings saving mechanism to ensure correct saving and loading of user preferences -- 🧰 **Tool Script Enhancement**: Added cross-platform cache cleanup tool with support for force cleanup and preview modes - -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.3 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reporting: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Related PRs: #22 (Timeout Control Feature), #19 (Image Settings Feature) diff --git a/RELEASE_NOTES/v2.2.3/zh-CN.md b/RELEASE_NOTES/v2.2.3/zh-CN.md deleted file mode 100644 index 766aace..0000000 --- a/RELEASE_NOTES/v2.2.3/zh-CN.md +++ /dev/null @@ -1,42 +0,0 @@ -# Release v2.2.3 - 超时控制与图片设置增强 - -## 🌟 亮点 -本版本新增了用户可控制的超时设置功能,以及灵活的图片上传设置选项,同时完善了 UV Cache 管理工具,提升整体使用体验。 - -## ✨ 新功能 -- ⏰ **用户超时控制**: 新增可自定义的超时设置功能,支持 30 秒至 2 小时的弹性设置 -- ⏱️ **倒数计时器**: 界面顶部显示实时倒数计时器,提供可视化的时间提醒 -- 🖼️ **图片大小限制**: 新增图片上传大小限制设置(无限制/1MB/3MB/5MB) -- 🔧 **Base64 兼容模式**: 新增 Base64 详细模式,提升部分 AI 模型的图片识别兼容性 -- 🧹 **UV Cache 管理工具**: 新增 `cleanup_cache.py` 脚本,协助管理和清理 UV cache 空间 - -## 🚀 改进功能 -- 📚 **文档结构优化**: 重新整理文档目录结构,将图片移至 `docs/{语言}/images/` 路径 -- 📖 **Cache 管理指南**: 新增详细的 UV Cache 管理指南,包含自动化清理方案 -- 🎯 **智能兼容性提示**: 当图片上传失败时自动显示 Base64 兼容模式建议 -- 🔄 **设置同步机制**: 改进图片设置在不同界面模式间的同步机制 - -## 🐛 问题修复 -- 🛡️ **超时处理优化**: 改进用户自定义超时与 MCP 系统超时的协调机制 -- 🖥️ **界面自动关闭**: 修复超时后界面自动关闭和资源清理逻辑 -- 📱 **响应式布局**: 优化超时控制组件在小屏幕设备上的显示效果 - -## 🔧 技术改进 -- 🎛️ **超时控制架构**: 实现前端倒数计时器与后端超时处理的分离设计 -- 📊 **图片处理优化**: 改进图片上传的大小检查和格式验证机制 -- 🗂️ **设置持久化**: 增强设置保存机制,确保用户偏好的正确保存和载入 -- 🧰 **工具脚本增强**: 新增跨平台的 cache 清理工具,支持强制清理和预览模式 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.3 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 相关 PR: #22 (超时控制功能), #19 (图片设置功能) diff --git a/RELEASE_NOTES/v2.2.3/zh-TW.md b/RELEASE_NOTES/v2.2.3/zh-TW.md deleted file mode 100644 index ce761b4..0000000 --- a/RELEASE_NOTES/v2.2.3/zh-TW.md +++ /dev/null @@ -1,42 +0,0 @@ -# Release v2.2.3 - 超時控制與圖片設定增強 - -## 🌟 亮點 -本版本新增了用戶可控制的超時設定功能,以及靈活的圖片上傳設定選項,同時完善了 UV Cache 管理工具,提升整體使用體驗。 - -## ✨ 新功能 -- ⏰ **用戶超時控制**: 新增可自訂的超時設定功能,支援 30 秒至 2 小時的彈性設定 -- ⏱️ **倒數計時器**: 介面頂部顯示即時倒數計時器,提供視覺化的時間提醒 -- 🖼️ **圖片大小限制**: 新增圖片上傳大小限制設定(無限制/1MB/3MB/5MB) -- 🔧 **Base64 相容模式**: 新增 Base64 詳細模式,提升部分 AI 模型的圖片識別相容性 -- 🧹 **UV Cache 管理工具**: 新增 `cleanup_cache.py` 腳本,協助管理和清理 UV cache 空間 - -## 🚀 改進功能 -- 📚 **文檔結構優化**: 重新整理文檔目錄結構,將圖片移至 `docs/{語言}/images/` 路徑 -- 📖 **Cache 管理指南**: 新增詳細的 UV Cache 管理指南,包含自動化清理方案 -- 🎯 **智能相容性提示**: 當圖片上傳失敗時自動顯示 Base64 相容模式建議 -- 🔄 **設定同步機制**: 改進圖片設定在不同介面模式間的同步機制 - -## 🐛 問題修復 -- 🛡️ **超時處理優化**: 改進用戶自訂超時與 MCP 系統超時的協調機制 -- 🖥️ **介面自動關閉**: 修復超時後介面自動關閉和資源清理邏輯 -- 📱 **響應式佈局**: 優化超時控制元件在小螢幕設備上的顯示效果 - -## 🔧 技術改進 -- 🎛️ **超時控制架構**: 實現前端倒數計時器與後端超時處理的分離設計 -- 📊 **圖片處理優化**: 改進圖片上傳的大小檢查和格式驗證機制 -- 🗂️ **設定持久化**: 增強設定保存機制,確保用戶偏好的正確保存和載入 -- 🧰 **工具腳本增強**: 新增跨平台的 cache 清理工具,支援強制清理和預覽模式 - -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.3 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 相關 PR: #22 (超時控制功能), #19 (圖片設定功能) diff --git a/RELEASE_NOTES/v2.2.4/en.md b/RELEASE_NOTES/v2.2.4/en.md deleted file mode 100644 index 80eba52..0000000 --- a/RELEASE_NOTES/v2.2.4/en.md +++ /dev/null @@ -1,23 +0,0 @@ -# Release v2.2.4 - GUI Experience Optimization & Bug Fixes - -## 🌟 Highlights -This version focuses on GUI user experience optimization, fixing image copy-paste duplication issues, reorganizing localization file structure, and improving interface text readability. - -## 🐛 Bug Fixes -- 🖼️ **Image Duplicate Paste Fix**: Fixed the issue where Ctrl+V image pasting in GUI would create duplicate images -- 🌐 **Localization Switch Fix**: Fixed image settings area text not translating correctly when switching languages -- 📝 **Font Readability Improvement**: Adjusted font sizes in image settings area for better readability - -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.4 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reports: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Project Homepage: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.4/zh-CN.md b/RELEASE_NOTES/v2.2.4/zh-CN.md deleted file mode 100644 index 827a39d..0000000 --- a/RELEASE_NOTES/v2.2.4/zh-CN.md +++ /dev/null @@ -1,23 +0,0 @@ -# Release v2.2.4 - GUI 体验优化与问题修复 - -## 🌟 亮点 -本版本专注于 GUI 使用体验的优化,修复了图片复制粘贴的重复问题,重新组织了语系文件结构,并改善了界面文字的可读性。 - -## 🐛 问题修复 -- 🖼️ **图片重复粘贴修复**: 解决 GUI 界面中使用 Ctrl+V 复制粘贴图片时出现重复粘贴的问题 -- 🌐 **语系切换修复**: 修复图片设定区域在语言切换时文字没有正确翻译的问题 -- 📝 **字体可读性改善**: 调整图片设定区域的字体大小,提升文字可读性 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.4 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 项目首页: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.4/zh-TW.md b/RELEASE_NOTES/v2.2.4/zh-TW.md deleted file mode 100644 index 05ff546..0000000 --- a/RELEASE_NOTES/v2.2.4/zh-TW.md +++ /dev/null @@ -1,23 +0,0 @@ -# Release v2.2.4 - GUI 體驗優化與問題修復 - -## 🌟 亮點 -本版本專注於 GUI 使用體驗的優化,修復了圖片複製貼上的重複問題,重新組織了語系檔案結構,並改善了介面文字的可讀性。 - -## 🐛 問題修復 -- 🖼️ **圖片重複貼上修復**: 解決 GUI 介面中使用 Ctrl+V 複製貼上圖片時出現重複貼上的問題 -- 🌐 **語系切換修復**: 修復圖片設定區域在語言切換時文字沒有正確翻譯的問題 -- 📝 **字體可讀性改善**: 調整圖片設定區域的字體大小,提升文字可讀性 - -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.4 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 專案首頁: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) \ No newline at end of file diff --git a/RELEASE_NOTES/v2.2.5/en.md b/RELEASE_NOTES/v2.2.5/en.md deleted file mode 100644 index c7a4033..0000000 --- a/RELEASE_NOTES/v2.2.5/en.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.5 - WSL Environment Support & Cross-Platform Enhancement - -## 🌟 Highlights -This version introduces comprehensive support for WSL (Windows Subsystem for Linux) environments, enabling WSL users to seamlessly use this tool with automatic Windows browser launching, significantly improving cross-platform development experience. - -## ✨ New Features -- 🐧 **WSL Environment Detection**: Automatically identifies WSL environments and provides specialized support logic -- 🌐 **Smart Browser Launching**: Automatically invokes Windows browser in WSL environments with multiple launch methods -- 🔧 **Cross-Platform Testing Enhancement**: Test functionality integrates WSL detection for improved test coverage - -## 🚀 Improvements -- 🎯 **Environment Detection Optimization**: Improved remote environment detection logic, WSL no longer misidentified as remote environment -- 📊 **System Information Enhancement**: System information tool now displays WSL environment status -- 🧪 **Testing Experience Improvement**: Test mode automatically attempts browser launching for better testing experience - -## 📦 Installation & Update -```bash -# Quick test latest version -uvx mcp-feedback-enhanced@latest test --gui - -# Update to specific version -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 Related Links -- Full Documentation: [README.md](../../README.md) -- Issue Reports: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- Project Homepage: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) diff --git a/RELEASE_NOTES/v2.2.5/zh-CN.md b/RELEASE_NOTES/v2.2.5/zh-CN.md deleted file mode 100644 index 717cf73..0000000 --- a/RELEASE_NOTES/v2.2.5/zh-CN.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.5 - WSL 环境支持与跨平台增强 - -## 🌟 亮点 -本版本新增了 WSL (Windows Subsystem for Linux) 环境的完整支持,让 WSL 用户能够无缝使用本工具并自动启动 Windows 浏览器,大幅提升跨平台开发体验。 - -## ✨ 新功能 -- 🐧 **WSL 环境检测**: 自动识别 WSL 环境,提供专门的支持逻辑 -- 🌐 **智能浏览器启动**: WSL 环境下自动调用 Windows 浏览器,支持多种启动方式 -- 🔧 **跨平台测试增强**: 测试功能整合 WSL 检测,提升测试覆盖率 - -## 🚀 改进功能 -- 🎯 **环境检测优化**: 改进远程环境检测逻辑,WSL 不再被误判为远程环境 -- 📊 **系统信息增强**: 系统信息工具新增 WSL 环境状态显示 -- 🧪 **测试体验提升**: 测试模式下自动尝试启动浏览器,提供更好的测试体验 - -## 📦 安装与更新 -```bash -# 快速测试最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 相关链接 -- 完整文档: [README.zh-CN.md](../../README.zh-CN.md) -- 问题报告: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 项目首页: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) diff --git a/RELEASE_NOTES/v2.2.5/zh-TW.md b/RELEASE_NOTES/v2.2.5/zh-TW.md deleted file mode 100644 index 666d7e6..0000000 --- a/RELEASE_NOTES/v2.2.5/zh-TW.md +++ /dev/null @@ -1,28 +0,0 @@ -# Release v2.2.5 - WSL 環境支援與跨平台增強 - -## 🌟 亮點 -本版本新增了 WSL (Windows Subsystem for Linux) 環境的完整支援,讓 WSL 用戶能夠無縫使用本工具並自動啟動 Windows 瀏覽器,大幅提升跨平台開發體驗。 - -## ✨ 新功能 -- 🐧 **WSL 環境檢測**: 自動識別 WSL 環境,提供專門的支援邏輯 -- 🌐 **智能瀏覽器啟動**: WSL 環境下自動調用 Windows 瀏覽器,支援多種啟動方式 -- 🔧 **跨平台測試增強**: 測試功能整合 WSL 檢測,提升測試覆蓋率 - -## 🚀 改進功能 -- 🎯 **環境檢測優化**: 改進遠端環境檢測邏輯,WSL 不再被誤判為遠端環境 -- 📊 **系統資訊增強**: 系統資訊工具新增 WSL 環境狀態顯示 -- 🧪 **測試體驗提升**: 測試模式下自動嘗試啟動瀏覽器,提供更好的測試體驗 - -## 📦 安裝與更新 -```bash -# 快速測試最新版本 -uvx mcp-feedback-enhanced@latest test --gui - -# 更新到特定版本 -uvx mcp-feedback-enhanced@v2.2.5 test -``` - -## 🔗 相關連結 -- 完整文檔: [README.zh-TW.md](../../README.zh-TW.md) -- 問題回報: [GitHub Issues](https://github.com/Minidoracat/mcp-feedback-enhanced/issues) -- 專案首頁: [GitHub Repository](https://github.com/Minidoracat/mcp-feedback-enhanced) diff --git a/docs/en/images/ssh-remote-connect-url.png b/docs/en/images/ssh-remote-connect-url.png new file mode 100644 index 0000000..7c67e88 Binary files /dev/null and b/docs/en/images/ssh-remote-connect-url.png differ diff --git a/docs/en/images/ssh-remote-debug-port.png b/docs/en/images/ssh-remote-debug-port.png new file mode 100644 index 0000000..ed04927 Binary files /dev/null and b/docs/en/images/ssh-remote-debug-port.png differ diff --git a/docs/en/images/ssh-remote-port-setting.png b/docs/en/images/ssh-remote-port-setting.png new file mode 100644 index 0000000..80a64bc Binary files /dev/null and b/docs/en/images/ssh-remote-port-setting.png differ diff --git a/docs/en/ssh-remote/browser-launch-issues.md b/docs/en/ssh-remote/browser-launch-issues.md new file mode 100644 index 0000000..cbe0d30 --- /dev/null +++ b/docs/en/ssh-remote/browser-launch-issues.md @@ -0,0 +1,115 @@ +# SSH Remote Environment Browser Launch Issues Solution + +## Problem Description + +When using MCP Feedback Enhanced in SSH Remote environments (such as Cursor SSH Remote, VS Code Remote SSH, etc.), you may encounter the following issues: + +- 🚫 Browser cannot launch automatically +- ❌ "Unable to launch browser" error message +- 🔗 Web UI cannot open in local browser + +## Root Cause Analysis + +SSH Remote environment limitations: +1. **Display Environment Isolation**: Remote server has no graphical interface environment +2. **Network Isolation**: Remote ports cannot be directly accessed locally +3. **No Browser Available**: Remote environments typically don't have browsers installed + +## Solution + +### Step 1: Configure Port (Optional) + +MCP Feedback Enhanced uses port **8765** by default, but you can customize the port: + + + +### Step 2: Wait for MCP Call + +**Important**: Do not manually start the Web UI. Instead, wait for the AI model to call the MCP tool to automatically start it. + +When the AI model calls the `interactive_feedback` tool, the system will automatically start the Web UI. + +### Step 3: Check Port and Connect + +If the browser doesn't launch automatically, you need to manually connect to the Web UI: + +#### Method 1: Check Port Forwarding +Check your SSH Remote environment's port forwarding settings to find the corresponding local port: + + + +#### Method 2: Use Debug Mode +Enable Debug mode in your IDE, select "Output" → "MCP Log" to see the Web UI URL: + + + +### Step 4: Open in Local Browser + +1. Copy the URL (usually `http://localhost:8765` or another port) +2. Paste and open in your local browser +3. Start using the Web UI for feedback + +## Port Forwarding Setup + +### VS Code Remote SSH +1. Press `Ctrl+Shift+P` in VS Code +2. Type "Forward a Port" +3. Enter the port number (default 8765) +4. Access `http://localhost:8765` in your local browser + +### Cursor SSH Remote +1. Check Cursor's port forwarding settings +2. Manually add port forwarding rule (port 8765) +3. Access the forwarded port in your local browser + +## Important Reminders + +### ⚠️ Do Not Start Manually +**Do NOT** manually execute commands like `uvx mcp-feedback-enhanced test --web`, as this cannot integrate with the MCP system. + +### ✅ Correct Process +1. Wait for AI model to call MCP tool +2. System automatically starts Web UI +3. Check port forwarding or Debug logs +4. Open corresponding URL in local browser + +## Frequently Asked Questions + +### Q: Why can't the browser launch automatically in SSH Remote environment? +A: SSH Remote environment is headless with no graphical interface, so browsers cannot be launched directly. You need to access through port forwarding in your local browser. + +### Q: How to confirm if Web UI started successfully? +A: Check IDE's Debug output or MCP Log. If you see "Web UI started" message, it means successful startup. + +### Q: What if the port is occupied? +A: Modify the port number in MCP settings, or wait for the system to automatically select another available port. + +### Q: Can't find port forwarding settings? +A: Check your SSH Remote tool documentation, or use Debug mode to view the URL in MCP Log. + +### Q: Why am I not receiving new MCP feedback? +A: There might be a WebSocket connection issue. **Solution**: Simply refresh the browser page to re-establish the WebSocket connection. + +### Q: Why isn't MCP being called? +A: Please confirm the MCP tool status shows green light (indicating normal operation). **Solution**: +- Check the MCP tool status indicator in your IDE +- If not green, try toggling the MCP tool on/off repeatedly +- Wait a few seconds for the system to reconnect + +### Q: Why can't Augment start MCP? +A: Sometimes errors may prevent the MCP tool from showing green status. **Solution**: +- Completely close and restart VS Code or Cursor +- Reopen the project +- Wait for MCP tool to reload and show green light + +## v2.3.0 Improvements + +Improvements for SSH Remote environments in this version: +- ✅ Automatic SSH Remote environment detection +- ✅ Clear guidance when browser cannot launch +- ✅ Display correct access URL +- ✅ Improved error messages and solution suggestions + +## Related Resources + +- [Main Documentation](../../README.md) diff --git a/docs/zh-CN/images/ssh-remote-connect-url.png b/docs/zh-CN/images/ssh-remote-connect-url.png new file mode 100644 index 0000000..7c67e88 Binary files /dev/null and b/docs/zh-CN/images/ssh-remote-connect-url.png differ diff --git a/docs/zh-CN/images/ssh-remote-debug-port.png b/docs/zh-CN/images/ssh-remote-debug-port.png new file mode 100644 index 0000000..ed04927 Binary files /dev/null and b/docs/zh-CN/images/ssh-remote-debug-port.png differ diff --git a/docs/zh-CN/images/ssh-remote-port-setting.png b/docs/zh-CN/images/ssh-remote-port-setting.png new file mode 100644 index 0000000..80a64bc Binary files /dev/null and b/docs/zh-CN/images/ssh-remote-port-setting.png differ diff --git a/docs/zh-CN/ssh-remote/browser-launch-issues.md b/docs/zh-CN/ssh-remote/browser-launch-issues.md new file mode 100644 index 0000000..ae6e539 --- /dev/null +++ b/docs/zh-CN/ssh-remote/browser-launch-issues.md @@ -0,0 +1,115 @@ +# SSH Remote 环境浏览器启动问题解决方案 + +## 问题描述 + +在 SSH Remote 环境(如 Cursor SSH Remote、VS Code Remote SSH 等)中使用 MCP Feedback Enhanced 时,可能会遇到以下问题: + +- 🚫 浏览器无法自动启动 +- ❌ 显示「无法启动浏览器」错误 +- 🔗 Web UI 无法在本地浏览器中打开 + +## 原因分析 + +SSH Remote 环境的限制: +1. **显示环境隔离**: 远程服务器没有图形界面环境 +2. **网络隔离**: 远程端口无法直接在本地访问 +3. **浏览器不存在**: 远程环境通常没有安装浏览器 + +## 解决方案 + +### 步骤一:设置端口(可选) + +MCP Feedback Enhanced 默认使用端口 **8765**,您也可以自定义端口: + + + +### 步骤二:等待 MCP 调用 + +**重要**:不要手动启动 Web UI,而是要等待 AI 模型调用 MCP 工具时自动启动。 + +当 AI 模型调用 `interactive_feedback` 工具时,系统会自动启动 Web UI。 + +### 步骤三:查看端口并连接 + +如果浏览器没有自动启动,您需要手动连接到 Web UI: + +#### 方法一:查看端口转发 +查看您的 SSH Remote 环境的端口转发设置,找到对应的本地端口: + + + +#### 方法二:使用 Debug 模式查看 +在 IDE 中开启 Debug 模式,选择「输出」→「MCP Log」,可以看到 Web UI 的 URL: + + + +### 步骤四:在本地浏览器打开 + +1. 复制 URL(通常是 `http://localhost:8765` 或其他端口) +2. 在本地浏览器中粘贴并打开 +3. 开始使用 Web UI 进行反馈 + +## 端口转发设置 + +### VS Code Remote SSH +1. 在 VS Code 中按 `Ctrl+Shift+P` +2. 输入 "Forward a Port" +3. 输入端口号(默认 8765) +4. 在本地浏览器中访问 `http://localhost:8765` + +### Cursor SSH Remote +1. 查看 Cursor 的端口转发设置 +2. 手动添加端口转发规则(端口 8765) +3. 在本地浏览器中访问转发的端口 + +## 重要提醒 + +### ⚠️ 不要手动启动 +**请勿**手动执行 `uvx mcp-feedback-enhanced test --web` 等指令,这样无法与 MCP 系统整合。 + +### ✅ 正确流程 +1. 等待 AI 模型调用 MCP 工具 +2. 系统自动启动 Web UI +3. 查看端口转发或 Debug 日志 +4. 在本地浏览器中打开对应 URL + +## 常见问题 + +### Q: 为什么在 SSH Remote 环境中无法自动打开浏览器? +A: SSH Remote 环境是无头环境(headless),没有图形界面,因此无法直接启动浏览器。需要通过端口转发在本地浏览器中访问。 + +### Q: 如何确认 Web UI 是否正常启动? +A: 查看 IDE 的 Debug 输出或 MCP Log,如果看到 "Web UI 已启动" 的信息,表示启动成功。 + +### Q: 端口被占用怎么办? +A: 在 MCP 设置中修改端口号,或者等待系统自动选择其他可用端口。 + +### Q: 找不到端口转发设置怎么办? +A: 查看您的 SSH Remote 工具文档,或使用 Debug 模式查看 MCP Log 中的 URL。 + +### Q: 为什么没有接收到 MCP 新的反馈? +A: 可能是 WebSocket 连接有问题。**解决方法**:直接重新刷新浏览器页面,这会重新建立 WebSocket 连接。 + +### Q: 为什么没有调用出 MCP? +A: 请确认 MCP 工具状态为绿灯(表示正常运作)。**解决方法**: +- 检查 IDE 中的 MCP 工具状态指示灯 +- 如果不是绿灯,尝试反复开关 MCP 工具 +- 等待几秒钟让系统重新连接 + +### Q: 为什么 Augment 无法启动 MCP? +A: 有时候可能会有错误导致 MCP 工具没有显示绿灯状态。**解决方法**: +- 完全关闭并重新启动 VS Code 或 Cursor +- 重新打开项目 +- 等待 MCP 工具重新加载并显示绿灯 + +## v2.3.0 改进 + +本版本针对 SSH Remote 环境的改进: +- ✅ 自动检测 SSH Remote 环境 +- ✅ 在无法启动浏览器时提供清晰的指引 +- ✅ 显示正确的访问 URL +- ✅ 改善错误提示和解决建议 + +## 相关资源 + +- [主要文档](../../README.zh-CN.md) diff --git a/docs/zh-TW/images/ssh-remote-connect-url.png b/docs/zh-TW/images/ssh-remote-connect-url.png new file mode 100644 index 0000000..7c67e88 Binary files /dev/null and b/docs/zh-TW/images/ssh-remote-connect-url.png differ diff --git a/docs/zh-TW/images/ssh-remote-debug-port.png b/docs/zh-TW/images/ssh-remote-debug-port.png new file mode 100644 index 0000000..ed04927 Binary files /dev/null and b/docs/zh-TW/images/ssh-remote-debug-port.png differ diff --git a/docs/zh-TW/images/ssh-remote-port-setting.png b/docs/zh-TW/images/ssh-remote-port-setting.png new file mode 100644 index 0000000..80a64bc Binary files /dev/null and b/docs/zh-TW/images/ssh-remote-port-setting.png differ diff --git a/docs/zh-TW/ssh-remote/browser-launch-issues.md b/docs/zh-TW/ssh-remote/browser-launch-issues.md new file mode 100644 index 0000000..d386007 --- /dev/null +++ b/docs/zh-TW/ssh-remote/browser-launch-issues.md @@ -0,0 +1,103 @@ +# SSH Remote 環境瀏覽器啟動問題解決方案 + +## 問題描述 + +在 SSH Remote 環境(如 Cursor SSH Remote、VS Code Remote SSH 、WSL 等)中使用 MCP Feedback Enhanced 時,可能會遇到以下問題: + +- 🚫 瀏覽器無法自動啟動 +- ❌ 顯示「無法啟動瀏覽器」錯誤 +- 🔗 Web UI 無法在本地瀏覽器中開啟 + +## 原因分析 + +SSH Remote 環境的限制: +1. **顯示環境隔離**: 遠端伺服器沒有圖形界面環境 +2. **網路隔離**: 遠端端口無法直接在本地訪問 +3. **瀏覽器不存在**: 遠端環境通常沒有安裝瀏覽器 + +## 解決方案 + +### 步驟一:設定端口(可選) + +MCP Feedback Enhanced 預設使用端口 **8765**,您也可以自定義端口: + + + +### 步驟二:等待 MCP 呼叫 + +**重要**:不要手動啟動 Web UI,而是要等待 AI 模型呼叫 MCP 工具時自動啟動。 + +當 AI 模型呼叫 `interactive_feedback` 工具時,系統會自動啟動 Web UI。 + +### 步驟三:查看端口並連接 + +如果瀏覽器沒有自動啟動,您需要手動連接到 Web UI: + +#### 方法一:查看端口轉發 +查看您的 SSH Remote 環境的端口轉發設定,找到對應的本地端口: + + + +#### 方法二:使用 Debug 模式查看 +在 IDE 中開啟 Debug 模式,選擇「輸出」→「MCP Log」,可以看到 Web UI 的 URL: + + + +### 步驟四:在本地瀏覽器開啟 + +1. 複製 URL(通常是 `http://localhost:8765` 或其他端口) +2. 在本地瀏覽器中貼上並開啟 +3. 開始使用 Web UI 進行回饋 + +## 端口轉發設定 + +### VS Code Remote SSH +1. 在 VS Code 中按 `Ctrl+Shift+P` +2. 輸入 "Forward a Port" +3. 輸入端口號(預設 8765) +4. 在本地瀏覽器中訪問 `http://localhost:8765` + +### Cursor SSH Remote +1. 查看 Cursor 的端口轉發設定 +2. 手動添加端口轉發規則(端口 8765) +3. 在本地瀏覽器中訪問轉發的端口 + +## 重要提醒 + +### ⚠️ 不要手動啟動 +**請勿**手動執行 `uvx mcp-feedback-enhanced test --web` 等指令,這樣無法與 MCP 系統整合。 + +### ✅ 正確流程 +1. 等待 AI 模型呼叫 MCP 工具 +2. 系統自動啟動 Web UI +3. 查看端口轉發或 Debug 日誌 +4. 在本地瀏覽器中開啟對應 URL + +## 常見問題 + +### Q: 為什麼在 SSH Remote 環境中無法自動開啟瀏覽器? +A: SSH Remote 環境是無頭環境(headless),沒有圖形界面,因此無法直接啟動瀏覽器。需要通過端口轉發在本地瀏覽器中訪問。 + +### Q: 如何確認 Web UI 是否正常啟動? +A: 查看 IDE 的 Debug 輸出或 MCP Log,如果看到 "Web UI 已啟動" 的訊息,表示啟動成功。 + +### Q: 端口被占用怎麼辦? +A: 在 MCP 設定中修改端口號,或者等待系統自動選擇其他可用端口。 + +### Q: 找不到端口轉發設定怎麼辦? +A: 查看您的 SSH Remote 工具文檔,或使用 Debug 模式查看 MCP Log 中的 URL。 + +### Q: 為什麼沒有接收到 MCP 新的反饋? +A: 可能是 WebSocket 連接有問題。**解決方法**:直接重新整理瀏覽器頁面,這會重新建立 WebSocket 連接。 + +### Q: 為什麼沒有呼叫出 MCP? +A: 請確認 MCP 工具狀態為綠燈(表示正常運作)。**解決方法**: +- 檢查 IDE 中的 MCP 工具狀態指示燈 +- 如果不是綠燈,嘗試反覆開關 MCP 工具 +- 等待幾秒鐘讓系統重新連接 + +### Q: 為什麼 Augment 無法啟動 MCP? +A: 有時候可能會有錯誤導致 MCP 工具沒有顯示綠燈狀態。**解決方法**: +- 完全關閉並重新啟動 VS Code 或 Cursor +- 重新開啟專案 +- 等待 MCP 工具重新載入並顯示綠燈 diff --git a/issues/WSL環境預設使用WebUI修復.md b/issues/WSL環境預設使用WebUI修復.md new file mode 100644 index 0000000..53c215b --- /dev/null +++ b/issues/WSL環境預設使用WebUI修復.md @@ -0,0 +1,86 @@ +# WSL 環境預設使用 Web UI 修復 + +## 任務描述 +修復 WSL 環境中 MCP 服務器錯誤地偵測為可使用 GUI 的問題。WSL 環境應該預設使用 Web UI,因為大多數 WSL 安裝都是 Linux 環境,沒有桌面應用支援。 + +## 問題分析 +根據 MCP log 顯示: +``` +[SERVER] 偵測到 WSL 環境(通過 /proc/version) +[SERVER] WSL 環境不被視為遠端環境 +[SERVER] 成功載入 PySide6,可使用 GUI +[SERVER] GUI 可用: True +``` + +問題在於 `can_use_gui()` 函數沒有考慮 WSL 環境的特殊性: +- WSL 環境不被視為遠端環境(正確) +- 但 WSL 環境中即使 PySide6 可以載入,也應該預設使用 Web UI + +## 解決方案 +採用方案 1:在 `can_use_gui()` 函數中直接檢查 WSL 環境 + +### 修改內容 +1. **文件**:`src\mcp_feedback_enhanced\server.py` +2. **函數**:`can_use_gui()` (第 203-230 行) +3. **修改邏輯**: + - 保持現有的遠端環境檢查 + - 在遠端環境檢查後,添加 WSL 環境檢查 + - 如果是 WSL 環境,直接返回 `False` + - 保持其餘 PySide6 載入檢查邏輯不變 + +### 修改前後對比 +**修改前**: +```python +def can_use_gui() -> bool: + if is_remote_environment(): + return False + + try: + from PySide6.QtWidgets import QApplication + debug_log("成功載入 PySide6,可使用 GUI") + return True + # ... +``` + +**修改後**: +```python +def can_use_gui() -> bool: + if is_remote_environment(): + return False + + # WSL 環境預設使用 Web UI + if is_wsl_environment(): + debug_log("WSL 環境偵測到,預設使用 Web UI") + return False + + try: + from PySide6.QtWidgets import QApplication + debug_log("成功載入 PySide6,可使用 GUI") + return True + # ... +``` + +## 預期結果 +修改後,WSL 環境的 MCP log 應該顯示: +``` +[SERVER] 偵測到 WSL 環境(通過 /proc/version) +[SERVER] WSL 環境不被視為遠端環境 +[SERVER] WSL 環境偵測到,預設使用 Web UI +[SERVER] GUI 可用: False +[SERVER] 建議介面: Web UI +``` + +## 影響範圍 +- ✅ WSL 環境將預設使用 Web UI +- ✅ 不影響其他環境的邏輯 +- ✅ 保持向後兼容性 +- ✅ 用戶仍可通過 `FORCE_WEB` 環境變數控制介面選擇 + +## 測試建議 +1. 在 WSL 環境中測試 MCP 服務器啟動 +2. 驗證日誌顯示正確的環境偵測結果 +3. 確認使用 Web UI 而非 GUI +4. 測試 `FORCE_WEB` 環境變數仍然有效 + +## 完成時間 +2025-06-08 01:45:00 diff --git a/src/mcp_feedback_enhanced/gui/locales/en/translations.json b/src/mcp_feedback_enhanced/gui/locales/en/translations.json index a1e8819..ebde0b5 100644 --- a/src/mcp_feedback_enhanced/gui/locales/en/translations.json +++ b/src/mcp_feedback_enhanced/gui/locales/en/translations.json @@ -192,7 +192,46 @@ "confirmClearAll": "Are you sure you want to clear all {count} images?", "confirmClearTitle": "Confirm Clear", "fileSizeExceeded": "Image {filename} size is {size}MB, exceeding 1MB limit!\nRecommend using image editing software to compress before uploading.", - "dataSizeExceeded": "Image {filename} data size exceeds 1MB limit!" + "dataSizeExceeded": "Image {filename} data size exceeds 1MB limit!", + "types": { + "network": "Network connection issue", + "file_io": "File read/write issue", + "process": "Process execution issue", + "timeout": "Operation timeout", + "user_cancel": "User cancelled the operation", + "system": "System issue", + "permission": "Insufficient permissions", + "validation": "Data validation failed", + "dependency": "Dependency issue", + "config": "Configuration issue" + }, + "solutions": { + "network": [ + "Check network connection", + "Verify firewall settings", + "Try restarting the application" + ], + "file_io": [ + "Check if file exists", + "Verify file permissions", + "Check available disk space" + ], + "process": [ + "Check if process is running", + "Verify system resources", + "Try restarting related services" + ], + "timeout": [ + "Increase timeout settings", + "Check network latency", + "Retry the operation later" + ], + "permission": [ + "Run as administrator", + "Check file/directory permissions", + "Contact system administrator" + ] + } }, "languageSelector": "🌐 Language", "languageNames": { diff --git a/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json b/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json index 2f30086..f415dd9 100644 --- a/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json +++ b/src/mcp_feedback_enhanced/gui/locales/zh-CN/translations.json @@ -172,7 +172,46 @@ "confirmClearAll": "确定要清除所有 {count} 张图片吗?", "confirmClearTitle": "确认清除", "fileSizeExceeded": "图片 {filename} 大小为 {size}MB,超过 1MB 限制!\n建议使用图片编辑软件压缩后再上传。", - "dataSizeExceeded": "图片 {filename} 数据大小超过 1MB 限制!" + "dataSizeExceeded": "图片 {filename} 数据大小超过 1MB 限制!", + "types": { + "network": "网络连接出现问题", + "file_io": "文件读写出现问题", + "process": "进程执行出现问题", + "timeout": "操作超时", + "user_cancel": "用户取消了操作", + "system": "系统出现问题", + "permission": "权限不足", + "validation": "数据验证失败", + "dependency": "依赖组件出现问题", + "config": "配置出现问题" + }, + "solutions": { + "network": [ + "检查网络连接是否正常", + "确认防火墙设置", + "尝试重新启动应用程序" + ], + "file_io": [ + "检查文件是否存在", + "确认文件权限", + "检查磁盘空间是否足够" + ], + "process": [ + "检查进程是否正在运行", + "确认系统资源是否足够", + "尝试重新启动相关服务" + ], + "timeout": [ + "增加超时时间设置", + "检查网络延迟", + "稍后重试操作" + ], + "permission": [ + "以管理员身份运行", + "检查文件/目录权限", + "联系系统管理员" + ] + } }, "aiSummary": "AI 工作摘要", "languageSelector": "🌐 语言选择", diff --git a/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json b/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json index d134d75..80dc117 100644 --- a/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json +++ b/src/mcp_feedback_enhanced/gui/locales/zh-TW/translations.json @@ -188,7 +188,46 @@ "confirmClearAll": "確定要清除所有 {count} 張圖片嗎?", "confirmClearTitle": "確認清除", "fileSizeExceeded": "圖片 {filename} 大小為 {size}MB,超過 1MB 限制!\n建議使用圖片編輯軟體壓縮後再上傳。", - "dataSizeExceeded": "圖片 {filename} 數據大小超過 1MB 限制!" + "dataSizeExceeded": "圖片 {filename} 數據大小超過 1MB 限制!", + "types": { + "network": "網絡連接出現問題", + "file_io": "文件讀寫出現問題", + "process": "進程執行出現問題", + "timeout": "操作超時", + "user_cancel": "用戶取消了操作", + "system": "系統出現問題", + "permission": "權限不足", + "validation": "數據驗證失敗", + "dependency": "依賴組件出現問題", + "config": "配置出現問題" + }, + "solutions": { + "network": [ + "檢查網絡連接是否正常", + "確認防火牆設置", + "嘗試重新啟動應用程序" + ], + "file_io": [ + "檢查文件是否存在", + "確認文件權限", + "檢查磁盤空間是否足夠" + ], + "process": [ + "檢查進程是否正在運行", + "確認系統資源是否足夠", + "嘗試重新啟動相關服務" + ], + "timeout": [ + "增加超時時間設置", + "檢查網絡延遲", + "稍後重試操作" + ], + "permission": [ + "以管理員身份運行", + "檢查文件/目錄權限", + "聯繫系統管理員" + ] + } }, "languageNames": { "zhTw": "繁體中文", diff --git a/src/mcp_feedback_enhanced/gui/widgets/image_upload.py b/src/mcp_feedback_enhanced/gui/widgets/image_upload.py index f2e79bf..88332a1 100644 --- a/src/mcp_feedback_enhanced/gui/widgets/image_upload.py +++ b/src/mcp_feedback_enhanced/gui/widgets/image_upload.py @@ -25,6 +25,7 @@ from PySide6.QtWidgets import QSizePolicy # 導入多語系支援 from ...i18n import t from ...debug import gui_debug_log as debug_log +from ...utils.resource_manager import get_resource_manager, create_temp_file from .image_preview import ImagePreviewWidget @@ -37,6 +38,7 @@ class ImageUploadWidget(QWidget): self.images: Dict[str, Dict[str, str]] = {} self.config_manager = config_manager self._last_paste_time = 0 # 添加最後貼上時間記錄 + self.resource_manager = get_resource_manager() # 獲取資源管理器 self._setup_ui() self.setAcceptDrops(True) # 啟動時清理舊的臨時文件 @@ -350,20 +352,19 @@ class ImageUploadWidget(QWidget): if mimeData.hasImage(): image = clipboard.image() if not image.isNull(): - # 創建一個唯一的臨時文件名 - temp_dir = Path.home() / ".cache" / "mcp-feedback-enhanced" - temp_dir.mkdir(parents=True, exist_ok=True) - - timestamp = int(time.time() * 1000) - temp_file = temp_dir / f"clipboard_{timestamp}_{uuid.uuid4().hex[:8]}.png" + # 使用資源管理器創建臨時文件 + temp_file = create_temp_file( + suffix=".png", + prefix=f"clipboard_{int(time.time() * 1000)}_" + ) # 保存剪貼板圖片 - if image.save(str(temp_file), "PNG"): + if image.save(temp_file, "PNG"): if os.path.getsize(temp_file) > 0: - self._add_images([str(temp_file)]) + self._add_images([temp_file]) debug_log(f"從剪貼板成功粘貼圖片: {temp_file}") else: - QMessageBox.warning(self, t('errors.warning'), t('errors.imageSaveEmpty', path=str(temp_file))) + QMessageBox.warning(self, t('errors.warning'), t('errors.imageSaveEmpty', path=temp_file)) else: QMessageBox.warning(self, t('errors.warning'), t('errors.imageSaveFailed')) else: diff --git a/src/mcp_feedback_enhanced/server.py b/src/mcp_feedback_enhanced/server.py index c0002e0..9f75b67 100644 --- a/src/mcp_feedback_enhanced/server.py +++ b/src/mcp_feedback_enhanced/server.py @@ -38,6 +38,12 @@ from .i18n import get_i18n_manager # 導入統一的調試功能 from .debug import server_debug_log as debug_log +# 導入錯誤處理框架 +from .utils.error_handler import ErrorHandler, ErrorType + +# 導入資源管理器 +from .utils.resource_manager import get_resource_manager, create_temp_file + # ===== 編碼初始化 ===== def init_encoding(): """初始化編碼設置,確保正確處理中文字符""" @@ -197,13 +203,21 @@ def is_remote_environment() -> bool: def can_use_gui() -> bool: """ 檢測是否可以使用圖形介面 - + + WSL 環境預設使用 Web UI,因為大多數 WSL 安裝都是 Linux 環境, + 沒有桌面應用支援,即使 PySide6 可以載入也應該使用 Web 介面。 + Returns: bool: True 表示可以使用 GUI,False 表示只能使用 Web UI """ if is_remote_environment(): return False - + + # WSL 環境預設使用 Web UI + if is_wsl_environment(): + debug_log("WSL 環境偵測到,預設使用 Web UI") + return False + try: from PySide6.QtWidgets import QApplication debug_log("成功載入 PySide6,可使用 GUI") @@ -228,8 +242,8 @@ def save_feedback_to_file(feedback_data: dict, file_path: str = None) -> str: str: 儲存的文件路徑 """ if file_path is None: - temp_fd, file_path = tempfile.mkstemp(suffix='.json', prefix='feedback_') - os.close(temp_fd) + # 使用資源管理器創建臨時文件 + file_path = create_temp_file(suffix='.json', prefix='feedback_') # 確保目錄存在 directory = os.path.dirname(file_path) @@ -401,9 +415,13 @@ def process_images(images_data: List[dict]) -> List[MCPImage]: debug_log(f"圖片 {i} ({file_name}) 處理成功,格式: {image_format}") except Exception as e: - debug_log(f"圖片 {i} 處理失敗: {e}") - import traceback - debug_log(f"詳細錯誤: {traceback.format_exc()}") + # 使用統一錯誤處理(不影響 JSON RPC) + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "圖片處理", "image_index": i}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"圖片 {i} 處理失敗 [錯誤ID: {error_id}]: {e}") debug_log(f"共處理 {len(mcp_images)} 張圖片") return mcp_images @@ -539,9 +557,18 @@ async def interactive_feedback( return feedback_items except Exception as e: - error_msg = f"回饋收集錯誤: {str(e)}" - debug_log(f"錯誤: {error_msg}") - return [TextContent(type="text", text=error_msg)] + # 使用統一錯誤處理,但不影響 JSON RPC 響應 + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "回饋收集", "project_dir": project_directory}, + error_type=ErrorType.SYSTEM + ) + + # 生成用戶友好的錯誤信息 + user_error_msg = ErrorHandler.format_user_error(e, include_technical=False) + debug_log(f"回饋收集錯誤 [錯誤ID: {error_id}]: {str(e)}") + + return [TextContent(type="text", text=user_error_msg)] async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: int) -> dict: @@ -565,10 +592,18 @@ async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: in # 傳遞 timeout 參數給 Web UI return await launch_web_feedback_ui(project_dir, summary, timeout) except ImportError as e: - debug_log(f"無法導入 Web UI 模組: {e}") + # 使用統一錯誤處理 + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "Web UI 模組導入", "module": "web"}, + error_type=ErrorType.DEPENDENCY + ) + user_error_msg = ErrorHandler.format_user_error(e, ErrorType.DEPENDENCY, include_technical=False) + debug_log(f"Web UI 模組導入失敗 [錯誤ID: {error_id}]: {e}") + return { "command_logs": "", - "interactive_feedback": f"Web UI 模組導入失敗: {str(e)}", + "interactive_feedback": user_error_msg, "images": [] } except TimeoutError as e: @@ -587,19 +622,31 @@ async def launch_web_ui_with_timeout(project_dir: str, summary: str, timeout: in "images": [] } except Exception as e: - error_msg = f"Web UI 錯誤: {e}" - debug_log(f"❌ {error_msg}") + # 使用統一錯誤處理 + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "Web UI 啟動", "timeout": timeout}, + error_type=ErrorType.SYSTEM + ) + user_error_msg = ErrorHandler.format_user_error(e, include_technical=False) + debug_log(f"❌ Web UI 錯誤 [錯誤ID: {error_id}]: {e}") + # 發生錯誤時也要停止 Web 服務器 try: from .web import stop_web_ui stop_web_ui() debug_log("Web UI 服務器已因錯誤而停止") except Exception as stop_error: + ErrorHandler.log_error_with_context( + stop_error, + context={"operation": "Web UI 服務器停止"}, + error_type=ErrorType.SYSTEM + ) debug_log(f"停止 Web UI 服務器時發生錯誤: {stop_error}") return { "command_logs": "", - "interactive_feedback": f"錯誤: {str(e)}", + "interactive_feedback": user_error_msg, "images": [] } diff --git a/src/mcp_feedback_enhanced/test_web_ui.py b/src/mcp_feedback_enhanced/test_web_ui.py index 863df70..3b8740f 100644 --- a/src/mcp_feedback_enhanced/test_web_ui.py +++ b/src/mcp_feedback_enhanced/test_web_ui.py @@ -95,11 +95,17 @@ def get_test_summary(): def find_free_port(): """Find a free port to use for testing""" - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(('', 0)) - s.listen(1) - port = s.getsockname()[1] - return port + try: + # 嘗試使用增強的端口管理 + from .web.utils.port_manager import PortManager + return PortManager.find_free_port_enhanced(preferred_port=8765, auto_cleanup=False) + except ImportError: + # 回退到原始方法 + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('', 0)) + s.listen(1) + port = s.getsockname()[1] + return port def test_web_ui(keep_running=False): """Test the Web UI functionality""" diff --git a/src/mcp_feedback_enhanced/testing/utils.py b/src/mcp_feedback_enhanced/testing/utils.py index 114bf50..7c62ed1 100644 --- a/src/mcp_feedback_enhanced/testing/utils.py +++ b/src/mcp_feedback_enhanced/testing/utils.py @@ -54,15 +54,26 @@ class TestUtils: @staticmethod def find_free_port(start_port: int = 8765, max_attempts: int = 100) -> int: - """尋找可用端口""" - for port in range(start_port, start_port + max_attempts): - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(('127.0.0.1', port)) - return port - except OSError: - continue - raise RuntimeError(f"無法找到可用端口 (嘗試範圍: {start_port}-{start_port + max_attempts})") + """尋找可用端口 - 使用增強的端口管理""" + try: + # 嘗試使用增強的端口管理 + from ..web.utils.port_manager import PortManager + return PortManager.find_free_port_enhanced( + preferred_port=start_port, + auto_cleanup=False, # 測試時不自動清理 + host='127.0.0.1', + max_attempts=max_attempts + ) + except ImportError: + # 回退到原始方法 + for port in range(start_port, start_port + max_attempts): + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(('127.0.0.1', port)) + return port + except OSError: + continue + raise RuntimeError(f"無法找到可用端口 (嘗試範圍: {start_port}-{start_port + max_attempts})") @staticmethod def is_port_open(host: str, port: int, timeout: float = 1.0) -> bool: diff --git a/src/mcp_feedback_enhanced/utils/__init__.py b/src/mcp_feedback_enhanced/utils/__init__.py new file mode 100644 index 0000000..2e2001e --- /dev/null +++ b/src/mcp_feedback_enhanced/utils/__init__.py @@ -0,0 +1,27 @@ +""" +MCP Feedback Enhanced 工具模組 +============================ + +提供各種工具類和函數,包括錯誤處理、資源管理等。 +""" + +from .error_handler import ErrorHandler, ErrorType +from .resource_manager import ( + ResourceManager, + get_resource_manager, + create_temp_file, + create_temp_dir, + register_process, + cleanup_all_resources +) + +__all__ = [ + 'ErrorHandler', + 'ErrorType', + 'ResourceManager', + 'get_resource_manager', + 'create_temp_file', + 'create_temp_dir', + 'register_process', + 'cleanup_all_resources' +] diff --git a/src/mcp_feedback_enhanced/utils/error_handler.py b/src/mcp_feedback_enhanced/utils/error_handler.py new file mode 100644 index 0000000..dd6a867 --- /dev/null +++ b/src/mcp_feedback_enhanced/utils/error_handler.py @@ -0,0 +1,455 @@ +""" +統一錯誤處理框架 +================ + +提供統一的錯誤處理機制,包括: +- 錯誤類型分類 +- 用戶友好錯誤信息 +- 錯誤上下文記錄 +- 解決方案建議 +- 國際化支持 + +注意:此模組不會影響 JSON RPC 通信,所有錯誤處理都在應用層進行。 +""" + +import os +import sys +import traceback +import time +from enum import Enum +from typing import Dict, Any, Optional, List, Tuple +from ..debug import debug_log + + +class ErrorType(Enum): + """錯誤類型枚舉""" + NETWORK = "network" # 網絡相關錯誤 + FILE_IO = "file_io" # 文件 I/O 錯誤 + PROCESS = "process" # 進程相關錯誤 + TIMEOUT = "timeout" # 超時錯誤 + USER_CANCEL = "user_cancel" # 用戶取消操作 + SYSTEM = "system" # 系統錯誤 + PERMISSION = "permission" # 權限錯誤 + VALIDATION = "validation" # 數據驗證錯誤 + DEPENDENCY = "dependency" # 依賴錯誤 + CONFIGURATION = "config" # 配置錯誤 + + +class ErrorSeverity(Enum): + """錯誤嚴重程度""" + LOW = "low" # 低:不影響核心功能 + MEDIUM = "medium" # 中:影響部分功能 + HIGH = "high" # 高:影響核心功能 + CRITICAL = "critical" # 嚴重:系統無法正常運行 + + +class ErrorHandler: + """統一錯誤處理器""" + + # 錯誤類型到用戶友好信息的映射 + _ERROR_MESSAGES = { + ErrorType.NETWORK: { + "zh-TW": "網絡連接出現問題", + "zh-CN": "网络连接出现问题", + "en": "Network connection issue" + }, + ErrorType.FILE_IO: { + "zh-TW": "文件讀寫出現問題", + "zh-CN": "文件读写出现问题", + "en": "File read/write issue" + }, + ErrorType.PROCESS: { + "zh-TW": "進程執行出現問題", + "zh-CN": "进程执行出现问题", + "en": "Process execution issue" + }, + ErrorType.TIMEOUT: { + "zh-TW": "操作超時", + "zh-CN": "操作超时", + "en": "Operation timeout" + }, + ErrorType.USER_CANCEL: { + "zh-TW": "用戶取消了操作", + "zh-CN": "用户取消了操作", + "en": "User cancelled the operation" + }, + ErrorType.SYSTEM: { + "zh-TW": "系統出現問題", + "zh-CN": "系统出现问题", + "en": "System issue" + }, + ErrorType.PERMISSION: { + "zh-TW": "權限不足", + "zh-CN": "权限不足", + "en": "Insufficient permissions" + }, + ErrorType.VALIDATION: { + "zh-TW": "數據驗證失敗", + "zh-CN": "数据验证失败", + "en": "Data validation failed" + }, + ErrorType.DEPENDENCY: { + "zh-TW": "依賴組件出現問題", + "zh-CN": "依赖组件出现问题", + "en": "Dependency issue" + }, + ErrorType.CONFIGURATION: { + "zh-TW": "配置出現問題", + "zh-CN": "配置出现问题", + "en": "Configuration issue" + } + } + + # 錯誤解決建議 + _ERROR_SOLUTIONS = { + ErrorType.NETWORK: { + "zh-TW": [ + "檢查網絡連接是否正常", + "確認防火牆設置", + "嘗試重新啟動應用程序" + ], + "zh-CN": [ + "检查网络连接是否正常", + "确认防火墙设置", + "尝试重新启动应用程序" + ], + "en": [ + "Check network connection", + "Verify firewall settings", + "Try restarting the application" + ] + }, + ErrorType.FILE_IO: { + "zh-TW": [ + "檢查文件是否存在", + "確認文件權限", + "檢查磁盤空間是否足夠" + ], + "zh-CN": [ + "检查文件是否存在", + "确认文件权限", + "检查磁盘空间是否足够" + ], + "en": [ + "Check if file exists", + "Verify file permissions", + "Check available disk space" + ] + }, + ErrorType.PROCESS: { + "zh-TW": [ + "檢查進程是否正在運行", + "確認系統資源是否足夠", + "嘗試重新啟動相關服務" + ], + "zh-CN": [ + "检查进程是否正在运行", + "确认系统资源是否足够", + "尝试重新启动相关服务" + ], + "en": [ + "Check if process is running", + "Verify system resources", + "Try restarting related services" + ] + }, + ErrorType.TIMEOUT: { + "zh-TW": [ + "增加超時時間設置", + "檢查網絡延遲", + "稍後重試操作" + ], + "zh-CN": [ + "增加超时时间设置", + "检查网络延迟", + "稍后重试操作" + ], + "en": [ + "Increase timeout settings", + "Check network latency", + "Retry the operation later" + ] + }, + ErrorType.PERMISSION: { + "zh-TW": [ + "以管理員身份運行", + "檢查文件/目錄權限", + "聯繫系統管理員" + ], + "zh-CN": [ + "以管理员身份运行", + "检查文件/目录权限", + "联系系统管理员" + ], + "en": [ + "Run as administrator", + "Check file/directory permissions", + "Contact system administrator" + ] + } + } + + @staticmethod + def get_current_language() -> str: + """獲取當前語言設置""" + try: + # 嘗試從 i18n 模組獲取當前語言 + from ..i18n import get_i18n_manager + return get_i18n_manager().get_current_language() + except Exception: + # 回退到環境變數或默認語言 + return os.getenv("MCP_LANGUAGE", "zh-TW") + + @staticmethod + def get_i18n_error_message(error_type: ErrorType) -> str: + """從國際化系統獲取錯誤信息""" + try: + from ..i18n import get_i18n_manager + i18n = get_i18n_manager() + key = f"errors.types.{error_type.value}" + message = i18n.t(key) + # 如果返回的是鍵本身,說明沒有找到翻譯,使用回退 + if message == key: + raise Exception("Translation not found") + return message + except Exception: + # 回退到內建映射 + language = ErrorHandler.get_current_language() + error_messages = ErrorHandler._ERROR_MESSAGES.get(error_type, {}) + return error_messages.get(language, error_messages.get("zh-TW", "發生未知錯誤")) + + @staticmethod + def get_i18n_error_solutions(error_type: ErrorType) -> List[str]: + """從國際化系統獲取錯誤解決方案""" + try: + from ..i18n import get_i18n_manager + i18n = get_i18n_manager() + key = f"errors.solutions.{error_type.value}" + solutions = i18n.t(key) + if isinstance(solutions, list) and len(solutions) > 0: + return solutions + # 如果沒有找到或為空,使用回退 + raise Exception("Solutions not found") + except Exception: + # 回退到內建映射 + language = ErrorHandler.get_current_language() + solutions = ErrorHandler._ERROR_SOLUTIONS.get(error_type, {}) + return solutions.get(language, solutions.get("zh-TW", [])) + + @staticmethod + def classify_error(error: Exception) -> ErrorType: + """ + 根據異常類型自動分類錯誤 + + Args: + error: Python 異常對象 + + Returns: + ErrorType: 錯誤類型 + """ + error_name = type(error).__name__ + error_message = str(error).lower() + + # 超時錯誤(優先檢查,避免被網絡錯誤覆蓋) + if 'timeout' in error_name.lower() or 'timeout' in error_message: + return ErrorType.TIMEOUT + + # 權限錯誤(優先檢查,避免被文件錯誤覆蓋) + if 'permission' in error_name.lower(): + return ErrorType.PERMISSION + if any(keyword in error_message for keyword in ['permission denied', 'access denied', 'forbidden']): + return ErrorType.PERMISSION + + # 網絡相關錯誤 + if any(keyword in error_name.lower() for keyword in ['connection', 'network', 'socket']): + return ErrorType.NETWORK + if any(keyword in error_message for keyword in ['connection', 'network', 'socket']): + return ErrorType.NETWORK + + # 文件 I/O 錯誤 + if any(keyword in error_name.lower() for keyword in ['file', 'ioerror']): # 使用更精確的匹配 + return ErrorType.FILE_IO + if any(keyword in error_message for keyword in ['file', 'directory', 'no such file']): + return ErrorType.FILE_IO + + # 進程相關錯誤 + if any(keyword in error_name.lower() for keyword in ['process', 'subprocess']): + return ErrorType.PROCESS + if any(keyword in error_message for keyword in ['process', 'command', 'executable']): + return ErrorType.PROCESS + + # 驗證錯誤 + if any(keyword in error_name.lower() for keyword in ['validation', 'value', 'type']): + return ErrorType.VALIDATION + + # 配置錯誤 + if any(keyword in error_message for keyword in ['config', 'setting', 'environment']): + return ErrorType.CONFIGURATION + + # 默認為系統錯誤 + return ErrorType.SYSTEM + + @staticmethod + def format_user_error( + error: Exception, + error_type: Optional[ErrorType] = None, + context: Optional[Dict[str, Any]] = None, + include_technical: bool = False + ) -> str: + """ + 將技術錯誤轉換為用戶友好的錯誤信息 + + Args: + error: Python 異常對象 + error_type: 錯誤類型(可選,會自動分類) + context: 錯誤上下文信息 + include_technical: 是否包含技術細節 + + Returns: + str: 用戶友好的錯誤信息 + """ + # 自動分類錯誤類型 + if error_type is None: + error_type = ErrorHandler.classify_error(error) + + # 獲取當前語言 + language = ErrorHandler.get_current_language() + + # 獲取用戶友好的錯誤信息(優先使用國際化系統) + user_message = ErrorHandler.get_i18n_error_message(error_type) + + # 構建完整的錯誤信息 + parts = [f"❌ {user_message}"] + + # 添加上下文信息 + if context: + if context.get("operation"): + if language == "en": + parts.append(f"Operation: {context['operation']}") + else: + parts.append(f"操作:{context['operation']}") + + if context.get("file_path"): + if language == "en": + parts.append(f"File: {context['file_path']}") + else: + parts.append(f"文件:{context['file_path']}") + + # 添加技術細節(如果需要) + if include_technical: + if language == "en": + parts.append(f"Technical details: {type(error).__name__}: {str(error)}") + else: + parts.append(f"技術細節:{type(error).__name__}: {str(error)}") + + return "\n".join(parts) + + @staticmethod + def get_error_solutions(error_type: ErrorType) -> List[str]: + """ + 獲取錯誤解決建議 + + Args: + error_type: 錯誤類型 + + Returns: + List[str]: 解決建議列表 + """ + return ErrorHandler.get_i18n_error_solutions(error_type) + + @staticmethod + def log_error_with_context( + error: Exception, + context: Optional[Dict[str, Any]] = None, + error_type: Optional[ErrorType] = None, + severity: ErrorSeverity = ErrorSeverity.MEDIUM + ) -> str: + """ + 記錄帶上下文的錯誤信息(不影響 JSON RPC) + + Args: + error: Python 異常對象 + context: 錯誤上下文信息 + error_type: 錯誤類型 + severity: 錯誤嚴重程度 + + Returns: + str: 錯誤 ID,用於追蹤 + """ + # 生成錯誤 ID + error_id = f"ERR_{int(time.time())}_{id(error) % 10000}" + + # 自動分類錯誤 + if error_type is None: + error_type = ErrorHandler.classify_error(error) + + # 構建錯誤記錄 + error_record = { + "error_id": error_id, + "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), + "error_type": error_type.value, + "severity": severity.value, + "exception_type": type(error).__name__, + "exception_message": str(error), + "context": context or {}, + "traceback": traceback.format_exc() if severity in [ErrorSeverity.HIGH, ErrorSeverity.CRITICAL] else None + } + + # 記錄到調試日誌(不影響 JSON RPC) + debug_log(f"錯誤記錄 [{error_id}]: {error_type.value} - {str(error)}") + + if context: + debug_log(f"錯誤上下文 [{error_id}]: {context}") + + # 對於嚴重錯誤,記錄完整堆棧跟蹤 + if severity in [ErrorSeverity.HIGH, ErrorSeverity.CRITICAL]: + debug_log(f"錯誤堆棧 [{error_id}]:\n{traceback.format_exc()}") + + return error_id + + @staticmethod + def create_error_response( + error: Exception, + context: Optional[Dict[str, Any]] = None, + error_type: Optional[ErrorType] = None, + include_solutions: bool = True, + for_user: bool = True + ) -> Dict[str, Any]: + """ + 創建標準化的錯誤響應 + + Args: + error: Python 異常對象 + context: 錯誤上下文 + error_type: 錯誤類型 + include_solutions: 是否包含解決建議 + for_user: 是否為用戶界面使用 + + Returns: + Dict[str, Any]: 標準化錯誤響應 + """ + # 自動分類錯誤 + if error_type is None: + error_type = ErrorHandler.classify_error(error) + + # 記錄錯誤 + error_id = ErrorHandler.log_error_with_context(error, context, error_type) + + # 構建響應 + response = { + "success": False, + "error_id": error_id, + "error_type": error_type.value, + "message": ErrorHandler.format_user_error(error, error_type, context, include_technical=not for_user) + } + + # 添加解決建議 + if include_solutions: + solutions = ErrorHandler.get_error_solutions(error_type) + response["solutions"] = solutions # 即使為空列表也添加 + + # 添加上下文(僅用於調試) + if context and not for_user: + response["context"] = context + + return response diff --git a/src/mcp_feedback_enhanced/utils/memory_monitor.py b/src/mcp_feedback_enhanced/utils/memory_monitor.py new file mode 100644 index 0000000..644161a --- /dev/null +++ b/src/mcp_feedback_enhanced/utils/memory_monitor.py @@ -0,0 +1,526 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +集成式內存監控系統 +================== + +提供與資源管理器深度集成的內存監控功能,包括: +- 系統和進程內存使用監控 +- 智能清理觸發機制 +- 內存洩漏檢測和趨勢分析 +- 性能優化建議 +""" + +import os +import gc +import time +import threading +import psutil +from typing import Dict, List, Optional, Callable, Any +from dataclasses import dataclass, field +from datetime import datetime, timedelta +from collections import deque +from ..debug import debug_log +from .error_handler import ErrorHandler, ErrorType + + +@dataclass +class MemorySnapshot: + """內存快照數據類""" + timestamp: datetime + system_total: int # 系統總內存 (bytes) + system_available: int # 系統可用內存 (bytes) + system_used: int # 系統已用內存 (bytes) + system_percent: float # 系統內存使用率 (%) + process_rss: int # 進程常駐內存 (bytes) + process_vms: int # 進程虛擬內存 (bytes) + process_percent: float # 進程內存使用率 (%) + gc_objects: int # Python 垃圾回收對象數量 + + +@dataclass +class MemoryAlert: + """內存警告數據類""" + level: str # warning, critical, emergency + message: str + timestamp: datetime + memory_percent: float + recommended_action: str + + +@dataclass +class MemoryStats: + """內存統計數據類""" + monitoring_duration: float # 監控持續時間 (秒) + snapshots_count: int # 快照數量 + average_system_usage: float # 平均系統內存使用率 + peak_system_usage: float # 峰值系統內存使用率 + average_process_usage: float # 平均進程內存使用率 + peak_process_usage: float # 峰值進程內存使用率 + alerts_count: int # 警告數量 + cleanup_triggers: int # 清理觸發次數 + memory_trend: str # 內存趨勢 (stable, increasing, decreasing) + + +class MemoryMonitor: + """集成式內存監控器""" + + def __init__(self, + warning_threshold: float = 0.8, + critical_threshold: float = 0.9, + emergency_threshold: float = 0.95, + monitoring_interval: int = 30, + max_snapshots: int = 1000): + """ + 初始化內存監控器 + + Args: + warning_threshold: 警告閾值 (0.0-1.0) + critical_threshold: 危險閾值 (0.0-1.0) + emergency_threshold: 緊急閾值 (0.0-1.0) + monitoring_interval: 監控間隔 (秒) + max_snapshots: 最大快照數量 + """ + self.warning_threshold = warning_threshold + self.critical_threshold = critical_threshold + self.emergency_threshold = emergency_threshold + self.monitoring_interval = monitoring_interval + self.max_snapshots = max_snapshots + + # 監控狀態 + self.is_monitoring = False + self.monitor_thread: Optional[threading.Thread] = None + self._stop_event = threading.Event() + + # 數據存儲 + self.snapshots: deque = deque(maxlen=max_snapshots) + self.alerts: List[MemoryAlert] = [] + self.max_alerts = 100 + + # 回調函數 + self.cleanup_callbacks: List[Callable] = [] + self.alert_callbacks: List[Callable[[MemoryAlert], None]] = [] + + # 統計數據 + self.start_time: Optional[datetime] = None + self.cleanup_triggers_count = 0 + + # 進程信息 + self.process = psutil.Process() + + debug_log("MemoryMonitor 初始化完成") + + def start_monitoring(self) -> bool: + """ + 開始內存監控 + + Returns: + bool: 是否成功啟動 + """ + if self.is_monitoring: + debug_log("內存監控已在運行") + return True + + try: + self.is_monitoring = True + self.start_time = datetime.now() + self._stop_event.clear() + + self.monitor_thread = threading.Thread( + target=self._monitoring_loop, + name="MemoryMonitor", + daemon=True + ) + self.monitor_thread.start() + + debug_log(f"內存監控已啟動,間隔 {self.monitoring_interval} 秒") + return True + + except Exception as e: + self.is_monitoring = False + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "啟動內存監控"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"啟動內存監控失敗 [錯誤ID: {error_id}]: {e}") + return False + + def stop_monitoring(self) -> bool: + """ + 停止內存監控 + + Returns: + bool: 是否成功停止 + """ + if not self.is_monitoring: + debug_log("內存監控未在運行") + return True + + try: + self.is_monitoring = False + self._stop_event.set() + + if self.monitor_thread and self.monitor_thread.is_alive(): + self.monitor_thread.join(timeout=5) + + debug_log("內存監控已停止") + return True + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "停止內存監控"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"停止內存監控失敗 [錯誤ID: {error_id}]: {e}") + return False + + def _monitoring_loop(self): + """內存監控主循環""" + debug_log("內存監控循環開始") + + while not self._stop_event.is_set(): + try: + # 收集內存快照 + snapshot = self._collect_memory_snapshot() + self.snapshots.append(snapshot) + + # 檢查內存使用情況 + self._check_memory_usage(snapshot) + + # 等待下次監控 + if self._stop_event.wait(self.monitoring_interval): + break + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "內存監控循環"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"內存監控循環錯誤 [錯誤ID: {error_id}]: {e}") + + # 發生錯誤時等待較短時間後重試 + if self._stop_event.wait(5): + break + + debug_log("內存監控循環結束") + + def _collect_memory_snapshot(self) -> MemorySnapshot: + """收集內存快照""" + try: + # 系統內存信息 + system_memory = psutil.virtual_memory() + + # 進程內存信息 + process_memory = self.process.memory_info() + process_percent = self.process.memory_percent() + + # Python 垃圾回收信息 + gc_objects = len(gc.get_objects()) + + return MemorySnapshot( + timestamp=datetime.now(), + system_total=system_memory.total, + system_available=system_memory.available, + system_used=system_memory.used, + system_percent=system_memory.percent, + process_rss=process_memory.rss, + process_vms=process_memory.vms, + process_percent=process_percent, + gc_objects=gc_objects + ) + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "收集內存快照"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"收集內存快照失敗 [錯誤ID: {error_id}]: {e}") + raise + + def _check_memory_usage(self, snapshot: MemorySnapshot): + """檢查內存使用情況並觸發相應動作""" + usage_percent = snapshot.system_percent / 100.0 + + # 檢查緊急閾值 + if usage_percent >= self.emergency_threshold: + alert = MemoryAlert( + level="emergency", + message=f"內存使用率達到緊急水平: {snapshot.system_percent:.1f}%", + timestamp=snapshot.timestamp, + memory_percent=snapshot.system_percent, + recommended_action="立即執行強制清理和垃圾回收" + ) + self._handle_alert(alert) + self._trigger_emergency_cleanup() + + # 檢查危險閾值 + elif usage_percent >= self.critical_threshold: + alert = MemoryAlert( + level="critical", + message=f"內存使用率達到危險水平: {snapshot.system_percent:.1f}%", + timestamp=snapshot.timestamp, + memory_percent=snapshot.system_percent, + recommended_action="執行資源清理和垃圾回收" + ) + self._handle_alert(alert) + self._trigger_cleanup() + + # 檢查警告閾值 + elif usage_percent >= self.warning_threshold: + alert = MemoryAlert( + level="warning", + message=f"內存使用率較高: {snapshot.system_percent:.1f}%", + timestamp=snapshot.timestamp, + memory_percent=snapshot.system_percent, + recommended_action="考慮執行輕量級清理" + ) + self._handle_alert(alert) + + def _handle_alert(self, alert: MemoryAlert): + """處理內存警告""" + # 添加到警告列表 + self.alerts.append(alert) + + # 限制警告數量 + if len(self.alerts) > self.max_alerts: + self.alerts = self.alerts[-self.max_alerts:] + + # 調用警告回調 + for callback in self.alert_callbacks: + try: + callback(alert) + except Exception as e: + debug_log(f"警告回調執行失敗: {e}") + + debug_log(f"內存警告 [{alert.level}]: {alert.message}") + + def _trigger_cleanup(self): + """觸發清理操作""" + self.cleanup_triggers_count += 1 + debug_log("觸發內存清理操作") + + # 執行 Python 垃圾回收 + collected = gc.collect() + debug_log(f"垃圾回收清理了 {collected} 個對象") + + # 調用清理回調 + for callback in self.cleanup_callbacks: + try: + callback() + except Exception as e: + debug_log(f"清理回調執行失敗: {e}") + + def _trigger_emergency_cleanup(self): + """觸發緊急清理操作""" + debug_log("觸發緊急內存清理操作") + + # 執行強制垃圾回收 + for _ in range(3): + collected = gc.collect() + debug_log(f"強制垃圾回收清理了 {collected} 個對象") + + # 調用清理回調(強制模式) + for callback in self.cleanup_callbacks: + try: + if hasattr(callback, '__call__'): + # 嘗試傳遞 force 參數 + import inspect + sig = inspect.signature(callback) + if 'force' in sig.parameters: + callback(force=True) + else: + callback() + else: + callback() + except Exception as e: + debug_log(f"緊急清理回調執行失敗: {e}") + + + def add_cleanup_callback(self, callback: Callable): + """添加清理回調函數""" + if callback not in self.cleanup_callbacks: + self.cleanup_callbacks.append(callback) + debug_log("添加清理回調函數") + + def add_alert_callback(self, callback: Callable[[MemoryAlert], None]): + """添加警告回調函數""" + if callback not in self.alert_callbacks: + self.alert_callbacks.append(callback) + debug_log("添加警告回調函數") + + def remove_cleanup_callback(self, callback: Callable): + """移除清理回調函數""" + if callback in self.cleanup_callbacks: + self.cleanup_callbacks.remove(callback) + debug_log("移除清理回調函數") + + def remove_alert_callback(self, callback: Callable[[MemoryAlert], None]): + """移除警告回調函數""" + if callback in self.alert_callbacks: + self.alert_callbacks.remove(callback) + debug_log("移除警告回調函數") + + def get_current_memory_info(self) -> Dict[str, Any]: + """獲取當前內存信息""" + try: + snapshot = self._collect_memory_snapshot() + return { + "timestamp": snapshot.timestamp.isoformat(), + "system": { + "total_gb": round(snapshot.system_total / (1024**3), 2), + "available_gb": round(snapshot.system_available / (1024**3), 2), + "used_gb": round(snapshot.system_used / (1024**3), 2), + "usage_percent": round(snapshot.system_percent, 1) + }, + "process": { + "rss_mb": round(snapshot.process_rss / (1024**2), 2), + "vms_mb": round(snapshot.process_vms / (1024**2), 2), + "usage_percent": round(snapshot.process_percent, 1) + }, + "gc_objects": snapshot.gc_objects, + "status": self._get_memory_status(snapshot.system_percent / 100.0) + } + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "獲取當前內存信息"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"獲取內存信息失敗 [錯誤ID: {error_id}]: {e}") + return {} + + def get_memory_stats(self) -> MemoryStats: + """獲取內存統計數據""" + if not self.snapshots: + return MemoryStats( + monitoring_duration=0.0, + snapshots_count=0, + average_system_usage=0.0, + peak_system_usage=0.0, + average_process_usage=0.0, + peak_process_usage=0.0, + alerts_count=0, + cleanup_triggers=0, + memory_trend="unknown" + ) + + # 計算統計數據 + system_usages = [s.system_percent for s in self.snapshots] + process_usages = [s.process_percent for s in self.snapshots] + + duration = 0.0 + if self.start_time: + duration = (datetime.now() - self.start_time).total_seconds() + + return MemoryStats( + monitoring_duration=duration, + snapshots_count=len(self.snapshots), + average_system_usage=sum(system_usages) / len(system_usages), + peak_system_usage=max(system_usages), + average_process_usage=sum(process_usages) / len(process_usages), + peak_process_usage=max(process_usages), + alerts_count=len(self.alerts), + cleanup_triggers=self.cleanup_triggers_count, + memory_trend=self._analyze_memory_trend() + ) + + def get_recent_alerts(self, limit: int = 10) -> List[MemoryAlert]: + """獲取最近的警告""" + return self.alerts[-limit:] if self.alerts else [] + + def _get_memory_status(self, usage_percent: float) -> str: + """獲取內存狀態描述""" + if usage_percent >= self.emergency_threshold: + return "emergency" + elif usage_percent >= self.critical_threshold: + return "critical" + elif usage_percent >= self.warning_threshold: + return "warning" + else: + return "normal" + + def _analyze_memory_trend(self) -> str: + """分析內存使用趨勢""" + if len(self.snapshots) < 10: + return "insufficient_data" + + # 取最近的快照進行趨勢分析 + recent_snapshots = list(self.snapshots)[-10:] + usages = [s.system_percent for s in recent_snapshots] + + # 簡單的線性趨勢分析 + first_half = usages[:5] + second_half = usages[5:] + + avg_first = sum(first_half) / len(first_half) + avg_second = sum(second_half) / len(second_half) + + diff = avg_second - avg_first + + if abs(diff) < 2.0: # 變化小於 2% + return "stable" + elif diff > 0: + return "increasing" + else: + return "decreasing" + + def force_cleanup(self): + """手動觸發清理操作""" + debug_log("手動觸發內存清理") + self._trigger_cleanup() + + def force_emergency_cleanup(self): + """手動觸發緊急清理操作""" + debug_log("手動觸發緊急內存清理") + self._trigger_emergency_cleanup() + + def reset_stats(self): + """重置統計數據""" + self.snapshots.clear() + self.alerts.clear() + self.cleanup_triggers_count = 0 + self.start_time = datetime.now() if self.is_monitoring else None + debug_log("內存監控統計數據已重置") + + def export_memory_data(self) -> Dict[str, Any]: + """導出內存數據""" + return { + "config": { + "warning_threshold": self.warning_threshold, + "critical_threshold": self.critical_threshold, + "emergency_threshold": self.emergency_threshold, + "monitoring_interval": self.monitoring_interval + }, + "current_info": self.get_current_memory_info(), + "stats": self.get_memory_stats().__dict__, + "recent_alerts": [ + { + "level": alert.level, + "message": alert.message, + "timestamp": alert.timestamp.isoformat(), + "memory_percent": alert.memory_percent, + "recommended_action": alert.recommended_action + } + for alert in self.get_recent_alerts() + ], + "is_monitoring": self.is_monitoring + } + + +# 全域內存監控器實例 +_memory_monitor: Optional[MemoryMonitor] = None +_monitor_lock = threading.Lock() + + +def get_memory_monitor() -> MemoryMonitor: + """獲取全域內存監控器實例""" + global _memory_monitor + if _memory_monitor is None: + with _monitor_lock: + if _memory_monitor is None: + _memory_monitor = MemoryMonitor() + return _memory_monitor diff --git a/src/mcp_feedback_enhanced/utils/resource_manager.py b/src/mcp_feedback_enhanced/utils/resource_manager.py new file mode 100644 index 0000000..8bd200c --- /dev/null +++ b/src/mcp_feedback_enhanced/utils/resource_manager.py @@ -0,0 +1,797 @@ +""" +統一資源管理器 +============== + +提供統一的資源管理功能,包括: +- 臨時文件和目錄管理 +- 進程生命週期追蹤 +- 自動資源清理 +- 資源使用監控 +""" + +import os +import sys +import time +import atexit +import shutil +import tempfile +import threading +import subprocess +import weakref +from pathlib import Path +from typing import Set, Dict, Any, Optional, List, Union +from ..debug import debug_log +from .error_handler import ErrorHandler, ErrorType + + +class ResourceType: + """資源類型常量""" + TEMP_FILE = "temp_file" + TEMP_DIR = "temp_dir" + PROCESS = "process" + FILE_HANDLE = "file_handle" + + +class ResourceManager: + """統一資源管理器 - 提供完整的資源生命週期管理""" + + _instance = None + _lock = threading.Lock() + + def __new__(cls): + """單例模式實現""" + if cls._instance is None: + with cls._lock: + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self): + """初始化資源管理器""" + if hasattr(self, '_initialized'): + return + + self._initialized = True + + # 資源追蹤集合 + self.temp_files: Set[str] = set() + self.temp_dirs: Set[str] = set() + self.processes: Dict[int, Dict[str, Any]] = {} + self.file_handles: Set[Any] = set() + + # 資源統計 + self.stats = { + "temp_files_created": 0, + "temp_dirs_created": 0, + "processes_registered": 0, + "cleanup_runs": 0, + "last_cleanup": None + } + + # 配置 + self.auto_cleanup_enabled = True + self.cleanup_interval = 300 # 5分鐘 + self.temp_file_max_age = 3600 # 1小時 + + # 清理線程 + self._cleanup_thread: Optional[threading.Thread] = None + self._stop_cleanup = threading.Event() + + # 註冊退出清理 + atexit.register(self.cleanup_all) + + # 啟動自動清理 + self._start_auto_cleanup() + + # 集成內存監控 + self._setup_memory_monitoring() + + debug_log("ResourceManager 初始化完成") + + def _setup_memory_monitoring(self): + """設置內存監控集成""" + try: + # 延遲導入避免循環依賴 + from .memory_monitor import get_memory_monitor + + self.memory_monitor = get_memory_monitor() + + # 註冊清理回調 + self.memory_monitor.add_cleanup_callback(self._memory_triggered_cleanup) + + # 啟動內存監控 + if self.memory_monitor.start_monitoring(): + debug_log("內存監控已集成到資源管理器") + else: + debug_log("內存監控啟動失敗") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "設置內存監控"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"設置內存監控失敗 [錯誤ID: {error_id}]: {e}") + + def _memory_triggered_cleanup(self, force: bool = False): + """內存監控觸發的清理操作""" + debug_log(f"內存監控觸發清理操作 (force={force})") + + try: + # 清理臨時文件 + cleaned_files = self.cleanup_temp_files() + + # 清理臨時目錄 + cleaned_dirs = self.cleanup_temp_dirs() + + # 清理文件句柄 + cleaned_handles = self.cleanup_file_handles() + + # 如果是強制清理,也清理進程 + cleaned_processes = 0 + if force: + cleaned_processes = self.cleanup_processes(force=True) + + debug_log(f"內存觸發清理完成: 文件={cleaned_files}, 目錄={cleaned_dirs}, " + f"句柄={cleaned_handles}, 進程={cleaned_processes}") + + # 更新統計 + self.stats["cleanup_runs"] += 1 + self.stats["last_cleanup"] = time.time() + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "內存觸發清理", "force": force}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"內存觸發清理失敗 [錯誤ID: {error_id}]: {e}") + + def create_temp_file( + self, + suffix: str = "", + prefix: str = "mcp_", + dir: Optional[str] = None, + text: bool = True + ) -> str: + """ + 創建臨時文件並追蹤 + + Args: + suffix: 文件後綴 + prefix: 文件前綴 + dir: 臨時目錄,None 使用系統默認 + text: 是否為文本模式 + + Returns: + str: 臨時文件路徑 + """ + try: + # 創建臨時文件 + fd, temp_path = tempfile.mkstemp( + suffix=suffix, + prefix=prefix, + dir=dir, + text=text + ) + os.close(fd) # 關閉文件描述符 + + # 追蹤文件 + self.temp_files.add(temp_path) + self.stats["temp_files_created"] += 1 + + debug_log(f"創建臨時文件: {temp_path}") + return temp_path + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "創建臨時文件", "suffix": suffix, "prefix": prefix}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"創建臨時文件失敗 [錯誤ID: {error_id}]: {e}") + raise + + def create_temp_dir( + self, + suffix: str = "", + prefix: str = "mcp_", + dir: Optional[str] = None + ) -> str: + """ + 創建臨時目錄並追蹤 + + Args: + suffix: 目錄後綴 + prefix: 目錄前綴 + dir: 父目錄,None 使用系統默認 + + Returns: + str: 臨時目錄路徑 + """ + try: + # 創建臨時目錄 + temp_dir = tempfile.mkdtemp( + suffix=suffix, + prefix=prefix, + dir=dir + ) + + # 追蹤目錄 + self.temp_dirs.add(temp_dir) + self.stats["temp_dirs_created"] += 1 + + debug_log(f"創建臨時目錄: {temp_dir}") + return temp_dir + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "創建臨時目錄", "suffix": suffix, "prefix": prefix}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"創建臨時目錄失敗 [錯誤ID: {error_id}]: {e}") + raise + + def register_process( + self, + process: Union[subprocess.Popen, int], + description: str = "", + auto_cleanup: bool = True + ) -> int: + """ + 註冊進程追蹤 + + Args: + process: 進程對象或 PID + description: 進程描述 + auto_cleanup: 是否自動清理 + + Returns: + int: 進程 PID + """ + try: + if isinstance(process, subprocess.Popen): + pid = process.pid + process_obj = process + else: + pid = process + process_obj = None + + # 註冊進程 + self.processes[pid] = { + "process": process_obj, + "description": description, + "auto_cleanup": auto_cleanup, + "registered_at": time.time(), + "last_check": time.time() + } + + self.stats["processes_registered"] += 1 + + debug_log(f"註冊進程追蹤: PID {pid} - {description}") + return pid + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "註冊進程", "description": description}, + error_type=ErrorType.PROCESS + ) + debug_log(f"註冊進程失敗 [錯誤ID: {error_id}]: {e}") + raise + + def register_file_handle(self, file_handle: Any) -> None: + """ + 註冊文件句柄追蹤 + + Args: + file_handle: 文件句柄對象 + """ + try: + # 使用弱引用避免循環引用 + self.file_handles.add(weakref.ref(file_handle)) + debug_log(f"註冊文件句柄: {type(file_handle).__name__}") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "註冊文件句柄"}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"註冊文件句柄失敗 [錯誤ID: {error_id}]: {e}") + + def unregister_temp_file(self, file_path: str) -> bool: + """ + 取消臨時文件追蹤 + + Args: + file_path: 文件路徑 + + Returns: + bool: 是否成功取消追蹤 + """ + try: + if file_path in self.temp_files: + self.temp_files.remove(file_path) + debug_log(f"取消臨時文件追蹤: {file_path}") + return True + return False + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "取消文件追蹤", "file_path": file_path}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"取消文件追蹤失敗 [錯誤ID: {error_id}]: {e}") + return False + + def unregister_process(self, pid: int) -> bool: + """ + 取消進程追蹤 + + Args: + pid: 進程 PID + + Returns: + bool: 是否成功取消追蹤 + """ + try: + if pid in self.processes: + del self.processes[pid] + debug_log(f"取消進程追蹤: PID {pid}") + return True + return False + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "取消進程追蹤", "pid": pid}, + error_type=ErrorType.PROCESS + ) + debug_log(f"取消進程追蹤失敗 [錯誤ID: {error_id}]: {e}") + return False + + def cleanup_temp_files(self, max_age: Optional[int] = None) -> int: + """ + 清理臨時文件 + + Args: + max_age: 最大文件年齡(秒),None 使用默認值 + + Returns: + int: 清理的文件數量 + """ + if max_age is None: + max_age = self.temp_file_max_age + + cleaned_count = 0 + current_time = time.time() + files_to_remove = set() + + for file_path in self.temp_files.copy(): + try: + if not os.path.exists(file_path): + files_to_remove.add(file_path) + continue + + # 檢查文件年齡 + file_age = current_time - os.path.getmtime(file_path) + if file_age > max_age: + os.remove(file_path) + files_to_remove.add(file_path) + cleaned_count += 1 + debug_log(f"清理過期臨時文件: {file_path}") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "清理臨時文件", "file_path": file_path}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"清理臨時文件失敗 [錯誤ID: {error_id}]: {e}") + files_to_remove.add(file_path) # 移除無效追蹤 + + # 移除已清理的文件追蹤 + self.temp_files -= files_to_remove + + return cleaned_count + + def cleanup_temp_dirs(self) -> int: + """ + 清理臨時目錄 + + Returns: + int: 清理的目錄數量 + """ + cleaned_count = 0 + dirs_to_remove = set() + + for dir_path in self.temp_dirs.copy(): + try: + if not os.path.exists(dir_path): + dirs_to_remove.add(dir_path) + continue + + # 嘗試刪除目錄 + shutil.rmtree(dir_path) + dirs_to_remove.add(dir_path) + cleaned_count += 1 + debug_log(f"清理臨時目錄: {dir_path}") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "清理臨時目錄", "dir_path": dir_path}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"清理臨時目錄失敗 [錯誤ID: {error_id}]: {e}") + dirs_to_remove.add(dir_path) # 移除無效追蹤 + + # 移除已清理的目錄追蹤 + self.temp_dirs -= dirs_to_remove + + return cleaned_count + + def cleanup_processes(self, force: bool = False) -> int: + """ + 清理進程 + + Args: + force: 是否強制終止進程 + + Returns: + int: 清理的進程數量 + """ + cleaned_count = 0 + processes_to_remove = [] + + for pid, process_info in self.processes.copy().items(): + try: + process_obj = process_info.get("process") + auto_cleanup = process_info.get("auto_cleanup", True) + + if not auto_cleanup: + continue + + # 檢查進程是否還在運行 + if process_obj and hasattr(process_obj, 'poll'): + if process_obj.poll() is None: # 進程還在運行 + if force: + debug_log(f"強制終止進程: PID {pid}") + process_obj.kill() + else: + debug_log(f"優雅終止進程: PID {pid}") + process_obj.terminate() + + # 等待進程結束 + try: + process_obj.wait(timeout=5) + cleaned_count += 1 + except subprocess.TimeoutExpired: + if not force: + debug_log(f"進程 {pid} 優雅終止超時,強制終止") + process_obj.kill() + process_obj.wait(timeout=3) + cleaned_count += 1 + + processes_to_remove.append(pid) + else: + # 使用 psutil 檢查進程 + try: + import psutil + if psutil.pid_exists(pid): + proc = psutil.Process(pid) + if force: + proc.kill() + else: + proc.terminate() + proc.wait(timeout=5) + cleaned_count += 1 + processes_to_remove.append(pid) + except ImportError: + debug_log("psutil 不可用,跳過進程檢查") + processes_to_remove.append(pid) + except Exception as e: + debug_log(f"清理進程 {pid} 失敗: {e}") + processes_to_remove.append(pid) + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "清理進程", "pid": pid}, + error_type=ErrorType.PROCESS + ) + debug_log(f"清理進程失敗 [錯誤ID: {error_id}]: {e}") + processes_to_remove.append(pid) + + # 移除已清理的進程追蹤 + for pid in processes_to_remove: + self.processes.pop(pid, None) + + return cleaned_count + + def cleanup_file_handles(self) -> int: + """ + 清理文件句柄 + + Returns: + int: 清理的句柄數量 + """ + cleaned_count = 0 + handles_to_remove = set() + + for handle_ref in self.file_handles.copy(): + try: + handle = handle_ref() + if handle is None: + # 弱引用已失效 + handles_to_remove.add(handle_ref) + continue + + # 嘗試關閉文件句柄 + if hasattr(handle, 'close') and not handle.closed: + handle.close() + cleaned_count += 1 + debug_log(f"關閉文件句柄: {type(handle).__name__}") + + handles_to_remove.add(handle_ref) + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "清理文件句柄"}, + error_type=ErrorType.FILE_IO + ) + debug_log(f"清理文件句柄失敗 [錯誤ID: {error_id}]: {e}") + handles_to_remove.add(handle_ref) + + # 移除已清理的句柄追蹤 + self.file_handles -= handles_to_remove + + return cleaned_count + + def cleanup_all(self, force: bool = False) -> Dict[str, int]: + """ + 清理所有資源 + + Args: + force: 是否強制清理 + + Returns: + Dict[str, int]: 清理統計 + """ + debug_log("開始全面資源清理...") + + results = { + "temp_files": 0, + "temp_dirs": 0, + "processes": 0, + "file_handles": 0 + } + + try: + # 清理文件句柄 + results["file_handles"] = self.cleanup_file_handles() + + # 清理進程 + results["processes"] = self.cleanup_processes(force=force) + + # 清理臨時文件 + results["temp_files"] = self.cleanup_temp_files(max_age=0) # 清理所有文件 + + # 清理臨時目錄 + results["temp_dirs"] = self.cleanup_temp_dirs() + + # 更新統計 + self.stats["cleanup_runs"] += 1 + self.stats["last_cleanup"] = time.time() + + total_cleaned = sum(results.values()) + debug_log(f"資源清理完成,共清理 {total_cleaned} 個資源: {results}") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "全面資源清理"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"全面資源清理失敗 [錯誤ID: {error_id}]: {e}") + + return results + + def _start_auto_cleanup(self) -> None: + """啟動自動清理線程""" + if not self.auto_cleanup_enabled or self._cleanup_thread: + return + + def cleanup_worker(): + """清理工作線程""" + while not self._stop_cleanup.wait(self.cleanup_interval): + try: + # 執行定期清理 + self.cleanup_temp_files() + self._check_process_health() + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "自動清理"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"自動清理失敗 [錯誤ID: {error_id}]: {e}") + + self._cleanup_thread = threading.Thread( + target=cleanup_worker, + name="ResourceManager-AutoCleanup", + daemon=True + ) + self._cleanup_thread.start() + debug_log("自動清理線程已啟動") + + def _check_process_health(self) -> None: + """檢查進程健康狀態""" + current_time = time.time() + + for pid, process_info in self.processes.items(): + try: + process_obj = process_info.get("process") + last_check = process_info.get("last_check", current_time) + + # 每分鐘檢查一次 + if current_time - last_check < 60: + continue + + # 更新檢查時間 + process_info["last_check"] = current_time + + # 檢查進程是否還在運行 + if process_obj and hasattr(process_obj, 'poll'): + if process_obj.poll() is not None: + # 進程已結束,移除追蹤 + debug_log(f"檢測到進程 {pid} 已結束,移除追蹤") + self.unregister_process(pid) + + except Exception as e: + debug_log(f"檢查進程 {pid} 健康狀態失敗: {e}") + + def stop_auto_cleanup(self) -> None: + """停止自動清理""" + if self._cleanup_thread: + self._stop_cleanup.set() + self._cleanup_thread.join(timeout=5) + self._cleanup_thread = None + debug_log("自動清理線程已停止") + + def get_resource_stats(self) -> Dict[str, Any]: + """ + 獲取資源統計信息 + + Returns: + Dict[str, Any]: 資源統計 + """ + current_stats = self.stats.copy() + current_stats.update({ + "current_temp_files": len(self.temp_files), + "current_temp_dirs": len(self.temp_dirs), + "current_processes": len(self.processes), + "current_file_handles": len(self.file_handles), + "auto_cleanup_enabled": self.auto_cleanup_enabled, + "cleanup_interval": self.cleanup_interval, + "temp_file_max_age": self.temp_file_max_age + }) + + # 添加內存監控統計 + try: + if hasattr(self, 'memory_monitor') and self.memory_monitor: + memory_info = self.memory_monitor.get_current_memory_info() + memory_stats = self.memory_monitor.get_memory_stats() + + current_stats.update({ + "memory_monitoring_enabled": self.memory_monitor.is_monitoring, + "current_memory_usage": memory_info.get("system", {}).get("usage_percent", 0), + "memory_status": memory_info.get("status", "unknown"), + "memory_cleanup_triggers": memory_stats.cleanup_triggers, + "memory_alerts_count": memory_stats.alerts_count + }) + except Exception as e: + debug_log(f"獲取內存統計失敗: {e}") + + return current_stats + + def get_detailed_info(self) -> Dict[str, Any]: + """ + 獲取詳細資源信息 + + Returns: + Dict[str, Any]: 詳細資源信息 + """ + return { + "temp_files": list(self.temp_files), + "temp_dirs": list(self.temp_dirs), + "processes": { + pid: { + "description": info.get("description", ""), + "auto_cleanup": info.get("auto_cleanup", True), + "registered_at": info.get("registered_at", 0), + "last_check": info.get("last_check", 0) + } + for pid, info in self.processes.items() + }, + "file_handles_count": len(self.file_handles), + "stats": self.get_resource_stats() + } + + def configure( + self, + auto_cleanup_enabled: Optional[bool] = None, + cleanup_interval: Optional[int] = None, + temp_file_max_age: Optional[int] = None + ) -> None: + """ + 配置資源管理器 + + Args: + auto_cleanup_enabled: 是否啟用自動清理 + cleanup_interval: 清理間隔(秒) + temp_file_max_age: 臨時文件最大年齡(秒) + """ + if auto_cleanup_enabled is not None: + old_enabled = self.auto_cleanup_enabled + self.auto_cleanup_enabled = auto_cleanup_enabled + + if old_enabled and not auto_cleanup_enabled: + self.stop_auto_cleanup() + elif not old_enabled and auto_cleanup_enabled: + self._start_auto_cleanup() + elif auto_cleanup_enabled and self._cleanup_thread is None: + # 如果啟用了自動清理但線程不存在,重新啟動 + self._start_auto_cleanup() + + if cleanup_interval is not None: + self.cleanup_interval = max(60, cleanup_interval) # 最小1分鐘 + + if temp_file_max_age is not None: + self.temp_file_max_age = max(300, temp_file_max_age) # 最小5分鐘 + + debug_log(f"ResourceManager 配置已更新: auto_cleanup={self.auto_cleanup_enabled}, " + f"interval={self.cleanup_interval}, max_age={self.temp_file_max_age}") + + +# 全局資源管理器實例 +_resource_manager = None + + +def get_resource_manager() -> ResourceManager: + """ + 獲取全局資源管理器實例 + + Returns: + ResourceManager: 資源管理器實例 + """ + global _resource_manager + if _resource_manager is None: + _resource_manager = ResourceManager() + return _resource_manager + + +# 便捷函數 +def create_temp_file(suffix: str = "", prefix: str = "mcp_", **kwargs) -> str: + """創建臨時文件的便捷函數""" + return get_resource_manager().create_temp_file(suffix=suffix, prefix=prefix, **kwargs) + + +def create_temp_dir(suffix: str = "", prefix: str = "mcp_", **kwargs) -> str: + """創建臨時目錄的便捷函數""" + return get_resource_manager().create_temp_dir(suffix=suffix, prefix=prefix, **kwargs) + + +def register_process(process: Union[subprocess.Popen, int], description: str = "", **kwargs) -> int: + """註冊進程的便捷函數""" + return get_resource_manager().register_process(process, description=description, **kwargs) + + +def cleanup_all_resources(force: bool = False) -> Dict[str, int]: + """清理所有資源的便捷函數""" + return get_resource_manager().cleanup_all(force=force) diff --git a/src/mcp_feedback_enhanced/web/locales/en/translation.json b/src/mcp_feedback_enhanced/web/locales/en/translation.json index f82e688..a77db81 100644 --- a/src/mcp_feedback_enhanced/web/locales/en/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/en/translation.json @@ -10,7 +10,7 @@ "commands": "⚡ Commands", "command": "⚡ Commands", "settings": "⚙️ Settings", - "combined": "📝 Combined Mode", + "combined": "📝 Workspace", "about": "ℹ️ About" }, "feedback": { @@ -73,7 +73,7 @@ "history": "Command History" }, "combined": { - "description": "Combined mode: AI summary and feedback input are on the same page for easy comparison.", + "description": "AI summary and feedback input are on the same page for easy comparison.", "summaryTitle": "📋 AI Work Summary", "feedbackTitle": "💬 Provide Feedback" }, @@ -86,12 +86,10 @@ "interface": "Interface Settings", "layoutMode": "Interface Layout Mode", "layoutModeDesc": "Select how AI summary and feedback input are displayed", - "separateMode": "Separate Mode", - "separateModeDesc": "AI summary and feedback are in separate tabs", - "combinedVertical": "Combined Mode (Vertical Layout)", - "combinedVerticalDesc": "AI summary on top, feedback input below, both on the same page", - "combinedHorizontal": "Combined Mode (Horizontal Layout)", - "combinedHorizontalDesc": "AI summary on left, feedback input on right, expanding summary viewing area", + "combinedVertical": "Vertical Layout", + "combinedVerticalDesc": "AI summary on top, feedback input below, suitable for standard screens", + "combinedHorizontal": "Horizontal Layout", + "combinedHorizontalDesc": "AI summary on left, feedback input on right, suitable for widescreen displays", "autoClose": "Auto Close Page", "autoCloseDesc": "Automatically close page after submitting feedback", "theme": "Theme", @@ -172,6 +170,15 @@ "timeoutDescription": "Due to prolonged inactivity, the session has timed out. The interface will automatically close in 3 seconds.", "closing": "Closing..." }, + "autoRefresh": { + "enable": "Auto Detect", + "seconds": "seconds", + "disabled": "Disabled", + "enabled": "Detecting", + "checking": "Checking", + "detected": "Detected", + "error": "Failed" + }, "dynamic": { "aiSummary": "Test Web UI Functionality\n\n🎯 **Test Items:**\n- Web UI server startup and operation\n- WebSocket real-time communication\n- Feedback submission functionality\n- Image upload and preview\n- Command execution functionality\n- Smart Ctrl+V image pasting\n- Multi-language interface functionality\n\n📋 **Test Steps:**\n1. Test image upload (drag-drop, file selection, clipboard)\n2. Press Ctrl+V in text box to test smart pasting\n3. Try switching languages (Traditional Chinese/Simplified Chinese/English)\n4. Test command execution functionality\n5. Submit feedback and images\n\nPlease test these features and provide feedback!", diff --git a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json index ca91af0..e1944df 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json @@ -10,7 +10,7 @@ "commands": "⚡ 命令", "command": "⚡ 命令", "settings": "⚙️ 设置", - "combined": "📝 合并模式", + "combined": "📝 工作区", "about": "ℹ️ 关于" }, "feedback": { @@ -73,7 +73,7 @@ "history": "命令历史" }, "combined": { - "description": "合并模式:AI 摘要和反馈输入在同一页面中,方便对照查看。", + "description": "AI 摘要和反馈输入在同一页面中,方便对照查看。", "summaryTitle": "📋 AI 工作摘要", "feedbackTitle": "💬 提供反馈" }, @@ -86,12 +86,10 @@ "interface": "界面设定", "layoutMode": "界面布局模式", "layoutModeDesc": "选择 AI 摘要和反馈输入的显示方式", - "separateMode": "分离模式", - "separateModeDesc": "AI 摘要和反馈分别在不同页签", - "combinedVertical": "合并模式(垂直布局)", - "combinedVerticalDesc": "AI 摘要在上,反馈输入在下,摘要和反馈在同一页面", - "combinedHorizontal": "合并模式(水平布局)", - "combinedHorizontalDesc": "AI 摘要在左,反馈输入在右,增大摘要可视区域", + "combinedVertical": "垂直布局", + "combinedVerticalDesc": "AI 摘要在上,反馈输入在下,适合标准屏幕使用", + "combinedHorizontal": "水平布局", + "combinedHorizontalDesc": "AI 摘要在左,反馈输入在右,适合宽屏幕使用", "autoClose": "自动关闭页面", "autoCloseDesc": "提交回馈后自动关闭页面", "theme": "主题", @@ -172,6 +170,15 @@ "timeoutDescription": "由于长时间无响应,会话已超时。界面将在 3 秒后自动关闭。", "closing": "正在关闭..." }, + "autoRefresh": { + "enable": "自动检测", + "seconds": "秒", + "disabled": "停用", + "enabled": "检测中", + "checking": "检查中", + "detected": "已检测", + "error": "失败" + }, "dynamic": { "aiSummary": "测试 Web UI 功能\n\n🎯 **功能测试项目:**\n- Web UI 服务器启动和运行\n- WebSocket 实时通讯\n- 反馈提交功能\n- 图片上传和预览\n- 命令执行功能\n- 智能 Ctrl+V 图片粘贴\n- 多语言界面功能\n\n📋 **测试步骤:**\n1. 测试图片上传(拖拽、选择文件、剪贴板)\n2. 在文本框内按 Ctrl+V 测试智能粘贴\n3. 尝试切换语言(繁中/简中/英文)\n4. 测试命令执行功能\n5. 提交反馈和图片\n\n请测试这些功能并提供反馈!", diff --git a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json index c43af12..4cdd204 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json @@ -10,7 +10,7 @@ "commands": "⚡ 命令", "command": "⚡ 命令", "settings": "⚙️ 設定", - "combined": "📝 合併模式", + "combined": "📝 工作區", "about": "ℹ️ 關於" }, "feedback": { @@ -73,7 +73,7 @@ "history": "命令歷史" }, "combined": { - "description": "合併模式:AI 摘要和回饋輸入在同一頁面中,方便對照查看。", + "description": "AI 摘要和回饋輸入在同一頁面中,方便對照查看。", "summaryTitle": "📋 AI 工作摘要", "feedbackTitle": "💬 提供回饋" }, @@ -86,12 +86,10 @@ "interface": "介面設定", "layoutMode": "界面佈局模式", "layoutModeDesc": "選擇 AI 摘要和回饋輸入的顯示方式", - "separateMode": "分離模式", - "separateModeDesc": "AI 摘要和回饋分別在不同頁籤", - "combinedVertical": "合併模式(垂直布局)", - "combinedVerticalDesc": "AI 摘要在上,回饋輸入在下,摘要和回饋在同一頁面", - "combinedHorizontal": "合併模式(水平布局)", - "combinedHorizontalDesc": "AI 摘要在左,回饋輸入在右,增大摘要可視區域", + "combinedVertical": "垂直佈局", + "combinedVerticalDesc": "AI 摘要在上,回饋輸入在下,適合標準螢幕使用", + "combinedHorizontal": "水平佈局", + "combinedHorizontalDesc": "AI 摘要在左,回饋輸入在右,適合寬螢幕使用", "autoClose": "自動關閉頁面", "autoCloseDesc": "提交回饋後自動關閉頁面", "theme": "主題", @@ -172,6 +170,15 @@ "timeoutDescription": "由於長時間無回應,會話已超時。介面將在 3 秒後自動關閉。", "closing": "正在關閉..." }, + "autoRefresh": { + "enable": "自動檢測", + "seconds": "秒", + "disabled": "停用", + "enabled": "檢測中", + "checking": "檢查中", + "detected": "已檢測", + "error": "失敗" + }, "dynamic": { "aiSummary": "測試 Web UI 功能\n\n🎯 **功能測試項目:**\n- Web UI 服務器啟動和運行\n- WebSocket 即時通訊\n- 回饋提交功能\n- 圖片上傳和預覽\n- 命令執行功能\n- 智能 Ctrl+V 圖片貼上\n- 多語言介面功能\n\n📋 **測試步驟:**\n1. 測試圖片上傳(拖拽、選擇檔案、剪貼簿)\n2. 在文字框內按 Ctrl+V 測試智能貼上\n3. 嘗試切換語言(繁中/簡中/英文)\n4. 測試命令執行功能\n5. 提交回饋和圖片\n\n請測試這些功能並提供回饋!", diff --git a/src/mcp_feedback_enhanced/web/main.py b/src/mcp_feedback_enhanced/web/main.py index 1e940bd..ac110d8 100644 --- a/src/mcp_feedback_enhanced/web/main.py +++ b/src/mcp_feedback_enhanced/web/main.py @@ -17,17 +17,23 @@ import threading import time import webbrowser from pathlib import Path -from typing import Dict, Optional +from typing import Dict, Optional, List +from datetime import datetime import uuid -from fastapi import FastAPI +from fastapi import FastAPI, Request, Response from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates +from fastapi.middleware.gzip import GZipMiddleware import uvicorn -from .models import WebFeedbackSession, FeedbackResult +from .models import WebFeedbackSession, FeedbackResult, CleanupReason, SessionStatus from .routes import setup_routes from .utils import find_free_port, get_browser_opener +from .utils.port_manager import PortManager +from .utils.compression_config import get_compression_manager +from ..utils.error_handler import ErrorHandler, ErrorType +from ..utils.memory_monitor import get_memory_monitor from ..debug import web_debug_log as debug_log from ..i18n import get_i18n_manager @@ -56,10 +62,20 @@ class WebUIManager: else: debug_log(f"未設定 MCP_WEB_PORT 環境變數,使用預設端口 {preferred_port}") - # 優先使用指定端口,確保 localStorage 的一致性 - self.port = port or find_free_port(preferred_port=preferred_port) + # 使用增強的端口管理,支持自動清理 + self.port = port or PortManager.find_free_port_enhanced( + preferred_port=preferred_port, + auto_cleanup=True, + host=self.host + ) self.app = FastAPI(title="MCP Feedback Enhanced") + # 設置壓縮和緩存中間件 + self._setup_compression_middleware() + + # 設置內存監控 + self._setup_memory_monitoring() + # 重構:使用單一活躍會話而非會話字典 self.current_session: Optional[WebFeedbackSession] = None self.sessions: Dict[str, WebFeedbackSession] = {} # 保留用於向後兼容 @@ -70,6 +86,17 @@ class WebUIManager: # 會話更新通知標記 self._pending_session_update = False + # 會話清理統計 + self.cleanup_stats = { + "total_cleanups": 0, + "expired_cleanups": 0, + "memory_pressure_cleanups": 0, + "manual_cleanups": 0, + "last_cleanup_time": None, + "total_cleanup_duration": 0.0, + "sessions_cleaned": 0 + } + self.server_thread = None self.server_process = None self.i18n = get_i18n_manager() @@ -83,6 +110,105 @@ class WebUIManager: debug_log(f"WebUIManager 初始化完成,將在 {self.host}:{self.port} 啟動") + def _setup_compression_middleware(self): + """設置壓縮和緩存中間件""" + # 獲取壓縮管理器 + compression_manager = get_compression_manager() + config = compression_manager.config + + # 添加 Gzip 壓縮中間件 + self.app.add_middleware( + GZipMiddleware, + minimum_size=config.minimum_size + ) + + # 添加緩存和壓縮統計中間件 + @self.app.middleware("http") + async def compression_and_cache_middleware(request: Request, call_next): + """壓縮和緩存中間件""" + response = await call_next(request) + + # 添加緩存頭 + if not config.should_exclude_path(request.url.path): + cache_headers = config.get_cache_headers(request.url.path) + for key, value in cache_headers.items(): + response.headers[key] = value + + # 更新壓縮統計(如果可能) + try: + content_length = int(response.headers.get('content-length', 0)) + content_encoding = response.headers.get('content-encoding', '') + was_compressed = 'gzip' in content_encoding + + if content_length > 0: + # 估算原始大小(如果已壓縮,假設壓縮比為 30%) + original_size = content_length if not was_compressed else int(content_length / 0.7) + compression_manager.update_stats(original_size, content_length, was_compressed) + except (ValueError, TypeError): + # 忽略統計錯誤,不影響正常響應 + pass + + return response + + debug_log("壓縮和緩存中間件設置完成") + + def _setup_memory_monitoring(self): + """設置內存監控""" + try: + self.memory_monitor = get_memory_monitor() + + # 添加 Web 應用特定的警告回調 + def web_memory_alert(alert): + debug_log(f"Web UI 內存警告 [{alert.level}]: {alert.message}") + + # 根據警告級別觸發不同的清理策略 + if alert.level == "critical": + # 危險級別:清理過期會話 + cleaned = self.cleanup_expired_sessions() + debug_log(f"內存危險警告觸發,清理了 {cleaned} 個過期會話") + elif alert.level == "emergency": + # 緊急級別:強制清理會話 + cleaned = self.cleanup_sessions_by_memory_pressure(force=True) + debug_log(f"內存緊急警告觸發,強制清理了 {cleaned} 個會話") + + self.memory_monitor.add_alert_callback(web_memory_alert) + + # 添加會話清理回調到內存監控 + def session_cleanup_callback(force: bool = False): + """內存監控觸發的會話清理回調""" + try: + if force: + # 強制清理:包括內存壓力清理 + cleaned = self.cleanup_sessions_by_memory_pressure(force=True) + debug_log(f"內存監控強制清理了 {cleaned} 個會話") + else: + # 常規清理:只清理過期會話 + cleaned = self.cleanup_expired_sessions() + debug_log(f"內存監控清理了 {cleaned} 個過期會話") + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "內存監控會話清理", "force": force}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"內存監控會話清理失敗 [錯誤ID: {error_id}]: {e}") + + self.memory_monitor.add_cleanup_callback(session_cleanup_callback) + + # 確保內存監控已啟動(ResourceManager 可能已經啟動了) + if not self.memory_monitor.is_monitoring: + self.memory_monitor.start_monitoring() + + debug_log("Web UI 內存監控設置完成,已集成會話清理回調") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "設置 Web UI 內存監控"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"設置 Web UI 內存監控失敗 [錯誤ID: {error_id}]: {e}") + def _setup_static_files(self): """設置靜態文件服務""" # Web UI 靜態文件 @@ -135,7 +261,7 @@ class WebUIManager: # 處理會話更新通知 if old_websocket: - # 有舊連接,立即發送會話更新通知 + # 有舊連接,立即發送會話更新通知並轉移連接 self._old_websocket_for_update = old_websocket self._new_session_for_update = session debug_log("已保存舊 WebSocket 連接,準備發送會話更新通知") @@ -143,10 +269,13 @@ class WebUIManager: # 立即發送會話更新通知 import asyncio try: - # 在後台任務中發送通知 + # 在後台任務中發送通知並轉移連接 asyncio.create_task(self._send_immediate_session_update()) except Exception as e: debug_log(f"創建會話更新任務失敗: {e}") + # 即使任務創建失敗,也要嘗試直接轉移連接 + session.websocket = old_websocket + debug_log("任務創建失敗,直接轉移 WebSocket 連接到新會話") self._pending_session_update = True else: # 沒有舊連接,標記需要發送會話更新通知(當新 WebSocket 連接建立時) @@ -262,16 +391,44 @@ class WebUIManager: if e.errno == 10048: # Windows: 位址已在使用中 retry_count += 1 if retry_count < max_retries: - debug_log(f"端口 {self.port} 被占用,嘗試下一個端口") - self.port = find_free_port(self.port + 1) + debug_log(f"端口 {self.port} 被占用,使用增強端口管理查找新端口") + # 使用增強的端口管理查找新端口 + try: + self.port = PortManager.find_free_port_enhanced( + preferred_port=self.port + 1, + auto_cleanup=False, # 啟動時不自動清理,避免誤殺其他服務 + host=self.host + ) + debug_log(f"找到新的可用端口: {self.port}") + except RuntimeError as port_error: + # 使用統一錯誤處理 + error_id = ErrorHandler.log_error_with_context( + port_error, + context={"operation": "端口查找", "current_port": self.port}, + error_type=ErrorType.NETWORK + ) + debug_log(f"無法找到可用端口 [錯誤ID: {error_id}]: {port_error}") + break else: debug_log("已達到最大重試次數,無法啟動伺服器") break else: - debug_log(f"伺服器啟動錯誤: {e}") + # 使用統一錯誤處理 + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "伺服器啟動", "host": self.host, "port": self.port}, + error_type=ErrorType.NETWORK + ) + debug_log(f"伺服器啟動錯誤 [錯誤ID: {error_id}]: {e}") break except Exception as e: - debug_log(f"伺服器運行錯誤: {e}") + # 使用統一錯誤處理 + error_id = ErrorHandler.log_error_with_context( + e, + context={"operation": "伺服器運行", "host": self.host, "port": self.port}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"伺服器運行錯誤 [錯誤ID: {error_id}]: {e}") break # 在新線程中啟動伺服器 @@ -351,8 +508,21 @@ class WebUIManager: old_websocket = self._old_websocket_for_update new_session = self._new_session_for_update - # 檢查舊連接是否仍然有效 - if old_websocket and not old_websocket.client_state.DISCONNECTED: + # 改進的連接有效性檢查 + websocket_valid = False + if old_websocket: + try: + # 檢查 WebSocket 連接狀態 + if hasattr(old_websocket, 'client_state'): + websocket_valid = old_websocket.client_state != old_websocket.client_state.DISCONNECTED + else: + # 如果沒有 client_state 屬性,嘗試發送測試消息來檢查連接 + websocket_valid = True + except Exception as check_error: + debug_log(f"檢查 WebSocket 連接狀態失敗: {check_error}") + websocket_valid = False + + if websocket_valid: try: # 發送會話更新通知 await old_websocket.send_json({ @@ -369,11 +539,18 @@ class WebUIManager: # 延遲一小段時間讓前端處理消息 await asyncio.sleep(0.2) + # 將 WebSocket 連接轉移到新會話 + new_session.websocket = old_websocket + debug_log("已將 WebSocket 連接轉移到新會話") + except Exception as send_error: debug_log(f"發送會話更新通知失敗: {send_error}") - - # 安全關閉舊連接 - await self._safe_close_websocket(old_websocket) + # 如果發送失敗,仍然嘗試轉移連接 + new_session.websocket = old_websocket + debug_log("發送失敗但仍轉移 WebSocket 連接到新會話") + else: + debug_log("舊 WebSocket 連接無效,設置待更新標記") + self._pending_session_update = True # 清理臨時變數 delattr(self, '_old_websocket_for_update') @@ -390,29 +567,24 @@ class WebUIManager: self._pending_session_update = True async def _safe_close_websocket(self, websocket): - """安全關閉 WebSocket 連接,避免事件循環衝突""" + """安全關閉 WebSocket 連接,避免事件循環衝突 - 僅在連接已轉移後調用""" if not websocket: return + # 注意:此方法現在主要用於清理,因為連接已經轉移到新會話 + # 只有在確認連接沒有被新會話使用時才關閉 try: # 檢查連接狀態 - if websocket.client_state.DISCONNECTED: + if hasattr(websocket, 'client_state') and websocket.client_state.DISCONNECTED: debug_log("WebSocket 已斷開,跳過關閉操作") return - # 嘗試正常關閉 - await asyncio.wait_for(websocket.close(code=1000, reason="會話更新"), timeout=2.0) - debug_log("已正常關閉舊 WebSocket 連接") + # 由於連接已轉移到新會話,這裡不再主動關閉 + # 讓新會話管理這個連接的生命週期 + debug_log("WebSocket 連接已轉移到新會話,跳過關閉操作") - except asyncio.TimeoutError: - debug_log("WebSocket 關閉超時,強制斷開") - except RuntimeError as e: - if "attached to a different loop" in str(e): - debug_log(f"WebSocket 事件循環衝突,忽略關閉錯誤: {e}") - else: - debug_log(f"WebSocket 關閉時發生運行時錯誤: {e}") except Exception as e: - debug_log(f"關閉 WebSocket 連接時發生未知錯誤: {e}") + debug_log(f"檢查 WebSocket 連接狀態時發生錯誤: {e}") async def _check_active_tabs(self) -> bool: """檢查是否有活躍標籤頁 - 優先檢查全局狀態,回退到 API""" @@ -451,13 +623,176 @@ class WebUIManager: """獲取伺服器 URL""" return f"http://{self.host}:{self.port}" + def cleanup_expired_sessions(self) -> int: + """清理過期會話""" + cleanup_start_time = time.time() + expired_sessions = [] + + # 掃描過期會話 + for session_id, session in self.sessions.items(): + if session.is_expired(): + expired_sessions.append(session_id) + + # 批量清理過期會話 + cleaned_count = 0 + for session_id in expired_sessions: + try: + session = self.sessions.get(session_id) + if session: + # 使用增強清理方法 + session._cleanup_sync_enhanced(CleanupReason.EXPIRED) + del self.sessions[session_id] + cleaned_count += 1 + + # 如果清理的是當前活躍會話,清空當前會話 + if self.current_session and self.current_session.session_id == session_id: + self.current_session = None + debug_log("清空過期的當前活躍會話") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"session_id": session_id, "operation": "清理過期會話"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"清理過期會話 {session_id} 失敗 [錯誤ID: {error_id}]: {e}") + + # 更新統計 + cleanup_duration = time.time() - cleanup_start_time + self.cleanup_stats.update({ + "total_cleanups": self.cleanup_stats["total_cleanups"] + 1, + "expired_cleanups": self.cleanup_stats["expired_cleanups"] + 1, + "last_cleanup_time": datetime.now().isoformat(), + "total_cleanup_duration": self.cleanup_stats["total_cleanup_duration"] + cleanup_duration, + "sessions_cleaned": self.cleanup_stats["sessions_cleaned"] + cleaned_count + }) + + if cleaned_count > 0: + debug_log(f"清理了 {cleaned_count} 個過期會話,耗時: {cleanup_duration:.2f}秒") + + return cleaned_count + + def cleanup_sessions_by_memory_pressure(self, force: bool = False) -> int: + """根據內存壓力清理會話""" + cleanup_start_time = time.time() + sessions_to_clean = [] + + # 根據優先級選擇要清理的會話 + # 優先級:已完成 > 已提交反饋 > 錯誤狀態 > 空閒時間最長 + for session_id, session in self.sessions.items(): + # 跳過當前活躍會話(除非強制清理) + if not force and self.current_session and session.session_id == self.current_session.session_id: + continue + + # 優先清理已完成或錯誤狀態的會話 + if session.status in [SessionStatus.COMPLETED, SessionStatus.ERROR, SessionStatus.TIMEOUT]: + sessions_to_clean.append((session_id, session, 1)) # 高優先級 + elif session.status == SessionStatus.FEEDBACK_SUBMITTED: + # 已提交反饋但空閒時間較長的會話 + if session.get_idle_time() > 300: # 5分鐘空閒 + sessions_to_clean.append((session_id, session, 2)) # 中優先級 + elif session.get_idle_time() > 600: # 10分鐘空閒 + sessions_to_clean.append((session_id, session, 3)) # 低優先級 + + # 按優先級排序 + sessions_to_clean.sort(key=lambda x: x[2]) + + # 清理會話(限制數量避免過度清理) + max_cleanup = min(len(sessions_to_clean), 5 if not force else len(sessions_to_clean)) + cleaned_count = 0 + + for i in range(max_cleanup): + session_id, session, priority = sessions_to_clean[i] + try: + # 使用增強清理方法 + session._cleanup_sync_enhanced(CleanupReason.MEMORY_PRESSURE) + del self.sessions[session_id] + cleaned_count += 1 + + # 如果清理的是當前活躍會話,清空當前會話 + if self.current_session and self.current_session.session_id == session_id: + self.current_session = None + debug_log("因內存壓力清空當前活躍會話") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"session_id": session_id, "operation": "內存壓力清理"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"內存壓力清理會話 {session_id} 失敗 [錯誤ID: {error_id}]: {e}") + + # 更新統計 + cleanup_duration = time.time() - cleanup_start_time + self.cleanup_stats.update({ + "total_cleanups": self.cleanup_stats["total_cleanups"] + 1, + "memory_pressure_cleanups": self.cleanup_stats["memory_pressure_cleanups"] + 1, + "last_cleanup_time": datetime.now().isoformat(), + "total_cleanup_duration": self.cleanup_stats["total_cleanup_duration"] + cleanup_duration, + "sessions_cleaned": self.cleanup_stats["sessions_cleaned"] + cleaned_count + }) + + if cleaned_count > 0: + debug_log(f"因內存壓力清理了 {cleaned_count} 個會話,耗時: {cleanup_duration:.2f}秒") + + return cleaned_count + + def get_session_cleanup_stats(self) -> dict: + """獲取會話清理統計""" + stats = self.cleanup_stats.copy() + stats.update({ + "active_sessions": len(self.sessions), + "current_session_id": self.current_session.session_id if self.current_session else None, + "expired_sessions": sum(1 for s in self.sessions.values() if s.is_expired()), + "idle_sessions": sum(1 for s in self.sessions.values() if s.get_idle_time() > 300), + "memory_usage_mb": 0 # 將在下面計算 + }) + + # 計算內存使用(如果可能) + try: + import psutil + process = psutil.Process() + stats["memory_usage_mb"] = round(process.memory_info().rss / (1024 * 1024), 2) + except: + pass + + return stats + + def _scan_expired_sessions(self) -> List[str]: + """掃描過期會話ID列表""" + expired_sessions = [] + for session_id, session in self.sessions.items(): + if session.is_expired(): + expired_sessions.append(session_id) + return expired_sessions + def stop(self): """停止 Web UI 服務""" # 清理所有會話 + cleanup_start_time = time.time() + session_count = len(self.sessions) + for session in list(self.sessions.values()): - session.cleanup() + try: + session._cleanup_sync_enhanced(CleanupReason.SHUTDOWN) + except Exception as e: + debug_log(f"停止服務時清理會話失敗: {e}") + self.sessions.clear() - + self.current_session = None + + # 更新統計 + cleanup_duration = time.time() - cleanup_start_time + self.cleanup_stats.update({ + "total_cleanups": self.cleanup_stats["total_cleanups"] + 1, + "manual_cleanups": self.cleanup_stats["manual_cleanups"] + 1, + "last_cleanup_time": datetime.now().isoformat(), + "total_cleanup_duration": self.cleanup_stats["total_cleanup_duration"] + cleanup_duration, + "sessions_cleaned": self.cleanup_stats["sessions_cleaned"] + session_count + }) + + debug_log(f"停止服務時清理了 {session_count} 個會話,耗時: {cleanup_duration:.2f}秒") + # 停止伺服器(注意:uvicorn 的 graceful shutdown 需要額外處理) if self.server_thread and self.server_thread.is_alive(): debug_log("正在停止 Web UI 服務") diff --git a/src/mcp_feedback_enhanced/web/models/__init__.py b/src/mcp_feedback_enhanced/web/models/__init__.py index d06dd01..36e7108 100644 --- a/src/mcp_feedback_enhanced/web/models/__init__.py +++ b/src/mcp_feedback_enhanced/web/models/__init__.py @@ -7,10 +7,12 @@ Web UI 資料模型模組 定義 Web UI 相關的資料結構和型別。 """ -from .feedback_session import WebFeedbackSession +from .feedback_session import WebFeedbackSession, SessionStatus, CleanupReason from .feedback_result import FeedbackResult __all__ = [ 'WebFeedbackSession', + 'SessionStatus', + 'CleanupReason', 'FeedbackResult' -] \ No newline at end of file +] \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/web/models/feedback_session.py b/src/mcp_feedback_enhanced/web/models/feedback_session.py index 746c2f2..05c8d6f 100644 --- a/src/mcp_feedback_enhanced/web/models/feedback_session.py +++ b/src/mcp_feedback_enhanced/web/models/feedback_session.py @@ -11,13 +11,17 @@ import asyncio import base64 import subprocess import threading +import time +from datetime import datetime, timedelta from enum import Enum from pathlib import Path -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Callable from fastapi import WebSocket from ...debug import web_debug_log as debug_log +from ...utils.resource_manager import get_resource_manager, register_process +from ...utils.error_handler import ErrorHandler, ErrorType class SessionStatus(Enum): @@ -28,6 +32,17 @@ class SessionStatus(Enum): COMPLETED = "completed" # 已完成 TIMEOUT = "timeout" # 超時 ERROR = "error" # 錯誤 + EXPIRED = "expired" # 已過期 + + +class CleanupReason(Enum): + """清理原因枚舉""" + TIMEOUT = "timeout" # 超時清理 + EXPIRED = "expired" # 過期清理 + MEMORY_PRESSURE = "memory_pressure" # 內存壓力清理 + MANUAL = "manual" # 手動清理 + ERROR = "error" # 錯誤清理 + SHUTDOWN = "shutdown" # 系統關閉清理 # 常數定義 MAX_IMAGE_SIZE = 1 * 1024 * 1024 # 1MB 圖片大小限制 @@ -38,7 +53,8 @@ TEMP_DIR = Path.home() / ".cache" / "interactive-feedback-mcp-web" class WebFeedbackSession: """Web 回饋會話管理""" - def __init__(self, session_id: str, project_directory: str, summary: str): + def __init__(self, session_id: str, project_directory: str, summary: str, + auto_cleanup_delay: int = 3600, max_idle_time: int = 1800): self.session_id = session_id self.project_directory = project_directory self.summary = summary @@ -54,18 +70,49 @@ class WebFeedbackSession: # 新增:會話狀態管理 self.status = SessionStatus.WAITING self.status_message = "等待用戶回饋" - self.created_at = asyncio.get_event_loop().time() + # 統一使用 time.time() 以避免時間基準不一致 + self.created_at = time.time() self.last_activity = self.created_at + # 新增:自動清理配置 + self.auto_cleanup_delay = auto_cleanup_delay # 自動清理延遲時間(秒) + self.max_idle_time = max_idle_time # 最大空閒時間(秒) + self.cleanup_timer: Optional[threading.Timer] = None + self.cleanup_callbacks: List[Callable] = [] # 清理回調函數列表 + + # 新增:清理統計 + self.cleanup_stats = { + "cleanup_count": 0, + "last_cleanup_time": None, + "cleanup_reason": None, + "cleanup_duration": 0.0, + "memory_freed": 0, + "resources_cleaned": 0 + } + # 確保臨時目錄存在 TEMP_DIR.mkdir(parents=True, exist_ok=True) + # 獲取資源管理器實例 + self.resource_manager = get_resource_manager() + + # 啟動自動清理定時器 + self._schedule_auto_cleanup() + + debug_log(f"會話 {self.session_id} 初始化完成,自動清理延遲: {auto_cleanup_delay}秒,最大空閒: {max_idle_time}秒") + def update_status(self, status: SessionStatus, message: str = None): """更新會話狀態""" self.status = status if message: self.status_message = message - self.last_activity = asyncio.get_event_loop().time() + # 統一使用 time.time() + self.last_activity = time.time() + + # 如果會話變為活躍狀態,重置清理定時器 + if status in [SessionStatus.ACTIVE, SessionStatus.FEEDBACK_SUBMITTED]: + self._schedule_auto_cleanup() + debug_log(f"會話 {self.session_id} 狀態更新: {status.value} - {self.status_message}") def get_status_info(self) -> dict: @@ -86,6 +133,117 @@ class WebFeedbackSession: """檢查會話是否活躍""" return self.status in [SessionStatus.WAITING, SessionStatus.ACTIVE, SessionStatus.FEEDBACK_SUBMITTED] + def is_expired(self) -> bool: + """檢查會話是否已過期""" + # 統一使用 time.time() + current_time = time.time() + + # 檢查是否超過最大空閒時間 + idle_time = current_time - self.last_activity + if idle_time > self.max_idle_time: + debug_log(f"會話 {self.session_id} 空閒時間過長: {idle_time:.1f}秒 > {self.max_idle_time}秒") + return True + + # 檢查是否處於已過期狀態 + if self.status == SessionStatus.EXPIRED: + return True + + # 檢查是否處於錯誤或超時狀態且超過一定時間 + if self.status in [SessionStatus.ERROR, SessionStatus.TIMEOUT]: + error_time = current_time - self.last_activity + if error_time > 300: # 錯誤狀態超過5分鐘視為過期 + debug_log(f"會話 {self.session_id} 錯誤狀態時間過長: {error_time:.1f}秒") + return True + + return False + + def get_age(self) -> float: + """獲取會話年齡(秒)""" + current_time = time.time() + return current_time - self.created_at + + def get_idle_time(self) -> float: + """獲取會話空閒時間(秒)""" + current_time = time.time() + return current_time - self.last_activity + + def _schedule_auto_cleanup(self): + """安排自動清理定時器""" + if self.cleanup_timer: + self.cleanup_timer.cancel() + + def auto_cleanup(): + """自動清理回調""" + try: + if not self._cleanup_done and self.is_expired(): + debug_log(f"會話 {self.session_id} 觸發自動清理(過期)") + # 使用異步方式執行清理 + import asyncio + try: + loop = asyncio.get_event_loop() + loop.create_task(self._cleanup_resources_enhanced(CleanupReason.EXPIRED)) + except RuntimeError: + # 如果沒有事件循環,使用同步清理 + self._cleanup_sync_enhanced(CleanupReason.EXPIRED) + else: + # 如果還沒過期,重新安排定時器 + self._schedule_auto_cleanup() + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={"session_id": self.session_id, "operation": "自動清理"}, + error_type=ErrorType.SYSTEM + ) + debug_log(f"自動清理失敗 [錯誤ID: {error_id}]: {e}") + + self.cleanup_timer = threading.Timer(self.auto_cleanup_delay, auto_cleanup) + self.cleanup_timer.daemon = True + self.cleanup_timer.start() + debug_log(f"會話 {self.session_id} 自動清理定時器已設置,{self.auto_cleanup_delay}秒後觸發") + + def extend_cleanup_timer(self, additional_time: int = None): + """延長清理定時器""" + if additional_time is None: + additional_time = self.auto_cleanup_delay + + if self.cleanup_timer: + self.cleanup_timer.cancel() + + self.cleanup_timer = threading.Timer(additional_time, lambda: None) + self.cleanup_timer.daemon = True + self.cleanup_timer.start() + + debug_log(f"會話 {self.session_id} 清理定時器已延長 {additional_time} 秒") + + def add_cleanup_callback(self, callback: Callable): + """添加清理回調函數""" + if callback not in self.cleanup_callbacks: + self.cleanup_callbacks.append(callback) + debug_log(f"會話 {self.session_id} 添加清理回調函數") + + def remove_cleanup_callback(self, callback: Callable): + """移除清理回調函數""" + if callback in self.cleanup_callbacks: + self.cleanup_callbacks.remove(callback) + debug_log(f"會話 {self.session_id} 移除清理回調函數") + + def get_cleanup_stats(self) -> dict: + """獲取清理統計信息""" + stats = self.cleanup_stats.copy() + stats.update({ + "session_id": self.session_id, + "age": self.get_age(), + "idle_time": self.get_idle_time(), + "is_expired": self.is_expired(), + "is_active": self.is_active(), + "status": self.status.value, + "has_websocket": self.websocket is not None, + "has_process": self.process is not None, + "command_logs_count": len(self.command_logs), + "images_count": len(self.images) + }) + return stats + async def wait_for_feedback(self, timeout: int = 600) -> dict: """ 等待用戶回饋,包含圖片,支援超時自動清理 @@ -249,6 +407,13 @@ class WebFeedbackSession: universal_newlines=True ) + # 註冊進程到資源管理器 + register_process( + self.process, + description=f"WebFeedbackSession-{self.session_id}-command", + auto_cleanup=True + ) + # 在背景線程中讀取輸出 async def read_output(): loop = asyncio.get_event_loop() @@ -281,7 +446,10 @@ class WebFeedbackSession: # 等待進程完成 if self.process: exit_code = self.process.wait() - + + # 從資源管理器取消註冊進程 + self.resource_manager.unregister_process(self.process.pid) + # 發送命令完成信號 if self.websocket: try: @@ -307,33 +475,72 @@ class WebFeedbackSession: pass async def _cleanup_resources_on_timeout(self): - """超時時清理所有資源""" + """超時時清理所有資源(保持向後兼容)""" + await self._cleanup_resources_enhanced(CleanupReason.TIMEOUT) + + async def _cleanup_resources_enhanced(self, reason: CleanupReason): + """增強的資源清理方法""" if self._cleanup_done: return # 避免重複清理 - + + cleanup_start_time = time.time() self._cleanup_done = True - debug_log(f"開始清理會話 {self.session_id} 的資源...") - + + debug_log(f"開始清理會話 {self.session_id} 的資源,原因: {reason.value}") + + # 更新清理統計 + self.cleanup_stats["cleanup_count"] += 1 + self.cleanup_stats["cleanup_reason"] = reason.value + self.cleanup_stats["last_cleanup_time"] = datetime.now().isoformat() + + resources_cleaned = 0 + memory_before = 0 + try: - # 1. 關閉 WebSocket 連接 + # 記錄清理前的內存使用(如果可能) + try: + import psutil + process = psutil.Process() + memory_before = process.memory_info().rss + except: + pass + + # 1. 取消自動清理定時器 + if self.cleanup_timer: + self.cleanup_timer.cancel() + self.cleanup_timer = None + resources_cleaned += 1 + + # 2. 關閉 WebSocket 連接 if self.websocket: try: - # 先通知前端超時 + # 根據清理原因發送不同的通知消息 + message_map = { + CleanupReason.TIMEOUT: "會話已超時,介面將自動關閉", + CleanupReason.EXPIRED: "會話已過期,介面將自動關閉", + CleanupReason.MEMORY_PRESSURE: "系統內存不足,會話將被清理", + CleanupReason.MANUAL: "會話已被手動清理", + CleanupReason.ERROR: "會話發生錯誤,將被清理", + CleanupReason.SHUTDOWN: "系統正在關閉,會話將被清理" + } + await self.websocket.send_json({ - "type": "session_timeout", - "message": "會話已超時,介面將自動關閉" + "type": "session_cleanup", + "reason": reason.value, + "message": message_map.get(reason, "會話將被清理") }) await asyncio.sleep(0.1) # 給前端一點時間處理消息 # 安全關閉 WebSocket await self._safe_close_websocket() debug_log(f"會話 {self.session_id} WebSocket 已關閉") + resources_cleaned += 1 except Exception as e: debug_log(f"關閉 WebSocket 時發生錯誤: {e}") finally: self.websocket = None - - # 2. 終止正在運行的命令進程 + + # 3. 終止正在運行的命令進程 if self.process: try: self.process.terminate() @@ -343,67 +550,213 @@ class WebFeedbackSession: except subprocess.TimeoutExpired: self.process.kill() debug_log(f"會話 {self.session_id} 命令進程已強制終止") + resources_cleaned += 1 except Exception as e: debug_log(f"終止命令進程時發生錯誤: {e}") finally: self.process = None - - # 3. 設置完成事件(防止其他地方還在等待) + + # 4. 設置完成事件(防止其他地方還在等待) self.feedback_completed.set() - - # 4. 清理臨時數據 + + # 5. 清理臨時數據 + logs_count = len(self.command_logs) + images_count = len(self.images) + self.command_logs.clear() self.images.clear() - - debug_log(f"會話 {self.session_id} 資源清理完成") - + self.settings.clear() + + if logs_count > 0 or images_count > 0: + resources_cleaned += logs_count + images_count + debug_log(f"清理了 {logs_count} 條日誌和 {images_count} 張圖片") + + # 6. 更新會話狀態 + if reason == CleanupReason.EXPIRED: + self.status = SessionStatus.EXPIRED + elif reason == CleanupReason.TIMEOUT: + self.status = SessionStatus.TIMEOUT + elif reason == CleanupReason.ERROR: + self.status = SessionStatus.ERROR + else: + self.status = SessionStatus.COMPLETED + + # 7. 調用清理回調函數 + for callback in self.cleanup_callbacks: + try: + if asyncio.iscoroutinefunction(callback): + await callback(self, reason) + else: + callback(self, reason) + except Exception as e: + debug_log(f"清理回調執行失敗: {e}") + + # 8. 計算清理效果 + cleanup_duration = time.time() - cleanup_start_time + memory_after = 0 + try: + import psutil + process = psutil.Process() + memory_after = process.memory_info().rss + except: + pass + + memory_freed = max(0, memory_before - memory_after) + + # 更新清理統計 + self.cleanup_stats.update({ + "cleanup_duration": cleanup_duration, + "memory_freed": memory_freed, + "resources_cleaned": resources_cleaned + }) + + debug_log(f"會話 {self.session_id} 資源清理完成,耗時: {cleanup_duration:.2f}秒," + f"清理資源: {resources_cleaned}個,釋放內存: {memory_freed}字節") + except Exception as e: - debug_log(f"清理會話 {self.session_id} 資源時發生錯誤: {e}") + error_id = ErrorHandler.log_error_with_context( + e, + context={ + "session_id": self.session_id, + "cleanup_reason": reason.value, + "operation": "增強資源清理" + }, + error_type=ErrorType.SYSTEM + ) + debug_log(f"清理會話 {self.session_id} 資源時發生錯誤 [錯誤ID: {error_id}]: {e}") + + # 即使發生錯誤也要更新統計 + self.cleanup_stats["cleanup_duration"] = time.time() - cleanup_start_time def _cleanup_sync(self): - """同步清理會話資源(但保留 WebSocket 連接)""" - if self._cleanup_done: + """同步清理會話資源(但保留 WebSocket 連接)- 保持向後兼容""" + self._cleanup_sync_enhanced(CleanupReason.MANUAL, preserve_websocket=True) + + def _cleanup_sync_enhanced(self, reason: CleanupReason, preserve_websocket: bool = False): + """增強的同步清理會話資源""" + if self._cleanup_done and not preserve_websocket: return - debug_log(f"同步清理會話 {self.session_id} 資源(保留 WebSocket)...") + cleanup_start_time = time.time() + debug_log(f"同步清理會話 {self.session_id} 資源,原因: {reason.value},保留WebSocket: {preserve_websocket}") - # 只清理進程,不清理 WebSocket 連接 - if self.process: + # 更新清理統計 + self.cleanup_stats["cleanup_count"] += 1 + self.cleanup_stats["cleanup_reason"] = reason.value + self.cleanup_stats["last_cleanup_time"] = datetime.now().isoformat() + + resources_cleaned = 0 + memory_before = 0 + + try: + # 記錄清理前的內存使用 try: - self.process.terminate() - self.process.wait(timeout=5) + import psutil + process = psutil.Process() + memory_before = process.memory_info().rss except: - try: - self.process.kill() - except: - pass - self.process = None + pass - # 清理臨時數據 - self.command_logs.clear() - # 注意:不設置 _cleanup_done = True,因為還需要清理 WebSocket + # 1. 取消自動清理定時器 + if self.cleanup_timer: + self.cleanup_timer.cancel() + self.cleanup_timer = None + resources_cleaned += 1 + + # 2. 清理進程 + if self.process: + try: + self.process.terminate() + self.process.wait(timeout=5) + debug_log(f"會話 {self.session_id} 命令進程已正常終止") + resources_cleaned += 1 + except: + try: + self.process.kill() + debug_log(f"會話 {self.session_id} 命令進程已強制終止") + resources_cleaned += 1 + except: + pass + self.process = None + + # 3. 清理臨時數據 + logs_count = len(self.command_logs) + images_count = len(self.images) + + self.command_logs.clear() + if not preserve_websocket: + self.images.clear() + self.settings.clear() + resources_cleaned += images_count + + resources_cleaned += logs_count + + # 4. 設置完成事件 + if not preserve_websocket: + self.feedback_completed.set() + + # 5. 更新狀態 + if not preserve_websocket: + if reason == CleanupReason.EXPIRED: + self.status = SessionStatus.EXPIRED + elif reason == CleanupReason.TIMEOUT: + self.status = SessionStatus.TIMEOUT + elif reason == CleanupReason.ERROR: + self.status = SessionStatus.ERROR + else: + self.status = SessionStatus.COMPLETED + + self._cleanup_done = True + + # 6. 調用清理回調函數(同步版本) + for callback in self.cleanup_callbacks: + try: + if not asyncio.iscoroutinefunction(callback): + callback(self, reason) + except Exception as e: + debug_log(f"同步清理回調執行失敗: {e}") + + # 7. 計算清理效果 + cleanup_duration = time.time() - cleanup_start_time + memory_after = 0 + try: + import psutil + process = psutil.Process() + memory_after = process.memory_info().rss + except: + pass + + memory_freed = max(0, memory_before - memory_after) + + # 更新清理統計 + self.cleanup_stats.update({ + "cleanup_duration": cleanup_duration, + "memory_freed": memory_freed, + "resources_cleaned": resources_cleaned + }) + + debug_log(f"會話 {self.session_id} 同步清理完成,耗時: {cleanup_duration:.2f}秒," + f"清理資源: {resources_cleaned}個,釋放內存: {memory_freed}字節") + + except Exception as e: + error_id = ErrorHandler.log_error_with_context( + e, + context={ + "session_id": self.session_id, + "cleanup_reason": reason.value, + "preserve_websocket": preserve_websocket, + "operation": "同步資源清理" + }, + error_type=ErrorType.SYSTEM + ) + debug_log(f"同步清理會話 {self.session_id} 資源時發生錯誤 [錯誤ID: {error_id}]: {e}") + + # 即使發生錯誤也要更新統計 + self.cleanup_stats["cleanup_duration"] = time.time() - cleanup_start_time def cleanup(self): """同步清理會話資源(保持向後兼容)""" - if self._cleanup_done: - return - - self._cleanup_done = True - debug_log(f"同步清理會話 {self.session_id} 資源...") - - if self.process: - try: - self.process.terminate() - self.process.wait(timeout=5) - except: - try: - self.process.kill() - except: - pass - self.process = None - - # 設置完成事件 - self.feedback_completed.set() + self._cleanup_sync_enhanced(CleanupReason.MANUAL) async def _safe_close_websocket(self): """安全關閉 WebSocket 連接,避免事件循環衝突""" diff --git a/src/mcp_feedback_enhanced/web/routes/main_routes.py b/src/mcp_feedback_enhanced/web/routes/main_routes.py index a8ca0eb..a1efb7b 100644 --- a/src/mcp_feedback_enhanced/web/routes/main_routes.py +++ b/src/mcp_feedback_enhanced/web/routes/main_routes.py @@ -33,15 +33,15 @@ def load_user_layout_settings() -> str: if settings_file.exists(): with open(settings_file, 'r', encoding='utf-8') as f: settings = json.load(f) - layout_mode = settings.get('layoutMode', 'separate') + layout_mode = settings.get('layoutMode', 'combined-vertical') debug_log(f"從設定檔案載入佈局模式: {layout_mode}") return layout_mode else: - debug_log("設定檔案不存在,使用預設佈局模式: separate") - return 'separate' + debug_log("設定檔案不存在,使用預設佈局模式: combined-vertical") + return 'combined-vertical' except Exception as e: - debug_log(f"載入佈局設定失敗: {e},使用預設佈局模式: separate") - return 'separate' + debug_log(f"載入佈局設定失敗: {e},使用預設佈局模式: combined-vertical") + return 'combined-vertical' def setup_routes(manager: 'WebUIManager'): @@ -140,6 +140,7 @@ def setup_routes(manager: 'WebUIManager'): ) return JSONResponse(content={ + "session_id": current_session.session_id, "project_directory": current_session.project_directory, "summary": current_session.summary, "feedback_completed": current_session.feedback_completed.is_set(), @@ -157,9 +158,13 @@ def setup_routes(manager: 'WebUIManager'): return await websocket.accept() - session.websocket = websocket - debug_log(f"WebSocket 連接建立: 當前活躍會話") + # 檢查會話是否已有 WebSocket 連接 + if session.websocket and session.websocket != websocket: + debug_log("會話已有 WebSocket 連接,替換為新連接") + + session.websocket = websocket + debug_log(f"WebSocket 連接建立: 當前活躍會話 {session.session_id}") # 發送連接成功消息 try: @@ -197,7 +202,14 @@ def setup_routes(manager: 'WebUIManager'): while True: data = await websocket.receive_text() message = json.loads(data) - await handle_websocket_message(manager, session, message) + + # 重新獲取當前會話,以防會話已切換 + current_session = manager.get_current_session() + if current_session and current_session.websocket == websocket: + await handle_websocket_message(manager, current_session, message) + else: + debug_log("會話已切換或 WebSocket 連接不匹配,忽略消息") + break except WebSocketDisconnect: debug_log(f"WebSocket 連接正常斷開") @@ -207,8 +219,9 @@ def setup_routes(manager: 'WebUIManager'): debug_log(f"WebSocket 錯誤: {e}") finally: # 安全清理 WebSocket 連接 - if session.websocket == websocket: - session.websocket = None + current_session = manager.get_current_session() + if current_session and current_session.websocket == websocket: + current_session.websocket = None debug_log("已清理會話中的 WebSocket 連接") @manager.app.post("/api/save-settings") diff --git a/src/mcp_feedback_enhanced/web/static/css/styles.css b/src/mcp_feedback_enhanced/web/static/css/styles.css index b268490..518fe87 100644 --- a/src/mcp_feedback_enhanced/web/static/css/styles.css +++ b/src/mcp_feedback_enhanced/web/static/css/styles.css @@ -1265,4 +1265,128 @@ body { .compatibility-hint-btn:hover { background: #f57c00; +} + +/* 自動刷新控制項樣式 */ +.section-header-with-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +/* AI 工作摘要標題樣式 */ +h3.combined-section-title { + font-size: 16px; + font-weight: 600; + color: var(--text-primary); + margin: 0; +} + +.auto-refresh-controls { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 6px 10px; + transition: all 0.3s ease; +} + +.auto-refresh-controls:hover { + border-color: var(--accent-color); + background: var(--surface-color); +} + +.auto-refresh-toggle { + display: flex; + align-items: center; + gap: 4px; + cursor: pointer; + user-select: none; +} + +.auto-refresh-toggle input[type="checkbox"] { + appearance: none; + width: 14px; + height: 14px; + border: 1px solid var(--border-color); + border-radius: 2px; + background: var(--bg-primary); + cursor: pointer; + transition: all 0.3s ease; + position: relative; +} + +.auto-refresh-toggle input[type="checkbox"]:checked { + background: var(--accent-color); + border-color: var(--accent-color); +} + +.auto-refresh-toggle input[type="checkbox"]:checked::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: white; + font-size: 9px; + font-weight: bold; +} + +.toggle-label { + color: var(--text-primary); + font-size: 14px; + font-weight: 500; + white-space: nowrap; +} + +.auto-refresh-interval { + display: flex; + align-items: center; + gap: 2px; +} + +.auto-refresh-interval input[type="number"] { + width: 50px; + background: var(--bg-primary); + color: var(--text-primary); + border: 1px solid var(--border-color); + border-radius: 3px; + padding: 4px 6px; + font-size: 13px; + text-align: center; +} + +.auto-refresh-interval input[type="number"]:focus { + outline: none; + border-color: var(--accent-color); +} + +.interval-unit { + color: var(--text-secondary); + font-size: 14px; + white-space: nowrap; +} + +.auto-refresh-status { + display: flex; + align-items: center; + gap: 3px; + padding: 1px 4px; + background: var(--bg-primary); + border-radius: 3px; + border: 1px solid var(--border-color); +} + +.auto-refresh-status .status-indicator { + font-size: 13px; +} + +.auto-refresh-status .status-text { + color: var(--text-secondary); + font-size: 14px; + white-space: nowrap; } \ No newline at end of file diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 0b512a6..c91257f 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -170,6 +170,13 @@ class FeedbackApp { this.heartbeatInterval = null; this.heartbeatFrequency = 30000; // 30秒 WebSocket 心跳 + // 新增:WebSocket 連接狀態管理 + this.connectionReady = false; + this.pendingSubmission = null; + this.connectionCheckInterval = null; + this.sessionUpdatePending = false; + this.reconnectDelay = 1000; // 重連延遲,會逐漸增加 + // UI 狀態 this.currentTab = 'feedback'; @@ -185,11 +192,17 @@ class FeedbackApp { // 設定 this.autoClose = false; - this.layoutMode = 'separate'; + this.layoutMode = 'combined-vertical'; // 語言設定 this.currentLanguage = 'zh-TW'; + // 自動刷新設定 + this.autoRefreshEnabled = false; + this.autoRefreshInterval = 5; // 默認5秒 + this.autoRefreshTimer = null; + this.lastKnownSessionId = null; + this.init(); } @@ -223,6 +236,9 @@ class FeedbackApp { // 確保狀態指示器使用正確的翻譯(在國際化系統載入後) this.updateStatusIndicators(); + // 初始化自動刷新功能 + this.initAutoRefresh(); + // 設置頁面關閉時的清理 window.addEventListener('beforeunload', () => { if (this.tabManager) { @@ -231,6 +247,9 @@ class FeedbackApp { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); } + if (this.autoRefreshTimer) { + clearInterval(this.autoRefreshTimer); + } }); console.log('MCP Feedback Enhanced 應用程式初始化完成'); @@ -259,6 +278,12 @@ class FeedbackApp { this.commandOutput = document.getElementById('commandOutput'); this.runCommandBtn = document.getElementById('runCommandBtn'); + // 自動刷新相關元素 + this.autoRefreshCheckbox = document.getElementById('autoRefreshEnabled'); + this.autoRefreshIntervalInput = document.getElementById('autoRefreshInterval'); + this.refreshStatusIndicator = document.getElementById('refreshStatusIndicator'); + this.refreshStatusText = document.getElementById('refreshStatusText'); + // 動態初始化圖片相關元素 this.initImageElements(); } @@ -409,15 +434,28 @@ class FeedbackApp { * 設置圖片事件監聽器 */ setupImageEventListeners() { + console.log(`🖼️ 設置圖片事件監聽器 - imageInput: ${this.imageInput?.id}, imageUploadArea: ${this.imageUploadArea?.id}`); + // 文件選擇事件 this.imageChangeHandler = (e) => { + console.log(`📁 文件選擇事件觸發 - input: ${e.target.id}, files: ${e.target.files.length}`); this.handleFileSelect(e.target.files); }; this.imageInput.addEventListener('change', this.imageChangeHandler); - // 點擊上傳區域 - this.imageClickHandler = () => { - this.imageInput.click(); + // 點擊上傳區域 - 使用更安全的方式確保只觸發對應的 input + this.imageClickHandler = (e) => { + e.preventDefault(); + e.stopPropagation(); + + // 確保我們觸發的是正確的 input 元素 + const targetInput = this.imageInput; + if (targetInput) { + console.log(`🖱️ 點擊上傳區域 - 觸發 input: ${targetInput.id}`); + targetInput.click(); + } else { + console.warn('⚠️ 沒有找到對應的 input 元素'); + } }; this.imageUploadArea.addEventListener('click', this.imageClickHandler); @@ -451,8 +489,10 @@ class FeedbackApp { // 重新初始化圖片元素(確保使用最新的佈局模式) this.initImageElements(); + console.log(`🔍 檢查圖片元素 - imageUploadArea: ${this.imageUploadArea?.id || 'null'}, imageInput: ${this.imageInput?.id || 'null'}`); + if (!this.imageUploadArea || !this.imageInput) { - console.warn('⚠️ 圖片處理初始化失敗 - 缺少必要元素'); + console.warn(`⚠️ 圖片處理初始化失敗 - imageUploadArea: ${!!this.imageUploadArea}, imageInput: ${!!this.imageInput}`); return; } @@ -486,6 +526,7 @@ class FeedbackApp { * 移除舊的圖片事件監聽器 */ removeImageEventListeners() { + // 移除當前主要元素的事件監聽器 if (this.imageInput && this.imageChangeHandler) { this.imageInput.removeEventListener('change', this.imageChangeHandler); } @@ -503,6 +544,32 @@ class FeedbackApp { this.imageUploadArea.removeEventListener('drop', this.imageDropHandler); } } + + // 額外清理:移除所有可能的圖片上傳區域的 click 事件監聽器 + const allImageUploadAreas = [ + document.getElementById('feedbackImageUploadArea'), + document.getElementById('combinedImageUploadArea') + ].filter(area => area); + + allImageUploadAreas.forEach(area => { + if (area && this.imageClickHandler) { + area.removeEventListener('click', this.imageClickHandler); + console.log(`🧹 已移除 ${area.id} 的 click 事件監聽器`); + } + }); + + // 清理所有可能的 input 元素的 change 事件監聽器 + const allImageInputs = [ + document.getElementById('feedbackImageInput'), + document.getElementById('combinedImageInput') + ].filter(input => input); + + allImageInputs.forEach(input => { + if (input && this.imageChangeHandler) { + input.removeEventListener('change', this.imageChangeHandler); + console.log(`🧹 已移除 ${input.id} 的 change 事件監聽器`); + } + }); } /** @@ -797,11 +864,11 @@ class FeedbackApp { } /** - * 檢查是否可以提交回饋 + * 檢查是否可以提交回饋(舊版本,保持兼容性) */ canSubmitFeedback() { - const canSubmit = this.feedbackState === 'waiting_for_feedback' && this.isConnected; - console.log(`🔍 檢查提交權限: feedbackState=${this.feedbackState}, isConnected=${this.isConnected}, canSubmit=${canSubmit}`); + const canSubmit = this.feedbackState === 'waiting_for_feedback' && this.isConnected && this.connectionReady; + console.log(`🔍 檢查提交權限: feedbackState=${this.feedbackState}, isConnected=${this.isConnected}, connectionReady=${this.connectionReady}, canSubmit=${canSubmit}`); return canSubmit; } @@ -965,11 +1032,13 @@ class FeedbackApp { this.websocket.onopen = () => { this.isConnected = true; + this.connectionReady = false; // 等待連接確認 this.updateConnectionStatus('connected', '已連接'); console.log('WebSocket 連接已建立'); - // 重置重連計數器 + // 重置重連計數器和延遲 this.reconnectAttempts = 0; + this.reconnectDelay = 1000; // 開始 WebSocket 心跳 this.startWebSocketHeartbeat(); @@ -982,6 +1051,23 @@ class FeedbackApp { console.log('🔄 WebSocket 重連後重置處理狀態'); this.setFeedbackState('waiting_for_feedback'); } + + // 如果有待處理的會話更新,處理它 + if (this.sessionUpdatePending) { + console.log('🔄 處理待處理的會話更新'); + this.sessionUpdatePending = false; + } + + // 如果有待提交的回饋,處理它 + if (this.pendingSubmission) { + console.log('🔄 處理待提交的回饋'); + setTimeout(() => { + if (this.connectionReady && this.pendingSubmission) { + this.submitFeedbackInternal(this.pendingSubmission); + this.pendingSubmission = null; + } + }, 500); // 等待連接完全就緒 + } }; this.websocket.onmessage = (event) => { @@ -995,6 +1081,7 @@ class FeedbackApp { this.websocket.onclose = (event) => { this.isConnected = false; + this.connectionReady = false; console.log('WebSocket 連接已關閉, code:', event.code, 'reason:', event.reason); // 停止心跳 @@ -1012,15 +1099,23 @@ class FeedbackApp { } else { this.updateConnectionStatus('disconnected', '已斷開'); + // 會話更新導致的正常關閉,立即重連 + if (event.code === 1000 && event.reason === '會話更新') { + console.log('🔄 會話更新導致的連接關閉,立即重連...'); + this.sessionUpdatePending = true; + setTimeout(() => { + this.setupWebSocket(); + }, 200); // 短延遲後重連 + } // 只有在非正常關閉時才重連 - if (event.code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) { + else if (event.code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; - const delay = Math.min(3000 * this.reconnectAttempts, 15000); // 最大延遲15秒 - console.log(`${delay/1000}秒後嘗試重連... (第${this.reconnectAttempts}次)`); + this.reconnectDelay = Math.min(this.reconnectDelay * 1.5, 15000); // 指數退避,最大15秒 + console.log(`${this.reconnectDelay / 1000}秒後嘗試重連... (第${this.reconnectAttempts}次)`); setTimeout(() => { console.log(`🔄 開始重連 WebSocket... (第${this.reconnectAttempts}次)`); this.setupWebSocket(); - }, delay); + }, this.reconnectDelay); } else if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.log('❌ 達到最大重連次數,停止重連'); this.showMessage('WebSocket 連接失敗,請刷新頁面重試', 'error'); @@ -1078,6 +1173,18 @@ class FeedbackApp { switch (data.type) { case 'connection_established': console.log('WebSocket 連接確認'); + this.connectionReady = true; + + // 如果有待提交的回饋,現在可以提交了 + if (this.pendingSubmission) { + console.log('🔄 連接就緒,提交待處理的回饋'); + setTimeout(() => { + if (this.pendingSubmission) { + this.submitFeedbackInternal(this.pendingSubmission); + this.pendingSubmission = null; + } + }, 100); + } break; case 'heartbeat_response': // 心跳回應,更新標籤頁活躍狀態 @@ -1149,8 +1256,11 @@ class FeedbackApp { document.title = `MCP Feedback - ${projectName}`; } - // 使用局部更新替代整頁刷新 - this.refreshPageContent(); + // 確保 WebSocket 連接就緒 + this.ensureWebSocketReady(() => { + // 使用局部更新替代整頁刷新 + this.refreshPageContent(); + }); } else { // 如果沒有會話信息,仍然重置狀態 console.log('⚠️ 會話更新沒有包含會話信息,僅重置狀態'); @@ -1160,6 +1270,51 @@ class FeedbackApp { console.log('✅ 會話更新處理完成'); } + /** + * 確保 WebSocket 連接就緒 + */ + ensureWebSocketReady(callback, maxWaitTime = 5000) { + const startTime = Date.now(); + + const checkConnection = () => { + if (this.isConnected && this.connectionReady) { + console.log('✅ WebSocket 連接已就緒'); + if (callback) callback(); + return; + } + + const elapsed = Date.now() - startTime; + if (elapsed >= maxWaitTime) { + console.log('⚠️ WebSocket 連接等待超時,強制執行回調'); + if (callback) callback(); + return; + } + + // 如果連接斷開,嘗試重連 + if (!this.isConnected) { + console.log('🔄 WebSocket 未連接,嘗試重連...'); + this.setupWebSocket(); + } + + // 繼續等待 + setTimeout(checkConnection, 200); + }; + + checkConnection(); + } + + /** + * 檢查是否可以提交回饋 + */ + canSubmitFeedback() { + const canSubmit = this.isConnected && + this.connectionReady && + this.feedbackState === 'waiting_for_feedback'; + + console.log(`🔍 檢查提交權限: isConnected=${this.isConnected}, connectionReady=${this.connectionReady}, feedbackState=${this.feedbackState}, canSubmit=${canSubmit}`); + return canSubmit; + } + async refreshPageContent() { console.log('🔄 局部更新頁面內容...'); @@ -1200,16 +1355,22 @@ class FeedbackApp { const sessionData = await response.json(); console.log('📥 獲取到最新會話資料:', sessionData); - // 2. 更新 AI 摘要內容 + // 2. 重置回饋狀態為等待新回饋(使用新的會話 ID) + if (sessionData.session_id) { + this.setFeedbackState('waiting_for_feedback', sessionData.session_id); + console.log('🔄 已重置回饋狀態為等待新回饋'); + } + + // 3. 更新 AI 摘要內容 this.updateAISummaryContent(sessionData.summary); - // 3. 重置回饋表單 + // 4. 重置回饋表單 this.resetFeedbackForm(); - // 4. 更新狀態指示器 + // 5. 更新狀態指示器 this.updateStatusIndicators(); - // 5. 更新頁面標題 + // 6. 更新頁面標題 if (sessionData.project_directory) { const projectName = sessionData.project_directory.split(/[/\\]/).pop(); document.title = `MCP Feedback - ${projectName}`; @@ -1410,9 +1571,9 @@ class FeedbackApp { word-wrap: break-word; `; messageDiv.textContent = message; - + document.body.appendChild(messageDiv); - + // 3秒後自動移除 setTimeout(() => { if (messageDiv.parentNode) { @@ -1450,12 +1611,12 @@ class FeedbackApp { async loadFeedbackInterface(sessionInfo) { if (!this.mainContainer) return; - + this.sessionInfo = sessionInfo; - + // 載入完整的回饋界面 this.mainContainer.innerHTML = await this.generateFeedbackHTML(sessionInfo); - + // 重新設置事件監聽器 this.setupFeedbackEventListeners(); } @@ -1618,22 +1779,46 @@ class FeedbackApp { // 檢查是否可以提交回饋 if (!this.canSubmitFeedback()) { - console.log('⚠️ 無法提交回饋 - 當前狀態:', this.feedbackState, '連接狀態:', this.isConnected); + console.log('⚠️ 無法提交回饋 - 當前狀態:', this.feedbackState, '連接狀態:', this.isConnected, '連接就緒:', this.connectionReady); if (this.feedbackState === 'feedback_submitted') { this.showMessage('回饋已提交,請等待下次 MCP 調用', 'warning'); } else if (this.feedbackState === 'processing') { this.showMessage('正在處理中,請稍候', 'warning'); - } else if (!this.isConnected) { - this.showMessage('WebSocket 未連接,正在嘗試重連...', 'error'); - // 嘗試重新建立連接 - this.setupWebSocket(); + } else if (!this.isConnected || !this.connectionReady) { + // 收集回饋數據,等待連接就緒後提交 + const feedbackData = this.collectFeedbackData(); + if (feedbackData) { + this.pendingSubmission = feedbackData; + this.showMessage('WebSocket 連接中,回饋將在連接就緒後自動提交...', 'info'); + + // 確保 WebSocket 連接 + this.ensureWebSocketReady(() => { + if (this.pendingSubmission) { + this.submitFeedbackInternal(this.pendingSubmission); + this.pendingSubmission = null; + } + }); + } } else { this.showMessage(`當前狀態不允許提交: ${this.feedbackState}`, 'warning'); } return; } + // 收集回饋數據並提交 + const feedbackData = this.collectFeedbackData(); + if (!feedbackData) { + return; + } + + this.submitFeedbackInternal(feedbackData); + } + + /** + * 收集回饋數據 + */ + collectFeedbackData() { // 根據當前佈局模式獲取回饋內容 let feedback = ''; if (this.layoutMode.startsWith('combined')) { @@ -1646,9 +1831,25 @@ class FeedbackApp { if (!feedback && this.images.length === 0) { this.showMessage('請提供回饋文字或上傳圖片', 'warning'); - return; + return null; } + return { + feedback: feedback, + images: [...this.images], // 創建副本 + settings: { + image_size_limit: this.imageSizeLimit, + enable_base64_detail: this.enableBase64Detail + } + }; + } + + /** + * 內部提交回饋方法 + */ + submitFeedbackInternal(feedbackData) { + console.log('📤 內部提交回饋...'); + // 設置處理狀態 this.setFeedbackState('processing'); @@ -1656,12 +1857,9 @@ class FeedbackApp { // 發送回饋 this.websocket.send(JSON.stringify({ type: 'submit_feedback', - feedback: feedback, - images: this.images, - settings: { - image_size_limit: this.imageSizeLimit, - enable_base64_detail: this.enableBase64Detail - } + feedback: feedbackData.feedback, + images: feedbackData.images, + settings: feedbackData.settings })); // 清空表單 @@ -1801,6 +1999,8 @@ class FeedbackApp { this.currentLanguage = settings.language || 'zh-TW'; this.imageSizeLimit = settings.imageSizeLimit || 0; this.enableBase64Detail = settings.enableBase64Detail || false; + this.autoRefreshEnabled = settings.autoRefreshEnabled || false; + this.autoRefreshInterval = settings.autoRefreshInterval || 5; // 處理 activeTab 設定 if (settings.activeTab) { @@ -1846,6 +2046,8 @@ class FeedbackApp { language: this.currentLanguage, imageSizeLimit: this.imageSizeLimit, enableBase64Detail: this.enableBase64Detail, + autoRefreshEnabled: this.autoRefreshEnabled, + autoRefreshInterval: this.autoRefreshInterval, activeTab: this.currentTab }; @@ -1904,6 +2106,15 @@ class FeedbackApp { if (this.enableBase64DetailCheckbox) { this.enableBase64DetailCheckbox.checked = this.enableBase64Detail; } + + // 應用自動刷新設定 + if (this.autoRefreshCheckbox) { + this.autoRefreshCheckbox.checked = this.autoRefreshEnabled; + } + + if (this.autoRefreshIntervalInput) { + this.autoRefreshIntervalInput.value = this.autoRefreshInterval; + } } applyLayoutMode() { @@ -1927,18 +2138,11 @@ class FeedbackApp { // 同步合併佈局和分頁中的內容 this.syncCombinedLayoutContent(); - // 如果是合併模式,確保內容同步 - if (this.layoutMode.startsWith('combined')) { - this.setupCombinedModeSync(); - // 如果當前頁籤不是合併模式,則切換到合併模式頁籤 - if (this.currentTab !== 'combined') { - this.currentTab = 'combined'; - } - } else { - // 分離模式時,如果當前頁籤是合併模式,則切換到回饋頁籤 - if (this.currentTab === 'combined') { - this.currentTab = 'feedback'; - } + // 確保合併模式內容同步 + this.setupCombinedModeSync(); + // 如果當前頁籤不是合併模式,則切換到合併模式頁籤 + if (this.currentTab !== 'combined') { + this.currentTab = 'combined'; } } @@ -1947,17 +2151,10 @@ class FeedbackApp { const feedbackTab = document.querySelector('.tab-button[data-tab="feedback"]'); const summaryTab = document.querySelector('.tab-button[data-tab="summary"]'); - if (this.layoutMode.startsWith('combined')) { - // 合併模式:顯示合併模式頁籤,隱藏回饋和AI摘要頁籤 - if (combinedTab) combinedTab.style.display = 'inline-block'; - if (feedbackTab) feedbackTab.style.display = 'none'; - if (summaryTab) summaryTab.style.display = 'none'; - } else { - // 分離模式:隱藏合併模式頁籤,顯示回饋和AI摘要頁籤 - if (combinedTab) combinedTab.style.display = 'none'; - if (feedbackTab) feedbackTab.style.display = 'inline-block'; - if (summaryTab) summaryTab.style.display = 'inline-block'; - } + // 只使用合併模式:顯示合併模式頁籤,隱藏回饋和AI摘要頁籤 + if (combinedTab) combinedTab.style.display = 'inline-block'; + if (feedbackTab) feedbackTab.style.display = 'none'; + if (summaryTab) summaryTab.style.display = 'none'; } syncCombinedLayoutContent() { @@ -2012,27 +2209,6 @@ class FeedbackApp { } setupCombinedModeSync() { - // 設置文字輸入的雙向同步 - const feedbackText = document.getElementById('feedbackText'); - const combinedFeedbackText = document.getElementById('combinedFeedbackText'); - - if (feedbackText && combinedFeedbackText) { - // 移除舊的事件監聽器(如果存在) - feedbackText.removeEventListener('input', this.syncToCombinetText); - combinedFeedbackText.removeEventListener('input', this.syncToSeparateText); - - // 添加新的事件監聽器 - this.syncToCombinetText = (e) => { - combinedFeedbackText.value = e.target.value; - }; - this.syncToSeparateText = (e) => { - feedbackText.value = e.target.value; - }; - - feedbackText.addEventListener('input', this.syncToCombinetText); - combinedFeedbackText.addEventListener('input', this.syncToSeparateText); - } - // 設置圖片設定的同步 this.setupImageSettingsSync(); @@ -2077,45 +2253,20 @@ class FeedbackApp { setupImageUploadSync() { // 設置合併模式的圖片上傳功能 - const combinedImageInput = document.getElementById('combinedImageInput'); - const combinedImageUploadArea = document.getElementById('combinedImageUploadArea'); - - if (combinedImageInput && combinedImageUploadArea) { - // 簡化的圖片上傳同步 - 只需要基本的事件監聽器 - combinedImageInput.addEventListener('change', (e) => { - this.handleFileSelect(e.target.files); - }); - - combinedImageUploadArea.addEventListener('click', () => { - combinedImageInput.click(); - }); - - // 拖放事件 - combinedImageUploadArea.addEventListener('dragover', (e) => { - e.preventDefault(); - combinedImageUploadArea.classList.add('dragover'); - }); - - combinedImageUploadArea.addEventListener('dragleave', (e) => { - e.preventDefault(); - combinedImageUploadArea.classList.remove('dragover'); - }); - - combinedImageUploadArea.addEventListener('drop', (e) => { - e.preventDefault(); - combinedImageUploadArea.classList.remove('dragover'); - this.handleFileSelect(e.dataTransfer.files); - }); - } + // 注意:所有事件監聽器現在由 setupImageEventListeners() 統一處理 + // 這個函數保留用於未來可能的同步邏輯,但不再設置重複的事件監聽器 + console.log('🔄 setupImageUploadSync: 事件監聽器由 setupImageEventListeners() 統一處理'); } resetSettings() { localStorage.removeItem('mcp-feedback-settings'); - this.layoutMode = 'separate'; + this.layoutMode = 'combined-vertical'; this.autoClose = false; this.currentLanguage = 'zh-TW'; this.imageSizeLimit = 0; this.enableBase64Detail = false; + this.autoRefreshEnabled = false; + this.autoRefreshInterval = 5; this.applySettings(); this.saveSettings(); } @@ -2169,6 +2320,203 @@ class FeedbackApp { // 不需要手動複製,updateStatusIndicator() 會處理所有狀態指示器 } + /** + * 初始化自動刷新功能 + */ + initAutoRefresh() { + console.log('🔄 初始化自動刷新功能...'); + + // 檢查必要元素是否存在 + if (!this.autoRefreshCheckbox || !this.autoRefreshIntervalInput) { + console.warn('⚠️ 自動刷新元素不存在,跳過初始化'); + return; + } + + // 設置開關事件監聽器 + this.autoRefreshCheckbox.addEventListener('change', (e) => { + this.autoRefreshEnabled = e.target.checked; + this.handleAutoRefreshToggle(); + this.saveSettings(); + }); + + // 設置間隔輸入事件監聽器 + this.autoRefreshIntervalInput.addEventListener('change', (e) => { + const newInterval = parseInt(e.target.value); + if (newInterval >= 5 && newInterval <= 300) { + this.autoRefreshInterval = newInterval; + this.saveSettings(); + + // 如果自動刷新已啟用,重新啟動定時器 + if (this.autoRefreshEnabled) { + this.stopAutoRefresh(); + this.startAutoRefresh(); + } + } + }); + + // 從設定中恢復狀態 + this.autoRefreshCheckbox.checked = this.autoRefreshEnabled; + this.autoRefreshIntervalInput.value = this.autoRefreshInterval; + + // 延遲更新狀態指示器,確保 i18n 已完全載入 + setTimeout(() => { + this.updateAutoRefreshStatus(); + + // 如果自動刷新已啟用,啟動自動檢測 + if (this.autoRefreshEnabled) { + console.log('🔄 自動刷新已啟用,啟動自動檢測...'); + this.startAutoRefresh(); + } + }, 100); + + console.log('✅ 自動刷新功能初始化完成'); + } + + /** + * 處理自動刷新開關切換 + */ + handleAutoRefreshToggle() { + if (this.autoRefreshEnabled) { + this.startAutoRefresh(); + } else { + this.stopAutoRefresh(); + } + this.updateAutoRefreshStatus(); + } + + /** + * 啟動自動刷新 + */ + startAutoRefresh() { + if (this.autoRefreshTimer) { + clearInterval(this.autoRefreshTimer); + } + + // 記錄當前會話 ID + this.lastKnownSessionId = this.currentSessionId; + + this.autoRefreshTimer = setInterval(() => { + this.checkForSessionUpdate(); + }, this.autoRefreshInterval * 1000); + + console.log(`🔄 自動刷新已啟動,間隔: ${this.autoRefreshInterval}秒`); + } + + /** + * 停止自動刷新 + */ + stopAutoRefresh() { + if (this.autoRefreshTimer) { + clearInterval(this.autoRefreshTimer); + this.autoRefreshTimer = null; + } + console.log('⏸️ 自動刷新已停止'); + } + + /** + * 檢查會話更新 + */ + async checkForSessionUpdate() { + try { + this.updateAutoRefreshStatus('checking'); + + const response = await fetch('/api/current-session'); + if (!response.ok) { + throw new Error(`API 請求失敗: ${response.status}`); + } + + const sessionData = await response.json(); + + // 檢查會話 ID 是否變化 + if (sessionData.session_id && sessionData.session_id !== this.lastKnownSessionId) { + console.log(`🔄 檢測到新會話: ${this.lastKnownSessionId} -> ${sessionData.session_id}`); + + // 更新記錄的會話 ID + this.lastKnownSessionId = sessionData.session_id; + this.currentSessionId = sessionData.session_id; + + // 觸發局部刷新 + await this.updatePageContentPartially(); + + this.updateAutoRefreshStatus('detected'); + + // 短暫顯示檢測成功狀態,然後恢復為檢測中 + setTimeout(() => { + if (this.autoRefreshEnabled) { + this.updateAutoRefreshStatus('enabled'); + } + }, 2000); + } else { + this.updateAutoRefreshStatus('enabled'); + } + + } catch (error) { + console.error('❌ 自動刷新檢測失敗:', error); + this.updateAutoRefreshStatus('error'); + + // 短暫顯示錯誤狀態,然後恢復 + setTimeout(() => { + if (this.autoRefreshEnabled) { + this.updateAutoRefreshStatus('enabled'); + } + }, 3000); + } + } + + /** + * 更新自動刷新狀態指示器 + */ + updateAutoRefreshStatus(status = null) { + console.log(`🔧 updateAutoRefreshStatus 被調用,status: ${status}`); + console.log(`🔧 refreshStatusIndicator: ${this.refreshStatusIndicator ? 'found' : 'null'}`); + console.log(`🔧 refreshStatusText: ${this.refreshStatusText ? 'found' : 'null'}`); + + if (!this.refreshStatusIndicator || !this.refreshStatusText) { + console.log(`⚠️ 自動檢測狀態元素未找到,跳過更新`); + return; + } + + let indicator = '⏸️'; + let textKey = 'autoRefresh.disabled'; + + if (status === null) { + status = this.autoRefreshEnabled ? 'enabled' : 'disabled'; + } + + switch (status) { + case 'enabled': + indicator = '🔄'; + textKey = 'autoRefresh.enabled'; + break; + case 'checking': + indicator = '🔍'; + textKey = 'autoRefresh.checking'; + break; + case 'detected': + indicator = '✅'; + textKey = 'autoRefresh.detected'; + break; + case 'error': + indicator = '❌'; + textKey = 'autoRefresh.error'; + break; + case 'disabled': + default: + indicator = '⏸️'; + textKey = 'autoRefresh.disabled'; + break; + } + + this.refreshStatusIndicator.textContent = indicator; + + // 使用多語系翻譯 + + const translatedText = window.i18nManager.t(textKey); + console.log(`🔄 自動檢測狀態翻譯: ${textKey} -> ${translatedText} (語言: ${window.i18nManager.currentLanguage})`); + this.refreshStatusText.textContent = translatedText; + + } + } diff --git a/src/mcp_feedback_enhanced/web/static/js/i18n.js b/src/mcp_feedback_enhanced/web/static/js/i18n.js index 9bd4d42..93bcb38 100644 --- a/src/mcp_feedback_enhanced/web/static/js/i18n.js +++ b/src/mcp_feedback_enhanced/web/static/js/i18n.js @@ -178,6 +178,10 @@ class I18nManager { if (window.feedbackApp) { window.feedbackApp.updateUIState(); window.feedbackApp.updateStatusIndicator(); + // 更新自動檢測狀態文字 + if (window.feedbackApp.updateAutoRefreshStatus) { + window.feedbackApp.updateAutoRefreshStatus(); + } } } diff --git a/src/mcp_feedback_enhanced/web/templates/feedback.html b/src/mcp_feedback_enhanced/web/templates/feedback.html index 2318419..4ae16be 100644 --- a/src/mcp_feedback_enhanced/web/templates/feedback.html +++ b/src/mcp_feedback_enhanced/web/templates/feedback.html @@ -23,18 +23,8 @@ /* 頁面特定的佈局模式樣式 */ - /* 佈局模式樣式 */ - /* 預設分離模式 - 顯示回饋和AI摘要頁籤,隱藏合併模式頁籤 */ - body.layout-separate .tab-button[data-tab="combined"] { - display: none; - } - - body.layout-separate .tab-button[data-tab="feedback"], - body.layout-separate .tab-button[data-tab="summary"] { - display: inline-block; - } - - /* 合併模式 - 顯示合併模式頁籤,隱藏回饋和AI摘要頁籤 */ + /* 佈局模式樣式 - 工作區模式 */ + /* 工作區模式 - 顯示工作區頁籤,隱藏回饋和AI摘要頁籤 */ body.layout-combined-vertical .tab-button[data-tab="combined"], body.layout-combined-horizontal .tab-button[data-tab="combined"] { display: inline-block; @@ -62,7 +52,7 @@ - /* 合併模式分頁的水平佈局樣式 */ + /* 工作區分頁的水平佈局樣式 */ #tab-combined.active.combined-horizontal .combined-content { display: flex !important; flex-direction: row !important; @@ -99,7 +89,7 @@ min-height: 200px; } - /* 合併模式分頁的垂直佈局樣式 */ + /* 工作區分頁的垂直佈局樣式 */ #tab-combined.active.combined-vertical .combined-content { display: flex !important; flex-direction: column !important; @@ -144,7 +134,7 @@ flex: 1; } - /* 合併模式基礎樣式 */ + /* 工作區基礎樣式 */ .combined-section { background: var(--bg-tertiary); border: 1px solid var(--border-color); @@ -395,9 +385,9 @@