mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 10:42:25 +08:00
Merge pull request #19 from Minidoracat/13-image-limit
✨ 新增圖片設定功能,包括圖片大小限制和 Base64 詳細模式選項,並更新相關文檔與界面。
This commit is contained in:
commit
f4d101a881
@ -128,7 +128,6 @@ For best results, add these rules to your AI assistant:
|
|||||||
|----------|---------|--------|---------|
|
|----------|---------|--------|---------|
|
||||||
| `FORCE_WEB` | Force use Web UI | `true`/`false` | `false` |
|
| `FORCE_WEB` | Force use Web UI | `true`/`false` | `false` |
|
||||||
| `MCP_DEBUG` | Debug mode | `true`/`false` | `false` |
|
| `MCP_DEBUG` | Debug mode | `true`/`false` | `false` |
|
||||||
| `INCLUDE_BASE64_DETAIL` | Full Base64 for images | `true`/`false` | `false` |
|
|
||||||
|
|
||||||
### Testing Options
|
### Testing Options
|
||||||
```bash
|
```bash
|
||||||
|
@ -128,7 +128,6 @@ uvx mcp-feedback-enhanced@latest test
|
|||||||
|------|------|-----|------|
|
|------|------|-----|------|
|
||||||
| `FORCE_WEB` | 强制使用 Web UI | `true`/`false` | `false` |
|
| `FORCE_WEB` | 强制使用 Web UI | `true`/`false` | `false` |
|
||||||
| `MCP_DEBUG` | 调试模式 | `true`/`false` | `false` |
|
| `MCP_DEBUG` | 调试模式 | `true`/`false` | `false` |
|
||||||
| `INCLUDE_BASE64_DETAIL` | 图片完整 Base64 | `true`/`false` | `false` |
|
|
||||||
|
|
||||||
### 测试选项
|
### 测试选项
|
||||||
```bash
|
```bash
|
||||||
|
@ -128,7 +128,6 @@ uvx mcp-feedback-enhanced@latest test
|
|||||||
|------|------|-----|------|
|
|------|------|-----|------|
|
||||||
| `FORCE_WEB` | 強制使用 Web UI | `true`/`false` | `false` |
|
| `FORCE_WEB` | 強制使用 Web UI | `true`/`false` | `false` |
|
||||||
| `MCP_DEBUG` | 調試模式 | `true`/`false` | `false` |
|
| `MCP_DEBUG` | 調試模式 | `true`/`false` | `false` |
|
||||||
| `INCLUDE_BASE64_DETAIL` | 圖片完整 Base64 | `true`/`false` | `false` |
|
|
||||||
|
|
||||||
### 測試選項
|
### 測試選項
|
||||||
```bash
|
```bash
|
||||||
|
@ -22,19 +22,29 @@ __version__ = "2.2.2"
|
|||||||
__author__ = "Minidoracat"
|
__author__ = "Minidoracat"
|
||||||
__email__ = "minidora0702@gmail.com"
|
__email__ = "minidora0702@gmail.com"
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from .server import main as run_server
|
from .server import main as run_server
|
||||||
from .gui import feedback_ui
|
|
||||||
|
|
||||||
# 導入新的 Web UI 模組
|
# 導入新的 Web UI 模組
|
||||||
from .web import WebUIManager, launch_web_feedback_ui, get_web_ui_manager, stop_web_ui
|
from .web import WebUIManager, launch_web_feedback_ui, get_web_ui_manager, stop_web_ui
|
||||||
|
|
||||||
|
# 條件性導入 GUI 模組(只有在不強制使用 Web 時才導入)
|
||||||
|
feedback_ui = None
|
||||||
|
if not os.getenv('FORCE_WEB', '').lower() in ('true', '1', 'yes'):
|
||||||
|
try:
|
||||||
|
from .gui import feedback_ui
|
||||||
|
except ImportError:
|
||||||
|
# 如果 GUI 依賴不可用,設為 None
|
||||||
|
feedback_ui = None
|
||||||
|
|
||||||
# 主要導出介面
|
# 主要導出介面
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"run_server",
|
"run_server",
|
||||||
"feedback_ui",
|
"feedback_ui",
|
||||||
"WebUIManager",
|
"WebUIManager",
|
||||||
"launch_web_feedback_ui",
|
"launch_web_feedback_ui",
|
||||||
"get_web_ui_manager",
|
"get_web_ui_manager",
|
||||||
"stop_web_ui",
|
"stop_web_ui",
|
||||||
"__version__",
|
"__version__",
|
||||||
"__author__",
|
"__author__",
|
||||||
|
@ -106,7 +106,7 @@ class FeedbackTab(QWidget):
|
|||||||
image_upload_layout.setSpacing(8)
|
image_upload_layout.setSpacing(8)
|
||||||
image_upload_layout.setContentsMargins(0, 8, 0, 0) # 與回饋輸入區域保持一致的邊距
|
image_upload_layout.setContentsMargins(0, 8, 0, 0) # 與回饋輸入區域保持一致的邊距
|
||||||
|
|
||||||
self.image_upload = ImageUploadWidget()
|
self.image_upload = ImageUploadWidget(config_manager=self.config_manager)
|
||||||
image_upload_layout.addWidget(self.image_upload, 1)
|
image_upload_layout.addWidget(self.image_upload, 1)
|
||||||
|
|
||||||
# 添加到分割器
|
# 添加到分割器
|
||||||
|
@ -14,8 +14,9 @@ from typing import Dict, List
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
||||||
QScrollArea, QGridLayout, QFileDialog, QMessageBox, QApplication
|
QScrollArea, QGridLayout, QFileDialog, QMessageBox, QApplication,
|
||||||
|
QComboBox, QCheckBox, QGroupBox, QFrame
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt, Signal
|
from PySide6.QtCore import Qt, Signal
|
||||||
from PySide6.QtGui import QFont, QDragEnterEvent, QDropEvent
|
from PySide6.QtGui import QFont, QDragEnterEvent, QDropEvent
|
||||||
@ -30,10 +31,11 @@ from .image_preview import ImagePreviewWidget
|
|||||||
class ImageUploadWidget(QWidget):
|
class ImageUploadWidget(QWidget):
|
||||||
"""圖片上傳元件"""
|
"""圖片上傳元件"""
|
||||||
images_changed = Signal()
|
images_changed = Signal()
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None, config_manager=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.images: Dict[str, Dict[str, str]] = {}
|
self.images: Dict[str, Dict[str, str]] = {}
|
||||||
|
self.config_manager = config_manager
|
||||||
self._setup_ui()
|
self._setup_ui()
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
# 啟動時清理舊的臨時文件
|
# 啟動時清理舊的臨時文件
|
||||||
@ -44,21 +46,104 @@ class ImageUploadWidget(QWidget):
|
|||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
layout.setSpacing(6)
|
layout.setSpacing(6)
|
||||||
layout.setContentsMargins(0, 8, 0, 8) # 調整邊距使其與其他區域一致
|
layout.setContentsMargins(0, 8, 0, 8) # 調整邊距使其與其他區域一致
|
||||||
|
|
||||||
# 標題
|
# 標題
|
||||||
self.title = QLabel(t('images.title'))
|
self.title = QLabel(t('images.title'))
|
||||||
self.title.setFont(QFont("", 10, QFont.Bold))
|
self.title.setFont(QFont("", 10, QFont.Bold))
|
||||||
self.title.setStyleSheet("color: #007acc; margin: 1px 0;")
|
self.title.setStyleSheet("color: #007acc; margin: 1px 0;")
|
||||||
layout.addWidget(self.title)
|
layout.addWidget(self.title)
|
||||||
|
|
||||||
|
# 圖片設定區域
|
||||||
|
self._create_settings_area(layout)
|
||||||
|
|
||||||
# 狀態標籤
|
# 狀態標籤
|
||||||
self.status_label = QLabel(t('images.status', count=0))
|
self.status_label = QLabel(t('images.status', count=0))
|
||||||
self.status_label.setStyleSheet("color: #9e9e9e; font-size: 10px; margin: 5px 0;")
|
self.status_label.setStyleSheet("color: #9e9e9e; font-size: 10px; margin: 5px 0;")
|
||||||
layout.addWidget(self.status_label)
|
layout.addWidget(self.status_label)
|
||||||
|
|
||||||
# 統一的圖片區域(整合按鈕、拖拽、預覽)
|
# 統一的圖片區域(整合按鈕、拖拽、預覽)
|
||||||
self._create_unified_image_area(layout)
|
self._create_unified_image_area(layout)
|
||||||
|
|
||||||
|
def _create_settings_area(self, layout: QVBoxLayout) -> None:
|
||||||
|
"""創建圖片設定區域"""
|
||||||
|
if not self.config_manager:
|
||||||
|
return # 如果沒有 config_manager,跳過設定區域
|
||||||
|
|
||||||
|
# 設定群組框
|
||||||
|
settings_group = QGroupBox(t('images.settings.title'))
|
||||||
|
settings_group.setStyleSheet("""
|
||||||
|
QGroupBox {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 9px;
|
||||||
|
color: #9e9e9e;
|
||||||
|
border: 1px solid #464647;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 6px;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
QGroupBox::title {
|
||||||
|
subcontrol-origin: margin;
|
||||||
|
left: 8px;
|
||||||
|
padding: 0 4px 0 4px;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
settings_layout = QHBoxLayout(settings_group)
|
||||||
|
settings_layout.setSpacing(12)
|
||||||
|
settings_layout.setContentsMargins(8, 8, 8, 8)
|
||||||
|
|
||||||
|
# 圖片大小限制設定
|
||||||
|
size_label = QLabel(t('images.settings.sizeLimit') + ":")
|
||||||
|
size_label.setStyleSheet("color: #cccccc; font-size: 9px;")
|
||||||
|
|
||||||
|
self.size_limit_combo = QComboBox()
|
||||||
|
self.size_limit_combo.addItem(t('images.settings.sizeLimitOptions.unlimited'), 0)
|
||||||
|
self.size_limit_combo.addItem(t('images.settings.sizeLimitOptions.1mb'), 1024*1024)
|
||||||
|
self.size_limit_combo.addItem(t('images.settings.sizeLimitOptions.3mb'), 3*1024*1024)
|
||||||
|
self.size_limit_combo.addItem(t('images.settings.sizeLimitOptions.5mb'), 5*1024*1024)
|
||||||
|
|
||||||
|
# 載入當前設定
|
||||||
|
current_limit = self.config_manager.get_image_size_limit()
|
||||||
|
for i in range(self.size_limit_combo.count()):
|
||||||
|
if self.size_limit_combo.itemData(i) == current_limit:
|
||||||
|
self.size_limit_combo.setCurrentIndex(i)
|
||||||
|
break
|
||||||
|
|
||||||
|
self.size_limit_combo.currentIndexChanged.connect(self._on_size_limit_changed)
|
||||||
|
|
||||||
|
# Base64 詳細模式設定
|
||||||
|
self.base64_checkbox = QCheckBox(t('images.settings.base64Detail'))
|
||||||
|
self.base64_checkbox.setChecked(self.config_manager.get_enable_base64_detail())
|
||||||
|
self.base64_checkbox.stateChanged.connect(self._on_base64_detail_changed)
|
||||||
|
self.base64_checkbox.setToolTip(t('images.settings.base64DetailHelp'))
|
||||||
|
|
||||||
|
# Base64 警告標籤
|
||||||
|
base64_warning = QLabel(t('images.settings.base64Warning'))
|
||||||
|
base64_warning.setStyleSheet("color: #ff9800; font-size: 8px;")
|
||||||
|
|
||||||
|
# 添加到佈局
|
||||||
|
settings_layout.addWidget(size_label)
|
||||||
|
settings_layout.addWidget(self.size_limit_combo)
|
||||||
|
settings_layout.addWidget(self.base64_checkbox)
|
||||||
|
settings_layout.addWidget(base64_warning)
|
||||||
|
settings_layout.addStretch()
|
||||||
|
|
||||||
|
layout.addWidget(settings_group)
|
||||||
|
|
||||||
|
def _on_size_limit_changed(self, index: int) -> None:
|
||||||
|
"""圖片大小限制變更處理"""
|
||||||
|
if self.config_manager:
|
||||||
|
size_bytes = self.size_limit_combo.itemData(index)
|
||||||
|
self.config_manager.set_image_size_limit(size_bytes)
|
||||||
|
debug_log(f"圖片大小限制已更新: {size_bytes} bytes")
|
||||||
|
|
||||||
|
def _on_base64_detail_changed(self, state: int) -> None:
|
||||||
|
"""Base64 詳細模式變更處理"""
|
||||||
|
if self.config_manager:
|
||||||
|
enabled = state == Qt.Checked
|
||||||
|
self.config_manager.set_enable_base64_detail(enabled)
|
||||||
|
debug_log(f"Base64 詳細模式已更新: {enabled}")
|
||||||
|
|
||||||
def _create_unified_image_area(self, layout: QVBoxLayout) -> None:
|
def _create_unified_image_area(self, layout: QVBoxLayout) -> None:
|
||||||
"""創建統一的圖片區域"""
|
"""創建統一的圖片區域"""
|
||||||
# 創建滾動區域
|
# 創建滾動區域
|
||||||
@ -327,12 +412,26 @@ class ImageUploadWidget(QWidget):
|
|||||||
|
|
||||||
file_size = os.path.getsize(file_path)
|
file_size = os.path.getsize(file_path)
|
||||||
debug_log(f"文件大小: {file_size} bytes")
|
debug_log(f"文件大小: {file_size} bytes")
|
||||||
|
|
||||||
# 更嚴格的大小限制(1MB)
|
# 動態圖片大小限制檢查
|
||||||
if file_size > 1 * 1024 * 1024:
|
size_limit = self.config_manager.get_image_size_limit() if self.config_manager else 1024*1024
|
||||||
|
if size_limit > 0 and file_size > size_limit:
|
||||||
|
# 格式化限制大小顯示
|
||||||
|
if size_limit >= 1024*1024:
|
||||||
|
limit_str = f"{size_limit/(1024*1024):.0f}MB"
|
||||||
|
else:
|
||||||
|
limit_str = f"{size_limit/1024:.0f}KB"
|
||||||
|
|
||||||
|
# 格式化文件大小顯示
|
||||||
|
if file_size >= 1024*1024:
|
||||||
|
size_str = f"{file_size/(1024*1024):.1f}MB"
|
||||||
|
else:
|
||||||
|
size_str = f"{file_size/1024:.1f}KB"
|
||||||
|
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self, t('errors.warning'),
|
self, t('errors.warning'),
|
||||||
t('errors.fileSizeExceeded', filename=os.path.basename(file_path), size=f"{file_size/1024/1024:.1f}")
|
t('images.sizeLimitExceeded', filename=os.path.basename(file_path), size=size_str, limit=limit_str) +
|
||||||
|
"\n\n" + t('images.sizeLimitExceededAdvice')
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -349,11 +448,25 @@ class ImageUploadWidget(QWidget):
|
|||||||
debug_log(f"讀取的數據為空!")
|
debug_log(f"讀取的數據為空!")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 再次檢查內存中的數據大小
|
# 再次檢查內存中的數據大小(使用配置的限制)
|
||||||
if len(raw_data) > 1 * 1024 * 1024:
|
size_limit = self.config_manager.get_image_size_limit() if self.config_manager else 1024*1024
|
||||||
|
if size_limit > 0 and len(raw_data) > size_limit:
|
||||||
|
# 格式化限制大小顯示
|
||||||
|
if size_limit >= 1024*1024:
|
||||||
|
limit_str = f"{size_limit/(1024*1024):.0f}MB"
|
||||||
|
else:
|
||||||
|
limit_str = f"{size_limit/1024:.0f}KB"
|
||||||
|
|
||||||
|
# 格式化文件大小顯示
|
||||||
|
if len(raw_data) >= 1024*1024:
|
||||||
|
size_str = f"{len(raw_data)/(1024*1024):.1f}MB"
|
||||||
|
else:
|
||||||
|
size_str = f"{len(raw_data)/1024:.1f}KB"
|
||||||
|
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self, t('errors.warning'),
|
self, t('errors.warning'),
|
||||||
t('errors.dataSizeExceeded', filename=os.path.basename(file_path))
|
t('images.sizeLimitExceeded', filename=os.path.basename(file_path), size=size_str, limit=limit_str) +
|
||||||
|
"\n\n" + t('images.sizeLimitExceededAdvice')
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -506,7 +619,7 @@ class ImageUploadWidget(QWidget):
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
return
|
return
|
||||||
event.ignore()
|
event.ignore()
|
||||||
|
|
||||||
def dragLeaveEvent(self, event) -> None:
|
def dragLeaveEvent(self, event) -> None:
|
||||||
@ -564,7 +677,29 @@ class ImageUploadWidget(QWidget):
|
|||||||
# 更新標題
|
# 更新標題
|
||||||
if hasattr(self, 'title'):
|
if hasattr(self, 'title'):
|
||||||
self.title.setText(t('images.title'))
|
self.title.setText(t('images.title'))
|
||||||
|
|
||||||
|
# 更新設定區域文字
|
||||||
|
if hasattr(self, 'size_limit_combo'):
|
||||||
|
# 保存當前選擇
|
||||||
|
current_data = self.size_limit_combo.currentData()
|
||||||
|
|
||||||
|
# 清除並重新添加選項
|
||||||
|
self.size_limit_combo.clear()
|
||||||
|
self.size_limit_combo.addItem(t('images.settings.sizeLimitOptions.unlimited'), 0)
|
||||||
|
self.size_limit_combo.addItem(t('images.settings.sizeLimitOptions.1mb'), 1024*1024)
|
||||||
|
self.size_limit_combo.addItem(t('images.settings.sizeLimitOptions.3mb'), 3*1024*1024)
|
||||||
|
self.size_limit_combo.addItem(t('images.settings.sizeLimitOptions.5mb'), 5*1024*1024)
|
||||||
|
|
||||||
|
# 恢復選擇
|
||||||
|
for i in range(self.size_limit_combo.count()):
|
||||||
|
if self.size_limit_combo.itemData(i) == current_data:
|
||||||
|
self.size_limit_combo.setCurrentIndex(i)
|
||||||
|
break
|
||||||
|
|
||||||
|
if hasattr(self, 'base64_checkbox'):
|
||||||
|
self.base64_checkbox.setText(t('images.settings.base64Detail'))
|
||||||
|
self.base64_checkbox.setToolTip(t('images.settings.base64DetailHelp'))
|
||||||
|
|
||||||
# 更新按鈕文字
|
# 更新按鈕文字
|
||||||
if hasattr(self, 'file_button'):
|
if hasattr(self, 'file_button'):
|
||||||
self.file_button.setText(t('buttons.selectFiles'))
|
self.file_button.setText(t('buttons.selectFiles'))
|
||||||
@ -572,10 +707,10 @@ class ImageUploadWidget(QWidget):
|
|||||||
self.paste_button.setText(t('buttons.pasteClipboard'))
|
self.paste_button.setText(t('buttons.pasteClipboard'))
|
||||||
if hasattr(self, 'clear_button'):
|
if hasattr(self, 'clear_button'):
|
||||||
self.clear_button.setText(t('buttons.clearAll'))
|
self.clear_button.setText(t('buttons.clearAll'))
|
||||||
|
|
||||||
# 更新拖拽區域文字
|
# 更新拖拽區域文字
|
||||||
if hasattr(self, 'drop_hint_label'):
|
if hasattr(self, 'drop_hint_label'):
|
||||||
self.drop_hint_label.setText(t('images.dragHint'))
|
self.drop_hint_label.setText(t('images.dragHint'))
|
||||||
|
|
||||||
# 更新狀態文字
|
# 更新狀態文字
|
||||||
self._update_status()
|
self._update_status()
|
@ -146,19 +146,38 @@ class ConfigManager:
|
|||||||
self.update_partial_config({'always_center_window': always_center})
|
self.update_partial_config({'always_center_window': always_center})
|
||||||
debug_log(f"視窗定位設置: {'總是中心顯示' if always_center else '智能定位'}")
|
debug_log(f"視窗定位設置: {'總是中心顯示' if always_center else '智能定位'}")
|
||||||
|
|
||||||
|
def get_image_size_limit(self) -> int:
|
||||||
|
"""獲取圖片大小限制(bytes),0 表示無限制"""
|
||||||
|
return self.get('image_size_limit', 0)
|
||||||
|
|
||||||
|
def set_image_size_limit(self, size_bytes: int) -> None:
|
||||||
|
"""設置圖片大小限制(bytes),0 表示無限制"""
|
||||||
|
self.update_partial_config({'image_size_limit': size_bytes})
|
||||||
|
size_mb = size_bytes / (1024 * 1024) if size_bytes > 0 else 0
|
||||||
|
debug_log(f"圖片大小限制設置: {'無限制' if size_bytes == 0 else f'{size_mb:.1f}MB'}")
|
||||||
|
|
||||||
|
def get_enable_base64_detail(self) -> bool:
|
||||||
|
"""獲取是否啟用 Base64 詳細模式"""
|
||||||
|
return self.get('enable_base64_detail', False)
|
||||||
|
|
||||||
|
def set_enable_base64_detail(self, enabled: bool) -> None:
|
||||||
|
"""設置是否啟用 Base64 詳細模式"""
|
||||||
|
self.update_partial_config({'enable_base64_detail': enabled})
|
||||||
|
debug_log(f"Base64 詳細模式設置: {'啟用' if enabled else '停用'}")
|
||||||
|
|
||||||
def reset_settings(self) -> None:
|
def reset_settings(self) -> None:
|
||||||
"""重置所有設定到預設值"""
|
"""重置所有設定到預設值"""
|
||||||
try:
|
try:
|
||||||
# 清空配置緩存
|
# 清空配置緩存
|
||||||
self._config_cache = {}
|
self._config_cache = {}
|
||||||
|
|
||||||
# 刪除配置文件
|
# 刪除配置文件
|
||||||
if self._config_file.exists():
|
if self._config_file.exists():
|
||||||
self._config_file.unlink()
|
self._config_file.unlink()
|
||||||
debug_log("配置文件已刪除")
|
debug_log("配置文件已刪除")
|
||||||
|
|
||||||
debug_log("所有設定已重置到預設值")
|
debug_log("所有設定已重置到預設值")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_log(f"重置設定失敗: {e}")
|
debug_log(f"重置設定失敗: {e}")
|
||||||
raise
|
raise
|
@ -271,18 +271,26 @@ class TabManager:
|
|||||||
result = {
|
result = {
|
||||||
"interactive_feedback": "",
|
"interactive_feedback": "",
|
||||||
"command_logs": "",
|
"command_logs": "",
|
||||||
"images": []
|
"images": [],
|
||||||
|
"settings": {}
|
||||||
}
|
}
|
||||||
|
|
||||||
# 獲取回饋文字和圖片
|
# 獲取回饋文字和圖片
|
||||||
if self.feedback_tab:
|
if self.feedback_tab:
|
||||||
result["interactive_feedback"] = self.feedback_tab.get_feedback_text()
|
result["interactive_feedback"] = self.feedback_tab.get_feedback_text()
|
||||||
result["images"] = self.feedback_tab.get_images_data()
|
result["images"] = self.feedback_tab.get_images_data()
|
||||||
|
|
||||||
# 獲取命令日誌
|
# 獲取命令日誌
|
||||||
if self.command_tab:
|
if self.command_tab:
|
||||||
result["command_logs"] = self.command_tab.get_command_logs()
|
result["command_logs"] = self.command_tab.get_command_logs()
|
||||||
|
|
||||||
|
# 獲取圖片設定
|
||||||
|
if self.config_manager:
|
||||||
|
result["settings"] = {
|
||||||
|
"image_size_limit": self.config_manager.get_image_size_limit(),
|
||||||
|
"enable_base64_detail": self.config_manager.get_enable_base64_detail()
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def restore_content(self, feedback_text: str, command_logs: str, images_data: list) -> None:
|
def restore_content(self, feedback_text: str, command_logs: str, images_data: list) -> None:
|
||||||
|
@ -76,7 +76,24 @@
|
|||||||
"paste_failed": "Paste failed, no image in clipboard",
|
"paste_failed": "Paste failed, no image in clipboard",
|
||||||
"paste_no_image": "No image in clipboard to paste",
|
"paste_no_image": "No image in clipboard to paste",
|
||||||
"paste_image_from_textarea": "Image intelligently pasted from text area to image area",
|
"paste_image_from_textarea": "Image intelligently pasted from text area to image area",
|
||||||
"images_clear": "Clear all images"
|
"images_clear": "Clear all images",
|
||||||
|
"settings": {
|
||||||
|
"title": "Image Settings",
|
||||||
|
"sizeLimit": "Image Size Limit",
|
||||||
|
"sizeLimitOptions": {
|
||||||
|
"unlimited": "Unlimited",
|
||||||
|
"1mb": "1MB",
|
||||||
|
"3mb": "3MB",
|
||||||
|
"5mb": "5MB"
|
||||||
|
},
|
||||||
|
"base64Detail": "Base64 Compatibility Mode",
|
||||||
|
"base64DetailHelp": "When enabled, includes complete Base64 image data in text to improve compatibility with AI models like Gemini",
|
||||||
|
"base64Warning": "⚠️ Increases transmission size",
|
||||||
|
"compatibilityHint": "💡 Images not recognized correctly?",
|
||||||
|
"enableBase64Hint": "Try enabling Base64 compatibility mode"
|
||||||
|
},
|
||||||
|
"sizeLimitExceeded": "Image {filename} size is {size}, exceeds {limit} limit!",
|
||||||
|
"sizeLimitExceededAdvice": "Please compress the image using image editing software, or adjust the image size limit setting."
|
||||||
},
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"settings": "Language Settings",
|
"settings": "Language Settings",
|
||||||
|
@ -61,7 +61,24 @@
|
|||||||
"paste_failed": "粘贴失败,剪贴板中没有图片",
|
"paste_failed": "粘贴失败,剪贴板中没有图片",
|
||||||
"paste_no_image": "剪贴板中没有图片可粘贴",
|
"paste_no_image": "剪贴板中没有图片可粘贴",
|
||||||
"paste_image_from_textarea": "已将图片从文本框智能贴到图片区域",
|
"paste_image_from_textarea": "已将图片从文本框智能贴到图片区域",
|
||||||
"images_clear": "清除所有图片"
|
"images_clear": "清除所有图片",
|
||||||
|
"settings": {
|
||||||
|
"title": "图片设置",
|
||||||
|
"sizeLimit": "图片大小限制",
|
||||||
|
"sizeLimitOptions": {
|
||||||
|
"unlimited": "无限制",
|
||||||
|
"1mb": "1MB",
|
||||||
|
"3mb": "3MB",
|
||||||
|
"5mb": "5MB"
|
||||||
|
},
|
||||||
|
"base64Detail": "Base64 兼容模式",
|
||||||
|
"base64DetailHelp": "启用后会在文本中包含完整的 Base64 图片数据,提升与 Gemini 等 AI 模型的兼容性",
|
||||||
|
"base64Warning": "⚠️ 会增加传输量",
|
||||||
|
"compatibilityHint": "💡 图片无法正确识别?",
|
||||||
|
"enableBase64Hint": "尝试启用 Base64 兼容模式"
|
||||||
|
},
|
||||||
|
"sizeLimitExceeded": "图片 {filename} 大小为 {size},超过 {limit} 限制!",
|
||||||
|
"sizeLimitExceededAdvice": "建议使用图片编辑软件压缩后再上传,或调整图片大小限制设置。"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "应用设置",
|
"title": "应用设置",
|
||||||
|
@ -77,7 +77,24 @@
|
|||||||
"paste_failed": "貼上失敗,剪貼簿中沒有圖片",
|
"paste_failed": "貼上失敗,剪貼簿中沒有圖片",
|
||||||
"paste_no_image": "剪貼簿中沒有圖片可貼上",
|
"paste_no_image": "剪貼簿中沒有圖片可貼上",
|
||||||
"paste_image_from_textarea": "已將圖片從文字框智能貼到圖片區域",
|
"paste_image_from_textarea": "已將圖片從文字框智能貼到圖片區域",
|
||||||
"images_clear": "清除所有圖片"
|
"images_clear": "清除所有圖片",
|
||||||
|
"settings": {
|
||||||
|
"title": "圖片設定",
|
||||||
|
"sizeLimit": "圖片大小限制",
|
||||||
|
"sizeLimitOptions": {
|
||||||
|
"unlimited": "無限制",
|
||||||
|
"1mb": "1MB",
|
||||||
|
"3mb": "3MB",
|
||||||
|
"5mb": "5MB"
|
||||||
|
},
|
||||||
|
"base64Detail": "Base64 相容模式",
|
||||||
|
"base64DetailHelp": "啟用後會在文字中包含完整的 Base64 圖片資料,提升與 Gemini 等 AI 模型的相容性",
|
||||||
|
"base64Warning": "⚠️ 會增加傳輸量",
|
||||||
|
"compatibilityHint": "💡 圖片無法正確識別?",
|
||||||
|
"enableBase64Hint": "嘗試啟用 Base64 相容模式"
|
||||||
|
},
|
||||||
|
"sizeLimitExceeded": "圖片 {filename} 大小為 {size},超過 {limit} 限制!",
|
||||||
|
"sizeLimitExceededAdvice": "建議使用圖片編輯軟體壓縮後再上傳,或調整圖片大小限制設定。"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "應用設置",
|
"title": "應用設置",
|
||||||
|
@ -281,10 +281,22 @@ def create_feedback_text(feedback_data: dict) -> str:
|
|||||||
# 如果 AI 助手不支援 MCP 圖片,可以提供完整 base64
|
# 如果 AI 助手不支援 MCP 圖片,可以提供完整 base64
|
||||||
debug_log(f"圖片 {i} Base64 已準備,長度: {len(img_base64)}")
|
debug_log(f"圖片 {i} Base64 已準備,長度: {len(img_base64)}")
|
||||||
|
|
||||||
# 可選:根據環境變數決定是否包含完整 base64
|
# 檢查是否啟用 Base64 詳細模式(從 UI 設定中獲取)
|
||||||
include_full_base64 = os.getenv("INCLUDE_BASE64_DETAIL", "").lower() in ("true", "1", "yes", "on")
|
include_full_base64 = feedback_data.get("settings", {}).get("enable_base64_detail", False)
|
||||||
|
|
||||||
if include_full_base64:
|
if include_full_base64:
|
||||||
img_info += f"\n 完整 Base64: data:image/png;base64,{img_base64}"
|
# 根據檔案名推斷 MIME 類型
|
||||||
|
file_name = img.get("name", "image.png")
|
||||||
|
if file_name.lower().endswith(('.jpg', '.jpeg')):
|
||||||
|
mime_type = 'image/jpeg'
|
||||||
|
elif file_name.lower().endswith('.gif'):
|
||||||
|
mime_type = 'image/gif'
|
||||||
|
elif file_name.lower().endswith('.webp'):
|
||||||
|
mime_type = 'image/webp'
|
||||||
|
else:
|
||||||
|
mime_type = 'image/png'
|
||||||
|
|
||||||
|
img_info += f"\n 完整 Base64: data:{mime_type};base64,{img_base64}"
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_log(f"圖片 {i} Base64 處理失敗: {e}")
|
debug_log(f"圖片 {i} Base64 處理失敗: {e}")
|
||||||
|
@ -171,5 +171,24 @@
|
|||||||
"contactDescription": "For technical support, issue reports, or feature suggestions, please contact us through Discord community or GitHub Issues.",
|
"contactDescription": "For technical support, issue reports, or feature suggestions, please contact us through Discord community or GitHub Issues.",
|
||||||
"thanks": "Thanks & Contributions",
|
"thanks": "Thanks & Contributions",
|
||||||
"thanksText": "Thanks to the original author Fábio Ferreira (@fabiomlferreira) for creating the original interactive-feedback-mcp project.\n\nThis enhanced version is developed and maintained by Minidoracat, greatly expanding the project functionality with GUI interface, image support, multi-language capabilities, and many other improvements.\n\nSpecial thanks to sanshao85's mcp-feedback-collector project for UI design inspiration.\n\nOpen source collaboration makes technology better!"
|
"thanksText": "Thanks to the original author Fábio Ferreira (@fabiomlferreira) for creating the original interactive-feedback-mcp project.\n\nThis enhanced version is developed and maintained by Minidoracat, greatly expanding the project functionality with GUI interface, image support, multi-language capabilities, and many other improvements.\n\nSpecial thanks to sanshao85's mcp-feedback-collector project for UI design inspiration.\n\nOpen source collaboration makes technology better!"
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"settings": {
|
||||||
|
"title": "Image Settings",
|
||||||
|
"sizeLimit": "Image Size Limit",
|
||||||
|
"sizeLimitOptions": {
|
||||||
|
"unlimited": "Unlimited",
|
||||||
|
"1mb": "1MB",
|
||||||
|
"3mb": "3MB",
|
||||||
|
"5mb": "5MB"
|
||||||
|
},
|
||||||
|
"base64Detail": "Base64 Compatibility Mode",
|
||||||
|
"base64DetailHelp": "When enabled, includes full Base64 image data in text, improving compatibility with certain AI models",
|
||||||
|
"base64Warning": "⚠️ Increases transmission size",
|
||||||
|
"compatibilityHint": "💡 Images not recognized correctly?",
|
||||||
|
"enableBase64Hint": "Try enabling Base64 compatibility mode"
|
||||||
|
},
|
||||||
|
"sizeLimitExceeded": "Image {filename} size is {size}, exceeds {limit} limit!",
|
||||||
|
"sizeLimitExceededAdvice": "Consider compressing the image with editing software before uploading, or adjust the image size limit settings."
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -171,5 +171,24 @@
|
|||||||
"contactDescription": "如需技术支持、问题反馈或功能建议,欢迎通过 Discord 社群或 GitHub Issues 与我们联系。",
|
"contactDescription": "如需技术支持、问题反馈或功能建议,欢迎通过 Discord 社群或 GitHub Issues 与我们联系。",
|
||||||
"thanks": "致谢与贡献",
|
"thanks": "致谢与贡献",
|
||||||
"thanksText": "感谢原作者 Fábio Ferreira (@fabiomlferreira) 创建了原始的 interactive-feedback-mcp 项目。\n\n本增强版本由 Minidoracat 开发和维护,大幅扩展了项目功能,新增了 GUI 界面、图片支持、多语言能力以及许多其他改进功能。\n\n同时感谢 sanshao85 的 mcp-feedback-collector 项目提供的 UI 设计灵感。\n\n开源协作让技术变得更美好!"
|
"thanksText": "感谢原作者 Fábio Ferreira (@fabiomlferreira) 创建了原始的 interactive-feedback-mcp 项目。\n\n本增强版本由 Minidoracat 开发和维护,大幅扩展了项目功能,新增了 GUI 界面、图片支持、多语言能力以及许多其他改进功能。\n\n同时感谢 sanshao85 的 mcp-feedback-collector 项目提供的 UI 设计灵感。\n\n开源协作让技术变得更美好!"
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"settings": {
|
||||||
|
"title": "图片设置",
|
||||||
|
"sizeLimit": "图片大小限制",
|
||||||
|
"sizeLimitOptions": {
|
||||||
|
"unlimited": "无限制",
|
||||||
|
"1mb": "1MB",
|
||||||
|
"3mb": "3MB",
|
||||||
|
"5mb": "5MB"
|
||||||
|
},
|
||||||
|
"base64Detail": "Base64 兼容模式",
|
||||||
|
"base64DetailHelp": "启用后会在文本中包含完整的 Base64 图片数据,提升与某些 AI 模型的兼容性",
|
||||||
|
"base64Warning": "⚠️ 会增加传输量",
|
||||||
|
"compatibilityHint": "💡 图片无法正确识别?",
|
||||||
|
"enableBase64Hint": "尝试启用 Base64 兼容模式"
|
||||||
|
},
|
||||||
|
"sizeLimitExceeded": "图片 {filename} 大小为 {size},超过 {limit} 限制!",
|
||||||
|
"sizeLimitExceededAdvice": "建议使用图片编辑软件压缩后再上传,或调整图片大小限制设置。"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -171,5 +171,24 @@
|
|||||||
"contactDescription": "如需技術支援、問題回報或功能建議,歡迎透過 Discord 社群或 GitHub Issues 與我們聯繫。",
|
"contactDescription": "如需技術支援、問題回報或功能建議,歡迎透過 Discord 社群或 GitHub Issues 與我們聯繫。",
|
||||||
"thanks": "致謝與貢獻",
|
"thanks": "致謝與貢獻",
|
||||||
"thanksText": "感謝原作者 Fábio Ferreira (@fabiomlferreira) 創建了原始的 interactive-feedback-mcp 專案。\n\n本增強版本由 Minidoracat 開發和維護,大幅擴展了專案功能,新增了 GUI 介面、圖片支援、多語言能力以及許多其他改進功能。\n\n同時感謝 sanshao85 的 mcp-feedback-collector 專案提供的 UI 設計靈感。\n\n開源協作讓技術變得更美好!"
|
"thanksText": "感謝原作者 Fábio Ferreira (@fabiomlferreira) 創建了原始的 interactive-feedback-mcp 專案。\n\n本增強版本由 Minidoracat 開發和維護,大幅擴展了專案功能,新增了 GUI 介面、圖片支援、多語言能力以及許多其他改進功能。\n\n同時感謝 sanshao85 的 mcp-feedback-collector 專案提供的 UI 設計靈感。\n\n開源協作讓技術變得更美好!"
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"settings": {
|
||||||
|
"title": "圖片設定",
|
||||||
|
"sizeLimit": "圖片大小限制",
|
||||||
|
"sizeLimitOptions": {
|
||||||
|
"unlimited": "無限制",
|
||||||
|
"1mb": "1MB",
|
||||||
|
"3mb": "3MB",
|
||||||
|
"5mb": "5MB"
|
||||||
|
},
|
||||||
|
"base64Detail": "Base64 相容模式",
|
||||||
|
"base64DetailHelp": "啟用後會在文字中包含完整的 Base64 圖片資料,提升與某些 AI 模型的相容性",
|
||||||
|
"base64Warning": "⚠️ 會增加傳輸量",
|
||||||
|
"compatibilityHint": "💡 圖片無法正確識別?",
|
||||||
|
"enableBase64Hint": "嘗試啟用 Base64 相容模式"
|
||||||
|
},
|
||||||
|
"sizeLimitExceeded": "圖片 {filename} 大小為 {size},超過 {limit} 限制!",
|
||||||
|
"sizeLimitExceededAdvice": "建議使用圖片編輯軟體壓縮後再上傳,或調整圖片大小限制設定。"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -34,6 +34,7 @@ class WebFeedbackSession:
|
|||||||
self.websocket: Optional[WebSocket] = None
|
self.websocket: Optional[WebSocket] = None
|
||||||
self.feedback_result: Optional[str] = None
|
self.feedback_result: Optional[str] = None
|
||||||
self.images: List[dict] = []
|
self.images: List[dict] = []
|
||||||
|
self.settings: dict = {} # 圖片設定
|
||||||
self.feedback_completed = threading.Event()
|
self.feedback_completed = threading.Event()
|
||||||
self.process: Optional[subprocess.Popen] = None
|
self.process: Optional[subprocess.Popen] = None
|
||||||
self.command_logs = []
|
self.command_logs = []
|
||||||
@ -73,7 +74,8 @@ class WebFeedbackSession:
|
|||||||
return {
|
return {
|
||||||
"logs": "\n".join(self.command_logs),
|
"logs": "\n".join(self.command_logs),
|
||||||
"interactive_feedback": self.feedback_result or "",
|
"interactive_feedback": self.feedback_result or "",
|
||||||
"images": self.images
|
"images": self.images,
|
||||||
|
"settings": self.settings
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
# 超時了,立即清理資源
|
# 超時了,立即清理資源
|
||||||
@ -87,18 +89,21 @@ class WebFeedbackSession:
|
|||||||
await self._cleanup_resources_on_timeout()
|
await self._cleanup_resources_on_timeout()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
async def submit_feedback(self, feedback: str, images: List[dict]):
|
async def submit_feedback(self, feedback: str, images: List[dict], settings: dict = None):
|
||||||
"""
|
"""
|
||||||
提交回饋和圖片
|
提交回饋和圖片
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
feedback: 文字回饋
|
feedback: 文字回饋
|
||||||
images: 圖片列表
|
images: 圖片列表
|
||||||
|
settings: 圖片設定(可選)
|
||||||
"""
|
"""
|
||||||
self.feedback_result = feedback
|
self.feedback_result = feedback
|
||||||
|
# 先設置設定,再處理圖片(因為處理圖片時需要用到設定)
|
||||||
|
self.settings = settings or {}
|
||||||
self.images = self._process_images(images)
|
self.images = self._process_images(images)
|
||||||
self.feedback_completed.set()
|
self.feedback_completed.set()
|
||||||
|
|
||||||
if self.websocket:
|
if self.websocket:
|
||||||
try:
|
try:
|
||||||
await self.websocket.close()
|
await self.websocket.close()
|
||||||
@ -108,23 +113,26 @@ class WebFeedbackSession:
|
|||||||
def _process_images(self, images: List[dict]) -> List[dict]:
|
def _process_images(self, images: List[dict]) -> List[dict]:
|
||||||
"""
|
"""
|
||||||
處理圖片數據,轉換為統一格式
|
處理圖片數據,轉換為統一格式
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
images: 原始圖片數據列表
|
images: 原始圖片數據列表
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[dict]: 處理後的圖片數據
|
List[dict]: 處理後的圖片數據
|
||||||
"""
|
"""
|
||||||
processed_images = []
|
processed_images = []
|
||||||
|
|
||||||
|
# 從設定中獲取圖片大小限制,如果沒有設定則使用預設值
|
||||||
|
size_limit = self.settings.get('image_size_limit', MAX_IMAGE_SIZE)
|
||||||
|
|
||||||
for img in images:
|
for img in images:
|
||||||
try:
|
try:
|
||||||
if not all(key in img for key in ["name", "data", "size"]):
|
if not all(key in img for key in ["name", "data", "size"]):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 檢查文件大小
|
# 檢查文件大小(只有當限制大於0時才檢查)
|
||||||
if img["size"] > MAX_IMAGE_SIZE:
|
if size_limit > 0 and img["size"] > size_limit:
|
||||||
debug_log(f"圖片 {img['name']} 超過大小限制,跳過")
|
debug_log(f"圖片 {img['name']} 超過大小限制 ({size_limit} bytes),跳過")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 解碼 base64 數據
|
# 解碼 base64 數據
|
||||||
|
@ -112,18 +112,20 @@ def setup_routes(manager: 'WebUIManager'):
|
|||||||
"""保存設定到檔案"""
|
"""保存設定到檔案"""
|
||||||
try:
|
try:
|
||||||
data = await request.json()
|
data = await request.json()
|
||||||
|
|
||||||
# 構建設定檔案路徑
|
# 使用與 GUI 版本相同的設定檔案路徑
|
||||||
settings_file = Path.cwd() / ".mcp_feedback_settings.json"
|
config_dir = Path.home() / ".config" / "mcp-feedback-enhanced"
|
||||||
|
config_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
settings_file = config_dir / "ui_settings.json"
|
||||||
|
|
||||||
# 保存設定到檔案
|
# 保存設定到檔案
|
||||||
with open(settings_file, 'w', encoding='utf-8') as f:
|
with open(settings_file, 'w', encoding='utf-8') as f:
|
||||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
debug_log(f"設定已保存到: {settings_file}")
|
debug_log(f"設定已保存到: {settings_file}")
|
||||||
|
|
||||||
return JSONResponse(content={"status": "success", "message": "設定已保存"})
|
return JSONResponse(content={"status": "success", "message": "設定已保存"})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_log(f"保存設定失敗: {e}")
|
debug_log(f"保存設定失敗: {e}")
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
@ -135,18 +137,20 @@ def setup_routes(manager: 'WebUIManager'):
|
|||||||
async def load_settings():
|
async def load_settings():
|
||||||
"""從檔案載入設定"""
|
"""從檔案載入設定"""
|
||||||
try:
|
try:
|
||||||
settings_file = Path.cwd() / ".mcp_feedback_settings.json"
|
# 使用與 GUI 版本相同的設定檔案路徑
|
||||||
|
config_dir = Path.home() / ".config" / "mcp-feedback-enhanced"
|
||||||
|
settings_file = config_dir / "ui_settings.json"
|
||||||
|
|
||||||
if settings_file.exists():
|
if settings_file.exists():
|
||||||
with open(settings_file, 'r', encoding='utf-8') as f:
|
with open(settings_file, 'r', encoding='utf-8') as f:
|
||||||
settings = json.load(f)
|
settings = json.load(f)
|
||||||
|
|
||||||
debug_log(f"設定已從檔案載入: {settings_file}")
|
debug_log(f"設定已從檔案載入: {settings_file}")
|
||||||
return JSONResponse(content=settings)
|
return JSONResponse(content=settings)
|
||||||
else:
|
else:
|
||||||
debug_log("設定檔案不存在,返回空設定")
|
debug_log("設定檔案不存在,返回空設定")
|
||||||
return JSONResponse(content={})
|
return JSONResponse(content={})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_log(f"載入設定失敗: {e}")
|
debug_log(f"載入設定失敗: {e}")
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
@ -158,16 +162,18 @@ def setup_routes(manager: 'WebUIManager'):
|
|||||||
async def clear_settings():
|
async def clear_settings():
|
||||||
"""清除設定檔案"""
|
"""清除設定檔案"""
|
||||||
try:
|
try:
|
||||||
settings_file = Path.cwd() / ".mcp_feedback_settings.json"
|
# 使用與 GUI 版本相同的設定檔案路徑
|
||||||
|
config_dir = Path.home() / ".config" / "mcp-feedback-enhanced"
|
||||||
|
settings_file = config_dir / "ui_settings.json"
|
||||||
|
|
||||||
if settings_file.exists():
|
if settings_file.exists():
|
||||||
settings_file.unlink()
|
settings_file.unlink()
|
||||||
debug_log(f"設定檔案已刪除: {settings_file}")
|
debug_log(f"設定檔案已刪除: {settings_file}")
|
||||||
else:
|
else:
|
||||||
debug_log("設定檔案不存在,無需刪除")
|
debug_log("設定檔案不存在,無需刪除")
|
||||||
|
|
||||||
return JSONResponse(content={"status": "success", "message": "設定已清除"})
|
return JSONResponse(content={"status": "success", "message": "設定已清除"})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_log(f"清除設定失敗: {e}")
|
debug_log(f"清除設定失敗: {e}")
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
@ -184,7 +190,8 @@ async def handle_websocket_message(manager: 'WebUIManager', session, data: dict)
|
|||||||
# 提交回饋
|
# 提交回饋
|
||||||
feedback = data.get("feedback", "")
|
feedback = data.get("feedback", "")
|
||||||
images = data.get("images", [])
|
images = data.get("images", [])
|
||||||
await session.submit_feedback(feedback, images)
|
settings = data.get("settings", {})
|
||||||
|
await session.submit_feedback(feedback, images, settings)
|
||||||
|
|
||||||
elif message_type == "run_command":
|
elif message_type == "run_command":
|
||||||
# 執行命令
|
# 執行命令
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
class PersistentSettings {
|
class PersistentSettings {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.settingsFile = '.mcp_feedback_settings.json';
|
this.settingsFile = 'ui_settings.json';
|
||||||
this.storageKey = 'mcp_feedback_settings';
|
this.storageKey = 'mcp_feedback_settings';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +91,10 @@ class FeedbackApp {
|
|||||||
this.isConnected = false; // 初始化連接狀態
|
this.isConnected = false; // 初始化連接狀態
|
||||||
this.websocket = null; // 初始化 WebSocket
|
this.websocket = null; // 初始化 WebSocket
|
||||||
this.isHandlingPaste = false; // 防止重複處理貼上事件的標記
|
this.isHandlingPaste = false; // 防止重複處理貼上事件的標記
|
||||||
|
|
||||||
|
// 圖片設定
|
||||||
|
this.imageSizeLimit = 0; // 0 表示無限制
|
||||||
|
this.enableBase64Detail = false;
|
||||||
|
|
||||||
// 立即檢查 DOM 狀態並初始化
|
// 立即檢查 DOM 狀態並初始化
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
@ -363,6 +367,9 @@ class FeedbackApp {
|
|||||||
if (resetSettingsBtn) {
|
if (resetSettingsBtn) {
|
||||||
resetSettingsBtn.addEventListener('click', () => this.resetSettings());
|
resetSettingsBtn.addEventListener('click', () => this.resetSettings());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 圖片設定監聽器
|
||||||
|
this.setupImageSettingsListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupSettingsListeners() {
|
setupSettingsListeners() {
|
||||||
@ -394,6 +401,118 @@ class FeedbackApp {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupImageSettingsListeners() {
|
||||||
|
// 圖片大小限制設定 - 原始分頁
|
||||||
|
const imageSizeLimit = document.getElementById('imageSizeLimit');
|
||||||
|
if (imageSizeLimit) {
|
||||||
|
imageSizeLimit.addEventListener('change', (e) => {
|
||||||
|
this.imageSizeLimit = parseInt(e.target.value);
|
||||||
|
this.saveSettings();
|
||||||
|
this.syncImageSettings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64 詳細模式設定 - 原始分頁
|
||||||
|
const enableBase64Detail = document.getElementById('enableBase64Detail');
|
||||||
|
if (enableBase64Detail) {
|
||||||
|
enableBase64Detail.addEventListener('change', (e) => {
|
||||||
|
this.enableBase64Detail = e.target.checked;
|
||||||
|
this.saveSettings();
|
||||||
|
this.syncImageSettings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 圖片大小限制設定 - 合併模式
|
||||||
|
const combinedImageSizeLimit = document.getElementById('combinedImageSizeLimit');
|
||||||
|
if (combinedImageSizeLimit) {
|
||||||
|
combinedImageSizeLimit.addEventListener('change', (e) => {
|
||||||
|
this.imageSizeLimit = parseInt(e.target.value);
|
||||||
|
this.saveSettings();
|
||||||
|
this.syncImageSettings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64 詳細模式設定 - 合併模式
|
||||||
|
const combinedEnableBase64Detail = document.getElementById('combinedEnableBase64Detail');
|
||||||
|
if (combinedEnableBase64Detail) {
|
||||||
|
combinedEnableBase64Detail.addEventListener('change', (e) => {
|
||||||
|
this.enableBase64Detail = e.target.checked;
|
||||||
|
this.saveSettings();
|
||||||
|
this.syncImageSettings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 相容性提示按鈕 - 原始分頁
|
||||||
|
const enableBase64Hint = document.getElementById('enableBase64Hint');
|
||||||
|
if (enableBase64Hint) {
|
||||||
|
enableBase64Hint.addEventListener('click', () => {
|
||||||
|
this.enableBase64Detail = true;
|
||||||
|
this.saveSettings();
|
||||||
|
this.syncImageSettings();
|
||||||
|
this.hideCompatibilityHint();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 相容性提示按鈕 - 合併模式
|
||||||
|
const combinedEnableBase64Hint = document.getElementById('combinedEnableBase64Hint');
|
||||||
|
if (combinedEnableBase64Hint) {
|
||||||
|
combinedEnableBase64Hint.addEventListener('click', () => {
|
||||||
|
this.enableBase64Detail = true;
|
||||||
|
this.saveSettings();
|
||||||
|
this.syncImageSettings();
|
||||||
|
this.hideCompatibilityHint();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
syncImageSettings() {
|
||||||
|
// 同步圖片大小限制設定
|
||||||
|
const imageSizeLimit = document.getElementById('imageSizeLimit');
|
||||||
|
const combinedImageSizeLimit = document.getElementById('combinedImageSizeLimit');
|
||||||
|
|
||||||
|
if (imageSizeLimit) {
|
||||||
|
imageSizeLimit.value = this.imageSizeLimit;
|
||||||
|
}
|
||||||
|
if (combinedImageSizeLimit) {
|
||||||
|
combinedImageSizeLimit.value = this.imageSizeLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步 Base64 詳細模式設定
|
||||||
|
const enableBase64Detail = document.getElementById('enableBase64Detail');
|
||||||
|
const combinedEnableBase64Detail = document.getElementById('combinedEnableBase64Detail');
|
||||||
|
|
||||||
|
if (enableBase64Detail) {
|
||||||
|
enableBase64Detail.checked = this.enableBase64Detail;
|
||||||
|
}
|
||||||
|
if (combinedEnableBase64Detail) {
|
||||||
|
combinedEnableBase64Detail.checked = this.enableBase64Detail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showCompatibilityHint() {
|
||||||
|
const compatibilityHint = document.getElementById('compatibilityHint');
|
||||||
|
const combinedCompatibilityHint = document.getElementById('combinedCompatibilityHint');
|
||||||
|
|
||||||
|
if (compatibilityHint) {
|
||||||
|
compatibilityHint.style.display = 'flex';
|
||||||
|
}
|
||||||
|
if (combinedCompatibilityHint) {
|
||||||
|
combinedCompatibilityHint.style.display = 'flex';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideCompatibilityHint() {
|
||||||
|
const compatibilityHint = document.getElementById('compatibilityHint');
|
||||||
|
const combinedCompatibilityHint = document.getElementById('combinedCompatibilityHint');
|
||||||
|
|
||||||
|
if (compatibilityHint) {
|
||||||
|
compatibilityHint.style.display = 'none';
|
||||||
|
}
|
||||||
|
if (combinedCompatibilityHint) {
|
||||||
|
combinedCompatibilityHint.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setupTabs() {
|
setupTabs() {
|
||||||
const tabButtons = document.querySelectorAll('.tab-button');
|
const tabButtons = document.querySelectorAll('.tab-button');
|
||||||
const tabContents = document.querySelectorAll('.tab-content');
|
const tabContents = document.querySelectorAll('.tab-content');
|
||||||
@ -674,8 +793,27 @@ class FeedbackApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addImage(file) {
|
addImage(file) {
|
||||||
if (file.size > 1024 * 1024) { // 1MB
|
// 檢查圖片大小限制
|
||||||
alert('圖片大小不能超過 1MB');
|
if (this.imageSizeLimit > 0 && file.size > this.imageSizeLimit) {
|
||||||
|
const limitMB = this.imageSizeLimit / (1024 * 1024);
|
||||||
|
const fileMB = file.size / (1024 * 1024);
|
||||||
|
|
||||||
|
const message = window.i18nManager ?
|
||||||
|
window.i18nManager.t('images.sizeLimitExceeded', {
|
||||||
|
filename: file.name,
|
||||||
|
size: fileMB.toFixed(1) + 'MB',
|
||||||
|
limit: limitMB.toFixed(0) + 'MB'
|
||||||
|
}) :
|
||||||
|
`圖片 ${file.name} 大小為 ${fileMB.toFixed(1)}MB,超過 ${limitMB.toFixed(0)}MB 限制!`;
|
||||||
|
|
||||||
|
const advice = window.i18nManager ?
|
||||||
|
window.i18nManager.t('images.sizeLimitExceededAdvice') :
|
||||||
|
'建議使用圖片編輯軟體壓縮後再上傳,或調整圖片大小限制設定。';
|
||||||
|
|
||||||
|
alert(message + '\n\n' + advice);
|
||||||
|
|
||||||
|
// 顯示相容性提示(如果圖片上傳失敗)
|
||||||
|
this.showCompatibilityHint();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -847,11 +985,15 @@ class FeedbackApp {
|
|||||||
type: img.type
|
type: img.type
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 發送回饋
|
// 發送回饋(包含圖片設定)
|
||||||
this.websocket.send(JSON.stringify({
|
this.websocket.send(JSON.stringify({
|
||||||
type: 'submit_feedback',
|
type: 'submit_feedback',
|
||||||
feedback: feedback,
|
feedback: feedback,
|
||||||
images: imageData
|
images: imageData,
|
||||||
|
settings: {
|
||||||
|
image_size_limit: this.imageSizeLimit,
|
||||||
|
enable_base64_detail: this.enableBase64Detail
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log('回饋已提交');
|
console.log('回饋已提交');
|
||||||
@ -960,13 +1102,29 @@ class FeedbackApp {
|
|||||||
this.autoClose = true; // 預設啟用
|
this.autoClose = true; // 預設啟用
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新自動關閉開關狀態
|
// 更新自動關閉開關狀態
|
||||||
const autoCloseToggle = document.getElementById('autoCloseToggle');
|
const autoCloseToggle = document.getElementById('autoCloseToggle');
|
||||||
if (autoCloseToggle) {
|
if (autoCloseToggle) {
|
||||||
autoCloseToggle.classList.toggle('active', this.autoClose);
|
autoCloseToggle.classList.toggle('active', this.autoClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 載入圖片設定
|
||||||
|
if (settings.imageSizeLimit !== undefined) {
|
||||||
|
this.imageSizeLimit = settings.imageSizeLimit;
|
||||||
|
} else {
|
||||||
|
this.imageSizeLimit = 0; // 預設無限制
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.enableBase64Detail !== undefined) {
|
||||||
|
this.enableBase64Detail = settings.enableBase64Detail;
|
||||||
|
} else {
|
||||||
|
this.enableBase64Detail = false; // 預設關閉
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步圖片設定到 UI
|
||||||
|
this.syncImageSettings();
|
||||||
|
|
||||||
// 確保語言選擇器與當前語言同步
|
// 確保語言選擇器與當前語言同步
|
||||||
this.syncLanguageSelector();
|
this.syncLanguageSelector();
|
||||||
|
|
||||||
@ -1053,6 +1211,8 @@ $ `;
|
|||||||
// 重置本地變數
|
// 重置本地變數
|
||||||
this.layoutMode = 'separate';
|
this.layoutMode = 'separate';
|
||||||
this.autoClose = true;
|
this.autoClose = true;
|
||||||
|
this.imageSizeLimit = 0;
|
||||||
|
this.enableBase64Detail = false;
|
||||||
|
|
||||||
// 更新佈局模式單選按鈕狀態
|
// 更新佈局模式單選按鈕狀態
|
||||||
const layoutRadios = document.querySelectorAll('input[name="layoutMode"]');
|
const layoutRadios = document.querySelectorAll('input[name="layoutMode"]');
|
||||||
@ -1066,6 +1226,9 @@ $ `;
|
|||||||
autoCloseToggle.classList.toggle('active', this.autoClose);
|
autoCloseToggle.classList.toggle('active', this.autoClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步圖片設定到 UI
|
||||||
|
this.syncImageSettings();
|
||||||
|
|
||||||
// 確保語言選擇器與當前語言同步
|
// 確保語言選擇器與當前語言同步
|
||||||
this.syncLanguageSelector();
|
this.syncLanguageSelector();
|
||||||
|
|
||||||
@ -1149,17 +1312,21 @@ $ `;
|
|||||||
const settings = {
|
const settings = {
|
||||||
layoutMode: this.layoutMode,
|
layoutMode: this.layoutMode,
|
||||||
autoClose: this.autoClose,
|
autoClose: this.autoClose,
|
||||||
|
imageSizeLimit: this.imageSizeLimit,
|
||||||
|
enableBase64Detail: this.enableBase64Detail,
|
||||||
language: window.i18nManager?.currentLanguage || 'zh-TW',
|
language: window.i18nManager?.currentLanguage || 'zh-TW',
|
||||||
activeTab: localStorage.getItem('activeTab'),
|
activeTab: localStorage.getItem('activeTab'),
|
||||||
lastSaved: new Date().toISOString()
|
lastSaved: new Date().toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.persistentSettings.saveSettings(settings);
|
await this.persistentSettings.saveSettings(settings);
|
||||||
|
|
||||||
// 同時保存到 localStorage 作為備用(向後兼容)
|
// 同時保存到 localStorage 作為備用(向後兼容)
|
||||||
localStorage.setItem('layoutMode', this.layoutMode);
|
localStorage.setItem('layoutMode', this.layoutMode);
|
||||||
localStorage.setItem('autoClose', this.autoClose.toString());
|
localStorage.setItem('autoClose', this.autoClose.toString());
|
||||||
|
localStorage.setItem('imageSizeLimit', this.imageSizeLimit.toString());
|
||||||
|
localStorage.setItem('enableBase64Detail', this.enableBase64Detail.toString());
|
||||||
|
|
||||||
console.log('設定已保存:', settings);
|
console.log('設定已保存:', settings);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('保存設定時發生錯誤:', error);
|
console.warn('保存設定時發生錯誤:', error);
|
||||||
|
@ -86,10 +86,30 @@ class I18nManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 支援巢狀鍵值的翻譯函數
|
// 支援巢狀鍵值的翻譯函數,支援參數替換
|
||||||
t(key, defaultValue = '') {
|
t(key, params = {}) {
|
||||||
const langData = this.translations[this.currentLanguage] || {};
|
const langData = this.translations[this.currentLanguage] || {};
|
||||||
return this.getNestedValue(langData, key) || defaultValue || key;
|
let translation = this.getNestedValue(langData, key);
|
||||||
|
|
||||||
|
// 如果沒有找到翻譯,返回預設值或鍵名
|
||||||
|
if (!translation) {
|
||||||
|
return typeof params === 'string' ? params : key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 params 是字串,當作預設值處理(向後相容)
|
||||||
|
if (typeof params === 'string') {
|
||||||
|
return translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 參數替換:將 {key} 替換為對應的值
|
||||||
|
if (typeof params === 'object' && params !== null) {
|
||||||
|
Object.keys(params).forEach(paramKey => {
|
||||||
|
const placeholder = `{${paramKey}}`;
|
||||||
|
translation = translation.replace(new RegExp(placeholder, 'g'), params[paramKey]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return translation;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNestedValue(obj, path) {
|
getNestedValue(obj, path) {
|
||||||
|
@ -798,6 +798,122 @@
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 圖片設定樣式 */
|
||||||
|
.image-settings-details {
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-settings-summary {
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
user-select: none;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-settings-summary:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-settings-content {
|
||||||
|
padding: 12px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-setting-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-setting-row:last-of-type {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-setting-label {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-setting-select {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-setting-checkbox-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-setting-checkbox {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
accent-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-setting-help {
|
||||||
|
color: var(--warning-color);
|
||||||
|
font-size: 11px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-setting-help-text {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-top: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 相容性提示樣式 */
|
||||||
|
.compatibility-hint {
|
||||||
|
background: rgba(33, 150, 243, 0.1);
|
||||||
|
border: 1px solid var(--info-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--info-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.compatibility-hint-btn {
|
||||||
|
background: var(--info-color);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
font-size: 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compatibility-hint-btn:hover {
|
||||||
|
background: #1976d2;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -859,8 +975,44 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 圖片設定區域 -->
|
||||||
|
<div class="input-group" style="margin-bottom: 12px;">
|
||||||
|
<details class="image-settings-details">
|
||||||
|
<summary class="image-settings-summary" data-i18n="images.settings.title">⚙️ 圖片設定</summary>
|
||||||
|
<div class="image-settings-content">
|
||||||
|
<div class="image-setting-row">
|
||||||
|
<label class="image-setting-label" data-i18n="images.settings.sizeLimit">圖片大小限制:</label>
|
||||||
|
<select id="imageSizeLimit" class="image-setting-select">
|
||||||
|
<option value="0" data-i18n="images.settings.sizeLimitOptions.unlimited">無限制</option>
|
||||||
|
<option value="1048576" data-i18n="images.settings.sizeLimitOptions.1mb">1MB</option>
|
||||||
|
<option value="3145728" data-i18n="images.settings.sizeLimitOptions.3mb">3MB</option>
|
||||||
|
<option value="5242880" data-i18n="images.settings.sizeLimitOptions.5mb">5MB</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="image-setting-row">
|
||||||
|
<label class="image-setting-checkbox-container">
|
||||||
|
<input type="checkbox" id="enableBase64Detail" class="image-setting-checkbox">
|
||||||
|
<span class="image-setting-checkmark"></span>
|
||||||
|
<span class="image-setting-label" data-i18n="images.settings.base64Detail">Base64 相容模式</span>
|
||||||
|
</label>
|
||||||
|
<small class="image-setting-help" data-i18n="images.settings.base64Warning">⚠️ 會增加傳輸量</small>
|
||||||
|
</div>
|
||||||
|
<div class="image-setting-help-text" data-i18n="images.settings.base64DetailHelp">
|
||||||
|
啟用後會在文字中包含完整的 Base64 圖片資料,提升與 Gemini 等 AI 模型的相容性
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label class="input-label" data-i18n="feedback.imageLabel">圖片附件(可選)</label>
|
<label class="input-label" data-i18n="feedback.imageLabel">圖片附件(可選)</label>
|
||||||
|
<!-- 相容性提示區域 -->
|
||||||
|
<div id="compatibilityHint" class="compatibility-hint" style="display: none;">
|
||||||
|
<span data-i18n="images.settings.compatibilityHint">💡 圖片無法正確識別?</span>
|
||||||
|
<button type="button" id="enableBase64Hint" class="compatibility-hint-btn" data-i18n="images.settings.enableBase64Hint">
|
||||||
|
嘗試啟用 Base64 相容模式
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div id="imageUploadArea" class="image-upload-area">
|
<div id="imageUploadArea" class="image-upload-area">
|
||||||
<div id="imageUploadText" data-i18n="feedback.imageUploadText">
|
<div id="imageUploadText" data-i18n="feedback.imageUploadText">
|
||||||
📎 點擊選擇圖片或拖放圖片到此處<br>
|
📎 點擊選擇圖片或拖放圖片到此處<br>
|
||||||
@ -951,8 +1103,44 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 圖片設定區域 -->
|
||||||
|
<div class="input-group" style="margin-bottom: 12px;">
|
||||||
|
<details class="image-settings-details">
|
||||||
|
<summary class="image-settings-summary" data-i18n="images.settings.title">⚙️ 圖片設定</summary>
|
||||||
|
<div class="image-settings-content">
|
||||||
|
<div class="image-setting-row">
|
||||||
|
<label class="image-setting-label" data-i18n="images.settings.sizeLimit">圖片大小限制:</label>
|
||||||
|
<select id="combinedImageSizeLimit" class="image-setting-select">
|
||||||
|
<option value="0" data-i18n="images.settings.sizeLimitOptions.unlimited">無限制</option>
|
||||||
|
<option value="1048576" data-i18n="images.settings.sizeLimitOptions.1mb">1MB</option>
|
||||||
|
<option value="3145728" data-i18n="images.settings.sizeLimitOptions.3mb">3MB</option>
|
||||||
|
<option value="5242880" data-i18n="images.settings.sizeLimitOptions.5mb">5MB</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="image-setting-row">
|
||||||
|
<label class="image-setting-checkbox-container">
|
||||||
|
<input type="checkbox" id="combinedEnableBase64Detail" class="image-setting-checkbox">
|
||||||
|
<span class="image-setting-checkmark"></span>
|
||||||
|
<span class="image-setting-label" data-i18n="images.settings.base64Detail">Base64 相容模式</span>
|
||||||
|
</label>
|
||||||
|
<small class="image-setting-help" data-i18n="images.settings.base64Warning">⚠️ 會增加傳輸量</small>
|
||||||
|
</div>
|
||||||
|
<div class="image-setting-help-text" data-i18n="images.settings.base64DetailHelp">
|
||||||
|
啟用後會在文字中包含完整的 Base64 圖片資料,提升與 Gemini 等 AI 模型的相容性
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label class="input-label" data-i18n="feedback.imageLabel">圖片附件(可選)</label>
|
<label class="input-label" data-i18n="feedback.imageLabel">圖片附件(可選)</label>
|
||||||
|
<!-- 相容性提示區域 -->
|
||||||
|
<div id="combinedCompatibilityHint" class="compatibility-hint" style="display: none;">
|
||||||
|
<span data-i18n="images.settings.compatibilityHint">💡 圖片無法正確識別?</span>
|
||||||
|
<button type="button" id="combinedEnableBase64Hint" class="compatibility-hint-btn" data-i18n="images.settings.enableBase64Hint">
|
||||||
|
嘗試啟用 Base64 相容模式
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div id="combinedImageUploadArea" class="image-upload-area" style="min-height: 100px;">
|
<div id="combinedImageUploadArea" class="image-upload-area" style="min-height: 100px;">
|
||||||
<div id="combinedImageUploadText" data-i18n="feedback.imageUploadText">
|
<div id="combinedImageUploadText" data-i18n="feedback.imageUploadText">
|
||||||
📎 點擊選擇圖片或拖放圖片到此處<br>
|
📎 點擊選擇圖片或拖放圖片到此處<br>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user