🔨 重新調整會話超時機制、增加設定功能

This commit is contained in:
Minidoracat 2025-06-28 07:10:15 +08:00
parent 60e64c90dc
commit 441df85e1e
10 changed files with 480 additions and 14 deletions

View File

@ -122,7 +122,13 @@
"resetError": "Error occurred while resetting settings", "resetError": "Error occurred while resetting settings",
"timeout": "Connection Timeout (seconds)", "timeout": "Connection Timeout (seconds)",
"autorefresh": "Auto Refresh", "autorefresh": "Auto Refresh",
"debug": "Debug Mode" "debug": "Debug Mode",
"sessionTimeoutTitle": "⏱️ Session Timeout Settings",
"sessionTimeoutEnable": "Enable Session Timeout",
"sessionTimeoutEnableDesc": "When enabled, the session will automatically close after the specified time",
"sessionTimeoutDuration": "Timeout Duration (seconds)",
"sessionTimeoutDurationDesc": "Set session timeout duration, range: 300-86400 seconds (5 minutes - 24 hours)",
"sessionTimeoutSeconds": "seconds"
}, },
"languages": { "languages": {
"zh-TW": "繁體中文", "zh-TW": "繁體中文",
@ -200,7 +206,8 @@
"timeout": "⏰ Session has timed out, interface will close automatically", "timeout": "⏰ Session has timed out, interface will close automatically",
"timeoutWarning": "Session is about to timeout", "timeoutWarning": "Session is about to timeout",
"timeoutDescription": "Due to prolonged inactivity, the session has timed out. The interface will automatically close in 3 seconds.", "timeoutDescription": "Due to prolonged inactivity, the session has timed out. The interface will automatically close in 3 seconds.",
"closing": "Closing..." "closing": "Closing...",
"label": "Session Timeout:"
}, },
"autoRefresh": { "autoRefresh": {
"enable": "Auto Detect", "enable": "Auto Detect",

View File

@ -123,7 +123,13 @@
"timeout": "连线逾时 (秒)", "timeout": "连线逾时 (秒)",
"autorefresh": "自动重新整理", "autorefresh": "自动重新整理",
"debug": "除错模式", "debug": "除错模式",
"autoCommitNoPrompt": "请先选择一个提示词作为自动提交内容" "autoCommitNoPrompt": "请先选择一个提示词作为自动提交内容",
"sessionTimeoutTitle": "⏱️ 会话超时设置",
"sessionTimeoutEnable": "启用会话超时",
"sessionTimeoutEnableDesc": "启用后,会话将在指定时间后自动关闭",
"sessionTimeoutDuration": "超时时间(秒)",
"sessionTimeoutDurationDesc": "设置会话超时时间范围300-86400 秒5分钟-24小时",
"sessionTimeoutSeconds": "秒"
}, },
"languages": { "languages": {
"zh-TW": "繁體中文", "zh-TW": "繁體中文",
@ -201,7 +207,8 @@
"timeout": "⏰ 会话已超时,界面将自动关闭", "timeout": "⏰ 会话已超时,界面将自动关闭",
"timeoutWarning": "会话即将超时", "timeoutWarning": "会话即将超时",
"timeoutDescription": "由于长时间无响应,会话已超时。界面将在 3 秒后自动关闭。", "timeoutDescription": "由于长时间无响应,会话已超时。界面将在 3 秒后自动关闭。",
"closing": "正在关闭..." "closing": "正在关闭...",
"label": "会话超时:"
}, },
"autoRefresh": { "autoRefresh": {
"enable": "自动检测", "enable": "自动检测",

View File

@ -128,7 +128,13 @@
"timeout": "連線逾時 (秒)", "timeout": "連線逾時 (秒)",
"autorefresh": "自動重新整理", "autorefresh": "自動重新整理",
"debug": "除錯模式", "debug": "除錯模式",
"autoCommitNoPrompt": "請先選擇一個提示詞作為自動提交內容" "autoCommitNoPrompt": "請先選擇一個提示詞作為自動提交內容",
"sessionTimeoutTitle": "⏱️ 會話超時設定",
"sessionTimeoutEnable": "啟用會話超時",
"sessionTimeoutEnableDesc": "啟用後,會話將在指定時間後自動關閉",
"sessionTimeoutDuration": "超時時間(秒)",
"sessionTimeoutDurationDesc": "設定會話超時時間範圍300-86400 秒5分鐘-24小時",
"sessionTimeoutSeconds": "秒"
}, },
"languages": { "languages": {
"zh-TW": "繁體中文", "zh-TW": "繁體中文",
@ -206,7 +212,9 @@
"timeout": "⏰ 會話已超時,介面將自動關閉", "timeout": "⏰ 會話已超時,介面將自動關閉",
"timeoutWarning": "會話即將超時", "timeoutWarning": "會話即將超時",
"timeoutDescription": "由於長時間無回應,會話已超時。介面將在 3 秒後自動關閉。", "timeoutDescription": "由於長時間無回應,會話已超時。介面將在 3 秒後自動關閉。",
"closing": "正在關閉..." "closing": "正在關閉...",
"triggered": "會話已超時,程序即將關閉",
"label": "會話超時:"
}, },
"autoRefresh": { "autoRefresh": {
"enable": "自動檢測", "enable": "自動檢測",

View File

@ -168,6 +168,11 @@ class WebFeedbackSession:
# 新增:活躍標籤頁管理 # 新增:活躍標籤頁管理
self.active_tabs: dict[str, Any] = {} self.active_tabs: dict[str, Any] = {}
# 新增:用戶設定的會話超時
self.user_timeout_enabled = False
self.user_timeout_seconds = 3600 # 預設 1 小時
self.user_timeout_timer: threading.Timer | None = None
# 確保臨時目錄存在 # 確保臨時目錄存在
TEMP_DIR.mkdir(parents=True, exist_ok=True) TEMP_DIR.mkdir(parents=True, exist_ok=True)
@ -421,6 +426,39 @@ class WebFeedbackSession:
) )
return stats return stats
def update_timeout_settings(self, enabled: bool, timeout_seconds: int = 3600):
"""
更新用戶設定的會話超時
Args:
enabled: 是否啟用超時
timeout_seconds: 超時秒數
"""
debug_log(f"更新會話超時設定: enabled={enabled}, seconds={timeout_seconds}")
# 先停止現有的計時器
if self.user_timeout_timer:
self.user_timeout_timer.cancel()
self.user_timeout_timer = None
self.user_timeout_enabled = enabled
self.user_timeout_seconds = timeout_seconds
# 如果啟用且會話還在等待中,啟動計時器
if enabled and self.status == SessionStatus.WAITING:
def timeout_handler():
debug_log(f"用戶設定的超時已到: {self.session_id}")
# 設置超時標誌
self.status = SessionStatus.TIMEOUT
self.status_message = "用戶設定的會話超時"
# 設置完成事件,讓 wait_for_feedback 結束等待
self.feedback_completed.set()
self.user_timeout_timer = threading.Timer(timeout_seconds, timeout_handler)
self.user_timeout_timer.start()
debug_log(f"已啟動用戶超時計時器: {timeout_seconds}")
async def wait_for_feedback(self, timeout: int = 600) -> dict[str, Any]: async def wait_for_feedback(self, timeout: int = 600) -> dict[str, Any]:
""" """
等待用戶回饋包含圖片支援超時自動清理 等待用戶回饋包含圖片支援超時自動清理
@ -450,6 +488,12 @@ class WebFeedbackSession:
completed = await loop.run_in_executor(None, wait_in_thread) completed = await loop.run_in_executor(None, wait_in_thread)
if completed: if completed:
# 檢查是否是用戶設定的超時
if self.status == SessionStatus.TIMEOUT and self.user_timeout_enabled:
debug_log(f"會話 {self.session_id} 因用戶設定超時而結束")
await self._cleanup_resources_on_timeout()
raise TimeoutError("會話已因用戶設定的超時而關閉")
debug_log(f"會話 {self.session_id} 收到用戶回饋") debug_log(f"會話 {self.session_id} 收到用戶回饋")
return { return {
"logs": "\n".join(self.command_logs), "logs": "\n".join(self.command_logs),
@ -753,6 +797,12 @@ class WebFeedbackSession:
self.cleanup_timer = None self.cleanup_timer = None
resources_cleaned += 1 resources_cleaned += 1
# 1.5. 取消用戶超時計時器
if self.user_timeout_timer:
self.user_timeout_timer.cancel()
self.user_timeout_timer = None
resources_cleaned += 1
# 2. 關閉 WebSocket 連接 # 2. 關閉 WebSocket 連接
if self.websocket: if self.websocket:
try: try:

View File

@ -672,6 +672,17 @@ async def handle_websocket_message(manager: "WebUIManager", session, data: dict)
debug_log(f"收到 pong 回應,時間戳: {data.get('timestamp', 'N/A')}") debug_log(f"收到 pong 回應,時間戳: {data.get('timestamp', 'N/A')}")
# 可以在這裡記錄延遲或更新連接狀態 # 可以在這裡記錄延遲或更新連接狀態
elif message_type == "update_timeout_settings":
# 處理超時設定更新
settings = data.get("settings", {})
debug_log(f"收到超時設定更新: {settings}")
if settings.get("enabled"):
session.update_timeout_settings(
enabled=True, timeout_seconds=settings.get("seconds", 3600)
)
else:
session.update_timeout_settings(enabled=False)
else: else:
debug_log(f"未知的消息類型: {message_type}") debug_log(f"未知的消息類型: {message_type}")

View File

@ -2011,3 +2011,67 @@ textarea:-ms-input-placeholder,
margin-bottom: 0 !important; margin-bottom: 0 !important;
} }
/* 會話超時倒數樣式 */
.session-timeout-display {
background: rgba(255, 193, 7, 0.1);
border: 1px solid rgba(255, 193, 7, 0.3);
border-radius: 4px;
padding: 2px 8px;
}
.session-timeout-display .timeout-label {
font-size: 12px;
color: var(--text-secondary);
margin-right: 4px;
}
.session-timeout-display .countdown-timer {
color: var(--text-primary);
}
.session-timeout-display.countdown-warning .countdown-timer {
color: var(--error-color);
animation: pulse 1s ease-in-out infinite;
}
/* 自動提交倒數樣式 */
.auto-submit-display {
background: rgba(76, 175, 80, 0.1);
border: 1px solid rgba(76, 175, 80, 0.3);
border-radius: 4px;
padding: 2px 8px;
}
.auto-submit-display .countdown-label {
font-size: 12px;
color: var(--text-secondary);
margin-right: 4px;
}
.auto-submit-display .countdown-timer {
color: var(--text-primary);
font-weight: 500;
}
/* 倒數控制按鈕 */
.countdown-control-btn {
background: transparent;
border: none;
padding: 2px 6px;
margin-left: 4px;
cursor: pointer;
font-size: 14px;
color: var(--text-secondary);
transition: color 0.2s ease;
}
.countdown-control-btn:hover {
color: var(--text-primary);
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.6; }
100% { opacity: 1; }
}

