Files
tsp-assistant/src/web/static/js/dashboard.js
2025-09-06 21:06:18 +08:00

1508 lines
57 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// TSP智能助手综合管理平台前端脚本
class TSPDashboard {
constructor() {
this.currentTab = 'dashboard';
this.charts = {};
this.refreshIntervals = {};
this.websocket = null;
this.sessionId = null;
this.isAgentMode = true;
this.init();
this.restorePageState();
}
init() {
this.bindEvents();
this.loadInitialData();
this.startAutoRefresh();
this.initCharts();
}
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);
});
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();
});
}
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 '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() {
// 每5秒刷新健康状态
this.refreshIntervals.health = setInterval(() => {
this.loadHealth();
}, 5000);
// 每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.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}%`;
// 更新性能图表
this.updatePerformanceChart(sessions, alerts, workorders);
} 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
}
});
}
}
updatePerformanceChart(sessions, alerts, workorders) {
if (!this.charts.performance) return;
const now = new Date();
const labels = [];
const sessionData = [];
const alertData = [];
// 生成过去24小时的数据点
for (let i = 23; i >= 0; i--) {
const time = new Date(now.getTime() - i * 60 * 60 * 1000);
labels.push(time.getHours() + ':00');
sessionData.push(Math.floor(Math.random() * 10) + 5); // 模拟数据
alertData.push(Math.floor(Math.random() * 5)); // 模拟数据
}
this.charts.performance.data.labels = labels;
this.charts.performance.data.datasets[0].data = sessionData;
this.charts.performance.data.datasets[1].data = alertData;
this.charts.performance.update();
}
// 对话功能
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 = '<i class="fas fa-circle me-1"></i>已连接';
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 = '<i class="fas fa-circle me-1"></i>未连接';
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 = '';
// 发送消息到服务器
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.addMessage('assistant', data.response, data.knowledge_used);
} else {
this.addMessage('assistant', '抱歉,处理您的消息时出现了错误。', null, true);
}
} catch (error) {
console.error('发送消息失败:', error);
this.addMessage('assistant', '网络连接错误,请稍后重试。', null, true);
}
}
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' ? '<i class="fas fa-user"></i>' : '<i class="fas fa-robot"></i>';
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content';
contentDiv.innerHTML = `
<div>${content}</div>
<div class="message-time">${new Date().toLocaleTimeString()}</div>
`;
if (knowledgeUsed && knowledgeUsed.length > 0) {
const knowledgeDiv = document.createElement('div');
knowledgeDiv.className = 'knowledge-info';
knowledgeDiv.innerHTML = `
<i class="fas fa-lightbulb me-1"></i>
使用了知识库: ${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 response = await fetch('/api/agent/status');
const data = await response.json();
if (data.success) {
document.getElementById('agent-current-state').textContent = data.status || '未知';
document.getElementById('agent-active-goals').textContent = data.active_goals || 0;
document.getElementById('agent-available-tools').textContent = data.available_tools || 0;
// 更新工具列表
this.updateToolsList(data.tools || []);
// 更新执行历史
this.updateExecutionHistory(data.execution_history || []);
}
} catch (error) {
console.error('加载Agent数据失败:', error);
}
}
updateToolsList(tools) {
const toolsList = document.getElementById('tools-list');
if (tools.length === 0) {
toolsList.innerHTML = '<div class="empty-state"><i class="fas fa-tools"></i><p>暂无工具</p></div>';
return;
}
const toolsHtml = tools.map(tool => `
<div class="d-flex justify-content-between align-items-center mb-2">
<div>
<strong>${tool.name}</strong>
<br>
<small class="text-muted">使用次数: ${tool.usage_count || 0}</small>
</div>
<div>
<span class="badge ${tool.success_rate >= 0.8 ? 'bg-success' : 'bg-warning'}">
${Math.round((tool.success_rate || 0) * 100)}%
</span>
</div>
</div>
`).join('');
toolsList.innerHTML = toolsHtml;
}
updateExecutionHistory(history) {
const historyContainer = document.getElementById('agent-execution-history');
if (history.length === 0) {
historyContainer.innerHTML = '<div class="empty-state"><i class="fas fa-history"></i><p>暂无执行历史</p></div>';
return;
}
const historyHtml = history.slice(-5).map(item => `
<div class="border-bottom pb-2 mb-2">
<div class="d-flex justify-content-between">
<strong>${item.type || '未知任务'}</strong>
<small class="text-muted">${new Date(item.timestamp).toLocaleString()}</small>
</div>
<div class="text-muted small">${item.description || '无描述'}</div>
<div class="mt-1">
<span class="badge ${item.success ? 'bg-success' : 'bg-danger'}">
${item.success ? '成功' : '失败'}
</span>
</div>
</div>
`).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 = `
<div class="empty-state">
<i class="fas fa-check-circle"></i>
<h5>暂无活跃预警</h5>
<p>系统运行正常,没有需要处理的预警</p>
</div>
`;
return;
}
const alertsHtml = alerts.map(alert => `
<div class="alert-item ${alert.level}">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1">
<div class="d-flex align-items-center mb-2">
<span class="badge bg-${this.getAlertColor(alert.level)} me-2">${this.getLevelText(alert.level)}</span>
<span class="fw-bold">${alert.rule_name || '未知规则'}</span>
<span class="ms-auto text-muted small">${this.formatTime(alert.created_at)}</span>
</div>
<div class="alert-message mb-2">${alert.message}</div>
<div class="alert-meta text-muted small">
类型: ${this.getTypeText(alert.alert_type)} |
级别: ${this.getLevelText(alert.level)}
</div>
</div>
<div class="ms-3">
<button class="btn btn-sm btn-outline-success" onclick="dashboard.resolveAlert(${alert.id})">
<i class="fas fa-check me-1"></i>解决
</button>
</div>
</div>
</div>
`).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 = '<div class="empty-state"><i class="fas fa-database"></i><p>暂无知识条目</p></div>';
return;
}
const knowledgeHtml = knowledge.map(item => `
<div class="knowledge-item">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1">
<h6 class="mb-1">${item.question}</h6>
<p class="text-muted mb-2">${item.answer}</p>
<div class="d-flex gap-3">
<small class="text-muted">分类: ${item.category}</small>
<small class="text-muted">置信度: ${Math.round(item.confidence_score * 100)}%</small>
<small class="text-muted">使用次数: ${item.usage_count || 0}</small>
<span class="badge ${item.is_verified ? 'bg-success' : 'bg-warning'}">
${item.is_verified ? '已验证' : '未验证'}
</span>
</div>
</div>
<div class="ms-3">
<div class="btn-group" role="group">
${item.is_verified ?
`<button class="btn btn-sm btn-outline-warning" onclick="dashboard.unverifyKnowledge(${item.id})" title="取消验证">
<i class="fas fa-times-circle"></i>
</button>` :
`<button class="btn btn-sm btn-outline-success" onclick="dashboard.verifyKnowledge(${item.id})" title="验证">
<i class="fas fa-check-circle"></i>
</button>`
}
<button class="btn btn-sm btn-outline-danger" onclick="dashboard.deleteKnowledge(${item.id})" title="删除">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>
`).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 = `
<div class="d-flex justify-content-between align-items-center">
<div>
<small class="text-muted">共 ${total} 条知识,第 ${page} / ${total_pages} 页</small>
</div>
<nav>
<ul class="pagination pagination-sm mb-0">
`;
// 上一页
if (page > 1) {
paginationHtml += `<li class="page-item"><a class="page-link" href="#" onclick="dashboard.loadKnowledge(${page - 1})">上一页</a></li>`;
}
// 页码
const startPage = Math.max(1, page - 2);
const endPage = Math.min(total_pages, page + 2);
for (let i = startPage; i <= endPage; i++) {
const activeClass = i === page ? 'active' : '';
paginationHtml += `<li class="page-item ${activeClass}"><a class="page-link" href="#" onclick="dashboard.loadKnowledge(${i})">${i}</a></li>`;
}
// 下一页
if (page < total_pages) {
paginationHtml += `<li class="page-item"><a class="page-link" href="#" onclick="dashboard.loadKnowledge(${page + 1})">下一页</a></li>`;
}
paginationHtml += `
</ul>
</nav>
</div>
`;
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 = '<div class="empty-state"><i class="fas fa-tasks"></i><p>暂无工单</p></div>';
return;
}
const workordersHtml = workorders.map(workorder => `
<div class="work-order-item">
<div class="d-flex justify-content-between align-items-start">
<div class="flex-grow-1">
<h6 class="mb-1">${workorder.title}</h6>
<p class="text-muted mb-2">${workorder.description}</p>
<div class="d-flex gap-3">
<span class="badge bg-${this.getPriorityColor(workorder.priority)}">${this.getPriorityText(workorder.priority)}</span>
<span class="badge bg-${this.getStatusColor(workorder.status)}">${this.getStatusText(workorder.status)}</span>
<small class="text-muted">分类: ${workorder.category}</small>
<small class="text-muted">创建时间: ${new Date(workorder.created_at).toLocaleString()}</small>
</div>
</div>
<div class="ms-3">
<button class="btn btn-sm btn-outline-primary" onclick="dashboard.updateWorkOrder(${workorder.id})">
<i class="fas fa-edit"></i>
</button>
</div>
</div>
</div>
`).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();
this.loadWorkOrders();
} else {
this.showNotification('创建工单失败', 'error');
}
} catch (error) {
console.error('创建工单失败:', error);
this.showNotification('创建工单失败', 'error');
}
}
// 数据分析
async loadAnalytics() {
try {
const response = await fetch('/api/analytics');
const analytics = await response.json();
this.updateAnalyticsDisplay(analytics);
} catch (error) {
console.error('加载分析数据失败:', error);
}
}
updateAnalyticsDisplay(analytics) {
// 更新分析报告
const reportContainer = document.getElementById('analytics-report');
if (analytics.summary) {
reportContainer.innerHTML = `
<div class="row">
<div class="col-md-6">
<h6>性能指标</h6>
<ul class="list-unstyled">
<li>总工单数: ${analytics.summary.total_orders || 0}</li>
<li>解决率: ${Math.round((analytics.summary.resolution_rate || 0) * 100)}%</li>
<li>平均解决时间: ${analytics.summary.avg_resolution_time_hours || 0}小时</li>
<li>平均满意度: ${analytics.summary.avg_satisfaction || 0}</li>
</ul>
</div>
<div class="col-md-6">
<h6>趋势分析</h6>
<ul class="list-unstyled">
<li>工单趋势: ${analytics.summary.trends?.orders_trend ? '上升' : '下降'}</li>
<li>满意度趋势: ${analytics.summary.trends?.satisfaction_trend ? '上升' : '下降'}</li>
<li>解决时间趋势: ${analytics.summary.trends?.resolution_time_trend ? '上升' : '下降'}</li>
</ul>
</div>
</div>
`;
}
// 更新类别分布图
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) document.getElementById('api-timeout').value = settings.api_timeout;
if (settings.max_history) document.getElementById('max-history').value = settings.max_history;
if (settings.refresh_interval) 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;
}
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
};
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');
}
}
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 = `
<div class="mb-3">
<strong>系统版本:</strong> ${info.version || '1.0.0'}
</div>
<div class="mb-3">
<strong>Python版本:</strong> ${info.python_version || '未知'}
</div>
<div class="mb-3">
<strong>数据库:</strong> ${info.database || 'SQLite'}
</div>
<div class="mb-3">
<strong>运行时间:</strong> ${info.uptime || '未知'}
</div>
<div class="mb-3">
<strong>内存使用:</strong> ${info.memory_usage || '0'} MB
</div>
`;
}
// 工具函数
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}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(notification);
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 3000);
}
showCreateWorkOrderModal() {
const modal = new bootstrap.Modal(document.getElementById('createWorkOrderModal'));
modal.show();
}
}
// 初始化应用
let dashboard;
document.addEventListener('DOMContentLoaded', () => {
dashboard = new TSPDashboard();
});