复制当前用户内容

This commit is contained in:
李振民 2025-06-25 15:38:17 +08:00
parent 6c421a24b2
commit fab9003b98
5 changed files with 560 additions and 30 deletions

View File

@ -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:

View File

@ -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();

View File

@ -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) {

View 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 模組載入完成');
})();

View File

@ -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>