diff --git a/src/mcp_feedback_enhanced/web/static/js/app.js b/src/mcp_feedback_enhanced/web/static/js/app.js
index e25ebcd..caadb01 100644
--- a/src/mcp_feedback_enhanced/web/static/js/app.js
+++ b/src/mcp_feedback_enhanced/web/static/js/app.js
@@ -207,19 +207,22 @@
// 11. 初始化自動提交管理器
self.initializeAutoSubmitManager();
- // 11. 應用設定到 UI
+ // 12. 初始化 Textarea 高度管理器
+ self.initializeTextareaHeightManager();
+
+ // 13. 應用設定到 UI
self.settingsManager.applyToUI();
- // 12. 初始化各個管理器
+ // 14. 初始化各個管理器
self.uiManager.initTabs();
self.imageHandler.init();
- // 13. 檢查並啟動自動提交(如果條件滿足)
+ // 15. 檢查並啟動自動提交(如果條件滿足)
setTimeout(function() {
self.checkAndStartAutoSubmit();
}, 500); // 延遲 500ms 確保所有初始化完成
- // 14. 建立 WebSocket 連接
+ // 16. 建立 WebSocket 連接
self.webSocketManager.connect();
resolve();
@@ -473,6 +476,47 @@
}
};
+ /**
+ * 初始化 Textarea 高度管理器
+ */
+ FeedbackApp.prototype.initializeTextareaHeightManager = function() {
+ console.log('📏 初始化 Textarea 高度管理器...');
+
+ try {
+ // 檢查 TextareaHeightManager 模組是否已載入
+ if (!window.MCPFeedback.TextareaHeightManager) {
+ console.warn('⚠️ TextareaHeightManager 模組未載入,跳過初始化');
+ return;
+ }
+
+ // 建立 TextareaHeightManager 實例
+ this.textareaHeightManager = new window.MCPFeedback.TextareaHeightManager({
+ settingsManager: this.settingsManager,
+ debounceDelay: 500 // 500ms 防抖延遲
+ });
+
+ // 初始化管理器
+ this.textareaHeightManager.initialize();
+
+ // 註冊 combinedFeedbackText textarea
+ const success = this.textareaHeightManager.registerTextarea(
+ 'combinedFeedbackText',
+ 'combinedFeedbackTextHeight'
+ );
+
+ if (success) {
+ console.log('✅ combinedFeedbackText 高度管理已啟用');
+ } else {
+ console.warn('⚠️ combinedFeedbackText 註冊失敗');
+ }
+
+ console.log('✅ Textarea 高度管理器初始化完成');
+
+ } catch (error) {
+ console.error('❌ Textarea 高度管理器初始化失敗:', error);
+ }
+ };
+
/**
* 處理 WebSocket 開啟
*/
@@ -1451,7 +1495,9 @@
this.imageHandler.cleanup();
}
-
+ if (this.textareaHeightManager) {
+ this.textareaHeightManager.destroy();
+ }
console.log('✅ 應用程式資源清理完成');
};
diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js
index 3524810..1ad2549 100644
--- a/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js
+++ b/src/mcp_feedback_enhanced/web/static/js/modules/settings-manager.js
@@ -40,7 +40,9 @@
sessionHistoryRetentionHours: 72,
// 用戶訊息記錄設定
userMessageRecordingEnabled: true,
- userMessagePrivacyLevel: 'full' // 'full', 'basic', 'disabled'
+ userMessagePrivacyLevel: 'full', // 'full', 'basic', 'disabled'
+ // UI 元素尺寸設定
+ combinedFeedbackTextHeight: 150 // combinedFeedbackText textarea 的高度(px)
};
// 當前設定
diff --git a/src/mcp_feedback_enhanced/web/static/js/modules/textarea-height-manager.js b/src/mcp_feedback_enhanced/web/static/js/modules/textarea-height-manager.js
new file mode 100644
index 0000000..a673e76
--- /dev/null
+++ b/src/mcp_feedback_enhanced/web/static/js/modules/textarea-height-manager.js
@@ -0,0 +1,267 @@
+/**
+ * Textarea 高度管理器
+ * 負責監聽 textarea 高度變化並持久化設定
+ */
+
+(function() {
+ 'use strict';
+
+ // 確保命名空間存在
+ window.MCPFeedback = window.MCPFeedback || {};
+ const Utils = window.MCPFeedback.Utils;
+
+ /**
+ * TextareaHeightManager 建構函數
+ */
+ function TextareaHeightManager(options) {
+ options = options || {};
+
+ // 設定管理器實例
+ this.settingsManager = options.settingsManager || null;
+
+ // 已註冊的 textarea 元素
+ this.registeredTextareas = new Map();
+
+ // ResizeObserver 實例
+ this.resizeObserver = null;
+
+ // 防抖計時器
+ this.debounceTimers = new Map();
+
+ // 防抖延遲(毫秒)
+ this.debounceDelay = options.debounceDelay || 500;
+
+ console.log('📏 TextareaHeightManager 建構函數初始化完成');
+ }
+
+ /**
+ * 初始化高度管理器
+ */
+ TextareaHeightManager.prototype.initialize = function() {
+ console.log('📏 開始初始化 TextareaHeightManager...');
+
+ // 檢查 ResizeObserver 支援
+ if (!window.ResizeObserver) {
+ console.warn('📏 瀏覽器不支援 ResizeObserver,將使用備用方案');
+ this.initializeFallback();
+ return;
+ }
+
+ // 建立 ResizeObserver
+ this.createResizeObserver();
+
+ console.log('✅ TextareaHeightManager 初始化完成');
+ };
+
+ /**
+ * 建立 ResizeObserver
+ */
+ TextareaHeightManager.prototype.createResizeObserver = function() {
+ const self = this;
+
+ this.resizeObserver = new ResizeObserver(function(entries) {
+ entries.forEach(function(entry) {
+ const element = entry.target;
+ const config = self.registeredTextareas.get(element);
+
+ if (config) {
+ self.handleResize(element, config);
+ }
+ });
+ });
+
+ console.log('📏 ResizeObserver 建立完成');
+ };
+
+ /**
+ * 處理 textarea 尺寸變化
+ */
+ TextareaHeightManager.prototype.handleResize = function(element, config) {
+ const self = this;
+ const settingKey = config.settingKey;
+
+ // 清除之前的防抖計時器
+ if (this.debounceTimers.has(settingKey)) {
+ clearTimeout(this.debounceTimers.get(settingKey));
+ }
+
+ // 設定新的防抖計時器
+ const timer = setTimeout(function() {
+ const currentHeight = element.offsetHeight;
+
+ // 檢查高度是否有變化
+ if (currentHeight !== config.lastHeight) {
+ console.log('📏 偵測到 ' + settingKey + ' 高度變化:', config.lastHeight + 'px → ' + currentHeight + 'px');
+
+ // 更新記錄的高度
+ config.lastHeight = currentHeight;
+
+ // 保存到設定
+ if (self.settingsManager) {
+ self.settingsManager.set(settingKey, currentHeight);
+ }
+ }
+
+ // 清除計時器記錄
+ self.debounceTimers.delete(settingKey);
+ }, this.debounceDelay);
+
+ this.debounceTimers.set(settingKey, timer);
+ };
+
+ /**
+ * 註冊 textarea 元素
+ */
+ TextareaHeightManager.prototype.registerTextarea = function(elementId, settingKey) {
+ const element = Utils.safeQuerySelector('#' + elementId);
+
+ if (!element) {
+ console.warn('📏 找不到元素:', elementId);
+ return false;
+ }
+
+ if (element.tagName.toLowerCase() !== 'textarea') {
+ console.warn('📏 元素不是 textarea:', elementId);
+ return false;
+ }
+
+ // 載入並應用保存的高度
+ this.loadAndApplyHeight(element, settingKey);
+
+ // 建立配置物件
+ const config = {
+ elementId: elementId,
+ settingKey: settingKey,
+ lastHeight: element.offsetHeight
+ };
+
+ // 註冊到 Map
+ this.registeredTextareas.set(element, config);
+
+ // 開始監聽
+ if (this.resizeObserver) {
+ this.resizeObserver.observe(element);
+ }
+
+ console.log('📏 已註冊 textarea:', elementId, '設定鍵:', settingKey);
+ return true;
+ };
+
+ /**
+ * 載入並應用保存的高度
+ */
+ TextareaHeightManager.prototype.loadAndApplyHeight = function(element, settingKey) {
+ if (!this.settingsManager) {
+ console.warn('📏 沒有設定管理器,無法載入高度設定');
+ return;
+ }
+
+ const savedHeight = this.settingsManager.get(settingKey);
+
+ if (savedHeight && typeof savedHeight === 'number' && savedHeight > 0) {
+ // 確保不小於最小高度
+ const minHeight = this.getMinHeight(element);
+ const finalHeight = Math.max(savedHeight, minHeight);
+
+ // 應用高度
+ element.style.height = finalHeight + 'px';
+
+ console.log('📏 已恢復 ' + settingKey + ' 高度:', finalHeight + 'px');
+ } else {
+ console.log('📏 沒有找到 ' + settingKey + ' 的保存高度,使用預設值');
+ }
+ };
+
+ /**
+ * 獲取元素的最小高度
+ */
+ TextareaHeightManager.prototype.getMinHeight = function(element) {
+ const computedStyle = window.getComputedStyle(element);
+ const minHeight = computedStyle.minHeight;
+
+ if (minHeight && minHeight !== 'none') {
+ const value = parseInt(minHeight);
+ if (!isNaN(value)) {
+ return value;
+ }
+ }
+
+ // 預設最小高度
+ return 150;
+ };
+
+ /**
+ * 取消註冊 textarea 元素
+ */
+ TextareaHeightManager.prototype.unregisterTextarea = function(elementId) {
+ const element = Utils.safeQuerySelector('#' + elementId);
+
+ if (!element) {
+ return false;
+ }
+
+ const config = this.registeredTextareas.get(element);
+
+ if (config) {
+ // 停止監聽
+ if (this.resizeObserver) {
+ this.resizeObserver.unobserve(element);
+ }
+
+ // 清除防抖計時器
+ if (this.debounceTimers.has(config.settingKey)) {
+ clearTimeout(this.debounceTimers.get(config.settingKey));
+ this.debounceTimers.delete(config.settingKey);
+ }
+
+ // 從 Map 中移除
+ this.registeredTextareas.delete(element);
+
+ console.log('📏 已取消註冊 textarea:', elementId);
+ return true;
+ }
+
+ return false;
+ };
+
+ /**
+ * 備用方案初始化(當不支援 ResizeObserver 時)
+ */
+ TextareaHeightManager.prototype.initializeFallback = function() {
+ console.log('📏 使用備用方案初始化...');
+
+ // 備用方案可以使用 MutationObserver 或定期檢查
+ // 這裡先實作基本功能,主要是載入保存的高度
+ console.log('📏 備用方案初始化完成(僅支援載入功能)');
+ };
+
+ /**
+ * 銷毀管理器
+ */
+ TextareaHeightManager.prototype.destroy = function() {
+ console.log('📏 開始銷毀 TextareaHeightManager...');
+
+ // 清除所有防抖計時器
+ this.debounceTimers.forEach(function(timer) {
+ clearTimeout(timer);
+ });
+ this.debounceTimers.clear();
+
+ // 停止所有監聽
+ if (this.resizeObserver) {
+ this.resizeObserver.disconnect();
+ this.resizeObserver = null;
+ }
+
+ // 清除註冊記錄
+ this.registeredTextareas.clear();
+
+ console.log('✅ TextareaHeightManager 銷毀完成');
+ };
+
+ // 將 TextareaHeightManager 加入命名空間
+ window.MCPFeedback.TextareaHeightManager = TextareaHeightManager;
+
+ console.log('✅ TextareaHeightManager 模組載入完成');
+
+})();
diff --git a/src/mcp_feedback_enhanced/web/templates/feedback.html b/src/mcp_feedback_enhanced/web/templates/feedback.html
index 6860100..de223d7 100644
--- a/src/mcp_feedback_enhanced/web/templates/feedback.html
+++ b/src/mcp_feedback_enhanced/web/templates/feedback.html
@@ -1153,6 +1153,7 @@
+