Files
assist/src/web/static/js/chat.js
2025-09-06 21:06:18 +08:00

411 lines
15 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.
// 实时对话前端脚本
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();
});