Files
assist/src/web/static/js/chat.js

411 lines
15 KiB
JavaScript
Raw Normal View History

2025-09-06 21:06:18 +08:00
// 实时对话前端脚本
class ChatClient {
constructor() {
this.websocket = null;
this.sessionId = null;
this.isConnected = false;
this.messageCount = 0;
this.init();
}
init() {
this.bindEvents();
this.updateConnectionStatus(false);
}
bindEvents() {
// 开始对话
document.getElementById('start-chat').addEventListener('click', () => this.startChat());
// 结束对话
document.getElementById('end-chat').addEventListener('click', () => this.endChat());
// 发送消息
document.getElementById('send-button').addEventListener('click', () => this.sendMessage());
// 回车发送
document.getElementById('message-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
this.sendMessage();
}
});
// 创建工单
document.getElementById('create-work-order').addEventListener('click', () => this.showWorkOrderModal());
document.getElementById('create-work-order-btn').addEventListener('click', () => this.createWorkOrder());
// 快速操作按钮
document.querySelectorAll('.quick-action-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const message = e.target.getAttribute('data-message');
document.getElementById('message-input').value = message;
this.sendMessage();
});
});
}
async startChat() {
try {
// 连接WebSocket
await this.connectWebSocket();
// 创建会话
const userId = document.getElementById('user-id').value || 'anonymous';
const workOrderId = document.getElementById('work-order-id').value || null;
const response = await this.sendWebSocketMessage({
type: 'create_session',
user_id: userId,
work_order_id: workOrderId ? parseInt(workOrderId) : null
});
if (response.type === 'session_created') {
this.sessionId = response.session_id;
this.updateSessionInfo();
this.enableChat();
this.addSystemMessage('对话已开始,请描述您的问题。');
} else {
this.showError('创建会话失败');
}
} catch (error) {
console.error('启动对话失败:', error);
this.showError('启动对话失败: ' + error.message);
}
}
async endChat() {
try {
if (this.sessionId) {
await this.sendWebSocketMessage({
type: 'end_session',
session_id: this.sessionId
});
}
this.sessionId = null;
this.disableChat();
this.addSystemMessage('对话已结束。');
} catch (error) {
console.error('结束对话失败:', error);
}
}
async sendMessage() {
const input = document.getElementById('message-input');
const message = input.value.trim();
if (!message || !this.sessionId) {
return;
}
// 清空输入框
input.value = '';
// 添加用户消息
this.addMessage('user', message);
// 显示打字指示器
this.showTypingIndicator();
try {
const response = await this.sendWebSocketMessage({
type: 'send_message',
session_id: this.sessionId,
message: message
});
this.hideTypingIndicator();
if (response.type === 'message_response' && response.result.success) {
const result = response.result;
// 添加助手回复
this.addMessage('assistant', result.content, {
knowledge_used: result.knowledge_used,
confidence_score: result.confidence_score,
work_order_id: result.work_order_id
});
// 更新工单ID
if (result.work_order_id) {
document.getElementById('work-order-id').value = result.work_order_id;
}
} else {
this.addMessage('assistant', '抱歉,我暂时无法处理您的问题。请稍后再试。');
}
} catch (error) {
this.hideTypingIndicator();
console.error('发送消息失败:', error);
this.addMessage('assistant', '发送消息失败,请检查网络连接。');
}
}
async createWorkOrder() {
const title = document.getElementById('wo-title').value;
const description = document.getElementById('wo-description').value;
const category = document.getElementById('wo-category').value;
const priority = document.getElementById('wo-priority').value;
if (!title || !description) {
this.showError('请填写工单标题和描述');
return;
}
try {
const response = await this.sendWebSocketMessage({
type: 'create_work_order',
session_id: this.sessionId,
title: title,
description: description,
category: category,
priority: priority
});
if (response.type === 'work_order_created' && response.result.success) {
const workOrderId = response.result.work_order_id;
document.getElementById('work-order-id').value = workOrderId;
this.addSystemMessage(`工单创建成功!工单号: ${response.result.order_id}`);
// 关闭模态框
const modal = bootstrap.Modal.getInstance(document.getElementById('workOrderModal'));
modal.hide();
// 清空表单
document.getElementById('work-order-form').reset();
} else {
this.showError('创建工单失败: ' + (response.result.error || '未知错误'));
}
} catch (error) {
console.error('创建工单失败:', error);
this.showError('创建工单失败: ' + error.message);
}
}
connectWebSocket() {
return new Promise((resolve, reject) => {
try {
this.websocket = new WebSocket('ws://localhost:8765');
// 设置连接超时
const timeout = setTimeout(() => {
if (this.websocket.readyState !== WebSocket.OPEN) {
this.websocket.close();
reject(new Error('WebSocket连接超时请检查服务器是否启动'));
}
}, 5000); // 5秒超时
this.websocket.onopen = () => {
clearTimeout(timeout);
this.isConnected = true;
this.updateConnectionStatus(true);
resolve();
};
this.websocket.onclose = () => {
clearTimeout(timeout);
this.isConnected = false;
this.updateConnectionStatus(false);
};
this.websocket.onerror = (error) => {
clearTimeout(timeout);
console.error('WebSocket错误:', error);
reject(new Error('WebSocket连接失败请检查服务器是否启动'));
};
this.websocket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
this.handleWebSocketMessage(data);
} catch (error) {
console.error('解析WebSocket消息失败:', error);
}
};
} catch (error) {
reject(error);
}
});
}
sendWebSocketMessage(message) {
return new Promise((resolve, reject) => {
if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) {
reject(new Error('WebSocket未连接'));
return;
}
const messageId = 'msg_' + Date.now();
message.messageId = messageId;
// 设置超时
const timeout = setTimeout(() => {
reject(new Error('请求超时'));
}, 10000);
// 监听响应
const handleResponse = (event) => {
try {
const data = JSON.parse(event.data);
if (data.messageId === messageId) {
clearTimeout(timeout);
this.websocket.removeEventListener('message', handleResponse);
resolve(data);
}
} catch (error) {
// 忽略解析错误
}
};
this.websocket.addEventListener('message', handleResponse);
this.websocket.send(JSON.stringify(message));
});
}
handleWebSocketMessage(data) {
// 处理WebSocket消息
console.log('收到WebSocket消息:', data);
}
addMessage(role, content, metadata = {}) {
const messagesContainer = document.getElementById('chat-messages');
// 如果是第一条消息,清空欢迎信息
if (this.messageCount === 0) {
messagesContainer.innerHTML = '';
}
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}`;
const avatar = document.createElement('div');
avatar.className = 'message-avatar';
avatar.textContent = role === 'user' ? 'U' : 'A';
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content';
contentDiv.innerHTML = content;
// 添加时间戳
const timeDiv = document.createElement('div');
timeDiv.className = 'message-time';
timeDiv.textContent = new Date().toLocaleTimeString();
contentDiv.appendChild(timeDiv);
// 添加元数据
if (metadata.knowledge_used && metadata.knowledge_used.length > 0) {
const knowledgeDiv = document.createElement('div');
knowledgeDiv.className = 'knowledge-info';
knowledgeDiv.innerHTML = `<i class="fas fa-lightbulb me-1"></i>基于 ${metadata.knowledge_used.length} 条知识库信息生成`;
contentDiv.appendChild(knowledgeDiv);
}
if (metadata.confidence_score) {
const confidenceDiv = document.createElement('div');
confidenceDiv.className = 'confidence-score';
confidenceDiv.textContent = `置信度: ${(metadata.confidence_score * 100).toFixed(1)}%`;
contentDiv.appendChild(confidenceDiv);
}
if (metadata.work_order_id) {
const workOrderDiv = document.createElement('div');
workOrderDiv.className = 'work-order-info';
workOrderDiv.innerHTML = `<i class="fas fa-ticket-alt me-1"></i>关联工单: ${metadata.work_order_id}`;
contentDiv.appendChild(workOrderDiv);
}
if (role === 'user') {
messageDiv.appendChild(contentDiv);
messageDiv.appendChild(avatar);
} else {
messageDiv.appendChild(avatar);
messageDiv.appendChild(contentDiv);
}
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
this.messageCount++;
}
addSystemMessage(content) {
const messagesContainer = document.getElementById('chat-messages');
const messageDiv = document.createElement('div');
messageDiv.className = 'text-center text-muted py-2';
messageDiv.innerHTML = `<small><i class="fas fa-info-circle me-1"></i>${content}</small>`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
showTypingIndicator() {
document.getElementById('typing-indicator').classList.add('show');
}
hideTypingIndicator() {
document.getElementById('typing-indicator').classList.remove('show');
}
updateConnectionStatus(connected) {
const statusElement = document.getElementById('connection-status');
if (connected) {
statusElement.className = 'connection-status connected';
statusElement.innerHTML = '<i class="fas fa-circle me-1"></i>已连接';
} else {
statusElement.className = 'connection-status disconnected';
statusElement.innerHTML = '<i class="fas fa-circle me-1"></i>未连接';
}
}
updateSessionInfo() {
const sessionInfo = document.getElementById('session-info');
sessionInfo.innerHTML = `
<div><strong>会话ID:</strong> ${this.sessionId}</div>
<div><strong>消息数:</strong> ${this.messageCount}</div>
<div><strong>状态:</strong> </div>
`;
}
enableChat() {
document.getElementById('start-chat').disabled = true;
document.getElementById('end-chat').disabled = false;
document.getElementById('message-input').disabled = false;
document.getElementById('send-button').disabled = false;
}
disableChat() {
document.getElementById('start-chat').disabled = false;
document.getElementById('end-chat').disabled = true;
document.getElementById('message-input').disabled = true;
document.getElementById('send-button').disabled = true;
}
showWorkOrderModal() {
if (!this.sessionId) {
this.showError('请先开始对话');
return;
}
const modal = new bootstrap.Modal(document.getElementById('workOrderModal'));
modal.show();
}
showError(message) {
this.addSystemMessage(`<span class="text-danger">错误: ${message}</span>`);
}
}
// 初始化聊天客户端
document.addEventListener('DOMContentLoaded', () => {
window.chatClient = new ChatClient();
});