diff --git a/src/mcp_feedback_enhanced/web/locales/en/translation.json b/src/mcp_feedback_enhanced/web/locales/en/translation.json index fd0c889..18c13c8 100644 --- a/src/mcp_feedback_enhanced/web/locales/en/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/en/translation.json @@ -260,6 +260,44 @@ "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!", "terminalWelcome": "Welcome to Interactive Feedback Terminal\n========================================\nProject Directory: {sessionId}\nEnter commands and press Enter or click Execute button\nSupported commands: ls, dir, pwd, cat, type, etc.\n\n$ " }, + "prompts": { + "management": { + "title": "Prompt Templates Management", + "description": "Manage your frequently used prompt templates for quick selection during feedback input", + "addNew": "Add New Prompt", + "edit": "Edit", + "delete": "Delete", + "confirmDelete": "Are you sure you want to delete this prompt?", + "emptyState": "No prompt templates created yet", + "emptyHint": "Click the 'Add New Prompt' button above to create your first prompt template", + "created": "Created", + "lastUsed": "Last Used", + "notFound": "Prompt not found", + "addSuccess": "Prompt added successfully", + "updateSuccess": "Prompt updated successfully", + "deleteSuccess": "Prompt deleted successfully" + }, + "buttons": { + "selectPrompt": "Select Prompt Template", + "useLastPrompt": "Use Last Prompt", + "noPrompts": "No prompt templates available, please add one in settings first", + "noLastPrompt": "No recently used prompt available", + "lastPromptApplied": "Last used prompt applied successfully", + "promptNotFound": "Prompt not found", + "promptApplied": "Prompt applied: " + }, + "modal": { + "addTitle": "Add New Prompt", + "editTitle": "Edit Prompt", + "nameLabel": "Prompt Name", + "contentLabel": "Prompt Content", + "namePlaceholder": "Enter prompt name...", + "contentPlaceholder": "Enter prompt content...", + "save": "Save", + "cancel": "Cancel", + "emptyFields": "Please fill in all required fields" + } + }, "about": { "title": "ℹ️ About", "description": "A powerful MCP server that provides human-in-the-loop interactive feedback functionality for AI-assisted development tools. Supports Web UI interface, with rich features including image upload, command execution, multi-language support, and more.", 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 2c65962..eda2973 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-CN/translation.json @@ -260,6 +260,44 @@ "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请测试这些功能并提供反馈!", "terminalWelcome": "欢迎使用交互反馈终端\n========================================\n项目目录: {sessionId}\n输入命令后按 Enter 或点击执行按钮\n支持的命令: ls, dir, pwd, cat, type 等\n\n$ " }, + "prompts": { + "management": { + "title": "常用提示词管理", + "description": "管理您的常用提示词模板,可在反馈输入时快速选用", + "addNew": "新增提示词", + "edit": "编辑", + "delete": "删除", + "confirmDelete": "确定要删除此提示词吗?", + "emptyState": "尚未建立任何常用提示词", + "emptyHint": "点击上方「新增提示词」按钮开始建立您的第一个提示词模板", + "created": "建立于", + "lastUsed": "最近使用", + "notFound": "找不到指定的提示词", + "addSuccess": "提示词已新增", + "updateSuccess": "提示词已更新", + "deleteSuccess": "提示词已删除" + }, + "buttons": { + "selectPrompt": "选择常用提示词", + "useLastPrompt": "使用上次提示词", + "noPrompts": "尚无常用提示词,请先在设置中新增", + "noLastPrompt": "尚无最近使用的提示词", + "lastPromptApplied": "已套用上次使用的提示词", + "promptNotFound": "找不到指定的提示词", + "promptApplied": "已套用提示词:" + }, + "modal": { + "addTitle": "新增提示词", + "editTitle": "编辑提示词", + "nameLabel": "提示词名称", + "contentLabel": "提示词内容", + "namePlaceholder": "请输入提示词名称...", + "contentPlaceholder": "请输入提示词内容...", + "save": "保存", + "cancel": "取消", + "emptyFields": "请填写所有必填栏位" + } + }, "about": { "title": "ℹ️ 关于", "description": "一个强大的 MCP 服务器,为 AI 辅助开发工具提供人在回路的交互反馈功能。支持 Web UI 界面,并具备图片上传、命令执行、多语言等丰富功能。", 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 a2b20b2..63119de 100644 --- a/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json +++ b/src/mcp_feedback_enhanced/web/locales/zh-TW/translation.json @@ -265,6 +265,44 @@ "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請測試這些功能並提供回饋!", "terminalWelcome": "歡迎使用互動回饋終端\n========================================\n專案目錄: {sessionId}\n輸入命令後按 Enter 或點擊執行按鈕\n支援的命令: ls, dir, pwd, cat, type 等\n\n$ " }, + "prompts": { + "management": { + "title": "常用提示詞管理", + "description": "管理您的常用提示詞模板,可在回饋輸入時快速選用", + "addNew": "新增提示詞", + "edit": "編輯", + "delete": "刪除", + "confirmDelete": "確定要刪除此提示詞嗎?", + "emptyState": "尚未建立任何常用提示詞", + "emptyHint": "點擊上方「新增提示詞」按鈕開始建立您的第一個提示詞模板", + "created": "建立於", + "lastUsed": "最近使用", + "notFound": "找不到指定的提示詞", + "addSuccess": "提示詞已新增", + "updateSuccess": "提示詞已更新", + "deleteSuccess": "提示詞已刪除" + }, + "buttons": { + "selectPrompt": "選擇常用提示詞", + "useLastPrompt": "使用上次提示詞", + "noPrompts": "尚無常用提示詞,請先在設定中新增", + "noLastPrompt": "尚無最近使用的提示詞", + "lastPromptApplied": "已套用上次使用的提示詞", + "promptNotFound": "找不到指定的提示詞", + "promptApplied": "已套用提示詞:" + }, + "modal": { + "addTitle": "新增提示詞", + "editTitle": "編輯提示詞", + "nameLabel": "提示詞名稱", + "contentLabel": "提示詞內容", + "namePlaceholder": "請輸入提示詞名稱...", + "contentPlaceholder": "請輸入提示詞內容...", + "save": "儲存", + "cancel": "取消", + "emptyFields": "請填寫所有必填欄位" + } + }, "about": { "title": "ℹ️ 關於", "description": "一個強大的 MCP 伺服器,為 AI 輔助開發工具提供人在回路的互動回饋功能。支援 Web UI 介面,並具備圖片上傳、命令執行、多語言等豐富功能。", diff --git a/src/mcp_feedback_enhanced/web/static/css/prompt-management.css b/src/mcp_feedback_enhanced/web/static/css/prompt-management.css new file mode 100644 index 0000000..9589a3f --- /dev/null +++ b/src/mcp_feedback_enhanced/web/static/css/prompt-management.css @@ -0,0 +1,444 @@ +/** + * 提示詞管理功能樣式 + * =================== + * + * 包含提示詞管理相關的所有 UI 樣式 + */ + +/* ===== 提示詞彈窗樣式 ===== */ + +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(4px); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + opacity: 0; + transition: opacity 0.3s ease; +} + +.modal-overlay.show { + opacity: 1; +} + +.modal-overlay.hide { + opacity: 0; +} + +.modal-container { + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 12px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); + max-width: 500px; + width: 90%; + max-height: 80vh; + display: flex; + flex-direction: column; + transform: scale(0.9) translateY(20px); + transition: transform 0.3s ease; +} + +.modal-container.modal-large { + max-width: 700px; +} + +.modal-overlay.show .modal-container { + transform: scale(1) translateY(0); +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px 20px; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); + border-radius: 12px 12px 0 0; +} + +.modal-title { + margin: 0; + font-size: 16px; + font-weight: 600; + color: var(--text-primary); +} + +.modal-close-btn { + background: none; + border: none; + font-size: 24px; + color: var(--text-secondary); + cursor: pointer; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: all 0.3s ease; +} + +.modal-close-btn:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} + +.modal-body { + padding: 20px; + flex: 1; + overflow-y: auto; +} + +.modal-footer { + padding: 16px 20px; + background: var(--bg-secondary); + border-top: 1px solid var(--border-color); + border-radius: 0 0 12px 12px; + display: flex; + justify-content: flex-end; + gap: 12px; +} + +/* ===== 提示詞表單樣式 ===== */ + +.prompt-form .input-group { + margin-bottom: 20px; +} + +.prompt-form .input-group:last-child { + margin-bottom: 0; +} + +.prompt-form .text-input { + width: 100%; + box-sizing: border-box; +} + +.prompt-form input[type="text"].text-input { + height: 40px !important; + min-height: 40px !important; + max-height: 40px !important; + padding: 8px 12px !important; + font-size: 14px; + line-height: 1.4; +} + +.prompt-form textarea.text-input { + min-height: 200px !important; + height: 200px !important; + padding: 12px !important; + font-size: 14px; + line-height: 1.4; + resize: vertical; +} + +/* ===== 提示詞列表樣式 ===== */ + +.prompt-list { + display: flex; + flex-direction: column; + gap: 12px; + max-height: 400px; + overflow-y: auto; +} + +.prompt-item { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 16px; + cursor: pointer; + transition: all 0.3s ease; +} + +.prompt-item:hover { + border-color: var(--accent-color); + background: rgba(0, 122, 204, 0.05); + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 122, 204, 0.1); +} + +.prompt-item-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.prompt-item-name { + margin: 0; + font-size: 14px; + font-weight: 600; + color: var(--text-primary); +} + +.prompt-item-date { + font-size: 12px; + color: var(--text-secondary); +} + +.prompt-item-content { + font-size: 13px; + color: var(--text-secondary); + line-height: 1.4; + margin-bottom: 8px; +} + +.prompt-item-used { + font-size: 11px; + color: var(--accent-color); + font-style: italic; +} + +.empty-state { + text-align: center; + color: var(--text-secondary); + font-style: italic; + padding: 40px 20px; +} + +/* ===== 設定頁籤中的提示詞管理樣式 ===== */ + +.prompt-management-section { + margin-top: 20px; +} + +.prompt-management-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.prompt-management-title { + font-size: 16px; + font-weight: 600; + color: var(--text-primary); + margin: 0; +} + +.prompt-add-btn { + padding: 8px 16px; + font-size: 13px; +} + +.prompt-settings-list { + display: flex; + flex-direction: column; + gap: 8px; + max-height: 300px; + overflow-y: auto; +} + +.prompt-settings-item { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 6px; + padding: 12px; + display: flex; + justify-content: space-between; + align-items: flex-start; + transition: all 0.3s ease; +} + +.prompt-settings-item:hover { + border-color: rgba(0, 122, 204, 0.5); + background: rgba(0, 122, 204, 0.05); +} + +.prompt-settings-info { + flex: 1; + margin-right: 12px; +} + +.prompt-settings-name { + font-size: 14px; + font-weight: 500; + color: var(--text-primary); + margin-bottom: 4px; +} + +.prompt-settings-content { + font-size: 12px; + color: var(--text-secondary); + line-height: 1.3; + max-height: 40px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.prompt-settings-meta { + font-size: 11px; + color: var(--text-secondary); + margin-top: 4px; +} + +.prompt-settings-actions { + display: flex; + gap: 8px; + flex-shrink: 0; +} + +.prompt-action-btn { + background: none; + border: 1px solid var(--border-color); + border-radius: 4px; + padding: 4px 8px; + font-size: 12px; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.3s ease; +} + +.prompt-action-btn:hover { + border-color: var(--accent-color); + color: var(--accent-color); + background: rgba(0, 122, 204, 0.1); +} + +.prompt-action-btn.delete:hover { + border-color: #dc3545; + color: #dc3545; + background: rgba(220, 53, 69, 0.1); +} + +/* ===== input-group 按鈕樣式 ===== */ + +.prompt-input-buttons { + display: flex; + gap: 8px; + margin-bottom: 8px; +} + +.prompt-input-btn { + padding: 6px 12px; + font-size: 12px; + border-radius: 4px; + border: 1px solid var(--border-color); + background: var(--bg-tertiary); + color: var(--text-primary); + cursor: pointer; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + gap: 4px; +} + +.prompt-input-btn:hover { + border-color: var(--accent-color); + background: rgba(0, 122, 204, 0.1); + color: var(--accent-color); +} + +.prompt-input-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.prompt-input-btn:disabled:hover { + border-color: var(--border-color); + background: var(--bg-tertiary); + color: var(--text-primary); +} + +/* ===== 響應式設計 ===== */ + +@media (max-width: 768px) { + .modal-container { + width: 95%; + max-height: 90vh; + } + + .modal-header { + padding: 12px 16px; + } + + .modal-body { + padding: 16px; + } + + .modal-footer { + padding: 12px 16px; + flex-direction: column-reverse; + gap: 8px; + } + + .modal-footer .btn { + width: 100%; + justify-content: center; + } + + .prompt-management-header { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } + + .prompt-settings-item { + flex-direction: column; + align-items: stretch; + } + + .prompt-settings-actions { + margin-top: 8px; + justify-content: flex-end; + } + + .prompt-input-buttons { + flex-direction: column; + } + + .prompt-input-btn { + justify-content: center; + } +} + +/* ===== 動畫效果 ===== */ + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.prompt-settings-item, +.prompt-item { + animation: fadeIn 0.3s ease; +} + +/* ===== 無障礙改進 ===== */ + +.prompt-item:focus, +.prompt-action-btn:focus, +.prompt-input-btn:focus { + outline: 2px solid var(--accent-color); + outline-offset: 2px; +} + +/* ===== 深色主題適配 ===== */ + +@media (prefers-color-scheme: dark) { + .modal-overlay { + background: rgba(0, 0, 0, 0.7); + } + + .prompt-item:hover, + .prompt-settings-item:hover { + background: rgba(0, 122, 204, 0.1); + } +} diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js index 46ed602..3335f0b 100644 --- a/src/mcp_feedback_enhanced/web/static/js/app.js +++ b/src/mcp_feedback_enhanced/web/static/js/app.js @@ -32,6 +32,12 @@ this.settingsManager = null; this.uiManager = null; + // 提示詞管理器 + this.promptManager = null; + this.promptModal = null; + this.promptSettingsUI = null; + this.promptInputButtons = null; + // 應用程式狀態 this.isInitialized = false; this.pendingSubmission = null; @@ -182,16 +188,17 @@ } }); + // 9. 初始化提示詞管理器 + self.initializePromptManagers(); - - // 8. 應用設定到 UI + // 10. 應用設定到 UI self.settingsManager.applyToUI(); - // 8. 初始化各個管理器 + // 11. 初始化各個管理器 self.uiManager.initTabs(); self.imageHandler.init(); - // 9. 建立 WebSocket 連接 + // 12. 建立 WebSocket 連接 self.webSocketManager.connect(); resolve(); @@ -369,7 +376,54 @@ } }; + /** + * 初始化提示詞管理器 + */ + FeedbackApp.prototype.initializePromptManagers = function() { + console.log('📝 初始化提示詞管理器...'); + try { + // 檢查提示詞模組是否已載入 + if (!window.MCPFeedback.Prompt) { + console.warn('⚠️ 提示詞模組未載入,跳過初始化'); + return; + } + + // 1. 初始化提示詞管理器 + this.promptManager = new window.MCPFeedback.Prompt.PromptManager({ + settingsManager: this.settingsManager + }); + this.promptManager.init(); + + // 2. 初始化提示詞彈窗 + this.promptModal = new window.MCPFeedback.Prompt.PromptModal(); + + // 3. 初始化設定頁籤 UI + this.promptSettingsUI = new window.MCPFeedback.Prompt.PromptSettingsUI({ + promptManager: this.promptManager, + promptModal: this.promptModal + }); + this.promptSettingsUI.init('#promptManagementContainer'); + + // 4. 初始化輸入按鈕 + this.promptInputButtons = new window.MCPFeedback.Prompt.PromptInputButtons({ + promptManager: this.promptManager, + promptModal: this.promptModal + }); + + // 初始化輸入按鈕到所有回饋輸入區域 + const inputContainers = [ + '#feedbackText', // 回饋分頁 + '#combinedFeedbackText' // 工作區分頁 + ]; + this.promptInputButtons.init(inputContainers); + + console.log('✅ 提示詞管理器初始化完成'); + + } catch (error) { + console.error('❌ 提示詞管理器初始化失敗:', error); + } + }; /** * 處理 WebSocket 開啟 diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/prompt/prompt-input-buttons.js b/src/mcp_feedback_enhanced/web/static/js/modules/prompt/prompt-input-buttons.js new file mode 100644 index 0000000..56afc5a --- /dev/null +++ b/src/mcp_feedback_enhanced/web/static/js/modules/prompt/prompt-input-buttons.js @@ -0,0 +1,384 @@ +/** + * MCP Feedback Enhanced - 提示詞輸入按鈕模組 + * ========================================== + * + * 處理 input-group 區域的提示詞功能按鈕 + */ + +(function() { + 'use strict'; + + // 確保命名空間存在 + window.MCPFeedback = window.MCPFeedback || {}; + window.MCPFeedback.Prompt = window.MCPFeedback.Prompt || {}; + + const Utils = window.MCPFeedback.Utils; + + /** + * 提示詞輸入按鈕管理器 + */ + function PromptInputButtons(options) { + options = options || {}; + + // 依賴注入 + this.promptManager = options.promptManager || null; + this.promptModal = options.promptModal || null; + + // UI 元素 + this.containers = []; + this.selectButtons = []; + this.lastUsedButtons = []; + + // 狀態 + this.isInitialized = false; + + console.log('🔘 PromptInputButtons 初始化完成'); + } + + /** + * 初始化輸入按鈕 + */ + PromptInputButtons.prototype.init = function(containerSelectors) { + if (!Array.isArray(containerSelectors)) { + containerSelectors = [containerSelectors]; + } + + let successCount = 0; + + containerSelectors.forEach((selector, index) => { + const container = document.querySelector(selector); + if (container) { + this.containers.push(container); + this.createButtons(container, index); + successCount++; + } else { + console.warn('⚠️ 找不到提示詞按鈕容器:', selector); + } + }); + + if (successCount > 0) { + // 設置事件監聽器 + this.setupEventListeners(); + + // 更新按鈕狀態 + this.updateButtonStates(); + + this.isInitialized = true; + console.log('✅ PromptInputButtons 初始化完成,成功創建', successCount, '組按鈕'); + return true; + } + + console.error('❌ 沒有成功創建任何提示詞按鈕'); + return false; + }; + + /** + * 創建按鈕 + */ + PromptInputButtons.prototype.createButtons = function(container, index) { + const buttonsHtml = ` +
+ `; + + // 在 input-group 的 label 後面插入按鈕 + const inputGroup = container.closest('.input-group') || container; + const label = inputGroup.querySelector('.input-label'); + + if (label) { + label.insertAdjacentHTML('afterend', buttonsHtml); + } else { + inputGroup.insertAdjacentHTML('afterbegin', buttonsHtml); + } + + // 獲取按鈕引用 + const buttonContainer = inputGroup.querySelector('.prompt-input-buttons'); + if (buttonContainer) { + this.selectButtons.push(buttonContainer.querySelector('.select-prompt-btn')); + this.lastUsedButtons.push(buttonContainer.querySelector('.last-prompt-btn')); + } + }; + + /** + * 設置事件監聽器 + */ + PromptInputButtons.prototype.setupEventListeners = function() { + const self = this; + + // 選擇提示詞按鈕事件 + this.selectButtons.forEach(function(button) { + if (button) { + button.addEventListener('click', function() { + const containerIndex = parseInt(button.getAttribute('data-container-index')); + self.handleSelectPrompt(containerIndex); + }); + } + }); + + // 使用上次提示詞按鈕事件 + this.lastUsedButtons.forEach(function(button) { + if (button) { + button.addEventListener('click', function() { + const containerIndex = parseInt(button.getAttribute('data-container-index')); + self.handleUseLastPrompt(containerIndex); + }); + } + }); + + // 設置提示詞管理器回調 + if (this.promptManager) { + this.promptManager.addPromptsChangeCallback(function() { + self.updateButtonStates(); + }); + + this.promptManager.addLastUsedChangeCallback(function() { + self.updateButtonStates(); + }); + } + + // 設置彈窗回調 + if (this.promptModal) { + this.promptModal.onSelect = function(promptId) { + self.handlePromptSelected(promptId); + }; + } + }; + + /** + * 處理選擇提示詞 + */ + PromptInputButtons.prototype.handleSelectPrompt = function(containerIndex) { + if (!this.promptManager || !this.promptModal) { + console.error('❌ PromptManager 或 PromptModal 未設定'); + return; + } + + const prompts = this.promptManager.getPromptsSortedByUsage(); + + if (prompts.length === 0) { + this.showError(this.t('prompts.buttons.noPrompts', '尚無常用提示詞,請先在設定中新增')); + return; + } + + // 記錄當前容器索引,用於後續插入文字 + this.currentContainerIndex = containerIndex; + + // 顯示選擇彈窗 + this.promptModal.showSelectModal(prompts); + }; + + /** + * 處理使用上次提示詞 + */ + PromptInputButtons.prototype.handleUseLastPrompt = function(containerIndex) { + if (!this.promptManager) { + console.error('❌ PromptManager 未設定'); + return; + } + + const lastPrompt = this.promptManager.getLastUsedPrompt(); + + if (!lastPrompt) { + this.showError(this.t('prompts.buttons.noLastPrompt', '尚無最近使用的提示詞')); + return; + } + + // 插入提示詞內容 + this.insertPromptContent(containerIndex, lastPrompt); + + // 更新使用記錄 + this.promptManager.usePrompt(lastPrompt.id); + + this.showSuccess(this.t('prompts.buttons.lastPromptApplied', '已套用上次使用的提示詞')); + }; + + /** + * 處理提示詞選擇完成 + */ + PromptInputButtons.prototype.handlePromptSelected = function(promptId) { + if (!this.promptManager) { + console.error('❌ PromptManager 未設定'); + return; + } + + const prompt = this.promptManager.getPromptById(promptId); + if (!prompt) { + this.showError(this.t('prompts.buttons.promptNotFound', '找不到指定的提示詞')); + return; + } + + // 插入提示詞內容 + this.insertPromptContent(this.currentContainerIndex, prompt); + + // 更新使用記錄 + this.promptManager.usePrompt(promptId); + + this.showSuccess(this.t('prompts.buttons.promptApplied', '已套用提示詞:') + prompt.name); + }; + + /** + * 插入提示詞內容到輸入框 + */ + PromptInputButtons.prototype.insertPromptContent = function(containerIndex, prompt) { + if (containerIndex < 0 || containerIndex >= this.containers.length) { + console.error('❌ 無效的容器索引:', containerIndex); + return; + } + + const container = this.containers[containerIndex]; + + // 檢查容器本身是否是輸入元素 + let textarea = null; + if (container.tagName === 'TEXTAREA' || container.tagName === 'INPUT') { + textarea = container; + } else { + // 如果不是,則在容器內查找 + textarea = container.querySelector('textarea') || container.querySelector('input[type="text"]'); + } + + if (!textarea) { + console.error('❌ 找不到輸入框,容器:', container); + return; + } + + // 獲取當前內容和游標位置 + const currentContent = textarea.value; + const cursorPosition = textarea.selectionStart; + + // 決定插入方式 + let newContent; + let newCursorPosition; + + if (currentContent.trim() === '') { + // 如果輸入框為空,直接插入 + newContent = prompt.content; + newCursorPosition = prompt.content.length; + } else { + // 如果有內容,在游標位置插入 + const beforeCursor = currentContent.substring(0, cursorPosition); + const afterCursor = currentContent.substring(cursorPosition); + + // 添加適當的分隔符 + const separator = beforeCursor.endsWith('\n') || beforeCursor === '' ? '' : '\n\n'; + + newContent = beforeCursor + separator + prompt.content + afterCursor; + newCursorPosition = beforeCursor.length + separator.length + prompt.content.length; + } + + // 更新內容 + textarea.value = newContent; + + // 設置游標位置 + textarea.focus(); + textarea.setSelectionRange(newCursorPosition, newCursorPosition); + + // 觸發 input 事件,確保其他監聽器能夠響應 + const inputEvent = new Event('input', { bubbles: true }); + textarea.dispatchEvent(inputEvent); + }; + + /** + * 更新按鈕狀態 + */ + PromptInputButtons.prototype.updateButtonStates = function() { + if (!this.promptManager) { + return; + } + + const prompts = this.promptManager.getAllPrompts(); + const lastPrompt = this.promptManager.getLastUsedPrompt(); + + // 更新選擇提示詞按鈕 + this.selectButtons.forEach(function(button) { + if (button) { + button.disabled = prompts.length === 0; + + if (prompts.length === 0) { + button.title = '尚無常用提示詞'; + } else { + button.title = `選擇常用提示詞 (${prompts.length} 個可用)`; + } + } + }); + + // 更新使用上次提示詞按鈕 + this.lastUsedButtons.forEach(function(button) { + if (button) { + button.disabled = !lastPrompt; + + if (!lastPrompt) { + button.title = '尚無最近使用的提示詞'; + } else { + button.title = `使用上次提示詞:${lastPrompt.name}`; + } + } + }); + }; + + /** + * 顯示成功訊息 + */ + PromptInputButtons.prototype.showSuccess = function(message) { + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + window.MCPFeedback.Utils.showMessage(message, 'success'); + } else { + console.log('✅', message); + } + }; + + /** + * 顯示錯誤訊息 + */ + PromptInputButtons.prototype.showError = function(message) { + if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) { + window.MCPFeedback.Utils.showMessage(message, 'error'); + } else { + alert(message); + } + }; + + /** + * 翻譯函數 + */ + PromptInputButtons.prototype.t = function(key, fallback) { + if (window.i18nManager && typeof window.i18nManager.t === 'function') { + return window.i18nManager.t(key, fallback); + } + return fallback || key; + }; + + /** + * 銷毀按鈕 + */ + PromptInputButtons.prototype.destroy = function() { + // 移除所有按鈕容器 + this.containers.forEach(function(container) { + const buttonContainer = container.querySelector('.prompt-input-buttons'); + if (buttonContainer) { + buttonContainer.remove(); + } + }); + + // 清空引用 + this.containers = []; + this.selectButtons = []; + this.lastUsedButtons = []; + this.isInitialized = false; + + console.log('🗑️ PromptInputButtons 已銷毀'); + }; + + // 將 PromptInputButtons 加入命名空間 + window.MCPFeedback.Prompt.PromptInputButtons = PromptInputButtons; + + console.log('✅ PromptInputButtons 模組載入完成'); + +})(); diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/prompt/prompt-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/prompt/prompt-manager.js new file mode 100644 index 0000000..1f9af72 --- /dev/null +++ b/src/mcp_feedback_enhanced/web/static/js/modules/prompt/prompt-manager.js @@ -0,0 +1,361 @@ +/** + * MCP Feedback Enhanced - 提示詞管理模組 + * ===================================== + * + * 處理常用提示詞的儲存、管理和操作 + */ + +(function() { + 'use strict'; + + // 確保命名空間和依賴存在 + window.MCPFeedback = window.MCPFeedback || {}; + window.MCPFeedback.Prompt = window.MCPFeedback.Prompt || {}; + const Utils = window.MCPFeedback.Utils; + + /** + * 提示詞管理器建構函數 + */ + function PromptManager(options) { + options = options || {}; + + // 設定管理器引用 + this.settingsManager = options.settingsManager || null; + + // 預設提示詞設定 + this.defaultPromptSettings = { + prompts: [], + lastUsedPromptId: null, + promptCounter: 0 + }; + + // 當前提示詞設定 + this.currentPromptSettings = Utils.deepClone(this.defaultPromptSettings); + + // 回調函數列表 + this.onPromptsChangeCallbacks = []; + this.onLastUsedChangeCallbacks = []; + + // 向後相容的單一回調 + if (options.onPromptsChange) { + this.onPromptsChangeCallbacks.push(options.onPromptsChange); + } + if (options.onLastUsedChange) { + this.onLastUsedChangeCallbacks.push(options.onLastUsedChange); + } + + console.log('✅ PromptManager 初始化完成'); + } + + /** + * 初始化提示詞管理器 + */ + PromptManager.prototype.init = function() { + if (this.settingsManager) { + // 從設定管理器載入提示詞資料 + this.loadFromSettings(); + } + + console.log('📋 PromptManager 初始化完成,提示詞數量:', this.currentPromptSettings.prompts.length); + return this; + }; + + /** + * 添加提示詞變更回調 + */ + PromptManager.prototype.addPromptsChangeCallback = function(callback) { + if (typeof callback === 'function') { + this.onPromptsChangeCallbacks.push(callback); + } + }; + + /** + * 添加最近使用變更回調 + */ + PromptManager.prototype.addLastUsedChangeCallback = function(callback) { + if (typeof callback === 'function') { + this.onLastUsedChangeCallbacks.push(callback); + } + }; + + /** + * 觸發提示詞變更回調 + */ + PromptManager.prototype.triggerPromptsChangeCallbacks = function() { + const prompts = this.currentPromptSettings.prompts; + this.onPromptsChangeCallbacks.forEach(function(callback) { + try { + callback(prompts); + } catch (error) { + console.error('❌ 提示詞變更回調執行失敗:', error); + } + }); + }; + + /** + * 觸發最近使用變更回調 + */ + PromptManager.prototype.triggerLastUsedChangeCallbacks = function(prompt) { + this.onLastUsedChangeCallbacks.forEach(function(callback) { + try { + callback(prompt); + } catch (error) { + console.error('❌ 最近使用變更回調執行失敗:', error); + } + }); + }; + + /** + * 從設定管理器載入提示詞資料 + */ + PromptManager.prototype.loadFromSettings = function() { + if (!this.settingsManager) { + console.warn('⚠️ SettingsManager 未設定,無法載入提示詞資料'); + return; + } + + const promptSettings = this.settingsManager.get('promptSettings'); + if (promptSettings) { + this.currentPromptSettings = this.mergePromptSettings(this.defaultPromptSettings, promptSettings); + console.log('📥 從設定載入提示詞資料:', this.currentPromptSettings.prompts.length, '個提示詞'); + } + }; + + /** + * 儲存提示詞資料到設定管理器 + */ + PromptManager.prototype.saveToSettings = function() { + if (!this.settingsManager) { + console.warn('⚠️ SettingsManager 未設定,無法儲存提示詞資料'); + return false; + } + + try { + this.settingsManager.set('promptSettings', this.currentPromptSettings); + console.log('💾 提示詞資料已儲存'); + return true; + } catch (error) { + console.error('❌ 儲存提示詞資料失敗:', error); + return false; + } + }; + + /** + * 合併提示詞設定 + */ + PromptManager.prototype.mergePromptSettings = function(defaultSettings, userSettings) { + const merged = Utils.deepClone(defaultSettings); + + if (userSettings.prompts && Array.isArray(userSettings.prompts)) { + merged.prompts = userSettings.prompts; + } + + if (userSettings.lastUsedPromptId) { + merged.lastUsedPromptId = userSettings.lastUsedPromptId; + } + + if (typeof userSettings.promptCounter === 'number') { + merged.promptCounter = userSettings.promptCounter; + } + + return merged; + }; + + /** + * 新增提示詞 + */ + PromptManager.prototype.addPrompt = function(name, content) { + if (!name || !content) { + throw new Error('提示詞名稱和內容不能為空'); + } + + // 檢查名稱是否重複 + if (this.getPromptByName(name)) { + throw new Error('提示詞名稱已存在'); + } + + const prompt = { + id: this.generatePromptId(), + name: name.trim(), + content: content.trim(), + createdAt: new Date().toISOString(), + lastUsedAt: null + }; + + this.currentPromptSettings.prompts.push(prompt); + this.saveToSettings(); + + // 觸發回調 + this.triggerPromptsChangeCallbacks(); + + console.log('➕ 新增提示詞:', prompt.name); + return prompt; + }; + + /** + * 更新提示詞 + */ + PromptManager.prototype.updatePrompt = function(id, name, content) { + if (!name || !content) { + throw new Error('提示詞名稱和內容不能為空'); + } + + const prompt = this.getPromptById(id); + if (!prompt) { + throw new Error('找不到指定的提示詞'); + } + + // 檢查名稱是否與其他提示詞重複 + const existingPrompt = this.getPromptByName(name); + if (existingPrompt && existingPrompt.id !== id) { + throw new Error('提示詞名稱已存在'); + } + + prompt.name = name.trim(); + prompt.content = content.trim(); + + this.saveToSettings(); + + // 觸發回調 + this.triggerPromptsChangeCallbacks(); + + console.log('✏️ 更新提示詞:', prompt.name); + return prompt; + }; + + /** + * 刪除提示詞 + */ + PromptManager.prototype.deletePrompt = function(id) { + const index = this.currentPromptSettings.prompts.findIndex(p => p.id === id); + if (index === -1) { + throw new Error('找不到指定的提示詞'); + } + + const prompt = this.currentPromptSettings.prompts[index]; + this.currentPromptSettings.prompts.splice(index, 1); + + // 如果刪除的是最近使用的提示詞,清除記錄 + if (this.currentPromptSettings.lastUsedPromptId === id) { + this.currentPromptSettings.lastUsedPromptId = null; + } + + this.saveToSettings(); + + // 觸發回調 + this.triggerPromptsChangeCallbacks(); + + console.log('🗑️ 刪除提示詞:', prompt.name); + return prompt; + }; + + /** + * 使用提示詞(更新最近使用記錄) + */ + PromptManager.prototype.usePrompt = function(id) { + const prompt = this.getPromptById(id); + if (!prompt) { + throw new Error('找不到指定的提示詞'); + } + + prompt.lastUsedAt = new Date().toISOString(); + this.currentPromptSettings.lastUsedPromptId = id; + + this.saveToSettings(); + + // 觸發回調 + this.triggerLastUsedChangeCallbacks(prompt); + + console.log('🎯 使用提示詞:', prompt.name); + return prompt; + }; + + /** + * 獲取所有提示詞 + */ + PromptManager.prototype.getAllPrompts = function() { + return [...this.currentPromptSettings.prompts]; + }; + + /** + * 根據 ID 獲取提示詞 + */ + PromptManager.prototype.getPromptById = function(id) { + return this.currentPromptSettings.prompts.find(p => p.id === id) || null; + }; + + /** + * 根據名稱獲取提示詞 + */ + PromptManager.prototype.getPromptByName = function(name) { + return this.currentPromptSettings.prompts.find(p => p.name === name) || null; + }; + + /** + * 獲取最近使用的提示詞 + */ + PromptManager.prototype.getLastUsedPrompt = function() { + if (!this.currentPromptSettings.lastUsedPromptId) { + return null; + } + return this.getPromptById(this.currentPromptSettings.lastUsedPromptId); + }; + + /** + * 獲取按使用時間排序的提示詞列表 + */ + PromptManager.prototype.getPromptsSortedByUsage = function() { + const prompts = [...this.currentPromptSettings.prompts]; + return prompts.sort((a, b) => { + // 最近使用的排在前面 + if (!a.lastUsedAt && !b.lastUsedAt) { + return new Date(b.createdAt) - new Date(a.createdAt); + } + if (!a.lastUsedAt) return 1; + if (!b.lastUsedAt) return -1; + return new Date(b.lastUsedAt) - new Date(a.lastUsedAt); + }); + }; + + /** + * 生成提示詞 ID + */ + PromptManager.prototype.generatePromptId = function() { + this.currentPromptSettings.promptCounter++; + return 'prompt_' + this.currentPromptSettings.promptCounter + '_' + Date.now(); + }; + + /** + * 重置所有提示詞資料 + */ + PromptManager.prototype.resetAllPrompts = function() { + this.currentPromptSettings = Utils.deepClone(this.defaultPromptSettings); + this.saveToSettings(); + + // 觸發回調 + this.triggerPromptsChangeCallbacks(); + + console.log('🔄 重置所有提示詞資料'); + }; + + /** + * 獲取提示詞統計資訊 + */ + PromptManager.prototype.getStatistics = function() { + const prompts = this.currentPromptSettings.prompts; + const usedPrompts = prompts.filter(p => p.lastUsedAt); + + return { + total: prompts.length, + used: usedPrompts.length, + unused: prompts.length - usedPrompts.length, + lastUsed: this.getLastUsedPrompt() + }; + }; + + // 將 PromptManager 加入命名空間 + window.MCPFeedback.Prompt.PromptManager = PromptManager; + + console.log('✅ PromptManager 模組載入完成'); + +})(); diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/prompt/prompt-modal.js b/src/mcp_feedback_enhanced/web/static/js/modules/prompt/prompt-modal.js new file mode 100644 index 0000000..b4a481f --- /dev/null +++ b/src/mcp_feedback_enhanced/web/static/js/modules/prompt/prompt-modal.js @@ -0,0 +1,458 @@ +/** + * MCP Feedback Enhanced - 提示詞彈窗管理模組 + * ========================================== + * + * 處理提示詞新增、編輯、選擇的彈窗介面 + */ + +(function() { + 'use strict'; + + // 確保命名空間存在 + window.MCPFeedback = window.MCPFeedback || {}; + window.MCPFeedback.Prompt = window.MCPFeedback.Prompt || {}; + + const Utils = window.MCPFeedback.Utils; + + /** + * 提示詞彈窗管理器 + */ + function PromptModal(options) { + options = options || {}; + + // 彈窗選項 + this.enableEscapeClose = options.enableEscapeClose !== false; + this.enableBackdropClose = options.enableBackdropClose !== false; + + // 當前彈窗引用 + this.currentModal = null; + this.keydownHandler = null; + + // 回調函數 + this.onSave = options.onSave || null; + this.onSelect = options.onSelect || null; + this.onCancel = options.onCancel || null; + + console.log('🔍 PromptModal 初始化完成'); + } + + /** + * 顯示新增提示詞彈窗 + */ + PromptModal.prototype.showAddModal = function() { + const modalData = { + type: 'add', + title: this.t('prompts.modal.addTitle', '新增提示詞'), + prompt: { + name: '', + content: '' + } + }; + + this.createAndShowModal(modalData); + }; + + /** + * 顯示編輯提示詞彈窗 + */ + PromptModal.prototype.showEditModal = function(prompt) { + if (!prompt) { + console.error('❌ 編輯提示詞時缺少提示詞資料'); + return; + } + + const modalData = { + type: 'edit', + title: this.t('prompts.modal.editTitle', '編輯提示詞'), + prompt: { + id: prompt.id, + name: prompt.name, + content: prompt.content + } + }; + + this.createAndShowModal(modalData); + }; + + /** + * 顯示選擇提示詞彈窗 + */ + PromptModal.prototype.showSelectModal = function(prompts) { + if (!prompts || !Array.isArray(prompts)) { + console.error('❌ 選擇提示詞時缺少提示詞列表'); + return; + } + + const modalData = { + type: 'select', + title: this.t('prompts.buttons.selectPrompt', '選擇常用提示詞'), + prompts: prompts + }; + + this.createAndShowModal(modalData); + }; + + /** + * 創建並顯示彈窗 + */ + PromptModal.prototype.createAndShowModal = function(modalData) { + // 如果已有彈窗,先關閉 + if (this.currentModal) { + this.closeModal(); + } + + // 創建彈窗 HTML + const modalHtml = this.createModalHTML(modalData); + + // 插入到頁面中 + document.body.insertAdjacentHTML('beforeend', modalHtml); + + // 獲取彈窗元素 + this.currentModal = document.getElementById('promptModal'); + + // 設置事件監聽器 + this.setupEventListeners(modalData); + + // 添加顯示動畫 + this.showModal(); + + // 聚焦到第一個輸入框 + this.focusFirstInput(); + }; + + /** + * 創建彈窗 HTML + */ + PromptModal.prototype.createModalHTML = function(modalData) { + const modalId = 'promptModal'; + + if (modalData.type === 'select') { + return this.createSelectModalHTML(modalId, modalData); + } else { + return this.createEditModalHTML(modalId, modalData); + } + }; + + /** + * 創建編輯彈窗 HTML + */ + PromptModal.prototype.createEditModalHTML = function(modalId, modalData) { + return ` + + `; + }; + + /** + * 創建選擇彈窗 HTML + */ + PromptModal.prototype.createSelectModalHTML = function(modalId, modalData) { + const promptsHtml = modalData.prompts.map(prompt => ` +