// TSP智能助手综合管理平台前端脚本 class TSPDashboard { constructor() { this.currentTab = 'dashboard'; this.charts = {}; this.refreshIntervals = {}; this.websocket = null; this.sessionId = null; this.isAgentMode = true; // 优化:添加前端缓存 this.cache = new Map(); this.cacheTimeout = 30000; // 30秒缓存 this.init(); this.restorePageState(); } async generateAISuggestion(workorderId) { const button = document.querySelector(`button[onclick="dashboard.generateAISuggestion(${workorderId})"]`); const textarea = document.getElementById(`aiSuggestion_${workorderId}`); try { // 添加加载状态 if (button) { button.classList.add('btn-loading'); button.disabled = true; } if (textarea) { textarea.classList.add('ai-loading'); textarea.value = '正在生成AI建议,请稍候...'; } const resp = await fetch(`/api/workorders/${workorderId}/ai-suggestion`, { method: 'POST' }); const data = await resp.json(); if (data.success) { if (textarea) { textarea.value = data.ai_suggestion || ''; textarea.classList.remove('ai-loading'); textarea.classList.add('success-animation'); // 移除成功动画类 setTimeout(() => { textarea.classList.remove('success-animation'); }, 600); } this.showNotification('AI建议已生成', 'success'); } else { throw new Error(data.error || '生成失败'); } } catch (e) { console.error('生成AI建议失败:', e); if (textarea) { textarea.value = 'AI建议生成失败,请重试'; textarea.classList.remove('ai-loading'); } this.showNotification('生成AI建议失败: ' + e.message, 'error'); } finally { // 移除加载状态 if (button) { button.classList.remove('btn-loading'); button.disabled = false; } } } async saveHumanResolution(workorderId) { try { const text = document.getElementById(`humanResolution_${workorderId}`).value.trim(); if (!text) { this.showNotification('请输入人工描述', 'warning'); return; } const resp = await fetch(`/api/workorders/${workorderId}/human-resolution`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ human_resolution: text }) }); const data = await resp.json(); if (data.success) { const simEl = document.getElementById(`aiSim_${workorderId}`); const apprEl = document.getElementById(`aiApproved_${workorderId}`); const approveBtn = document.getElementById(`approveBtn_${workorderId}`); const percent = Math.round((data.similarity || 0) * 100); // 更新相似度显示,使用语义相似度 if (simEl) { simEl.innerHTML = `语义相似度: ${percent}%`; // 使用新的CSS类 if (percent >= 90) { simEl.className = 'similarity-badge high'; } else if (percent >= 80) { simEl.className = 'similarity-badge medium'; } else { simEl.className = 'similarity-badge low'; } simEl.title = this.getSimilarityExplanation(percent); } // 更新审批状态 if (apprEl) { if (data.use_human_resolution) { apprEl.textContent = '将使用人工描述入库'; apprEl.className = 'status-badge human-resolution'; } else if (data.approved) { apprEl.textContent = '已自动审批'; apprEl.className = 'status-badge approved'; } else { apprEl.textContent = '未审批'; apprEl.className = 'status-badge pending'; } } // 更新审批按钮状态 if (approveBtn) { const canApprove = data.approved || data.use_human_resolution; approveBtn.disabled = !canApprove; if (data.use_human_resolution) { approveBtn.textContent = '使用人工描述入库'; approveBtn.className = 'approve-btn'; approveBtn.title = 'AI准确率低于90%,将使用人工描述入库'; } else if (data.approved) { approveBtn.textContent = '已自动审批'; approveBtn.className = 'approve-btn approved'; approveBtn.title = 'AI建议与人工描述高度一致'; } else { approveBtn.textContent = '审批入库'; approveBtn.className = 'approve-btn'; approveBtn.title = '手动审批入库'; } } // 显示更详细的反馈信息 const message = this.getSimilarityMessage(percent, data.approved, data.use_human_resolution); this.showNotification(message, data.approved ? 'success' : data.use_human_resolution ? 'warning' : 'info'); } else { throw new Error(data.error || '保存失败'); } } catch (e) { console.error('保存人工描述失败:', e); this.showNotification('保存人工描述失败: ' + e.message, 'error'); } } async approveToKnowledge(workorderId) { try { const resp = await fetch(`/api/workorders/${workorderId}/approve-to-knowledge`, { method: 'POST' }); const data = await resp.json(); if (data.success) { const contentType = data.used_content === 'human_resolution' ? '人工描述' : 'AI建议'; const confidence = Math.round((data.confidence_score || 0) * 100); this.showNotification(`已入库为知识条目!使用${contentType},置信度: ${confidence}%`, 'success'); } else { throw new Error(data.error || '入库失败'); } } catch (e) { console.error('入库失败:', e); this.showNotification('入库失败: ' + e.message, 'error'); } } init() { this.bindEvents(); // 优化:并行加载初始数据,提高响应速度 this.loadInitialDataAsync(); this.startAutoRefresh(); this.initCharts(); } async loadInitialDataAsync() { // 并行加载多个数据源 try { await Promise.all([ this.loadDashboard(), this.loadWorkOrders(), this.loadConversationHistory(), this.loadKnowledgeBase() ]); } catch (error) { console.error('并行加载数据失败:', error); // 回退到串行加载 await this.loadInitialData(); } } // 优化:添加缓存方法 getCachedData(key) { const cached = this.cache.get(key); if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { return cached.data; } return null; } setCachedData(key, data) { this.cache.set(key, { data: data, timestamp: Date.now() }); } clearCache() { this.cache.clear(); } bindEvents() { // 标签页切换 document.querySelectorAll('[data-tab]').forEach(tab => { tab.addEventListener('click', (e) => { e.preventDefault(); this.switchTab(tab.dataset.tab); }); }); // 对话控制 document.getElementById('start-chat').addEventListener('click', () => this.startChat()); document.getElementById('end-chat').addEventListener('click', () => this.endChat()); document.getElementById('create-work-order').addEventListener('click', () => this.showCreateWorkOrderModal()); document.getElementById('send-button').addEventListener('click', () => this.sendMessage()); document.getElementById('message-input').addEventListener('keypress', (e) => { if (e.key === 'Enter') this.sendMessage(); }); // 快速操作按钮 document.querySelectorAll('.quick-action-btn').forEach(btn => { btn.addEventListener('click', () => { const message = btn.dataset.message; document.getElementById('message-input').value = message; this.sendMessage(); }); }); // Agent控制 document.getElementById('agent-mode-toggle').addEventListener('change', (e) => { this.toggleAgentMode(e.target.checked); }); // Agent对话功能 document.getElementById('send-agent-message').addEventListener('click', () => this.sendAgentMessage()); document.getElementById('clear-agent-chat').addEventListener('click', () => this.clearAgentChat()); document.getElementById('agent-message-input').addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendAgentMessage(); } }); // Agent控制按钮 document.getElementById('trigger-sample-action').addEventListener('click', () => this.triggerSampleAction()); document.getElementById('clear-agent-history').addEventListener('click', () => this.clearAgentHistory()); document.getElementById('start-agent-monitoring').addEventListener('click', () => this.startAgentMonitoring()); document.getElementById('stop-agent-monitoring').addEventListener('click', () => this.stopAgentMonitoring()); document.getElementById('proactive-monitoring').addEventListener('click', () => this.proactiveMonitoring()); document.getElementById('intelligent-analysis').addEventListener('click', () => this.intelligentAnalysis()); // 预警管理 document.getElementById('refresh-alerts').addEventListener('click', () => this.loadAlerts()); document.getElementById('alert-filter').addEventListener('change', () => this.updateAlertsDisplay()); // 知识库管理 document.getElementById('search-knowledge').addEventListener('click', () => this.searchKnowledge()); document.getElementById('knowledge-search').addEventListener('keypress', (e) => { if (e.key === 'Enter') this.searchKnowledge(); }); // 工单管理 document.getElementById('refresh-workorders').addEventListener('click', () => this.loadWorkOrders()); document.getElementById('workorder-status-filter').addEventListener('change', () => this.loadWorkOrders()); document.getElementById('workorder-priority-filter').addEventListener('change', () => this.loadWorkOrders()); // 模态框 document.getElementById('create-work-order-btn').addEventListener('click', () => this.createWorkOrder()); document.getElementById('add-knowledge-btn').addEventListener('click', () => this.addKnowledge()); document.getElementById('upload-file-btn').addEventListener('click', () => this.uploadFile()); // 置信度滑块 document.getElementById('knowledge-confidence').addEventListener('input', (e) => { document.getElementById('confidence-value').textContent = e.target.value; }); document.getElementById('file-confidence').addEventListener('input', (e) => { document.getElementById('file-confidence-value').textContent = e.target.value; }); // 处理方式选择 document.getElementById('process-method').addEventListener('change', (e) => { const manualDiv = document.getElementById('manual-question-div'); if (e.target.value === 'manual') { manualDiv.style.display = 'block'; } else { manualDiv.style.display = 'none'; } }); // 系统设置 document.getElementById('system-settings-form').addEventListener('submit', (e) => { e.preventDefault(); this.saveSystemSettings(); }); // API测试按钮事件 const testApiBtn = document.getElementById('test-api-connection'); if (testApiBtn) { testApiBtn.addEventListener('click', () => this.testApiConnection()); } const testModelBtn = document.getElementById('test-model-response'); if (testModelBtn) { testModelBtn.addEventListener('click', () => this.testModelResponse()); } // 重启服务按钮事件 const restartBtn = document.getElementById('restart-service'); if (restartBtn) { restartBtn.addEventListener('click', () => { if (confirm('确定要重启服务吗?这将中断当前连接。')) { this.showNotification('重启服务功能待实现', 'info'); } }); } } switchTab(tabName) { // 更新导航状态 document.querySelectorAll('.nav-link').forEach(link => { link.classList.remove('active'); }); document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); // 显示对应内容 document.querySelectorAll('.tab-content').forEach(content => { content.style.display = 'none'; }); document.getElementById(`${tabName}-tab`).style.display = 'block'; this.currentTab = tabName; // 保存当前页面状态 this.savePageState(); // 加载对应数据 switch(tabName) { case 'dashboard': this.loadDashboardData(); break; case 'chat': this.loadChatData(); break; case 'agent': this.loadAgentData(); break; case 'alerts': this.loadAlerts(); break; case 'knowledge': this.loadKnowledge(); break; case 'workorders': this.loadWorkOrders(); break; case 'conversation-history': this.loadConversationHistory(); break; case 'token-monitor': this.loadTokenMonitor(); break; case 'ai-monitor': this.loadAIMonitor(); break; case 'system-optimizer': this.loadSystemOptimizer(); break; case 'analytics': this.loadAnalytics(); break; case 'settings': this.loadSettings(); break; } } savePageState() { const state = { currentTab: this.currentTab, timestamp: Date.now() }; localStorage.setItem('tsp_dashboard_state', JSON.stringify(state)); } restorePageState() { try { const savedState = localStorage.getItem('tsp_dashboard_state'); if (savedState) { const state = JSON.parse(savedState); // 如果状态保存时间不超过1小时,则恢复 if (Date.now() - state.timestamp < 3600000) { this.switchTab(state.currentTab); } } } catch (error) { console.warn('恢复页面状态失败:', error); } } async loadInitialData() { await Promise.all([ this.loadHealth(), this.loadDashboardData(), this.loadSystemInfo() ]); } startAutoRefresh() { // 每15秒刷新健康状态(减少 /api/health 日志) this.refreshIntervals.health = setInterval(() => { this.loadHealth(); }, 15000); // 每10秒刷新当前标签页数据 this.refreshIntervals.currentTab = setInterval(() => { this.refreshCurrentTab(); }, 10000); } refreshCurrentTab() { switch(this.currentTab) { case 'dashboard': this.loadDashboardData(); break; case 'alerts': this.loadAlerts(); break; case 'agent': this.loadAgentData(); break; } } async loadHealth() { try { const response = await fetch('/api/health'); const data = await response.json(); this.updateHealthDisplay(data); } catch (error) { console.error('加载健康状态失败:', error); } } updateHealthDisplay(health) { const healthScore = health.health_score || 0; const healthStatus = health.status || 'unknown'; // 更新健康指示器 const healthDot = document.getElementById('health-dot'); const healthStatusText = document.getElementById('health-status'); const systemHealthDot = document.getElementById('system-health-dot'); const systemHealthText = document.getElementById('system-health-text'); const healthProgress = document.getElementById('health-progress'); if (healthDot) { healthDot.className = `health-dot ${healthStatus}`; } if (healthStatusText) { healthStatusText.textContent = this.getHealthStatusText(healthStatus); } if (systemHealthDot) { systemHealthDot.className = `health-dot ${healthStatus}`; } if (systemHealthText) { systemHealthText.textContent = this.getHealthStatusText(healthStatus); } if (healthProgress) { healthProgress.style.width = `${healthScore * 100}%`; healthProgress.className = `progress-bar ${this.getHealthColor(healthScore)}`; } // 更新内存和CPU使用率 const memoryUsage = health.memory_usage || 0; const memoryProgress = document.getElementById('memory-progress'); if (memoryProgress) { memoryProgress.style.width = `${memoryUsage}%`; } const cpuUsage = health.cpu_usage || 0; const cpuProgress = document.getElementById('cpu-progress'); if (cpuProgress) { cpuProgress.style.width = `${cpuUsage}%`; } } getHealthStatusText(status) { const statusMap = { 'excellent': '优秀', 'good': '良好', 'fair': '一般', 'poor': '较差', 'critical': '严重', 'unknown': '未知' }; return statusMap[status] || status; } getHealthColor(score) { if (score >= 0.8) return 'bg-success'; if (score >= 0.6) return 'bg-info'; if (score >= 0.4) return 'bg-warning'; return 'bg-danger'; } async loadDashboardData() { try { const [sessionsResponse, alertsResponse, workordersResponse, knowledgeResponse] = await Promise.all([ fetch('/api/chat/sessions'), fetch('/api/alerts'), fetch('/api/workorders'), fetch('/api/knowledge/stats') ]); const sessions = await sessionsResponse.json(); const alerts = await alertsResponse.json(); const workorders = await workordersResponse.json(); const knowledge = await knowledgeResponse.json(); // 更新统计卡片 document.getElementById('total-sessions').textContent = sessions.sessions?.length || 0; document.getElementById('total-alerts').textContent = alerts.length || 0; document.getElementById('total-workorders').textContent = workorders.filter(w => w.status === 'open').length || 0; document.getElementById('knowledge-count').textContent = knowledge.total_entries || 0; // 更新知识库详细统计 document.getElementById('knowledge-total').textContent = knowledge.total_entries || 0; document.getElementById('knowledge-active').textContent = knowledge.active_entries || 0; const confidencePercent = Math.round((knowledge.average_confidence || 0) * 100); document.getElementById('knowledge-confidence').style.width = `${confidencePercent}%`; document.getElementById('knowledge-confidence').setAttribute('aria-valuenow', confidencePercent); document.getElementById('knowledge-confidence').textContent = `${confidencePercent}%`; // 更新性能图表 await this.updatePerformanceChart(sessions, alerts, workorders); // 更新系统健康状态 await this.updateSystemHealth(); } catch (error) { console.error('加载仪表板数据失败:', error); } } initCharts() { // 性能趋势图 const performanceCtx = document.getElementById('performanceChart'); if (performanceCtx) { this.charts.performance = new Chart(performanceCtx, { type: 'line', data: { labels: [], datasets: [{ label: '工单数量', data: [], borderColor: '#007bff', backgroundColor: 'rgba(0, 123, 255, 0.1)', tension: 0.4 }, { label: '预警数量', data: [], borderColor: '#dc3545', backgroundColor: 'rgba(220, 53, 69, 0.1)', tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } } } }); } // 分析图表 const analyticsCtx = document.getElementById('analyticsChart'); if (analyticsCtx) { this.charts.analytics = new Chart(analyticsCtx, { type: 'line', data: { labels: [], datasets: [{ label: '满意度', data: [], borderColor: '#28a745', backgroundColor: 'rgba(40, 167, 69, 0.1)', tension: 0.4 }, { label: '解决时间(小时)', data: [], borderColor: '#ffc107', backgroundColor: 'rgba(255, 193, 7, 0.1)', tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } } } }); } // 类别分布图 const categoryCtx = document.getElementById('categoryChart'); if (categoryCtx) { this.charts.category = new Chart(categoryCtx, { type: 'doughnut', data: { labels: [], datasets: [{ data: [], backgroundColor: [ '#007bff', '#28a745', '#ffc107', '#dc3545', '#17a2b8' ] }] }, options: { responsive: true, maintainAspectRatio: false } }); } } async updatePerformanceChart(sessions, alerts, workorders) { if (!this.charts.performance) return; try { // 获取真实的分析数据 const response = await fetch('/api/analytics?days=7&dimension=performance'); const analyticsData = await response.json(); if (analyticsData.trend && analyticsData.trend.length > 0) { // 使用真实数据 const labels = analyticsData.trend.map(item => { const date = new Date(item.date); return `${date.getMonth() + 1}/${date.getDate()}`; }); const workorderData = analyticsData.trend.map(item => item.workorders || 0); const alertData = analyticsData.trend.map(item => item.alerts || 0); this.charts.performance.data.labels = labels; this.charts.performance.data.datasets[0].data = workorderData; this.charts.performance.data.datasets[1].data = alertData; this.charts.performance.update(); } else { // 如果没有真实数据,显示提示 this.charts.performance.data.labels = ['暂无数据']; this.charts.performance.data.datasets[0].data = [0]; this.charts.performance.data.datasets[1].data = [0]; this.charts.performance.update(); } } catch (error) { console.error('获取性能趋势数据失败:', error); // 出错时显示空数据 this.charts.performance.data.labels = ['数据加载失败']; this.charts.performance.data.datasets[0].data = [0]; this.charts.performance.data.datasets[1].data = [0]; this.charts.performance.update(); } } // 更新系统健康状态显示 async updateSystemHealth() { try { const response = await fetch('/api/settings'); const settings = await response.json(); // 更新健康分数 const healthScore = Math.max(0, 100 - (settings.memory_usage_percent || 0) - (settings.cpu_usage_percent || 0)); const healthProgress = document.getElementById('health-progress'); const healthDot = document.getElementById('system-health-dot'); const healthText = document.getElementById('system-health-text'); if (healthProgress) { healthProgress.style.width = `${healthScore}%`; healthProgress.setAttribute('aria-valuenow', healthScore); } if (healthDot) { healthDot.className = 'health-dot'; if (healthScore >= 80) healthDot.classList.add('excellent'); else if (healthScore >= 60) healthDot.classList.add('good'); else if (healthScore >= 40) healthDot.classList.add('fair'); else if (healthScore >= 20) healthDot.classList.add('poor'); else healthDot.classList.add('critical'); } if (healthText) { const statusText = healthScore >= 80 ? '优秀' : healthScore >= 60 ? '良好' : healthScore >= 40 ? '一般' : healthScore >= 20 ? '较差' : '严重'; healthText.textContent = `${statusText} (${healthScore}%)`; } // 更新内存使用 const memoryProgress = document.getElementById('memory-progress'); if (memoryProgress && settings.memory_usage_percent !== undefined) { memoryProgress.style.width = `${settings.memory_usage_percent}%`; memoryProgress.setAttribute('aria-valuenow', settings.memory_usage_percent); } // 更新CPU使用 const cpuProgress = document.getElementById('cpu-progress'); if (cpuProgress && settings.cpu_usage_percent !== undefined) { cpuProgress.style.width = `${settings.cpu_usage_percent}%`; cpuProgress.setAttribute('aria-valuenow', settings.cpu_usage_percent); } } catch (error) { console.error('更新系统健康状态失败:', error); } } // 对话功能 async startChat() { try { const userId = document.getElementById('user-id').value; const workOrderId = document.getElementById('work-order-id').value; const response = await fetch('/api/chat/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: userId, work_order_id: workOrderId ? parseInt(workOrderId) : null }) }); const data = await response.json(); if (data.success) { this.sessionId = data.session_id; document.getElementById('start-chat').disabled = true; document.getElementById('end-chat').disabled = false; document.getElementById('message-input').disabled = false; document.getElementById('send-button').disabled = false; document.getElementById('session-info').textContent = `会话ID: ${this.sessionId}`; document.getElementById('connection-status').className = 'badge bg-success'; document.getElementById('connection-status').innerHTML = '已连接'; this.showNotification('对话已开始', 'success'); } else { this.showNotification('开始对话失败', 'error'); } } catch (error) { console.error('开始对话失败:', error); this.showNotification('开始对话失败', 'error'); } } async endChat() { try { if (!this.sessionId) return; const response = await fetch(`/api/chat/session/${this.sessionId}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { this.sessionId = null; document.getElementById('start-chat').disabled = false; document.getElementById('end-chat').disabled = true; document.getElementById('message-input').disabled = true; document.getElementById('send-button').disabled = true; document.getElementById('session-info').textContent = '未开始对话'; document.getElementById('connection-status').className = 'badge bg-secondary'; document.getElementById('connection-status').innerHTML = '未连接'; this.showNotification('对话已结束', 'info'); } else { this.showNotification('结束对话失败', 'error'); } } catch (error) { console.error('结束对话失败:', error); this.showNotification('结束对话失败', 'error'); } } async sendMessage() { const messageInput = document.getElementById('message-input'); const message = messageInput.value.trim(); if (!message || !this.sessionId) return; // 显示用户消息 this.addMessage('user', message); messageInput.value = ''; // 显示占位提示:小奇正在查询中 const typingId = this.showTypingIndicator(); // 发送消息到服务器 try { const response = await fetch('/api/chat/message', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: this.sessionId, message: message }) }); const data = await response.json(); if (data.success) { this.updateTypingIndicator(typingId, data.response, data.knowledge_used); } else { this.updateTypingIndicator(typingId, '抱歉,处理您的消息时出现了错误。', null, true); } } catch (error) { console.error('发送消息失败:', error); this.updateTypingIndicator(typingId, '网络连接错误,请稍后重试。', null, true); } } showTypingIndicator() { const messagesContainer = document.getElementById('chat-messages'); const id = `typing-${Date.now()}`; const messageDiv = document.createElement('div'); messageDiv.className = 'message assistant'; messageDiv.id = id; const avatar = document.createElement('div'); avatar.className = 'message-avatar'; avatar.innerHTML = ''; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; contentDiv.innerHTML = `
小奇正在查询中,请稍后…
${new Date().toLocaleTimeString()}
`; messageDiv.appendChild(avatar); messageDiv.appendChild(contentDiv); messagesContainer.appendChild(messageDiv); messagesContainer.scrollTop = messagesContainer.scrollHeight; return id; } updateTypingIndicator(typingId, content, knowledgeUsed = null, isError = false) { const node = document.getElementById(typingId); if (!node) { // 回退:若占位不存在则直接追加 this.addMessage('assistant', content, knowledgeUsed, isError); return; } const contentDiv = node.querySelector('.message-content'); if (contentDiv) { contentDiv.innerHTML = `
${content}
${new Date().toLocaleTimeString()}
`; if (knowledgeUsed && knowledgeUsed.length > 0) { const knowledgeDiv = document.createElement('div'); knowledgeDiv.className = 'knowledge-info'; knowledgeDiv.innerHTML = ` 使用了知识库: ${knowledgeUsed.map(k => k.question || k.source || '实时数据').join(', ')} `; contentDiv.appendChild(knowledgeDiv); } if (isError) { contentDiv.style.borderLeft = '4px solid #dc3545'; } else { contentDiv.style.borderLeft = ''; } } const messagesContainer = document.getElementById('chat-messages'); messagesContainer.scrollTop = messagesContainer.scrollHeight; } addMessage(role, content, knowledgeUsed = null, isError = false) { const messagesContainer = document.getElementById('chat-messages'); // 移除欢迎消息 const welcomeMsg = messagesContainer.querySelector('.text-center'); if (welcomeMsg) { welcomeMsg.remove(); } const messageDiv = document.createElement('div'); messageDiv.className = `message ${role}`; const avatar = document.createElement('div'); avatar.className = 'message-avatar'; avatar.innerHTML = role === 'user' ? '' : ''; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; contentDiv.innerHTML = `
${content}
${new Date().toLocaleTimeString()}
`; if (knowledgeUsed && knowledgeUsed.length > 0) { const knowledgeDiv = document.createElement('div'); knowledgeDiv.className = 'knowledge-info'; knowledgeDiv.innerHTML = ` 使用了知识库: ${knowledgeUsed.map(k => k.question).join(', ')} `; contentDiv.appendChild(knowledgeDiv); } if (isError) { contentDiv.style.borderLeft = '4px solid #dc3545'; } messageDiv.appendChild(avatar); messageDiv.appendChild(contentDiv); messagesContainer.appendChild(messageDiv); // 滚动到底部 messagesContainer.scrollTop = messagesContainer.scrollHeight; } // Agent功能 async toggleAgentMode(enabled) { try { const response = await fetch('/api/agent/toggle', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled }) }); const data = await response.json(); if (data.success) { this.isAgentMode = enabled; this.showNotification(`Agent模式已${enabled ? '启用' : '禁用'}`, 'success'); this.loadAgentData(); } else { this.showNotification('切换Agent模式失败', 'error'); } } catch (error) { console.error('切换Agent模式失败:', error); this.showNotification('切换Agent模式失败', 'error'); } } async loadAgentData() { try { const [statusResp, toolsResp] = await Promise.all([ fetch('/api/agent/status'), fetch('/api/agent/tools/stats') ]); const data = await statusResp.json(); const toolsData = await toolsResp.json(); if (data.success) { document.getElementById('agent-current-state').textContent = data.status || '未知'; document.getElementById('agent-active-goals').textContent = data.active_goals || 0; const tools = (toolsData.success ? toolsData.tools : (data.tools || [])) || []; document.getElementById('agent-available-tools').textContent = tools.length || 0; // 更新工具列表(使用真实统计) this.updateToolsList(tools); // 更新执行历史 this.updateAgentExecutionHistory(data.execution_history || []); } } catch (error) { console.error('加载Agent数据失败:', error); } } updateToolsList(tools) { const toolsList = document.getElementById('tools-list'); if (!tools || tools.length === 0) { toolsList.innerHTML = '

暂无工具

'; return; } const toolsHtml = tools.map(tool => { const usage = tool.usage_count || 0; const success = Math.round((tool.success_rate || 0) * 100); const meta = tool.metadata || {}; return `
${tool.name} ${meta.description ? `
${meta.description}
` : ''} 使用次数: ${usage}
${success}%
`; }).join(''); toolsList.innerHTML = toolsHtml; // 绑定执行事件 toolsList.querySelectorAll('button[data-tool]').forEach(btn => { btn.addEventListener('click', async () => { const tool = btn.getAttribute('data-tool'); // 简单参数输入(可扩展为动态表单) let params = {}; try { const input = prompt('请输入执行参数(JSON):', '{}'); if (input) params = JSON.parse(input); } catch (e) { this.showNotification('参数格式错误,应为JSON', 'warning'); return; } try { const resp = await fetch('/api/agent/tools/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tool, parameters: params }) }); const res = await resp.json(); if (res.success) { this.showNotification(`工具 ${tool} 执行成功`, 'success'); await this.loadAgentData(); } else { this.showNotification(res.error || `工具 ${tool} 执行失败`, 'error'); } } catch (err) { console.error('执行工具失败:', err); this.showNotification('执行工具失败: ' + err.message, 'error'); } }); }); // 追加自定义工具注册入口 const addDiv = document.createElement('div'); addDiv.className = 'mt-3'; addDiv.innerHTML = `
`; toolsList.appendChild(addDiv); document.getElementById('register-tool-btn').addEventListener('click', async () => { const name = document.getElementById('custom-tool-name').value.trim(); const description = document.getElementById('custom-tool-desc').value.trim(); if (!name) { this.showNotification('请输入工具名称', 'warning'); return; } try { const resp = await fetch('/api/agent/tools/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, description }) }); const res = await resp.json(); if (res.success) { this.showNotification('工具注册成功', 'success'); this.loadAgentData(); } else { this.showNotification(res.error || '工具注册失败', 'error'); } } catch (e) { console.error('注册工具失败:', e); this.showNotification('注册工具失败', 'error'); } }); } updateExecutionHistory(history) { const historyContainer = document.getElementById('agent-execution-history'); if (history.length === 0) { historyContainer.innerHTML = '

暂无执行历史

'; return; } const historyHtml = history.slice(-5).map(item => `
${item.type || '未知任务'} ${new Date(item.timestamp).toLocaleString()}
${item.description || '无描述'}
${item.success ? '成功' : '失败'}
`).join(''); historyContainer.innerHTML = historyHtml; } async startAgentMonitoring() { try { const response = await fetch('/api/agent/monitoring/start', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification('Agent监控已启动', 'success'); } else { this.showNotification('启动Agent监控失败', 'error'); } } catch (error) { console.error('启动Agent监控失败:', error); this.showNotification('启动Agent监控失败', 'error'); } } async stopAgentMonitoring() { try { const response = await fetch('/api/agent/monitoring/stop', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification('Agent监控已停止', 'success'); } else { this.showNotification('停止Agent监控失败', 'error'); } } catch (error) { console.error('停止Agent监控失败:', error); this.showNotification('停止Agent监控失败', 'error'); } } async proactiveMonitoring() { try { const response = await fetch('/api/agent/proactive-monitoring', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification(`主动监控完成,发现 ${data.proactive_actions?.length || 0} 个行动机会`, 'info'); } else { this.showNotification('主动监控失败', 'error'); } } catch (error) { console.error('主动监控失败:', error); this.showNotification('主动监控失败', 'error'); } } async intelligentAnalysis() { try { const response = await fetch('/api/agent/intelligent-analysis', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification('智能分析完成', 'success'); // 更新分析图表 this.updateAnalyticsChart(data.analysis); } else { this.showNotification('智能分析失败', 'error'); } } catch (error) { console.error('智能分析失败:', error); this.showNotification('智能分析失败', 'error'); } } updateAnalyticsChart(analysis) { if (!this.charts.analytics || !analysis) return; // 更新分析图表数据 const labels = analysis.trends?.dates || []; const satisfactionData = analysis.trends?.satisfaction || []; const resolutionTimeData = analysis.trends?.resolution_time || []; this.charts.analytics.data.labels = labels; this.charts.analytics.data.datasets[0].data = satisfactionData; this.charts.analytics.data.datasets[1].data = resolutionTimeData; this.charts.analytics.update(); } // 预警管理 async loadAlerts() { try { const response = await fetch('/api/alerts'); const alerts = await response.json(); this.updateAlertsDisplay(alerts); this.updateAlertStatistics(alerts); } catch (error) { console.error('加载预警失败:', error); } } updateAlertsDisplay(alerts) { const container = document.getElementById('alerts-container'); if (alerts.length === 0) { container.innerHTML = `
暂无活跃预警