View File

@ -271,7 +271,16 @@
} }
}, 800); // 延遲 800ms 確保所有初始化完成且避免與其他音效衝突 }, 800); // 延遲 800ms 確保所有初始化完成且避免與其他音效衝突
// 17. 建立 WebSocket 連接 // 17. 初始化會話超時設定
if (self.settingsManager.get('sessionTimeoutEnabled')) {
const timeoutSettings = {
enabled: self.settingsManager.get('sessionTimeoutEnabled'),
seconds: self.settingsManager.get('sessionTimeoutSeconds')
};
self.webSocketManager.updateSessionTimeoutSettings(timeoutSettings);
}
// 18. 建立 WebSocket 連接
self.webSocketManager.connect(); self.webSocketManager.connect();
resolve(); resolve();
@ -361,6 +370,9 @@
// 設置設定管理器的事件監聽器 // 設置設定管理器的事件監聽器
self.settingsManager.setupEventListeners(); self.settingsManager.setupEventListeners();
// 設置用戶活動監聽(用於重置會話超時)
self.setupUserActivityListeners();
console.log('✅ 事件監聽器設置完成'); console.log('✅ 事件監聽器設置完成');
resolve(); resolve();
}); });
@ -836,13 +848,20 @@
// 4. 重置回饋狀態為等待中 // 4. 重置回饋狀態為等待中
if (self.uiManager) { if (self.uiManager) {
self.uiManager.setFeedbackState( self.uiManager.setFeedbackState(window.MCPFeedback.Utils.CONSTANTS.FEEDBACK_WAITING, self.currentSessionId);
window.MCPFeedback.Utils.CONSTANTS.FEEDBACK_WAITING,
self.currentSessionId
);
} }
// 5. 檢查並啟動自動提交 // 5. 重新啟動會話超時計時器(如果已啟用)
if (self.settingsManager && self.settingsManager.get('sessionTimeoutEnabled')) {
console.log('🔄 新會話創建,重新啟動會話超時計時器');
const timeoutSettings = {
enabled: self.settingsManager.get('sessionTimeoutEnabled'),
seconds: self.settingsManager.get('sessionTimeoutSeconds')
};
self.webSocketManager.updateSessionTimeoutSettings(timeoutSettings);
}
// 6. 檢查並啟動自動提交
self.checkAndStartAutoSubmit(); self.checkAndStartAutoSubmit();
console.log('✅ 局部更新完成,頁面已準備好接收新的回饋'); console.log('✅ 局部更新完成,頁面已準備好接收新的回饋');
@ -1158,6 +1177,12 @@
this.autoSubmitManager.stop(); this.autoSubmitManager.stop();
} }
// 停止會話超時計時器
if (this.webSocketManager) {
console.log('⏸️ 提交反饋,停止會話超時計時器');
this.webSocketManager.stopSessionTimeout();
}
// 3. 發送回饋到 AI 助手 // 3. 發送回饋到 AI 助手
const success = this.webSocketManager.send({ const success = this.webSocketManager.send({
type: 'submit_feedback', type: 'submit_feedback',
@ -1898,6 +1923,30 @@
} }
}; };
/**
* 設置用戶活動監聽器用於重置會話超時
*/
FeedbackApp.prototype.setupUserActivityListeners = function() {
const self = this;
// 定義需要監聽的活動事件
const activityEvents = ['click', 'keypress', 'mousemove', 'touchstart', 'scroll'];
// 防抖處理,避免過於頻繁地重置計時器
const resetTimeout = window.MCPFeedback.Utils.DOM.debounce(function() {
if (self.webSocketManager) {
self.webSocketManager.resetSessionTimeout();
}
}, 5000, false); // 5秒內的連續活動只重置一次
// 為每個事件添加監聽器
activityEvents.forEach(function(eventType) {
document.addEventListener(eventType, resetTimeout, { passive: true });
});
console.log('✅ 用戶活動監聽器已設置');
};
/** /**
* 清理資源 * 清理資源
*/ */

