UNPKG

dynamic-interaction

Version:

Dynamic interaction 动态交互mcp,用于cursor、windsurf、trae 等 AI 智能编辑器 Agent 运行时交互使用

300 lines 11.5 kB
/** * WebSocket 通信服务 * 处理与服务器的 WebSocket 连接和消息传递 */ import { WEBSOCKET_CONFIG } from '../config/constants.js'; import { eventBus, APP_EVENTS } from '../core/events.js'; import { notificationService } from './notification.js'; class WebSocketService { ws; reconnectAttempts = 0; reconnectTimeoutId = null; initialize() { this.connectWebSocket(); } sendCommand(command) { if (this.isConnected()) { this.ws.send(JSON.stringify({ type: 'command', data: command })); return true; } console.warn('WebSocket 未连接,无法发送命令。'); return false; } sendFeedback(text, images) { if (this.isConnected()) { eventBus.emit(APP_EVENTS.STATUS_CHANGED, { type: 'message', status: 'sending' }); const imageData = images.map(img => img.dataUrl || img.name); this.ws.send(JSON.stringify({ type: 'submit_feedback', data: { text, imageData } })); setTimeout(() => { eventBus.emit(APP_EVENTS.STATUS_CHANGED, { type: 'message', status: 'waiting' }); }, 200); return true; } alert('WebSocket 连接已关闭,请刷新页面重试'); return false; } connectWebSocket() { if (this.reconnectTimeoutId) { clearTimeout(this.reconnectTimeoutId); this.reconnectTimeoutId = null; } this.updateConnectionUI(); this.ws = new WebSocket(`ws://${window.location.host}`); window.ws = this.ws; // 保持向后兼容 this.setupEventHandlers(); } setupEventHandlers() { if (!this.ws) return; this.ws.onopen = () => this.handleOpen(); this.ws.onmessage = (event) => this.handleMessage(event); this.ws.onclose = () => this.handleClose(); this.ws.onerror = (err) => this.handleError(err); } handleOpen() { const summaryDiv = document.getElementById('summary'); summaryDiv.textContent = 'WebSocket 连接已建立,等待 AI 响应...'; this.reconnectAttempts = 0; eventBus.emit(APP_EVENTS.WS_CONNECTED); // 发送客户端就绪消息 this.ws.send(JSON.stringify({ type: 'client_ready' })); this.checkNotificationPermission(); } handleMessage(event) { try { const data = JSON.parse(event.data); eventBus.emit(APP_EVENTS.WS_MESSAGE_RECEIVED, data); this.processMessage(data); } catch (e) { console.error('解析 WebSocket 消息时出错:', e); const summaryDiv = document.getElementById('summary'); summaryDiv.textContent = '收到无效消息'; } } handleClose() { eventBus.emit(APP_EVENTS.WS_DISCONNECTED); this.scheduleReconnect(); } handleError(err) { console.error('WebSocket 错误:', err); } processMessage(data) { const summaryDiv = document.getElementById('summary'); switch (data.type) { case 'summary': this.handleSummaryMessage(data, summaryDiv); break; case 'server_log': this.handleServerLogMessage(data); break; case 'pong': this.handlePongMessage(data); break; case 'system_info': this.handleSystemInfoMessage(data); break; case 'feedback_status': this.handleFeedbackStatusMessage(data); break; case 'timeout': this.handleTimeoutMessage(); break; case 'stop_timer': this.handleStopTimerMessage(); break; case 'notification': this.handleNotificationMessage(data); break; case 'session_request': this.handleSessionRequestMessage(data); break; } } handleSummaryMessage(data, summaryDiv) { if (data.data !== undefined && data.data !== null) { summaryDiv.innerHTML = marked.parse(data.data); eventBus.emit(APP_EVENTS.STATUS_CHANGED, { type: 'message', status: 'received' }); eventBus.emit(APP_EVENTS.FEEDBACK_SUCCESS); } else { summaryDiv.textContent = 'AI Agent 正在准备摘要...'; console.warn('收到的摘要消息格式不完整,缺少 "data" 字段。', data); } } handleServerLogMessage(data) { const { level, text } = data.data; console[level]('%c[Server] ' + text, 'color: grey'); } handlePongMessage(data) { if (window.statusBar) { window.statusBar.handlePong(data.data); } } handleSystemInfoMessage(data) { if (window.statusBar && data.data) { const sysInfo = data.data; window.statusBar.updateSystemInfo(sysInfo); if (sysInfo.sessionStartTime && sysInfo.leaseTimeoutSeconds && sysInfo.leaseTimeoutSeconds > 0) { window.statusBar.startSessionTimer(sysInfo.sessionStartTime, sysInfo.leaseTimeoutSeconds); } } } handleFeedbackStatusMessage(data) { if (window.statusBar && data.data?.status) { window.statusBar.updateMessageStatus(data.data.status); eventBus.emit(APP_EVENTS.FEEDBACK_SUCCESS); } } handleTimeoutMessage() { if (window.statusBar) { window.statusBar.updateConnectionStatus('connected'); window.statusBar.stopSessionTimer(); window.statusBar.updateMessageStatus('timeout'); } // 禁用输入控件 const feedbackInput = document.getElementById('feedback-input'); const sendButton = document.getElementById('send-button'); if (feedbackInput) feedbackInput.disabled = true; if (sendButton) sendButton.disabled = true; } handleStopTimerMessage() { if (window.statusBar) { window.statusBar.stopSessionTimer(); } } handleNotificationMessage(data) { const notificationData = data.data; if (!notificationData?.summary) return; this.showNotificationPanel(notificationData.summary); this.hideInteractionPanel(); if (notificationService.isPageHidden()) { notificationService.showAINotification(notificationData.summary); } } handleSessionRequestMessage(data) { const sessionData = data.data; if (!sessionData?.summary) return; this.hideNotificationPanel(); this.showInteractionPanel(); const summaryDiv = document.getElementById('summary'); summaryDiv.innerHTML = marked.parse(sessionData.summary); if (window.statusBar && sessionData.startTime && sessionData.timeoutSeconds) { window.statusBar.startSessionTimer(sessionData.startTime, sessionData.timeoutSeconds); } eventBus.emit(APP_EVENTS.FEEDBACK_SUCCESS); if (notificationService.isPageHidden()) { notificationService.showSessionRequestNotification(sessionData.summary); } } showNotificationPanel(summary) { const notificationPanel = document.getElementById('notification-panel'); if (notificationPanel) { notificationPanel.style.display = 'block'; notificationPanel.innerHTML = this.createNotificationHTML(summary); if (window.lucide) { window.lucide.createIcons(); } } } hideInteractionPanel() { const feedbackPanel = document.getElementById('feedback-panel'); if (feedbackPanel) { feedbackPanel.style.display = 'none'; } } hideNotificationPanel() { const notificationPanel = document.getElementById('notification-panel'); if (notificationPanel) { notificationPanel.style.display = 'none'; } } showInteractionPanel() { const feedbackPanel = document.getElementById('feedback-panel'); if (feedbackPanel) { feedbackPanel.style.display = 'block'; } } createNotificationHTML(summary) { return ` <div class="notification-display"> <div class="notification-header"> <div class="notification-title"> <i data-lucide="bell" class="icon notification-icon"></i> <span>AI 通知</span> <span class="notification-badge">通知模式</span> </div> </div> <hr class="notification-divider"> <div class="notification-content"> <div class="notification-summary">${marked.parse(summary)}</div> <div class="notification-meta"> <div class="notification-timestamp"> <i data-lucide="clock" class="icon"></i> <span>刚刚</span> </div> <div class="notification-actions"> <button class="acknowledge-btn" onclick="acknowledgeNotification()"> <i data-lucide="check" class="icon"></i> 已知晓 </button> </div> </div> </div> </div> `; } scheduleReconnect() { if (this.reconnectAttempts >= WEBSOCKET_CONFIG.MAX_RECONNECT_ATTEMPTS) { console.error('已达到最大重连次数,停止重连。'); const summaryDiv = document.getElementById('summary'); summaryDiv.textContent = '无法连接到服务器,请检查网络并刷新页面。'; summaryDiv.style.color = 'red'; return; } const delay = Math.min(WEBSOCKET_CONFIG.INITIAL_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts), WEBSOCKET_CONFIG.MAX_RECONNECT_DELAY); const summaryDiv = document.getElementById('summary'); summaryDiv.textContent = '连接已断开,正在尝试重新连接...'; summaryDiv.style.color = '#ff9500'; this.reconnectAttempts++; this.reconnectTimeoutId = window.setTimeout(() => this.connectWebSocket(), delay); } updateConnectionUI() { if (this.reconnectAttempts > 0 && window.statusBar) { window.statusBar.updateConnectionStatus('reconnecting'); } const summaryDiv = document.getElementById('summary'); summaryDiv.textContent = '正在连接 WebSocket...'; summaryDiv.style.color = 'inherit'; } async checkNotificationPermission() { if (notificationService.checkSupport()) { const permission = notificationService.getPermissionStatus(); if (permission === 'default') { await notificationService.requestPermission(); } } } isConnected() { return this.ws?.readyState === WebSocket.OPEN; } } export const webSocketService = new WebSocketService(); // 全局函数保持向后兼容 window.acknowledgeNotification = function () { const notificationPanel = document.getElementById('notification-panel'); if (notificationPanel) { notificationPanel.style.display = 'none'; } }; //# sourceMappingURL=websocket.js.map