mirror of
https://github.com/Minidoracat/mcp-feedback-enhanced.git
synced 2025-07-27 02:22:26 +08:00
复制当前用户内容
This commit is contained in:
parent
6c421a24b2
commit
fab9003b98
@ -585,7 +585,10 @@ class WebUIManager:
|
|||||||
|
|
||||||
if has_active_tabs:
|
if has_active_tabs:
|
||||||
debug_log("檢測到活躍標籤頁,不開啟新瀏覽器視窗")
|
debug_log("檢測到活躍標籤頁,不開啟新瀏覽器視窗")
|
||||||
debug_log(f"用戶可以在現有標籤頁中查看更新:{url}")
|
debug_log(f"向現有標籤頁發送刷新通知:{url}")
|
||||||
|
|
||||||
|
# 向現有標籤頁發送刷新通知
|
||||||
|
await self.notify_existing_tab_to_refresh()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# 沒有活躍標籤頁,開啟新瀏覽器視窗
|
# 沒有活躍標籤頁,開啟新瀏覽器視窗
|
||||||
@ -799,40 +802,46 @@ class WebUIManager:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_log(f"檢查 WebSocket 連接狀態時發生錯誤: {e}")
|
debug_log(f"檢查 WebSocket 連接狀態時發生錯誤: {e}")
|
||||||
|
|
||||||
async def _check_active_tabs(self) -> bool:
|
async def notify_existing_tab_to_refresh(self):
|
||||||
"""檢查是否有活躍標籤頁 - 優先檢查全局狀態,回退到 API"""
|
"""通知現有標籤頁刷新顯示新會話內容"""
|
||||||
try:
|
try:
|
||||||
# 首先檢查全局標籤頁狀態
|
if not self.current_session or not self.current_session.websocket:
|
||||||
global_count = self.get_global_active_tabs_count()
|
debug_log("沒有活躍的WebSocket連接,無法發送刷新通知")
|
||||||
if global_count > 0:
|
return
|
||||||
debug_log(f"檢測到 {global_count} 個全局活躍標籤頁")
|
|
||||||
|
# 構建刷新通知消息
|
||||||
|
refresh_message = {
|
||||||
|
"type": "session_updated",
|
||||||
|
"action": "new_session_created",
|
||||||
|
"message": "新的 MCP 會話已創建,頁面將自動刷新",
|
||||||
|
"session_info": {
|
||||||
|
"session_id": self.current_session.session_id,
|
||||||
|
"project_directory": self.current_session.project_directory,
|
||||||
|
"summary": self.current_session.summary,
|
||||||
|
"status": self.current_session.status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 發送刷新通知
|
||||||
|
await self.current_session.websocket.send_json(refresh_message)
|
||||||
|
debug_log(f"已向現有標籤頁發送刷新通知: {self.current_session.session_id}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
debug_log(f"發送刷新通知失敗: {e}")
|
||||||
|
|
||||||
|
async def _check_active_tabs(self) -> bool:
|
||||||
|
"""檢查是否有活躍標籤頁 - 直接檢查WebSocket連接"""
|
||||||
|
try:
|
||||||
|
# 直接檢查是否有活躍的WebSocket連接
|
||||||
|
if self.current_session and self.current_session.websocket:
|
||||||
|
debug_log("檢測到活躍的WebSocket連接")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# 如果全局狀態沒有活躍標籤頁,嘗試通過 API 檢查
|
debug_log("沒有檢測到活躍的WebSocket連接")
|
||||||
# 等待一小段時間讓服務器完全啟動
|
|
||||||
await asyncio.sleep(0.5)
|
|
||||||
|
|
||||||
# 調用活躍標籤頁 API
|
|
||||||
import aiohttp
|
|
||||||
|
|
||||||
timeout = aiohttp.ClientTimeout(total=2)
|
|
||||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
||||||
async with session.get(
|
|
||||||
f"{self.get_server_url()}/api/active-tabs"
|
|
||||||
) as response:
|
|
||||||
if response.status == 200:
|
|
||||||
data = await response.json()
|
|
||||||
tab_count = data.get("count", 0)
|
|
||||||
debug_log(f"API 檢測到 {tab_count} 個活躍標籤頁")
|
|
||||||
return bool(tab_count > 0)
|
|
||||||
debug_log(f"檢查活躍標籤頁失敗,狀態碼:{response.status}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except TimeoutError:
|
|
||||||
debug_log("檢查活躍標籤頁超時")
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug_log(f"檢查活躍標籤頁時發生錯誤:{e}")
|
debug_log(f"檢查活躍連接時發生錯誤:{e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_server_url(self) -> str:
|
def get_server_url(self) -> str:
|
||||||
|
@ -684,6 +684,30 @@
|
|||||||
FeedbackApp.prototype._originalHandleSessionUpdated = function(data) {
|
FeedbackApp.prototype._originalHandleSessionUpdated = function(data) {
|
||||||
console.log('🔄 處理會話更新:', data.session_info);
|
console.log('🔄 處理會話更新:', data.session_info);
|
||||||
|
|
||||||
|
// 檢查是否是新會話創建的通知
|
||||||
|
if (data.action === 'new_session_created') {
|
||||||
|
console.log('🆕 檢測到新會話創建,準備刷新頁面顯示新內容');
|
||||||
|
|
||||||
|
// 播放音效通知
|
||||||
|
if (this.audioManager) {
|
||||||
|
this.audioManager.playNotification();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顯示新會話通知
|
||||||
|
window.MCPFeedback.Utils.showMessage(
|
||||||
|
data.message || '新的 MCP 會話已創建,正在刷新頁面...',
|
||||||
|
window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS
|
||||||
|
);
|
||||||
|
|
||||||
|
// 延遲一小段時間讓用戶看到通知,然後刷新頁面
|
||||||
|
setTimeout(function() {
|
||||||
|
console.log('🔄 刷新頁面以顯示新會話內容');
|
||||||
|
window.location.reload();
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
return; // 提前返回,不執行後續的局部更新邏輯
|
||||||
|
}
|
||||||
|
|
||||||
// 播放音效通知
|
// 播放音效通知
|
||||||
if (this.audioManager) {
|
if (this.audioManager) {
|
||||||
this.audioManager.playNotification();
|
this.audioManager.playNotification();
|
||||||
|
@ -258,6 +258,30 @@
|
|||||||
self.showSessionDetails();
|
self.showSessionDetails();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 复制当前会话内容按钮
|
||||||
|
const copySessionButton = DOMUtils ?
|
||||||
|
DOMUtils.safeQuerySelector('#copyCurrentSessionContent') :
|
||||||
|
document.querySelector('#copyCurrentSessionContent');
|
||||||
|
if (copySessionButton) {
|
||||||
|
copySessionButton.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
self.copyCurrentSessionContent();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制当前用户内容按钮
|
||||||
|
const copyUserButton = DOMUtils ?
|
||||||
|
DOMUtils.safeQuerySelector('#copyCurrentUserContent') :
|
||||||
|
document.querySelector('#copyCurrentUserContent');
|
||||||
|
if (copyUserButton) {
|
||||||
|
copyUserButton.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
self.copyCurrentUserContent();
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -648,6 +672,242 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制当前会话内容
|
||||||
|
*/
|
||||||
|
SessionManager.prototype.copyCurrentSessionContent = function() {
|
||||||
|
console.log('📋 复制当前会话内容...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const currentSession = this.dataManager.getCurrentSession();
|
||||||
|
if (!currentSession) {
|
||||||
|
this.showMessage('没有当前会话数据', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = this.formatCurrentSessionContent(currentSession);
|
||||||
|
this.copyToClipboard(content, '当前会话内容已复制到剪贴板');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('复制当前会话内容失败:', error);
|
||||||
|
this.showMessage('复制失败,请重试', 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制当前用户发送的内容
|
||||||
|
*/
|
||||||
|
SessionManager.prototype.copyCurrentUserContent = function() {
|
||||||
|
console.log('📝 复制当前用户发送的内容...');
|
||||||
|
console.log('📝 this.dataManager 存在吗?', !!this.dataManager);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!this.dataManager) {
|
||||||
|
console.log('📝 dataManager 不存在,尝试其他方式获取数据');
|
||||||
|
this.showMessage('数据管理器未初始化', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSession = this.dataManager.getCurrentSession();
|
||||||
|
console.log('📝 当前会话数据:', currentSession);
|
||||||
|
|
||||||
|
if (!currentSession) {
|
||||||
|
console.log('📝 没有当前会话数据');
|
||||||
|
this.showMessage('当前会话没有数据', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('📝 用户消息数组:', currentSession.user_messages);
|
||||||
|
console.log('📝 用户消息数组长度:', currentSession.user_messages ? currentSession.user_messages.length : 'undefined');
|
||||||
|
|
||||||
|
if (!currentSession.user_messages || currentSession.user_messages.length === 0) {
|
||||||
|
console.log('📝 没有用户消息记录');
|
||||||
|
this.showMessage('当前会话没有用户消息记录', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在这里也添加调试信息
|
||||||
|
console.log('📝 准备格式化用户消息,数量:', currentSession.user_messages.length);
|
||||||
|
console.log('📝 第一条消息内容:', currentSession.user_messages[0]);
|
||||||
|
|
||||||
|
const content = this.formatCurrentUserContent(currentSession.user_messages);
|
||||||
|
console.log('📝 格式化后的内容长度:', content.length);
|
||||||
|
console.log('📝 格式化后的内容预览:', content.substring(0, 200));
|
||||||
|
|
||||||
|
this.copyToClipboard(content, '当前用户内容已复制到剪贴板');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('📝 复制当前用户内容失败:', error);
|
||||||
|
console.error('📝 错误堆栈:', error.stack);
|
||||||
|
this.showMessage('复制失败,请重试', 'error');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化当前会话内容
|
||||||
|
*/
|
||||||
|
SessionManager.prototype.formatCurrentSessionContent = function(sessionData) {
|
||||||
|
const lines = [];
|
||||||
|
lines.push('# MCP Feedback Enhanced - 当前会话内容');
|
||||||
|
lines.push('');
|
||||||
|
lines.push(`**会话ID**: ${sessionData.session_id || 'N/A'}`);
|
||||||
|
lines.push(`**项目目录**: ${sessionData.project_directory || 'N/A'}`);
|
||||||
|
lines.push(`**摘要**: ${sessionData.summary || 'N/A'}`);
|
||||||
|
lines.push(`**状态**: ${sessionData.status || 'N/A'}`);
|
||||||
|
lines.push(`**创建时间**: ${sessionData.created_at || 'N/A'}`);
|
||||||
|
lines.push(`**更新时间**: ${sessionData.updated_at || 'N/A'}`);
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
if (sessionData.user_messages && sessionData.user_messages.length > 0) {
|
||||||
|
lines.push('## 用户消息');
|
||||||
|
sessionData.user_messages.forEach((msg, index) => {
|
||||||
|
lines.push(`### 消息 ${index + 1}`);
|
||||||
|
lines.push(msg);
|
||||||
|
lines.push('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sessionData.ai_responses && sessionData.ai_responses.length > 0) {
|
||||||
|
lines.push('## AI 响应');
|
||||||
|
sessionData.ai_responses.forEach((response, index) => {
|
||||||
|
lines.push(`### 响应 ${index + 1}`);
|
||||||
|
lines.push(response);
|
||||||
|
lines.push('');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化当前用户内容
|
||||||
|
*/
|
||||||
|
SessionManager.prototype.formatCurrentUserContent = function(userMessages) {
|
||||||
|
const lines = [];
|
||||||
|
lines.push('# MCP Feedback Enhanced - 用户发送内容');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
userMessages.forEach((msg, index) => {
|
||||||
|
lines.push(`## 消息 ${index + 1}`);
|
||||||
|
|
||||||
|
// 调试:输出完整的消息对象
|
||||||
|
console.log(`📝 消息 ${index + 1} 完整对象:`, msg);
|
||||||
|
console.log(`📝 消息 ${index + 1} 所有属性:`, Object.keys(msg));
|
||||||
|
|
||||||
|
// 添加时间戳信息 - 简化版本,直接使用当前时间
|
||||||
|
let timeStr = '未知时间';
|
||||||
|
|
||||||
|
// 检查是否有时间戳字段
|
||||||
|
if (msg.timestamp) {
|
||||||
|
// 如果时间戳看起来不正常(太小),直接使用当前时间
|
||||||
|
if (msg.timestamp < 1000000000) { // 小于2001年的时间戳,可能是相对时间
|
||||||
|
timeStr = new Date().toLocaleString('zh-CN');
|
||||||
|
console.log('📝 时间戳异常,使用当前时间:', msg.timestamp);
|
||||||
|
} else {
|
||||||
|
// 正常处理时间戳
|
||||||
|
let timestamp = msg.timestamp;
|
||||||
|
if (timestamp > 1e12) {
|
||||||
|
// 毫秒时间戳
|
||||||
|
timeStr = new Date(timestamp).toLocaleString('zh-CN');
|
||||||
|
} else {
|
||||||
|
// 秒时间戳
|
||||||
|
timeStr = new Date(timestamp * 1000).toLocaleString('zh-CN');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 没有时间戳,使用当前时间
|
||||||
|
timeStr = new Date().toLocaleString('zh-CN');
|
||||||
|
console.log('📝 没有时间戳字段,使用当前时间');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(`**时间**: ${timeStr}`);
|
||||||
|
|
||||||
|
// 添加提交方式
|
||||||
|
if (msg.submission_method) {
|
||||||
|
const methodText = msg.submission_method === 'auto' ? '自动提交' : '手动提交';
|
||||||
|
lines.push(`**提交方式**: ${methodText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理消息内容
|
||||||
|
if (msg.content !== undefined) {
|
||||||
|
// 完整记录模式 - 显示实际内容
|
||||||
|
lines.push(`**内容**: ${msg.content}`);
|
||||||
|
|
||||||
|
// 如果有图片,显示图片数量
|
||||||
|
if (msg.images && msg.images.length > 0) {
|
||||||
|
lines.push(`**图片数量**: ${msg.images.length}`);
|
||||||
|
}
|
||||||
|
} else if (msg.content_length !== undefined) {
|
||||||
|
// 基本统计模式 - 显示统计信息
|
||||||
|
lines.push(`**内容长度**: ${msg.content_length} 字符`);
|
||||||
|
lines.push(`**图片数量**: ${msg.image_count || 0}`);
|
||||||
|
lines.push(`**有内容**: ${msg.has_content ? '是' : '否'}`);
|
||||||
|
} else if (msg.privacy_note) {
|
||||||
|
// 隐私保护模式
|
||||||
|
lines.push(`**内容**: [内容记录已停用 - 隐私设置]`);
|
||||||
|
} else {
|
||||||
|
// 兜底情况 - 尝试显示对象的JSON格式
|
||||||
|
lines.push(`**原始数据**: ${JSON.stringify(msg, null, 2)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('');
|
||||||
|
});
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制到剪贴板
|
||||||
|
*/
|
||||||
|
SessionManager.prototype.copyToClipboard = function(text, successMessage) {
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
this.showMessage(successMessage, 'success');
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('复制到剪贴板失败:', err);
|
||||||
|
this.fallbackCopyToClipboard(text, successMessage);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.fallbackCopyToClipboard(text, successMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 降级复制方法
|
||||||
|
*/
|
||||||
|
SessionManager.prototype.fallbackCopyToClipboard = function(text, successMessage) {
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = text;
|
||||||
|
textArea.style.position = 'fixed';
|
||||||
|
textArea.style.left = '-999999px';
|
||||||
|
textArea.style.top = '-999999px';
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
textArea.select();
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
this.showMessage(successMessage, 'success');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('降级复制失败:', err);
|
||||||
|
this.showMessage('复制失败,请手动复制', 'error');
|
||||||
|
} finally {
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示消息
|
||||||
|
*/
|
||||||
|
SessionManager.prototype.showMessage = function(message, type) {
|
||||||
|
if (window.MCPFeedback && window.MCPFeedback.Utils && window.MCPFeedback.Utils.showMessage) {
|
||||||
|
const messageType = type === 'success' ? window.MCPFeedback.Utils.CONSTANTS.MESSAGE_SUCCESS :
|
||||||
|
type === 'warning' ? window.MCPFeedback.Utils.CONSTANTS.MESSAGE_WARNING :
|
||||||
|
window.MCPFeedback.Utils.CONSTANTS.MESSAGE_ERROR;
|
||||||
|
window.MCPFeedback.Utils.showMessage(message, messageType);
|
||||||
|
} else {
|
||||||
|
console.log(`[${type.toUpperCase()}] ${message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 全域匯出會話歷史方法
|
// 全域匯出會話歷史方法
|
||||||
window.MCPFeedback.SessionManager.exportSessionHistory = function() {
|
window.MCPFeedback.SessionManager.exportSessionHistory = function() {
|
||||||
if (window.MCPFeedback && window.MCPFeedback.app && window.MCPFeedback.app.sessionManager) {
|
if (window.MCPFeedback && window.MCPFeedback.app && window.MCPFeedback.app.sessionManager) {
|
||||||
|
235
src/mcp_feedback_enhanced/web/static/js/modules/tab-manager.js
Normal file
235
src/mcp_feedback_enhanced/web/static/js/modules/tab-manager.js
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
/**
|
||||||
|
* MCP Feedback Enhanced - 標籤頁管理模組
|
||||||
|
* ====================================
|
||||||
|
*
|
||||||
|
* 處理多標籤頁狀態同步和智能瀏覽器管理
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// 確保命名空間和依賴存在
|
||||||
|
window.MCPFeedback = window.MCPFeedback || {};
|
||||||
|
const Utils = window.MCPFeedback.Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 標籤頁管理器建構函數
|
||||||
|
*/
|
||||||
|
function TabManager() {
|
||||||
|
this.tabId = Utils.generateId('tab');
|
||||||
|
this.heartbeatInterval = null;
|
||||||
|
this.heartbeatFrequency = Utils.CONSTANTS.DEFAULT_TAB_HEARTBEAT_FREQUENCY;
|
||||||
|
this.storageKey = 'mcp_feedback_tabs';
|
||||||
|
this.lastActivityKey = 'mcp_feedback_last_activity';
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化標籤頁管理器
|
||||||
|
*/
|
||||||
|
TabManager.prototype.init = function() {
|
||||||
|
// 註冊當前標籤頁
|
||||||
|
this.registerTab();
|
||||||
|
|
||||||
|
// 向服務器註冊標籤頁
|
||||||
|
this.registerTabToServer();
|
||||||
|
|
||||||
|
// 開始心跳
|
||||||
|
this.startHeartbeat();
|
||||||
|
|
||||||
|
// 監聽頁面關閉事件
|
||||||
|
const self = this;
|
||||||
|
window.addEventListener('beforeunload', function() {
|
||||||
|
self.unregisterTab();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 監聽 localStorage 變化(其他標籤頁的狀態變化)
|
||||||
|
window.addEventListener('storage', function(e) {
|
||||||
|
if (e.key === self.storageKey) {
|
||||||
|
self.handleTabsChange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('📋 TabManager 初始化完成,標籤頁 ID: ' + this.tabId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 註冊當前標籤頁
|
||||||
|
*/
|
||||||
|
TabManager.prototype.registerTab = function() {
|
||||||
|
const tabs = this.getActiveTabs();
|
||||||
|
tabs[this.tabId] = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
url: window.location.href,
|
||||||
|
active: true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Utils.isLocalStorageSupported()) {
|
||||||
|
localStorage.setItem(this.storageKey, JSON.stringify(tabs));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateLastActivity();
|
||||||
|
console.log('✅ 標籤頁已註冊: ' + this.tabId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 註銷當前標籤頁
|
||||||
|
*/
|
||||||
|
TabManager.prototype.unregisterTab = function() {
|
||||||
|
const tabs = this.getActiveTabs();
|
||||||
|
delete tabs[this.tabId];
|
||||||
|
|
||||||
|
if (Utils.isLocalStorageSupported()) {
|
||||||
|
localStorage.setItem(this.storageKey, JSON.stringify(tabs));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('❌ 標籤頁已註銷: ' + this.tabId);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 開始心跳
|
||||||
|
*/
|
||||||
|
TabManager.prototype.startHeartbeat = function() {
|
||||||
|
const self = this;
|
||||||
|
this.heartbeatInterval = setInterval(function() {
|
||||||
|
self.sendHeartbeat();
|
||||||
|
}, this.heartbeatFrequency);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 發送心跳
|
||||||
|
*/
|
||||||
|
TabManager.prototype.sendHeartbeat = function() {
|
||||||
|
const tabs = this.getActiveTabs();
|
||||||
|
if (tabs[this.tabId]) {
|
||||||
|
tabs[this.tabId].timestamp = Date.now();
|
||||||
|
|
||||||
|
if (Utils.isLocalStorageSupported()) {
|
||||||
|
localStorage.setItem(this.storageKey, JSON.stringify(tabs));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateLastActivity();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新最後活動時間
|
||||||
|
*/
|
||||||
|
TabManager.prototype.updateLastActivity = function() {
|
||||||
|
if (Utils.isLocalStorageSupported()) {
|
||||||
|
localStorage.setItem(this.lastActivityKey, Date.now().toString());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 獲取活躍標籤頁
|
||||||
|
*/
|
||||||
|
TabManager.prototype.getActiveTabs = function() {
|
||||||
|
if (!Utils.isLocalStorageSupported()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(this.storageKey);
|
||||||
|
const tabs = stored ? Utils.safeJsonParse(stored, {}) : {};
|
||||||
|
|
||||||
|
// 清理過期的標籤頁
|
||||||
|
const now = Date.now();
|
||||||
|
const expiredThreshold = Utils.CONSTANTS.TAB_EXPIRED_THRESHOLD;
|
||||||
|
|
||||||
|
for (const tabId in tabs) {
|
||||||
|
if (tabs.hasOwnProperty(tabId)) {
|
||||||
|
if (now - tabs[tabId].timestamp > expiredThreshold) {
|
||||||
|
delete tabs[tabId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabs;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('獲取活躍標籤頁失敗:', error);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 檢查是否有活躍標籤頁
|
||||||
|
*/
|
||||||
|
TabManager.prototype.hasActiveTabs = function() {
|
||||||
|
const tabs = this.getActiveTabs();
|
||||||
|
return Object.keys(tabs).length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 檢查是否為唯一活躍標籤頁
|
||||||
|
*/
|
||||||
|
TabManager.prototype.isOnlyActiveTab = function() {
|
||||||
|
const tabs = this.getActiveTabs();
|
||||||
|
return Object.keys(tabs).length === 1 && tabs[this.tabId];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 處理其他標籤頁狀態變化
|
||||||
|
*/
|
||||||
|
TabManager.prototype.handleTabsChange = function() {
|
||||||
|
console.log('🔄 檢測到其他標籤頁狀態變化');
|
||||||
|
// 可以在這裡添加更多邏輯
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向服務器註冊標籤頁
|
||||||
|
*/
|
||||||
|
TabManager.prototype.registerTabToServer = function() {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
fetch('/api/register-tab', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
tabId: this.tabId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(function(response) {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json();
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ 標籤頁服務器註冊失敗: ' + response.status);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function(data) {
|
||||||
|
if (data) {
|
||||||
|
console.log('✅ 標籤頁已向服務器註冊: ' + self.tabId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
console.warn('⚠️ 標籤頁服務器註冊錯誤: ' + error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理資源
|
||||||
|
*/
|
||||||
|
TabManager.prototype.cleanup = function() {
|
||||||
|
if (this.heartbeatInterval) {
|
||||||
|
clearInterval(this.heartbeatInterval);
|
||||||
|
this.heartbeatInterval = null;
|
||||||
|
}
|
||||||
|
this.unregisterTab();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 獲取當前標籤頁 ID
|
||||||
|
*/
|
||||||
|
TabManager.prototype.getTabId = function() {
|
||||||
|
return this.tabId;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 將 TabManager 加入命名空間
|
||||||
|
window.MCPFeedback.TabManager = TabManager;
|
||||||
|
|
||||||
|
console.log('✅ TabManager 模組載入完成');
|
||||||
|
|
||||||
|
})();
|
@ -651,6 +651,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="session-actions">
|
<div class="session-actions">
|
||||||
<button class="btn-small" id="viewSessionDetails" data-i18n="sessionManagement.viewDetails">詳細資訊</button>
|
<button class="btn-small" id="viewSessionDetails" data-i18n="sessionManagement.viewDetails">詳細資訊</button>
|
||||||
|
<button class="btn-small btn-primary" id="copyCurrentSessionContent" title="复制当前会话内容">📋 复制会话内容</button>
|
||||||
|
<button class="btn-small btn-secondary" id="copyCurrentUserContent" title="复制当前用户发送的内容">📝 复制用户内容</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user