View File

@ -50,7 +50,10 @@
userMessageRecordingEnabled: true, userMessageRecordingEnabled: true,
userMessagePrivacyLevel: 'full', // 'full', 'basic', 'disabled' userMessagePrivacyLevel: 'full', // 'full', 'basic', 'disabled'
// UI 元素尺寸設定 // UI 元素尺寸設定
combinedFeedbackTextHeight: 150 // combinedFeedbackText textarea 的高度px combinedFeedbackTextHeight: 150, // combinedFeedbackText textarea 的高度px
// 會話超時設定
sessionTimeoutEnabled: false, // 預設關閉
sessionTimeoutSeconds: 3600 // 預設 1 小時(秒)
}; };
// 當前設定 // 當前設定
@ -417,6 +420,9 @@
// 應用用戶訊息記錄設定 // 應用用戶訊息記錄設定
this.applyUserMessageSettings(); this.applyUserMessageSettings();
// 應用會話超時設定
this.applySessionTimeoutSettings();
}; };
/** /**
@ -599,6 +605,28 @@
this.updatePrivacyLevelDescription(this.currentSettings.userMessagePrivacyLevel); this.updatePrivacyLevelDescription(this.currentSettings.userMessagePrivacyLevel);
}; };
/**
* 應用會話超時設定
*/
SettingsManager.prototype.applySessionTimeoutSettings = function() {
// 更新會話超時啟用開關
const sessionTimeoutEnabled = Utils.safeQuerySelector('#sessionTimeoutEnabled');
if (sessionTimeoutEnabled) {
sessionTimeoutEnabled.checked = this.currentSettings.sessionTimeoutEnabled;
}
// 更新會話超時時間輸入框
const sessionTimeoutSeconds = Utils.safeQuerySelector('#sessionTimeoutSeconds');
if (sessionTimeoutSeconds) {
sessionTimeoutSeconds.value = this.currentSettings.sessionTimeoutSeconds;
}
console.log('會話超時設定已應用到 UI:', {
enabled: this.currentSettings.sessionTimeoutEnabled,
seconds: this.currentSettings.sessionTimeoutSeconds
});
};
/** /**
* 更新隱私等級描述文字 * 更新隱私等級描述文字
*/ */
@ -896,6 +924,59 @@
}); });
} }
// 會話超時啟用開關
const sessionTimeoutEnabled = Utils.safeQuerySelector('#sessionTimeoutEnabled');
if (sessionTimeoutEnabled) {
sessionTimeoutEnabled.addEventListener('change', function() {
const newValue = sessionTimeoutEnabled.checked;
self.set('sessionTimeoutEnabled', newValue);
console.log('會話超時狀態已更新:', newValue);
// 觸發 WebSocket 通知後端更新超時設定
if (window.MCPFeedback && window.MCPFeedback.app && window.MCPFeedback.app.webSocketManager) {
window.MCPFeedback.app.webSocketManager.send({
type: 'update_timeout_settings',
settings: {
enabled: newValue,
seconds: self.get('sessionTimeoutSeconds')
}
});
}
});
}
// 會話超時時間設定
const sessionTimeoutSeconds = Utils.safeQuerySelector('#sessionTimeoutSeconds');
if (sessionTimeoutSeconds) {
sessionTimeoutSeconds.addEventListener('change', function(e) {
const seconds = parseInt(e.target.value);
// 驗證輸入值範圍
if (isNaN(seconds) || seconds < 300) {
e.target.value = 300;
self.set('sessionTimeoutSeconds', 300);
} else if (seconds > 86400) {
e.target.value = 86400;
self.set('sessionTimeoutSeconds', 86400);
} else {
self.set('sessionTimeoutSeconds', seconds);
}
console.log('會話超時時間已更新:', self.get('sessionTimeoutSeconds'), '秒');
// 觸發 WebSocket 通知後端更新超時設定
if (window.MCPFeedback && window.MCPFeedback.app && window.MCPFeedback.app.webSocketManager) {
window.MCPFeedback.app.webSocketManager.send({
type: 'update_timeout_settings',
settings: {
enabled: self.get('sessionTimeoutEnabled'),
seconds: self.get('sessionTimeoutSeconds')
}
});
}
});
}
// 重置設定 // 重置設定
const resetBtn = Utils.safeQuerySelector('#resetSettingsBtn'); const resetBtn = Utils.safeQuerySelector('#resetSettingsBtn');
if (resetBtn) { if (resetBtn) {

View File

@ -47,6 +47,15 @@
// 網路狀態檢測 // 網路狀態檢測
this.networkOnline = navigator.onLine; this.networkOnline = navigator.onLine;
this.setupNetworkStatusDetection(); this.setupNetworkStatusDetection();
// 會話超時計時器
this.sessionTimeoutTimer = null;
this.sessionTimeoutInterval = null; // 用於更新倒數顯示
this.sessionTimeoutRemaining = 0; // 剩餘秒數
this.sessionTimeoutSettings = {
enabled: false,
seconds: 3600
};
} }
/** /**
@ -285,6 +294,12 @@
timestamp: data.timestamp timestamp: data.timestamp
}); });
break; break;
case 'update_timeout_settings':
// 處理超時設定更新
if (data.settings) {
this.updateSessionTimeoutSettings(data.settings);
}
break;
default: default:
// 其他訊息類型由外部處理 // 其他訊息類型由外部處理
break; break;
@ -461,11 +476,139 @@
return true; return true;
}; };
/**
* 更新會話超時設定
*/
WebSocketManager.prototype.updateSessionTimeoutSettings = function(settings) {
this.sessionTimeoutSettings = settings;
console.log('會話超時設定已更新:', settings);
// 重新啟動計時器
if (settings.enabled) {
this.startSessionTimeout();
} else {
this.stopSessionTimeout();
}
};
/**
* 啟動會話超時計時器
*/
WebSocketManager.prototype.startSessionTimeout = function() {
// 先停止現有計時器
this.stopSessionTimeout();
if (!this.sessionTimeoutSettings.enabled) {
return;
}
const timeoutSeconds = this.sessionTimeoutSettings.seconds;
this.sessionTimeoutRemaining = timeoutSeconds;
console.log('啟動會話超時計時器:', timeoutSeconds, '秒');
// 顯示倒數計時器
const displayElement = document.getElementById('sessionTimeoutDisplay');
if (displayElement) {
displayElement.style.display = '';
}
const self = this;
// 更新倒數顯示
function updateDisplay() {
const minutes = Math.floor(self.sessionTimeoutRemaining / 60);
const seconds = self.sessionTimeoutRemaining % 60;
const displayText = minutes.toString().padStart(2, '0') + ':' +
seconds.toString().padStart(2, '0');
const timerElement = document.getElementById('sessionTimeoutTimer');
if (timerElement) {
timerElement.textContent = displayText;
}
// 當剩餘時間少於60秒時改變顯示樣式
if (self.sessionTimeoutRemaining < 60 && displayElement) {
displayElement.classList.add('countdown-warning');
}
}
// 立即更新一次顯示
updateDisplay();
// 每秒更新倒數
this.sessionTimeoutInterval = setInterval(function() {
self.sessionTimeoutRemaining--;
updateDisplay();
if (self.sessionTimeoutRemaining <= 0) {
clearInterval(self.sessionTimeoutInterval);
self.sessionTimeoutInterval = null;
console.log('會話超時,準備關閉程序');
// 發送超時通知給後端
if (self.isConnected) {
self.send({
type: 'user_timeout',
timestamp: Date.now()
});
}
// 顯示超時訊息
const timeoutMessage = window.i18nManager ?
window.i18nManager.t('sessionTimeout.triggered', '會話已超時,程序即將關閉') :
'會話已超時,程序即將關閉';
Utils.showMessage(timeoutMessage, Utils.CONSTANTS.MESSAGE_WARNING);
// 延遲關閉,讓用戶看到訊息
setTimeout(function() {
window.close();
}, 3000);
}
}, 1000);
};
/**
* 停止會話超時計時器
*/
WebSocketManager.prototype.stopSessionTimeout = function() {
if (this.sessionTimeoutTimer) {
clearTimeout(this.sessionTimeoutTimer);
this.sessionTimeoutTimer = null;
}
if (this.sessionTimeoutInterval) {
clearInterval(this.sessionTimeoutInterval);
this.sessionTimeoutInterval = null;
}
// 隱藏倒數顯示
const displayElement = document.getElementById('sessionTimeoutDisplay');
if (displayElement) {
displayElement.style.display = 'none';
displayElement.classList.remove('countdown-warning');
}
console.log('會話超時計時器已停止');
};
/**
* 重置會話超時計時器用戶有活動時調用
*/
WebSocketManager.prototype.resetSessionTimeout = function() {
if (this.sessionTimeoutSettings.enabled) {
console.log('重置會話超時計時器');
this.startSessionTimeout();
}
};
/** /**
* 關閉連接 * 關閉連接
*/ */
WebSocketManager.prototype.close = function() { WebSocketManager.prototype.close = function() {
this.stopHeartbeat(); this.stopHeartbeat();
this.stopSessionTimeout();
if (this.websocket) { if (this.websocket) {
this.websocket.close(); this.websocket.close();
this.websocket = null; this.websocket = null;

View File

@ -429,9 +429,10 @@
</div> </div>
<!-- 倒數計時器(條件顯示) --> <!-- 倒數計時器(條件顯示) -->
<div id="countdownDisplay" class="countdown-display-compact" style="display: none;"> <div id="countdownDisplay" class="countdown-display-compact auto-submit-display" style="display: none;">
<span class="info-separator">·</span> <span class="info-separator">·</span>
<span>⏱️</span> <span>⏱️</span>
<span class="countdown-label" data-i18n="autoSubmit.countdownLabel">提交倒數:</span>
<span id="countdownTimer" class="countdown-timer">--:--</span> <span id="countdownTimer" class="countdown-timer">--:--</span>
<button id="countdownPauseBtn" class="countdown-control-btn" <button id="countdownPauseBtn" class="countdown-control-btn"
data-i18n-title="autoSubmit.pauseCountdown" data-i18n-title="autoSubmit.pauseCountdown"
@ -442,6 +443,14 @@
</button> </button>
</div> </div>
<!-- 會話超時倒數(條件顯示) -->
<div id="sessionTimeoutDisplay" class="countdown-display-compact session-timeout-display" style="display: none;">
<span class="info-separator">·</span>
<span></span>
<span class="timeout-label" data-i18n="sessionTimeout.label">會話超時:</span>
<span id="sessionTimeoutTimer" class="countdown-timer">--:--</span>
</div>
<!-- 分隔符 --> <!-- 分隔符 -->
<span class="info-separator">·</span> <span class="info-separator">·</span>
@ -870,6 +879,43 @@
</div> </div>
</div> </div>
<!-- 會話超時設定卡片 -->
<div class="settings-card">
<div class="settings-card-header">
<h3 class="settings-card-title" data-i18n="settingsUI.sessionTimeoutTitle">⏱️ 會話超時設定</h3>
</div>
<div class="settings-card-body">
<div class="setting-item">
<div class="setting-info">
<div class="setting-label" data-i18n="settingsUI.sessionTimeoutEnable">啟用會話超時</div>
<div class="setting-description" data-i18n="settingsUI.sessionTimeoutEnableDesc">
啟用後,會話將在指定時間後自動關閉
</div>
</div>
<div class="setting-control">
<label class="toggle-switch">
<input type="checkbox" id="sessionTimeoutEnabled" class="toggle-input">
<span class="toggle-slider"></span>
</label>
</div>
</div>
<div class="setting-item">
<div class="setting-info">
<div class="setting-label" data-i18n="settingsUI.sessionTimeoutDuration">超時時間(秒)</div>
<div class="setting-description" data-i18n="settingsUI.sessionTimeoutDurationDesc">
設定會話超時時間範圍300-86400 秒5分鐘-24小時
</div>
</div>
<div class="setting-control">
<input type="number" id="sessionTimeoutSeconds" min="300" max="86400" value="3600"
class="form-input" style="width: 120px;">
<span class="input-suffix" data-i18n="settingsUI.sessionTimeoutSeconds"></span>
</div>
</div>
</div>
</div>
<!-- 會話歷史管理卡片 --> <!-- 會話歷史管理卡片 -->
<div class="settings-card"> <div class="settings-card">
<div class="settings-card-header"> <div class="settings-card-header">