系统运行正常,没有需要处理的预警

`; return; } const alertsHtml = alerts.map(alert => `
${this.getLevelText(alert.level)} ${alert.rule_name || '未知规则'} ${this.formatTime(alert.created_at)}
${alert.message}
类型: ${this.getTypeText(alert.alert_type)} | 级别: ${this.getLevelText(alert.level)}
`).join(''); container.innerHTML = alertsHtml; } updateAlertStatistics(alerts) { const stats = alerts.reduce((acc, alert) => { acc[alert.level] = (acc[alert.level] || 0) + 1; acc.total = (acc.total || 0) + 1; return acc; }, {}); document.getElementById('critical-alerts').textContent = stats.critical || 0; document.getElementById('warning-alerts').textContent = stats.warning || 0; document.getElementById('info-alerts').textContent = stats.info || 0; document.getElementById('total-alerts-count').textContent = stats.total || 0; } async resolveAlert(alertId) { try { const response = await fetch(`/api/alerts/${alertId}/resolve`, { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification('预警已解决', 'success'); this.loadAlerts(); } else { this.showNotification('解决预警失败', 'error'); } } catch (error) { console.error('解决预警失败:', error); this.showNotification('解决预警失败', 'error'); } } // 知识库管理 async loadKnowledge(page = 1) { try { const response = await fetch(`/api/knowledge?page=${page}&per_page=10`); const data = await response.json(); if (data.knowledge) { this.updateKnowledgeDisplay(data.knowledge); this.updateKnowledgePagination(data); } else { // 兼容旧格式 this.updateKnowledgeDisplay(data); } } catch (error) { console.error('加载知识库失败:', error); } } updateKnowledgeDisplay(knowledge) { const container = document.getElementById('knowledge-list'); if (knowledge.length === 0) { container.innerHTML = '

暂无知识条目

'; return; } const knowledgeHtml = knowledge.map(item => `
${item.question}

${item.answer}

分类: ${item.category} 置信度: ${Math.round(item.confidence_score * 100)}% 使用次数: ${item.usage_count || 0} ${item.is_verified ? '已验证' : '未验证'}
${item.is_verified ? `` : `` }
`).join(''); container.innerHTML = knowledgeHtml; } updateKnowledgePagination(data) { const paginationContainer = document.getElementById('knowledge-pagination'); if (!paginationContainer) return; const { page, total_pages, total } = data; if (total_pages <= 1) { paginationContainer.innerHTML = ''; return; } let paginationHtml = `
共 ${total} 条知识,第 ${page} / ${total_pages} 页
`; paginationContainer.innerHTML = paginationHtml; } async searchKnowledge() { const query = document.getElementById('knowledge-search').value.trim(); if (!query) { this.loadKnowledge(); return; } try { const response = await fetch(`/api/knowledge/search?q=${encodeURIComponent(query)}`); const results = await response.json(); this.updateKnowledgeDisplay(results); } catch (error) { console.error('搜索知识库失败:', error); } } async addKnowledge() { const question = document.getElementById('knowledge-question').value.trim(); const answer = document.getElementById('knowledge-answer').value.trim(); const category = document.getElementById('knowledge-category').value; const confidence = parseFloat(document.getElementById('knowledge-confidence').value); if (!question || !answer) { this.showNotification('请填写完整信息', 'warning'); return; } try { const response = await fetch('/api/knowledge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ question, answer, category, confidence_score: confidence }) }); const data = await response.json(); if (data.success) { this.showNotification('知识添加成功', 'success'); bootstrap.Modal.getInstance(document.getElementById('addKnowledgeModal')).hide(); document.getElementById('knowledge-form').reset(); this.loadKnowledge(); } else { this.showNotification('添加知识失败', 'error'); } } catch (error) { console.error('添加知识失败:', error); this.showNotification('添加知识失败', 'error'); } } async uploadFile() { const fileInput = document.getElementById('file-input'); const processMethod = document.getElementById('process-method').value; const category = document.getElementById('file-category').value; const confidence = parseFloat(document.getElementById('file-confidence').value); const manualQuestion = document.getElementById('manual-question').value.trim(); if (!fileInput.files[0]) { this.showNotification('请选择文件', 'warning'); return; } if (processMethod === 'manual' && !manualQuestion) { this.showNotification('请指定问题', 'warning'); return; } // 显示进度条 const progressDiv = document.getElementById('upload-progress'); const progressBar = progressDiv.querySelector('.progress-bar'); const statusText = document.getElementById('upload-status'); progressDiv.style.display = 'block'; progressBar.style.width = '0%'; statusText.textContent = '正在上传文件...'; try { const formData = new FormData(); formData.append('file', fileInput.files[0]); formData.append('process_method', processMethod); formData.append('category', category); formData.append('confidence_score', confidence); if (manualQuestion) { formData.append('manual_question', manualQuestion); } // 模拟进度更新 let progress = 0; const progressInterval = setInterval(() => { progress += Math.random() * 20; if (progress > 90) progress = 90; progressBar.style.width = progress + '%'; if (progress < 30) { statusText.textContent = '正在上传文件...'; } else if (progress < 60) { statusText.textContent = '正在解析文件内容...'; } else if (progress < 90) { statusText.textContent = '正在生成知识库...'; } }, 500); const response = await fetch('/api/knowledge/upload', { method: 'POST', body: formData }); clearInterval(progressInterval); progressBar.style.width = '100%'; statusText.textContent = '处理完成!'; const data = await response.json(); setTimeout(() => { progressDiv.style.display = 'none'; if (data.success) { this.showNotification(`文件处理成功,生成了 ${data.knowledge_count || 0} 条知识`, 'success'); bootstrap.Modal.getInstance(document.getElementById('uploadFileModal')).hide(); document.getElementById('file-upload-form').reset(); this.loadKnowledge(); } else { this.showNotification(data.error || '文件处理失败', 'error'); } }, 1000); } catch (error) { console.error('文件上传失败:', error); progressDiv.style.display = 'none'; this.showNotification('文件上传失败', 'error'); } } async verifyKnowledge(knowledgeId) { try { const response = await fetch(`/api/knowledge/verify/${knowledgeId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ verified_by: 'admin' }) }); const data = await response.json(); if (data.success) { this.showNotification('知识库验证成功', 'success'); this.loadKnowledge(); } else { this.showNotification('知识库验证失败', 'error'); } } catch (error) { console.error('验证知识库失败:', error); this.showNotification('验证知识库失败', 'error'); } } async unverifyKnowledge(knowledgeId) { try { const response = await fetch(`/api/knowledge/unverify/${knowledgeId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); if (data.success) { this.showNotification('取消验证成功', 'success'); this.loadKnowledge(); } else { this.showNotification('取消验证失败', 'error'); } } catch (error) { console.error('取消验证失败:', error); this.showNotification('取消验证失败', 'error'); } } async deleteKnowledge(knowledgeId) { if (!confirm('确定要删除这条知识库条目吗?')) { return; } try { const response = await fetch(`/api/knowledge/delete/${knowledgeId}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { this.showNotification('知识库删除成功', 'success'); this.loadKnowledge(); } else { this.showNotification('知识库删除失败', 'error'); } } catch (error) { console.error('删除知识库失败:', error); this.showNotification('删除知识库失败', 'error'); } } // 工单管理 async loadWorkOrders() { try { const statusFilter = document.getElementById('workorder-status-filter').value; const priorityFilter = document.getElementById('workorder-priority-filter').value; let url = '/api/workorders'; const params = new URLSearchParams(); if (statusFilter !== 'all') params.append('status', statusFilter); if (priorityFilter !== 'all') params.append('priority', priorityFilter); if (params.toString()) url += '?' + params.toString(); const response = await fetch(url); const workorders = await response.json(); this.updateWorkOrdersDisplay(workorders); this.updateWorkOrderStatistics(workorders); } catch (error) { console.error('加载工单失败:', error); } } updateWorkOrdersDisplay(workorders) { const container = document.getElementById('workorders-list'); if (workorders.length === 0) { container.innerHTML = '

暂无工单

'; return; } const workordersHtml = workorders.map(workorder => `
${workorder.title}

${workorder.description ? workorder.description.substring(0, 100) + (workorder.description.length > 100 ? '...' : '') : '无处理过程'}

${this.getPriorityText(workorder.priority)} ${this.getStatusText(workorder.status)} 分类: ${workorder.category} 创建时间: ${new Date(workorder.created_at).toLocaleString()}
`).join(''); container.innerHTML = workordersHtml; } updateWorkOrderStatistics(workorders) { const stats = workorders.reduce((acc, wo) => { acc.total = (acc.total || 0) + 1; acc[wo.status] = (acc[wo.status] || 0) + 1; return acc; }, {}); document.getElementById('workorders-total').textContent = stats.total || 0; document.getElementById('workorders-open').textContent = stats.open || 0; document.getElementById('workorders-progress').textContent = stats.in_progress || 0; document.getElementById('workorders-resolved').textContent = stats.resolved || 0; } async createWorkOrder() { const title = document.getElementById('wo-title').value.trim(); const description = document.getElementById('wo-description').value.trim(); const category = document.getElementById('wo-category').value; const priority = document.getElementById('wo-priority').value; if (!title || !description) { this.showNotification('请填写完整信息', 'warning'); return; } try { const response = await fetch('/api/workorders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, description, category, priority }) }); const data = await response.json(); if (data.success) { this.showNotification('工单创建成功', 'success'); bootstrap.Modal.getInstance(document.getElementById('createWorkOrderModal')).hide(); document.getElementById('work-order-form').reset(); // 立即刷新工单列表和统计 await this.loadWorkOrders(); await this.loadAnalytics(); } else { this.showNotification('创建工单失败: ' + (data.error || '未知错误'), 'error'); } } catch (error) { console.error('创建工单失败:', error); this.showNotification('创建工单失败', 'error'); } } async viewWorkOrderDetails(workorderId) { try { const response = await fetch(`/api/workorders/${workorderId}`); const workorder = await response.json(); if (workorder.error) { this.showNotification('获取工单详情失败', 'error'); return; } this.showWorkOrderDetailsModal(workorder); } catch (error) { console.error('获取工单详情失败:', error); this.showNotification('获取工单详情失败', 'error'); } } showWorkOrderDetailsModal(workorder) { // 创建模态框HTML const modalHtml = ` `; // 移除已存在的模态框 const existingModal = document.getElementById('workOrderDetailsModal'); if (existingModal) { existingModal.remove(); } // 添加新的模态框到页面 document.body.insertAdjacentHTML('beforeend', modalHtml); // 显示模态框 const modal = new bootstrap.Modal(document.getElementById('workOrderDetailsModal')); modal.show(); // 模态框关闭时移除DOM元素 document.getElementById('workOrderDetailsModal').addEventListener('hidden.bs.modal', function() { this.remove(); }); } async updateWorkOrder(workorderId) { try { // 获取工单详情 const response = await fetch(`/api/workorders/${workorderId}`); const workorder = await response.json(); if (workorder.id) { this.showEditWorkOrderModal(workorder); } else { throw new Error(workorder.error || '获取工单详情失败'); } } catch (error) { console.error('获取工单详情失败:', error); this.showNotification('获取工单详情失败: ' + error.message, 'error'); } } async deleteWorkOrder(workorderId) { console.log('deleteWorkOrder called with ID:', workorderId); if (!confirm('确定要删除这个工单吗?此操作不可撤销。')) { console.log('用户取消了删除操作'); return; } try { console.log('发送删除请求到:', `/api/workorders/${workorderId}`); const response = await fetch(`/api/workorders/${workorderId}`, { method: 'DELETE' }); console.log('删除响应状态:', response.status); const data = await response.json(); console.log('删除响应数据:', data); if (data.success) { this.showNotification('工单删除成功', 'success'); // 立即刷新工单列表和统计 await this.loadWorkOrders(); await this.loadAnalytics(); } else { this.showNotification('删除工单失败: ' + (data.error || '未知错误'), 'error'); } } catch (error) { console.error('删除工单失败:', error); this.showNotification('删除工单失败: ' + error.message, 'error'); } } showEditWorkOrderModal(workorder) { // 创建编辑工单模态框 const modalHtml = ` `; // 移除已存在的模态框 const existingModal = document.getElementById('editWorkOrderModal'); if (existingModal) { existingModal.remove(); } // 添加新模态框到页面 document.body.insertAdjacentHTML('beforeend', modalHtml); // 显示模态框 const modal = new bootstrap.Modal(document.getElementById('editWorkOrderModal')); modal.show(); // 模态框关闭时清理 document.getElementById('editWorkOrderModal').addEventListener('hidden.bs.modal', function() { this.remove(); }); } async saveWorkOrder(workorderId) { try { // 获取表单数据 const formData = { title: document.getElementById('editTitle').value, description: document.getElementById('editDescription').value, category: document.getElementById('editCategory').value, priority: document.getElementById('editPriority').value, status: document.getElementById('editStatus').value, resolution: document.getElementById('editResolution').value, satisfaction_score: parseInt(document.getElementById('editSatisfactionScore').value) || null }; // 验证必填字段 if (!formData.title.trim() || !formData.description.trim()) { this.showNotification('标题和描述不能为空', 'error'); return; } // 发送更新请求 const response = await fetch(`/api/workorders/${workorderId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(formData) }); const result = await response.json(); if (result.success) { this.showNotification('工单更新成功', 'success'); // 关闭模态框 const modal = bootstrap.Modal.getInstance(document.getElementById('editWorkOrderModal')); modal.hide(); // 刷新工单列表和统计 await this.loadWorkOrders(); await this.loadAnalytics(); } else { throw new Error(result.error || '更新工单失败'); } } catch (error) { console.error('更新工单失败:', error); this.showNotification('更新工单失败: ' + error.message, 'error'); } } // 工单导入功能 showImportModal() { // 显示导入模态框 const modal = new bootstrap.Modal(document.getElementById('importWorkOrderModal')); modal.show(); // 重置表单 document.getElementById('excel-file-input').value = ''; document.getElementById('import-progress').classList.add('d-none'); document.getElementById('import-result').classList.add('d-none'); } async downloadTemplate() { try { // 直接请求文件接口,避免浏览器跨源/权限限制 const resp = await fetch('/api/workorders/import/template/file'); if (!resp.ok) throw new Error('下载接口返回错误'); const blob = await resp.blob(); const blobUrl = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = blobUrl; a.download = '工单导入模板.xlsx'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(blobUrl); this.showNotification('模板下载成功', 'success'); } catch (error) { console.error('下载模板失败:', error); this.showNotification('下载模板失败: ' + error.message, 'error'); } } async importWorkOrders() { const fileInput = document.getElementById('excel-file-input'); const file = fileInput.files[0]; if (!file) { this.showNotification('请选择要导入的Excel文件', 'error'); return; } // 验证文件类型 if (!file.name.match(/\.(xlsx|xls)$/)) { this.showNotification('只支持Excel文件(.xlsx, .xls)', 'error'); return; } // 验证文件大小 if (file.size > 16 * 1024 * 1024) { this.showNotification('文件大小不能超过16MB', 'error'); return; } // 显示进度条 document.getElementById('import-progress').classList.remove('d-none'); document.getElementById('import-result').classList.add('d-none'); try { // 创建FormData const formData = new FormData(); formData.append('file', file); // 发送导入请求 const response = await fetch('/api/workorders/import', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { // 显示成功消息 document.getElementById('import-progress').classList.add('d-none'); document.getElementById('import-result').classList.remove('d-none'); document.getElementById('import-success-message').textContent = `成功导入 ${result.imported_count} 个工单`; this.showNotification(result.message, 'success'); // 刷新工单列表 this.loadWorkOrders(); // 3秒后关闭模态框 setTimeout(() => { const modal = bootstrap.Modal.getInstance(document.getElementById('importWorkOrderModal')); modal.hide(); }, 3000); } else { throw new Error(result.error || '导入工单失败'); } } catch (error) { console.error('导入工单失败:', error); document.getElementById('import-progress').classList.add('d-none'); this.showNotification('导入工单失败: ' + error.message, 'error'); } } // 对话历史管理 async loadConversationHistory(page = 1, perPage = 10) { try { const response = await fetch(`/api/conversations?page=${page}&per_page=${perPage}`); const data = await response.json(); if (data.success) { this.renderConversationList(data.conversations || []); this.renderConversationPagination(data.pagination || {}); this.updateConversationStats(data.stats || {}); } else { throw new Error(data.error || '加载对话历史失败'); } } catch (error) { console.error('加载对话历史失败:', error); this.showNotification('加载对话历史失败: ' + error.message, 'error'); } } renderConversationList(conversations) { const container = document.getElementById('conversation-list'); if (!conversations || conversations.length === 0) { container.innerHTML = `

暂无对话记录

`; return; } const html = conversations.map(conv => `
用户: ${conv.user_id || '匿名'}
${new Date(conv.timestamp).toLocaleString()}

用户: ${conv.user_message?.substring(0, 100)}${conv.user_message?.length > 100 ? '...' : ''}

助手: ${conv.assistant_response?.substring(0, 100)}${conv.assistant_response?.length > 100 ? '...' : ''}

响应时间: ${conv.response_time || 0}ms ${conv.work_order_id ? `工单: ${conv.work_order_id}` : ''}
`).join(''); container.innerHTML = html; } renderConversationPagination(pagination) { const container = document.getElementById('conversation-pagination'); if (!pagination || !pagination.total_pages || pagination.total_pages <= 1) { container.innerHTML = ''; return; } const currentPage = pagination.current_page || 1; const totalPages = pagination.total_pages; let html = ''; container.innerHTML = html; } updateConversationStats(stats) { document.getElementById('conversation-total').textContent = stats.total || 0; document.getElementById('conversation-today').textContent = stats.today || 0; document.getElementById('conversation-avg-response').textContent = `${stats.avg_response_time || 0}ms`; document.getElementById('conversation-active-users').textContent = stats.active_users || 0; } async refreshConversationHistory() { // 先尝试触发一次合并迁移(幂等,重复调用也安全) try { await fetch('/api/conversations/migrate-merge', { method: 'POST' }); } catch (e) { /* 忽略迁移失败 */ } await this.loadConversationHistory(); this.showNotification('对话历史已刷新', 'success'); } async clearAllConversations() { if (!confirm('确定要清空所有对话历史吗?此操作不可恢复!')) { return; } try { const response = await fetch('/api/conversations/clear', { method: 'DELETE' }); const data = await response.json(); if (data.success) { this.showNotification('对话历史已清空', 'success'); await this.loadConversationHistory(); } else { throw new Error(data.error || '清空对话历史失败'); } } catch (error) { console.error('清空对话历史失败:', error); this.showNotification('清空对话历史失败: ' + error.message, 'error'); } } async deleteConversation(conversationId) { if (!confirm('确定要删除这条对话记录吗?')) { return; } try { const response = await fetch(`/api/conversations/${conversationId}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { this.showNotification('对话记录已删除', 'success'); await this.loadConversationHistory(); } else { throw new Error(data.error || '删除对话记录失败'); } } catch (error) { console.error('删除对话记录失败:', error); this.showNotification('删除对话记录失败: ' + error.message, 'error'); } } async viewConversation(conversationId) { try { const response = await fetch(`/api/conversations/${conversationId}`); const data = await response.json(); if (data.success) { data.user_id = data.user_id || '匿名'; this.showConversationModal(data); } else { throw new Error(data.error || '获取对话详情失败'); } } catch (error) { console.error('获取对话详情失败:', error); this.showNotification('获取对话详情失败: ' + error.message, 'error'); } } showConversationModal(conversation) { const modalHtml = ` `; // 移除已存在的模态框 const existingModal = document.getElementById('conversationModal'); if (existingModal) { existingModal.remove(); } // 添加新模态框 document.body.insertAdjacentHTML('beforeend', modalHtml); // 显示模态框 const modal = new bootstrap.Modal(document.getElementById('conversationModal')); modal.show(); } async filterConversations() { const search = document.getElementById('conversation-search').value; const userFilter = document.getElementById('conversation-user-filter').value; const dateFilter = document.getElementById('conversation-date-filter').value; try { const params = new URLSearchParams(); if (search) params.append('search', search); if (userFilter) params.append('user_id', userFilter); if (dateFilter) params.append('date_filter', dateFilter); const response = await fetch(`/api/conversations?${params.toString()}`); const data = await response.json(); if (data.success) { this.renderConversationList(data.conversations || []); this.renderConversationPagination(data.pagination || {}); } else { throw new Error(data.error || '筛选对话失败'); } } catch (error) { console.error('筛选对话失败:', error); this.showNotification('筛选对话失败: ' + error.message, 'error'); } } // Token监控 async loadTokenMonitor() { try { const response = await fetch('/api/token-monitor/stats'); const data = await response.json(); if (data.success) { this.updateTokenStats(data); this.loadTokenChart(); this.loadTokenRecords(); } else { throw new Error(data.error || '加载Token监控数据失败'); } } catch (error) { console.error('加载Token监控数据失败:', error); this.showNotification('加载Token监控数据失败: ' + error.message, 'error'); } } updateTokenStats(stats) { document.getElementById('token-today').textContent = stats.today_tokens || 0; document.getElementById('token-month').textContent = stats.month_tokens || 0; document.getElementById('token-cost').textContent = `¥${stats.total_cost || 0}`; document.getElementById('token-budget').textContent = `¥${stats.budget_limit || 1000}`; } async loadTokenChart() { try { const response = await fetch('/api/token-monitor/chart'); const data = await response.json(); if (data.success) { this.renderTokenChart(data); } } catch (error) { console.error('加载Token图表失败:', error); } } renderTokenChart(data) { const ctx = document.getElementById('tokenChart').getContext('2d'); if (this.charts.tokenChart) { this.charts.tokenChart.destroy(); } this.charts.tokenChart = new Chart(ctx, { type: 'line', data: { labels: data.labels || [], datasets: [{ label: 'Token消耗', data: data.tokens || [], borderColor: '#007bff', backgroundColor: 'rgba(0, 123, 255, 0.1)', tension: 0.4 }, { label: '成本', data: data.costs || [], borderColor: '#28a745', backgroundColor: 'rgba(40, 167, 69, 0.1)', tension: 0.4, yAxisID: 'y1' }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { type: 'linear', display: true, position: 'left', title: { display: true, text: 'Token数量' } }, y1: { type: 'linear', display: true, position: 'right', title: { display: true, text: '成本 (元)' }, grid: { drawOnChartArea: false, }, } } } }); } async loadTokenRecords() { try { const response = await fetch('/api/token-monitor/records'); const data = await response.json(); if (data.success) { this.renderTokenRecords(data.records || []); } } catch (error) { console.error('加载Token记录失败:', error); } } renderTokenRecords(records) { const tbody = document.getElementById('token-records'); if (!records || records.length === 0) { tbody.innerHTML = ` 暂无记录 `; return; } const html = records.map(record => ` ${new Date(record.timestamp).toLocaleString()} ${record.user_id || '匿名'} ${record.model || 'qwen-turbo'} ${record.input_tokens || 0} ${record.output_tokens || 0} ${record.total_tokens || 0} ¥${record.cost || 0} ${record.response_time || 0}ms `).join(''); tbody.innerHTML = html; } async saveTokenSettings() { const dailyThreshold = document.getElementById('daily-threshold').value; const monthlyBudget = document.getElementById('monthly-budget').value; const enableAlerts = document.getElementById('enable-alerts').checked; try { const response = await fetch('/api/token-monitor/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ daily_threshold: parseInt(dailyThreshold), monthly_budget: parseFloat(monthlyBudget), enable_alerts: enableAlerts }) }); const data = await response.json(); if (data.success) { this.showNotification('Token设置已保存', 'success'); } else { throw new Error(data.error || '保存Token设置失败'); } } catch (error) { console.error('保存Token设置失败:', error); this.showNotification('保存Token设置失败: ' + error.message, 'error'); } } async updateTokenChart(period) { // 更新按钮状态 document.querySelectorAll('#tokenChart').forEach(btn => { btn.classList.remove('active'); }); event.target.classList.add('active'); try { const response = await fetch(`/api/token-monitor/chart?period=${period}`); const data = await response.json(); if (data.success) { this.renderTokenChart(data); } } catch (error) { console.error('更新Token图表失败:', error); } } async exportTokenData() { try { const response = await fetch('/api/token-monitor/export'); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'token_usage_data.xlsx'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); this.showNotification('Token数据导出成功', 'success'); } catch (error) { console.error('导出Token数据失败:', error); this.showNotification('导出Token数据失败: ' + error.message, 'error'); } } async refreshTokenData() { await this.loadTokenMonitor(); this.showNotification('Token数据已刷新', 'success'); } // AI监控 async loadAIMonitor() { try { const response = await fetch('/api/ai-monitor/stats'); const data = await response.json(); if (data.success) { this.updateAIStats(data); this.loadModelComparisonChart(); this.loadErrorDistributionChart(); this.loadErrorLog(); } else { throw new Error(data.error || '加载AI监控数据失败'); } } catch (error) { console.error('加载AI监控数据失败:', error); this.showNotification('加载AI监控数据失败: ' + error.message, 'error'); } } updateAIStats(stats) { document.getElementById('ai-success-rate').textContent = `${stats.success_rate || 0}%`; document.getElementById('ai-response-time').textContent = `${stats.avg_response_time || 0}ms`; document.getElementById('ai-error-rate').textContent = `${stats.error_rate || 0}%`; document.getElementById('ai-total-calls').textContent = stats.total_calls || 0; } async loadModelComparisonChart() { try { const response = await fetch('/api/ai-monitor/model-comparison'); const data = await response.json(); if (data.success) { this.renderModelComparisonChart(data); } } catch (error) { console.error('加载模型对比图表失败:', error); } } renderModelComparisonChart(data) { const ctx = document.getElementById('modelComparisonChart').getContext('2d'); if (this.charts.modelComparisonChart) { this.charts.modelComparisonChart.destroy(); } this.charts.modelComparisonChart = new Chart(ctx, { type: 'bar', data: { labels: data.models || [], datasets: [{ label: '成功率 (%)', data: data.success_rates || [], backgroundColor: 'rgba(40, 167, 69, 0.8)' }, { label: '平均响应时间 (ms)', data: data.response_times || [], backgroundColor: 'rgba(255, 193, 7, 0.8)', yAxisID: 'y1' }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { type: 'linear', display: true, position: 'left', title: { display: true, text: '成功率 (%)' } }, y1: { type: 'linear', display: true, position: 'right', title: { display: true, text: '响应时间 (ms)' }, grid: { drawOnChartArea: false, }, } } } }); } async loadErrorDistributionChart() { try { const response = await fetch('/api/ai-monitor/error-distribution'); const data = await response.json(); if (data.success) { this.renderErrorDistributionChart(data); } } catch (error) { console.error('加载错误分布图表失败:', error); } } renderErrorDistributionChart(data) { const ctx = document.getElementById('errorDistributionChart').getContext('2d'); if (this.charts.errorDistributionChart) { this.charts.errorDistributionChart.destroy(); } this.charts.errorDistributionChart = new Chart(ctx, { type: 'doughnut', data: { labels: data.error_types || [], datasets: [{ data: data.counts || [], backgroundColor: [ '#dc3545', '#fd7e14', '#ffc107', '#17a2b8', '#6c757d' ] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } } }); } async loadErrorLog() { try { const response = await fetch('/api/ai-monitor/error-log'); const data = await response.json(); if (data.success) { this.renderErrorLog(data.errors || []); } } catch (error) { console.error('加载错误日志失败:', error); } } renderErrorLog(errors) { const tbody = document.getElementById('error-log'); if (!errors || errors.length === 0) { tbody.innerHTML = ` 暂无错误记录 `; return; } const html = errors.map(error => ` ${new Date(error.timestamp).toLocaleString()} ${error.error_type || '未知'} ${error.error_message || ''} ${error.model || 'qwen-turbo'} ${error.user_id || '匿名'} `).join(''); tbody.innerHTML = html; } async refreshErrorLog() { await this.loadErrorLog(); this.showNotification('错误日志已刷新', 'success'); } async clearErrorLog() { if (!confirm('确定要清空错误日志吗?')) { return; } try { const response = await fetch('/api/ai-monitor/error-log', { method: 'DELETE' }); const data = await response.json(); if (data.success) { this.showNotification('错误日志已清空', 'success'); await this.loadErrorLog(); } else { throw new Error(data.error || '清空错误日志失败'); } } catch (error) { console.error('清空错误日志失败:', error); this.showNotification('清空错误日志失败: ' + error.message, 'error'); } } // 系统优化 async loadSystemOptimizer() { try { const response = await fetch('/api/system-optimizer/status'); const data = await response.json(); if (data.success) { this.updateSystemStats(data); this.loadSecuritySettings(); this.loadTrafficSettings(); this.loadCostSettings(); } else { throw new Error(data.error || '加载系统优化数据失败'); } } catch (error) { console.error('加载系统优化数据失败:', error); this.showNotification('加载系统优化数据失败: ' + error.message, 'error'); } } updateSystemStats(stats) { document.getElementById('cpu-usage').textContent = `${stats.cpu_usage || 0}%`; document.getElementById('memory-usage-percent').textContent = `${stats.memory_usage || 0}%`; document.getElementById('disk-usage').textContent = `${stats.disk_usage || 0}%`; document.getElementById('network-latency').textContent = `${stats.network_latency || 0}ms`; // 更新健康指标 this.updateHealthIndicator('system-health-indicator', stats.system_health || 95); this.updateHealthIndicator('database-health-indicator', stats.database_health || 98); this.updateHealthIndicator('api-health-indicator', stats.api_health || 92); this.updateHealthIndicator('cache-health-indicator', stats.cache_health || 99); document.getElementById('system-health-score').textContent = `${stats.system_health || 95}%`; document.getElementById('database-health-score').textContent = `${stats.database_health || 98}%`; document.getElementById('api-health-score').textContent = `${stats.api_health || 92}%`; document.getElementById('cache-health-score').textContent = `${stats.cache_health || 99}%`; } updateHealthIndicator(elementId, score) { const element = document.getElementById(elementId); if (!element) return; element.className = 'health-dot'; if (score >= 95) element.classList.add('excellent'); else if (score >= 85) element.classList.add('good'); else if (score >= 70) element.classList.add('fair'); else if (score >= 50) element.classList.add('poor'); else element.classList.add('critical'); } async optimizeCPU() { try { const response = await fetch('/api/system-optimizer/optimize-cpu', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification(data.message || 'CPU优化完成', 'success'); this.updateOptimizationProgress('cpu-optimization', data.progress || 100); // 刷新状态并回落进度条 await this.loadSystemOptimizer(); setTimeout(() => this.updateOptimizationProgress('cpu-optimization', 0), 1500); } else { throw new Error(data.error || 'CPU优化失败'); } } catch (error) { console.error('CPU优化失败:', error); this.showNotification('CPU优化失败: ' + error.message, 'error'); } } async optimizeMemory() { try { const response = await fetch('/api/system-optimizer/optimize-memory', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification(data.message || '内存优化完成', 'success'); this.updateOptimizationProgress('memory-optimization', data.progress || 100); await this.loadSystemOptimizer(); setTimeout(() => this.updateOptimizationProgress('memory-optimization', 0), 1500); } else { throw new Error(data.error || '内存优化失败'); } } catch (error) { console.error('内存优化失败:', error); this.showNotification('内存优化失败: ' + error.message, 'error'); } } async optimizeDisk() { try { const response = await fetch('/api/system-optimizer/optimize-disk', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification(data.message || '磁盘优化完成', 'success'); this.updateOptimizationProgress('disk-optimization', data.progress || 100); await this.loadSystemOptimizer(); setTimeout(() => this.updateOptimizationProgress('disk-optimization', 0), 1500); } else { throw new Error(data.error || '磁盘优化失败'); } } catch (error) { console.error('磁盘优化失败:', error); this.showNotification('磁盘优化失败: ' + error.message, 'error'); } } updateOptimizationProgress(elementId, progress) { const element = document.getElementById(elementId); if (element) { element.style.width = `${progress}%`; } } async saveSecuritySettings() { const settings = { input_validation: document.getElementById('input-validation').checked, rate_limiting: document.getElementById('rate-limiting').checked, sql_injection_protection: document.getElementById('sql-injection-protection').checked, xss_protection: document.getElementById('xss-protection').checked }; try { const response = await fetch('/api/system-optimizer/security-settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings) }); const data = await response.json(); if (data.success) { this.showNotification('安全设置已保存', 'success'); } else { throw new Error(data.error || '保存安全设置失败'); } } catch (error) { console.error('保存安全设置失败:', error); this.showNotification('保存安全设置失败: ' + error.message, 'error'); } } async saveTrafficSettings() { const settings = { request_limit: parseInt(document.getElementById('request-limit').value), concurrent_limit: parseInt(document.getElementById('concurrent-limit').value), ip_whitelist: document.getElementById('ip-whitelist').value.split('\n').filter(ip => ip.trim()) }; try { const response = await fetch('/api/system-optimizer/traffic-settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings) }); const data = await response.json(); if (data.success) { this.showNotification('流量设置已保存', 'success'); } else { throw new Error(data.error || '保存流量设置失败'); } } catch (error) { console.error('保存流量设置失败:', error); this.showNotification('保存流量设置失败: ' + error.message, 'error'); } } async saveCostSettings() { const settings = { monthly_budget_limit: parseFloat(document.getElementById('monthly-budget-limit').value), per_call_cost_limit: parseFloat(document.getElementById('per-call-cost-limit').value), auto_cost_control: document.getElementById('auto-cost-control').checked }; try { const response = await fetch('/api/system-optimizer/cost-settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings) }); const data = await response.json(); if (data.success) { this.showNotification('成本设置已保存', 'success'); } else { throw new Error(data.error || '保存成本设置失败'); } } catch (error) { console.error('保存成本设置失败:', error); this.showNotification('保存成本设置失败: ' + error.message, 'error'); } } async runHealthCheck() { try { const response = await fetch('/api/system-optimizer/health-check', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification('健康检查完成', 'success'); this.updateSystemStats(data); } else { throw new Error(data.error || '健康检查失败'); } } catch (error) { console.error('健康检查失败:', error); this.showNotification('健康检查失败: ' + error.message, 'error'); } } async refreshSystemStatus() { await this.loadSystemOptimizer(); this.showNotification('系统状态已刷新', 'success'); } async clearCache() { try { const response = await fetch('/api/system-optimizer/clear-cache', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification(data.message || '缓存已清理', 'success'); await this.loadSystemOptimizer(); } else { throw new Error(data.error || '清理缓存失败'); } } catch (error) { console.error('清理缓存失败:', error); this.showNotification('清理缓存失败: ' + error.message, 'error'); } } async optimizeAll() { try { const response = await fetch('/api/system-optimizer/optimize-all', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification(data.message || '一键优化完成', 'success'); await this.loadSystemOptimizer(); ['cpu-optimization','memory-optimization','disk-optimization'].forEach(id => this.updateOptimizationProgress(id, 100)); setTimeout(() => ['cpu-optimization','memory-optimization','disk-optimization'].forEach(id => this.updateOptimizationProgress(id, 0)), 1500); } else { throw new Error(data.error || '一键优化失败'); } } catch (error) { console.error('一键优化失败:', error); this.showNotification('一键优化失败: ' + error.message, 'error'); } } async loadSecuritySettings() { try { const response = await fetch('/api/system-optimizer/security-settings'); const data = await response.json(); if (data.success) { document.getElementById('input-validation').checked = data.input_validation || false; document.getElementById('rate-limiting').checked = data.rate_limiting || false; document.getElementById('sql-injection-protection').checked = data.sql_injection_protection || false; document.getElementById('xss-protection').checked = data.xss_protection || false; } } catch (error) { console.error('加载安全设置失败:', error); } } async loadTrafficSettings() { try { const response = await fetch('/api/system-optimizer/traffic-settings'); const data = await response.json(); if (data.success) { document.getElementById('request-limit').value = data.request_limit || 100; document.getElementById('concurrent-limit').value = data.concurrent_limit || 50; document.getElementById('ip-whitelist').value = (data.ip_whitelist || []).join('\n'); } } catch (error) { console.error('加载流量设置失败:', error); } } async loadCostSettings() { try { const response = await fetch('/api/system-optimizer/cost-settings'); const data = await response.json(); if (data.success) { document.getElementById('monthly-budget-limit').value = data.monthly_budget_limit || 1000; document.getElementById('per-call-cost-limit').value = data.per_call_cost_limit || 0.1; document.getElementById('auto-cost-control').checked = data.auto_cost_control || false; } } catch (error) { console.error('加载成本设置失败:', error); } } // 数据分析 async loadAnalytics() { try { const response = await fetch('/api/analytics'); const analytics = await response.json(); this.updateAnalyticsDisplay(analytics); this.initializeCharts(); } catch (error) { console.error('加载分析数据失败:', error); } } // 初始化图表 initializeCharts() { this.charts = {}; this.updateCharts(); } // 更新所有图表 async updateCharts() { try { const timeRange = document.getElementById('timeRange').value; const chartType = document.getElementById('chartType').value; const dataDimension = document.getElementById('dataDimension').value; // 获取数据 const response = await fetch(`/api/analytics?timeRange=${timeRange}&dimension=${dataDimension}`); const data = await response.json(); // 更新统计卡片 this.updateStatisticsCards(data); // 更新图表 this.updateMainChart(data, chartType); this.updateDistributionChart(data); this.updateTrendChart(data); this.updatePriorityChart(data); // 更新分析报告 this.updateAnalyticsReport(data); } catch (error) { console.error('更新图表失败:', error); this.showNotification('更新图表失败: ' + error.message, 'error'); } } // 更新统计卡片 updateStatisticsCards(data) { const total = data.workorders?.total || 0; const open = data.workorders?.open || 0; const resolved = data.workorders?.resolved || 0; const avgSatisfaction = data.satisfaction?.average || 0; document.getElementById('totalWorkorders').textContent = total; document.getElementById('openWorkorders').textContent = open; document.getElementById('resolvedWorkorders').textContent = resolved; document.getElementById('avgSatisfaction').textContent = avgSatisfaction.toFixed(1); // 更新进度条 if (total > 0) { document.getElementById('openProgress').style.width = `${(open / total) * 100}%`; document.getElementById('resolvedProgress').style.width = `${(resolved / total) * 100}%`; document.getElementById('satisfactionProgress').style.width = `${(avgSatisfaction / 5) * 100}%`; } } // 更新主图表 updateMainChart(data, chartType) { const ctx = document.getElementById('mainChart').getContext('2d'); // 销毁现有图表 if (this.charts.mainChart) { this.charts.mainChart.destroy(); } const chartData = this.prepareChartData(data, chartType); this.charts.mainChart = new Chart(ctx, { type: chartType, data: chartData, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: '数据分析趋势' }, legend: { display: true, position: 'top' } }, scales: chartType === 'pie' || chartType === 'doughnut' ? {} : { x: { display: true, title: { display: true, text: '时间' } }, y: { display: true, title: { display: true, text: '数量' } } } } }); } // 更新分布图表 updateDistributionChart(data) { const ctx = document.getElementById('distributionChart').getContext('2d'); if (this.charts.distributionChart) { this.charts.distributionChart.destroy(); } const categories = data.workorders?.by_category || {}; const labels = Object.keys(categories); const values = Object.values(categories); this.charts.distributionChart = new Chart(ctx, { type: 'doughnut', data: { labels: labels, datasets: [{ data: values, backgroundColor: [ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40' ] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: '工单分类分布' }, legend: { display: true, position: 'bottom' } } } }); } // 更新趋势图表 updateTrendChart(data) { const ctx = document.getElementById('trendChart').getContext('2d'); if (this.charts.trendChart) { this.charts.trendChart.destroy(); } const trendData = data.trend || []; const labels = trendData.map(item => item.date); const workorders = trendData.map(item => item.workorders); const alerts = trendData.map(item => item.alerts); this.charts.trendChart = new Chart(ctx, { type: 'line', data: { labels: labels, datasets: [{ label: '工单数量', data: workorders, borderColor: '#36A2EB', backgroundColor: 'rgba(54, 162, 235, 0.1)', tension: 0.4 }, { label: '预警数量', data: alerts, borderColor: '#FF6384', backgroundColor: 'rgba(255, 99, 132, 0.1)', tension: 0.4 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: '时间趋势分析' } }, scales: { x: { display: true, title: { display: true, text: '日期' } }, y: { display: true, title: { display: true, text: '数量' } } } } }); } // 更新优先级图表 updatePriorityChart(data) { const ctx = document.getElementById('priorityChart').getContext('2d'); if (this.charts.priorityChart) { this.charts.priorityChart.destroy(); } const priorities = data.workorders?.by_priority || {}; const labels = Object.keys(priorities).map(p => this.getPriorityText(p)); const values = Object.values(priorities); this.charts.priorityChart = new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [{ label: '工单数量', data: values, backgroundColor: [ '#28a745', // 低 - 绿色 '#ffc107', // 中 - 黄色 '#fd7e14', // 高 - 橙色 '#dc3545' // 紧急 - 红色 ] }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: '优先级分布' } }, scales: { y: { beginAtZero: true } } } }); } // 准备图表数据 prepareChartData(data, chartType) { const trendData = data.trend || []; const labels = trendData.map(item => item.date); const workorders = trendData.map(item => item.workorders); if (chartType === 'pie' || chartType === 'doughnut') { const categories = data.workorders?.by_category || {}; return { labels: Object.keys(categories), datasets: [{ data: Object.values(categories), backgroundColor: [ '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', '#FF9F40' ] }] }; } else { return { labels: labels, datasets: [{ label: '工单数量', data: workorders, borderColor: '#36A2EB', backgroundColor: 'rgba(54, 162, 235, 0.1)', tension: chartType === 'line' ? 0.4 : 0 }] }; } } // 导出图表 exportChart(chartId) { if (this.charts[chartId]) { const link = document.createElement('a'); link.download = `${chartId}_chart.png`; link.href = this.charts[chartId].toBase64Image(); link.click(); } } // 全屏图表 fullscreenChart(chartId) { // 这里可以实现全屏显示功能 this.showNotification('全屏功能开发中', 'info'); } // 导出报告 async exportReport() { try { const response = await fetch('/api/analytics/export'); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'analytics_report.xlsx'; link.click(); window.URL.revokeObjectURL(url); } catch (error) { console.error('导出报告失败:', error); this.showNotification('导出报告失败: ' + error.message, 'error'); } } // 打印报告 printReport() { window.print(); } // Agent执行历史相关功能 async refreshAgentHistory() { try { const response = await fetch('/api/agent/action-history?limit=20'); const data = await response.json(); if (data.success) { this.updateAgentExecutionHistory(data.history); this.showNotification(`已加载 ${data.count} 条执行历史`, 'success'); } else { throw new Error(data.error || '获取执行历史失败'); } } catch (error) { console.error('刷新Agent历史失败:', error); this.showNotification('刷新Agent历史失败: ' + error.message, 'error'); } } async triggerSampleAction() { try { const response = await fetch('/api/agent/trigger-sample', { method: 'POST', headers: { 'Content-Type': 'application/json', } }); const data = await response.json(); if (data.success) { this.showNotification(data.message, 'success'); // 刷新执行历史 await this.refreshAgentHistory(); } else { throw new Error(data.error || '触发示例动作失败'); } } catch (error) { console.error('触发示例动作失败:', error); this.showNotification('触发示例动作失败: ' + error.message, 'error'); } } async clearAgentHistory() { if (!confirm('确定要清空Agent执行历史吗?此操作不可恢复。')) { return; } try { const response = await fetch('/api/agent/clear-history', { method: 'POST', headers: { 'Content-Type': 'application/json', } }); const data = await response.json(); if (data.success) { this.showNotification(data.message, 'success'); // 清空显示 this.updateAgentExecutionHistory([]); } else { throw new Error(data.error || '清空历史失败'); } } catch (error) { console.error('清空Agent历史失败:', error); this.showNotification('清空Agent历史失败: ' + error.message, 'error'); } } updateAgentExecutionHistory(history) { const container = document.getElementById('agent-execution-history'); if (!history || history.length === 0) { container.innerHTML = `

暂无执行历史

`; return; } const historyHtml = history.map(record => { const startTime = new Date(record.start_time * 1000).toLocaleString(); const endTime = new Date(record.end_time * 1000).toLocaleString(); const duration = Math.round((record.end_time - record.start_time) * 100) / 100; const priorityColor = { 5: 'danger', 4: 'warning', 3: 'info', 2: 'secondary', 1: 'light' }[record.priority] || 'secondary'; const confidenceColor = record.confidence >= 0.8 ? 'success' : record.confidence >= 0.5 ? 'warning' : 'danger'; return `
${record.description}
优先级 ${record.priority} 置信度 ${(record.confidence * 100).toFixed(0)}% ${record.success ? '成功' : '失败'}
开始: ${startTime} | 耗时: ${duration}秒
${record.action_type}
${record.result && record.result.message ? `
结果: ${record.result.message}
` : ''}
`; }).join(''); container.innerHTML = historyHtml; } // 更新分析报告 updateAnalyticsReport(data) { const reportContainer = document.getElementById('analytics-report'); if (!reportContainer) return; const summary = data.summary || {}; const workorders = data.workorders || {}; const satisfaction = data.satisfaction || {}; const alerts = data.alerts || {}; const performance = data.performance || {}; const reportHtml = `
工单统计概览
总工单数 ${workorders.total || 0}
待处理 ${workorders.open || 0}
处理中 ${workorders.in_progress || 0}
已解决 ${workorders.resolved || 0}
已关闭 ${workorders.closed || 0}
满意度分析
平均满意度 ${satisfaction.average || 0}/5.0
5星评价 ${satisfaction.distribution?.['5'] || 0} 个
4星评价 ${satisfaction.distribution?.['4'] || 0} 个
3星评价 ${satisfaction.distribution?.['3'] || 0} 个
2星及以下 ${(satisfaction.distribution?.['2'] || 0) + (satisfaction.distribution?.['1'] || 0)} 个
预警统计
总预警数 ${alerts.total || 0}
活跃预警 ${alerts.active || 0}
已解决 ${alerts.resolved || 0}
性能指标
响应时间 ${performance.response_time || 0} 秒
系统可用性 ${performance.uptime || 0}%
错误率 ${performance.error_rate || 0}%
吞吐量 ${performance.throughput || 0} 请求/小时
关键指标总结
${summary.resolution_rate || 0}%

解决率

${summary.avg_satisfaction || 0}

平均满意度

${summary.active_alerts || 0}

活跃预警

${summary.total_workorders || 0}

总工单数

`; reportContainer.innerHTML = reportHtml; } updateAnalyticsDisplay(analytics) { // 更新分析报告 const reportContainer = document.getElementById('analytics-report'); if (analytics.summary) { reportContainer.innerHTML = `
性能指标
趋势分析
`; } // 更新类别分布图 if (analytics.category_distribution && this.charts.category) { const labels = Object.keys(analytics.category_distribution); const data = Object.values(analytics.category_distribution); this.charts.category.data.labels = labels; this.charts.category.data.datasets[0].data = data; this.charts.category.update(); } } // 系统设置 async loadSettings() { try { const response = await fetch('/api/settings'); const settings = await response.json(); this.updateSettingsDisplay(settings); } catch (error) { console.error('加载设置失败:', error); } } updateSettingsDisplay(settings) { if (settings.api_timeout !== undefined) document.getElementById('api-timeout').value = settings.api_timeout; if (settings.max_history !== undefined) document.getElementById('max-history').value = settings.max_history; if (settings.refresh_interval !== undefined) document.getElementById('refresh-interval').value = settings.refresh_interval; if (settings.auto_monitoring !== undefined) document.getElementById('auto-monitoring').checked = settings.auto_monitoring; if (settings.agent_mode !== undefined) document.getElementById('agent-mode').checked = settings.agent_mode; // 新增:API与模型、端口、日志级别(如页面存在对应输入框则填充) const map = [ ['api-provider','api_provider'], ['api-base-url','api_base_url'], ['api-key','api_key'], ['model-name','model_name'], ['model-temperature','model_temperature'], ['model-max-tokens','model_max_tokens'], ['server-port','server_port'], ['websocket-port','websocket_port'], ['log-level','log_level'] ]; map.forEach(([id, key]) => { const el = document.getElementById(id); if (el && settings[key] !== undefined) el.value = settings[key]; }); // 更新温度滑块显示值 const tempSlider = document.getElementById('model-temperature'); const tempValue = document.getElementById('temperature-value'); if (tempSlider && tempValue) { tempSlider.addEventListener('input', function() { tempValue.textContent = this.value; }); tempValue.textContent = tempSlider.value; } // 更新服务状态显示 this.updateServiceStatus(settings); } async saveSystemSettings() { const settings = { api_timeout: parseInt(document.getElementById('api-timeout').value), max_history: parseInt(document.getElementById('max-history').value), refresh_interval: parseInt(document.getElementById('refresh-interval').value), auto_monitoring: document.getElementById('auto-monitoring').checked, agent_mode: document.getElementById('agent-mode').checked, api_provider: document.getElementById('api-provider')?.value || '', api_base_url: document.getElementById('api-base-url')?.value || '', api_key: document.getElementById('api-key')?.value || '', model_name: document.getElementById('model-name')?.value || '', model_temperature: parseFloat(document.getElementById('model-temperature')?.value || 0.7), model_max_tokens: parseInt(document.getElementById('model-max-tokens')?.value || 1000), server_port: parseInt(document.getElementById('server-port')?.value || 5000), websocket_port: parseInt(document.getElementById('websocket-port')?.value || 8765), log_level: document.getElementById('log-level')?.value || 'INFO' }; try { const response = await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings) }); const data = await response.json(); if (data.success) { this.showNotification('设置保存成功', 'success'); } else { this.showNotification('保存设置失败', 'error'); } } catch (error) { console.error('保存设置失败:', error); this.showNotification('保存设置失败', 'error'); } } // 更新服务状态显示 updateServiceStatus(settings) { // 更新仪表板服务状态卡片 if (settings.current_server_port !== undefined) { const webPortEl = document.getElementById('web-port-status'); if (webPortEl) webPortEl.textContent = settings.current_server_port; } if (settings.current_websocket_port !== undefined) { const wsPortEl = document.getElementById('ws-port-status'); if (wsPortEl) wsPortEl.textContent = settings.current_websocket_port; } if (settings.log_level !== undefined) { const logLevelEl = document.getElementById('log-level-status'); if (logLevelEl) logLevelEl.textContent = settings.log_level; } if (settings.uptime_seconds !== undefined) { const uptimeEl = document.getElementById('uptime-status'); if (uptimeEl) { const hours = Math.floor(settings.uptime_seconds / 3600); const minutes = Math.floor((settings.uptime_seconds % 3600) / 60); uptimeEl.textContent = `${hours}小时${minutes}分钟`; } } // 更新系统设置页面的当前端口显示 const currentPortEl = document.getElementById('current-server-port'); if (currentPortEl && settings.current_server_port !== undefined) { currentPortEl.textContent = settings.current_server_port; } } // 刷新服务状态 async refreshServiceStatus() { try { const response = await fetch('/api/settings'); const settings = await response.json(); this.updateServiceStatus(settings); this.showNotification('服务状态已刷新', 'success'); } catch (error) { console.error('刷新服务状态失败:', error); this.showNotification('刷新服务状态失败', 'error'); } } // 测试API连接 async testApiConnection() { try { const apiProvider = document.getElementById('api-provider').value; const apiBaseUrl = document.getElementById('api-base-url').value; const apiKey = document.getElementById('api-key').value; const modelName = document.getElementById('model-name').value; const response = await fetch('/api/test/connection', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ api_provider: apiProvider, api_base_url: apiBaseUrl, api_key: apiKey, model_name: modelName }) }); const result = await response.json(); if (result.success) { this.showNotification(`API连接测试成功: ${result.message}`, 'success'); } else { this.showNotification(`API连接测试失败: ${result.error}`, 'error'); } } catch (error) { console.error('API连接测试失败:', error); this.showNotification('API连接测试失败', 'error'); } } // 测试模型回答 async testModelResponse() { try { const testMessage = prompt('请输入测试消息:', '你好,请简单介绍一下你自己'); if (!testMessage) return; const response = await fetch('/api/test/model', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ test_message: testMessage }) }); const result = await response.json(); if (result.success) { const message = `模型回答测试成功:\n\n问题: ${result.test_message}\n\n回答: ${result.response}\n\n响应时间: ${result.response_time}`; alert(message); this.showNotification('模型回答测试成功', 'success'); } else { this.showNotification(`模型回答测试失败: ${result.error}`, 'error'); } } catch (error) { console.error('模型回答测试失败:', error); this.showNotification('模型回答测试失败', 'error'); } } async loadSystemInfo() { try { const response = await fetch('/api/system/info'); const info = await response.json(); this.updateSystemInfoDisplay(info); } catch (error) { console.error('加载系统信息失败:', error); } } updateSystemInfoDisplay(info) { const container = document.getElementById('system-info'); container.innerHTML = `
系统版本: ${info.version || '1.0.0'}
Python版本: ${info.python_version || '未知'}
数据库: ${info.database || 'SQLite'}
运行时间: ${info.uptime || '未知'}
内存使用: ${info.memory_usage || '0'} MB
`; } // 工具函数 getLevelText(level) { const levelMap = { 'critical': '严重', 'error': '错误', 'warning': '警告', 'info': '信息' }; return levelMap[level] || level; } getTypeText(type) { const typeMap = { 'performance': '性能', 'quality': '质量', 'volume': '量级', 'system': '系统', 'business': '业务' }; return typeMap[type] || type; } getAlertColor(level) { const colorMap = { 'critical': 'danger', 'error': 'danger', 'warning': 'warning', 'info': 'info' }; return colorMap[level] || 'secondary'; } getPriorityText(priority) { const priorityMap = { 'low': '低', 'medium': '中', 'high': '高', 'urgent': '紧急' }; return priorityMap[priority] || priority; } getPriorityColor(priority) { const colorMap = { 'low': 'secondary', 'medium': 'primary', 'high': 'warning', 'urgent': 'danger' }; return colorMap[priority] || 'secondary'; } getStatusText(status) { const statusMap = { 'open': '待处理', 'in_progress': '处理中', 'resolved': '已解决', 'closed': '已关闭' }; return statusMap[status] || status; } getStatusColor(status) { const colorMap = { 'open': 'warning', 'in_progress': 'info', 'resolved': 'success', 'closed': 'secondary' }; return colorMap[status] || 'secondary'; } formatTime(timestamp) { const date = new Date(timestamp); const now = new Date(); const diff = now - date; if (diff < 60000) { // 1分钟内 return '刚刚'; } else if (diff < 3600000) { // 1小时内 return `${Math.floor(diff / 60000)}分钟前`; } else if (diff < 86400000) { // 1天内 return `${Math.floor(diff / 3600000)}小时前`; } else { return date.toLocaleDateString(); } } showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification alert alert-${type === 'error' ? 'danger' : type} alert-dismissible fade show`; notification.innerHTML = ` ${message} `; document.body.appendChild(notification); setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 3000); } getSimilarityExplanation(percent) { if (percent >= 95) { return "语义高度相似,AI建议与人工描述基本一致,建议自动审批"; } else if (percent >= 90) { return "语义较为相似,AI建议与人工描述大体一致,建议人工审核"; } else if (percent >= 80) { return "语义部分相似,AI建议与人工描述有一定差异,需要人工判断"; } else if (percent >= 60) { return "语义相似度较低,AI建议与人工描述差异较大,建议使用人工描述"; } else { return "语义差异很大,AI建议与人工描述差异很大,优先使用人工描述"; } } getSimilarityMessage(percent, approved, useHumanResolution = false) { if (useHumanResolution) { return `人工描述已保存!语义相似度: ${percent}%,AI准确率低于90%,将使用人工描述入库`; } else if (approved) { return `人工描述已保存!语义相似度: ${percent}%,已自动审批入库`; } else if (percent >= 90) { return `人工描述已保存!语义相似度: ${percent}%,建议人工审核后审批`; } else if (percent >= 80) { return `人工描述已保存!语义相似度: ${percent}%,需要人工判断是否审批`; } else { return `人工描述已保存!语义相似度: ${percent}%,建议使用人工描述入库`; } } showCreateWorkOrderModal() { const modal = new bootstrap.Modal(document.getElementById('createWorkOrderModal')); modal.show(); } // 新增Agent对话功能 async sendAgentMessage() { const messageInput = document.getElementById('agent-message-input'); const message = messageInput.value.trim(); if (!message) { this.showNotification('请输入消息', 'warning'); return; } try { // 显示发送状态 const sendBtn = document.getElementById('send-agent-message'); const originalText = sendBtn.innerHTML; sendBtn.innerHTML = '发送中...'; sendBtn.disabled = true; const response = await fetch('/api/agent/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message, context: { user_id: 'admin', session_id: `agent_session_${Date.now()}` } }) }); const data = await response.json(); if (data.success) { this.showNotification('Agent响应成功', 'success'); // 清空输入框 messageInput.value = ''; // 刷新执行历史 await this.loadAgentExecutionHistory(); } else { this.showNotification('Agent响应失败: ' + (data.error || '未知错误'), 'error'); } } catch (error) { console.error('发送Agent消息失败:', error); this.showNotification('发送Agent消息失败: ' + error.message, 'error'); } finally { // 恢复按钮状态 const sendBtn = document.getElementById('send-agent-message'); sendBtn.innerHTML = '发送'; sendBtn.disabled = false; } } // 清空Agent对话 clearAgentChat() { document.getElementById('agent-message-input').value = ''; this.showNotification('对话已清空', 'info'); } // 加载Agent执行历史 async loadAgentExecutionHistory() { try { const response = await fetch('/api/agent/action-history?limit=10'); const data = await response.json(); if (data.success) { this.updateExecutionHistory(data.history); } } catch (error) { console.error('加载Agent执行历史失败:', error); } } // 触发示例动作 async triggerSampleAction() { try { const response = await fetch('/api/agent/trigger-sample', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification('示例动作执行成功', 'success'); await this.loadAgentExecutionHistory(); } else { this.showNotification('示例动作执行失败: ' + (data.error || '未知错误'), 'error'); } } catch (error) { console.error('触发示例动作失败:', error); this.showNotification('触发示例动作失败: ' + error.message, 'error'); } } // 清空Agent历史 async clearAgentHistory() { if (!confirm('确定要清空Agent执行历史吗?')) { return; } try { const response = await fetch('/api/agent/clear-history', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification('Agent历史已清空', 'success'); await this.loadAgentExecutionHistory(); } else { this.showNotification('清空Agent历史失败: ' + (data.error || '未知错误'), 'error'); } } catch (error) { console.error('清空Agent历史失败:', error); this.showNotification('清空Agent历史失败: ' + error.message, 'error'); } } } // 飞书同步管理器 class FeishuSyncManager { constructor() { this.loadConfig(); this.refreshStatus(); } async loadConfig() { try { const response = await fetch('/api/feishu-sync/config'); const data = await response.json(); if (data.success) { const config = data.config; document.getElementById('appId').value = config.feishu.app_id || ''; document.getElementById('appSecret').value = ''; document.getElementById('appToken').value = config.feishu.app_token || ''; document.getElementById('tableId').value = config.feishu.table_id || ''; // 显示配置状态 const statusBadge = config.feishu.status === 'active' ? '已配置' : '未配置'; // 可以在这里添加状态显示 } } catch (error) { console.error('加载配置失败:', error); } } async saveConfig() { const config = { app_id: document.getElementById('appId').value, app_secret: document.getElementById('appSecret').value, app_token: document.getElementById('appToken').value, table_id: document.getElementById('tableId').value }; if (!config.app_id || !config.app_secret || !config.app_token || !config.table_id) { this.showNotification('请填写完整的配置信息', 'error'); return; } try { const response = await fetch('/api/feishu-sync/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); const data = await response.json(); if (data.success) { this.showNotification('配置保存成功', 'success'); } else { this.showNotification('配置保存失败: ' + data.error, 'error'); } } catch (error) { this.showNotification('配置保存失败: ' + error.message, 'error'); } } async testConnection() { try { this.showNotification('正在测试连接...', 'info'); const response = await fetch('/api/feishu-sync/test-connection'); const data = await response.json(); if (data.success) { this.showNotification('飞书连接正常', 'success'); } else { this.showNotification('连接失败: ' + data.error, 'error'); } } catch (error) { this.showNotification('连接测试失败: ' + error.message, 'error'); } } async syncFromFeishu() { try { const limit = document.getElementById('syncLimit').value; this.showNotification('开始从飞书同步数据...', 'info'); this.showProgress(true); const response = await fetch('/api/feishu-sync/sync-from-feishu', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ generate_ai_suggestions: false, limit: parseInt(limit) }) }); const data = await response.json(); if (data.success) { this.showNotification(data.message, 'success'); this.addSyncLog(data.message); this.refreshStatus(); } else { this.showNotification('同步失败: ' + data.error, 'error'); this.addSyncLog('同步失败: ' + data.error); } } catch (error) { this.showNotification('同步失败: ' + error.message, 'error'); this.addSyncLog('同步失败: ' + error.message); } finally { this.showProgress(false); } } async syncWithAI() { try { const limit = document.getElementById('syncLimit').value; this.showNotification('开始同步数据并生成AI建议...', 'info'); this.showProgress(true); const response = await fetch('/api/feishu-sync/sync-from-feishu', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ generate_ai_suggestions: true, limit: parseInt(limit) }) }); const data = await response.json(); if (data.success) { this.showNotification(data.message, 'success'); this.addSyncLog(data.message); this.refreshStatus(); } else { this.showNotification('同步失败: ' + data.error, 'error'); this.addSyncLog('同步失败: ' + data.error); } } catch (error) { this.showNotification('同步失败: ' + error.message, 'error'); this.addSyncLog('同步失败: ' + error.message); } finally { this.showProgress(false); } } async previewFeishuData() { try { this.showNotification('正在获取飞书数据预览...', 'info'); const response = await fetch('/api/feishu-sync/preview-feishu-data'); const data = await response.json(); if (data.success) { this.displayPreviewData(data.preview_data); this.showNotification(`获取到 ${data.total_count} 条预览数据`, 'success'); } else { this.showNotification('获取预览数据失败: ' + data.error, 'error'); } } catch (error) { this.showNotification('获取预览数据失败: ' + error.message, 'error'); } } displayPreviewData(data) { const tbody = document.querySelector('#previewTable tbody'); tbody.innerHTML = ''; data.forEach(item => { const row = document.createElement('tr'); row.innerHTML = ` ${item.record_id} ${item.fields['TR Number'] || '-'} ${item.fields['TR Description'] || '-'} ${item.fields['Type of problem'] || '-'} ${item.fields['Source'] || '-'} ${item.fields['TR (Priority/Status)'] || '-'} `; tbody.appendChild(row); }); document.getElementById('previewSection').style.display = 'block'; } async refreshStatus() { try { const response = await fetch('/api/feishu-sync/status'); const data = await response.json(); if (data.success) { const status = data.status; document.getElementById('totalLocalWorkorders').textContent = status.total_local_workorders || 0; document.getElementById('syncedWorkorders').textContent = status.synced_workorders || 0; document.getElementById('unsyncedWorkorders').textContent = status.unsynced_workorders || 0; } } catch (error) { console.error('刷新状态失败:', error); } } showProgress(show) { const progress = document.getElementById('syncProgress'); if (show) { progress.style.display = 'block'; const bar = progress.querySelector('.progress-bar'); bar.style.width = '100%'; } else { setTimeout(() => { progress.style.display = 'none'; const bar = progress.querySelector('.progress-bar'); bar.style.width = '0%'; }, 1000); } } addSyncLog(message) { const log = document.getElementById('syncLog'); const timestamp = new Date().toLocaleString(); const logEntry = document.createElement('div'); logEntry.innerHTML = `[${timestamp}] ${message}`; if (log.querySelector('.text-muted')) { log.innerHTML = ''; } log.appendChild(logEntry); log.scrollTop = log.scrollHeight; } async exportConfig() { try { const response = await fetch('/api/feishu-sync/config/export'); const data = await response.json(); if (data.success) { // 创建下载链接 const blob = new Blob([data.config], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `feishu_config_${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.showNotification('配置导出成功', 'success'); } else { this.showNotification('配置导出失败: ' + data.error, 'error'); } } catch (error) { this.showNotification('配置导出失败: ' + error.message, 'error'); } } showImportModal() { const modal = new bootstrap.Modal(document.getElementById('importConfigModal')); modal.show(); } async importConfig() { try { const configJson = document.getElementById('configJson').value.trim(); if (!configJson) { this.showNotification('请输入配置JSON数据', 'warning'); return; } const response = await fetch('/api/feishu-sync/config/import', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config: configJson }) }); const data = await response.json(); if (data.success) { this.showNotification('配置导入成功', 'success'); this.loadConfig(); this.refreshStatus(); // 关闭模态框 const modal = bootstrap.Modal.getInstance(document.getElementById('importConfigModal')); modal.hide(); document.getElementById('configJson').value = ''; } else { this.showNotification('配置导入失败: ' + data.error, 'error'); } } catch (error) { this.showNotification('配置导入失败: ' + error.message, 'error'); } } async resetConfig() { if (confirm('确定要重置所有配置吗?此操作不可撤销!')) { try { const response = await fetch('/api/feishu-sync/config/reset', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showNotification('配置重置成功', 'success'); this.loadConfig(); this.refreshStatus(); } else { this.showNotification('配置重置失败: ' + data.error, 'error'); } } catch (error) { this.showNotification('配置重置失败: ' + error.message, 'error'); } } } async createWorkorder(recordId) { if (confirm(`确定要从飞书记录 ${recordId} 创建工单吗?`)) { try { this.showNotification('正在创建工单...', 'info'); const response = await fetch('/api/feishu-sync/create-workorder', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ record_id: recordId }) }); const data = await response.json(); if (data.success) { this.showNotification(data.message, 'success'); // 刷新工单列表(如果用户在工单页面) if (typeof window.refreshWorkOrders === 'function') { window.refreshWorkOrders(); } } else { this.showNotification('创建工单失败: ' + data.message, 'error'); } } catch (error) { this.showNotification('创建工单失败: ' + error.message, 'error'); } } } showNotification(message, type = 'info') { const container = document.getElementById('notificationContainer'); const alert = document.createElement('div'); alert.className = `alert alert-${type === 'error' ? 'danger' : type} alert-dismissible fade show`; alert.innerHTML = ` ${message} `; container.appendChild(alert); setTimeout(() => { if (alert.parentNode) { alert.parentNode.removeChild(alert); } }, 5000); } } // 初始化应用 let dashboard; let feishuSync; document.addEventListener('DOMContentLoaded', () => { dashboard = new TSPDashboard(); feishuSync = new FeishuSyncManager(); });