RAG/static/chat/script.js

1062 lines
32 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.

// 全局变量
let currentConversationId = null;
let currentUser = null; // 当前登录用户对象
let isStreaming = false;
let currentStreamResponse = null;
let currentStreamReader = null;
// API 基础 URL
const API_BASE_URL = '/';
// 认证状态
let isAuthenticated = false;
// DOM 元素 - 延迟获取确保DOM已完全加载
let elements = {};
// 初始化应用
document.addEventListener('DOMContentLoaded', () => {
// 等待DOM完全加载后获取元素
setTimeout(() => {
initializeApp();
}, 100);
});
function initializeApp() {
// 获取DOM元素
getDOMElements();
// 绑定事件监听器
bindEventListeners();
// 检查本地存储中的登录状态
checkAuthStatus();
// 更新用户信息显示
updateUserInfoDisplay();
// 如果未登录,显示登录模态框
if (!isAuthenticated) {
showLoginModal();
} else {
// 设置当前登录用户名
if (elements.newChatLogin) {
elements.newChatLogin.value = currentUser.username;
}
// 加载对话列表
loadConversations();
}
}
function getDOMElements() {
elements = {
// 侧边栏
conversationsList: document.getElementById('conversationsList'),
newChatBtn: document.getElementById('newChatBtn'),
currentUsername: document.getElementById('currentUsername'),
currentUserRole: document.getElementById('currentUserRole'),
logoutBtn: document.getElementById('logoutBtn'),
notLoggedInInfo: document.getElementById('notLoggedInInfo'),
loggedInInfo: document.getElementById('loggedInInfo'),
quickLoginBtn: document.getElementById('quickLoginBtn'),
// 主聊天区域
welcomeScreen: document.getElementById('welcomeScreen'),
chatScreen: document.getElementById('chatScreen'),
chatTitle: document.getElementById('chatTitle'),
messagesContainer: document.getElementById('messagesContainer'),
messageInput: document.getElementById('messageInput'),
sendBtn: document.getElementById('sendBtn'),
typingIndicator: document.getElementById('typingIndicator'),
startChatBtn: document.getElementById('startChatBtn'),
// 模态框
deleteChatModal: document.getElementById('deleteChatModal'),
confirmDeleteChat: document.getElementById('confirmDeleteChat'),
// 登录/注册模态框
loginModal: document.getElementById('loginModal'),
loginForm: document.getElementById('loginForm'),
loginUsername: document.getElementById('loginUsername'),
loginPassword: document.getElementById('loginPassword'),
loginCloseBtn: document.getElementById('loginCloseBtn'),
showRegisterBtn: document.getElementById('showRegisterBtn'),
registerModal: document.getElementById('registerModal'),
registerForm: document.getElementById('registerForm'),
registerUsername: document.getElementById('registerUsername'),
registerPassword: document.getElementById('registerPassword'),
registerConfirmPassword: document.getElementById('registerConfirmPassword'),
registerCloseBtn: document.getElementById('registerCloseBtn'),
showLoginBtn: document.getElementById('showLoginBtn')
};
}
function bindEventListeners() {
// 侧边栏事件
if (elements.newChatBtn) {
elements.newChatBtn.addEventListener('click', createNewConversation);
}
if (elements.startChatBtn) {
elements.startChatBtn.addEventListener('click', createNewConversation);
}
// 消息输入事件
if (elements.messageInput) {
elements.messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (isStreaming) {
stopStreamResponse();
} else {
sendMessage();
}
}
});
}
if (elements.sendBtn) {
elements.sendBtn.addEventListener('click', () => {
if (isStreaming) {
stopStreamResponse();
} else {
sendMessage();
}
});
}
// 模态框事件(仅保留删除对话模态框)
if (elements.deleteChatModal) {
const deleteChatClose = elements.deleteChatModal.querySelector('.close-btn');
const deleteChatCancel = elements.deleteChatModal.querySelector('.cancel-btn');
if (deleteChatClose) deleteChatClose.addEventListener('click', hideDeleteChatModal);
if (deleteChatCancel) deleteChatCancel.addEventListener('click', hideDeleteChatModal);
}
// 登录/注册模态框事件
if (elements.loginModal) {
// 登录模态框关闭按钮
if (elements.loginCloseBtn) {
elements.loginCloseBtn.addEventListener('click', hideLoginModal);
}
// 切换到注册模态框
if (elements.showRegisterBtn) {
elements.showRegisterBtn.addEventListener('click', () => {
hideLoginModal();
showRegisterModal();
});
}
// 登录表单提交
if (elements.loginForm) {
elements.loginForm.addEventListener('submit', (e) => {
e.preventDefault();
handleLogin();
});
}
// 点击模态框外部关闭
window.addEventListener('click', (e) => {
if (e.target === elements.loginModal) hideLoginModal();
});
}
if (elements.registerModal) {
// 注册模态框关闭按钮
if (elements.registerCloseBtn) {
elements.registerCloseBtn.addEventListener('click', hideRegisterModal);
}
// 切换到登录模态框
if (elements.showLoginBtn) {
elements.showLoginBtn.addEventListener('click', () => {
hideRegisterModal();
showLoginModal();
});
}
// 注册表单提交
if (elements.registerForm) {
elements.registerForm.addEventListener('submit', (e) => {
e.preventDefault();
handleRegister();
});
}
// 点击模态框外部关闭
window.addEventListener('click', (e) => {
if (e.target === elements.registerModal) hideRegisterModal();
});
}
// 确认创建新对话
if (elements.confirmNewChat) {
elements.confirmNewChat.addEventListener('click', createNewConversation);
}
// 确认删除对话
if (elements.confirmDeleteChat) {
elements.confirmDeleteChat.addEventListener('click', deleteCurrentConversation);
}
// 聊天界面事件 - 已移除待开发功能按钮
// 注销按钮事件
if (elements.logoutBtn) {
elements.logoutBtn.addEventListener('click', handleLogout);
}
// 快速登录按钮事件
if (elements.quickLoginBtn) {
elements.quickLoginBtn.addEventListener('click', showLoginModal);
}
}
// 认证相关函数
// 检查本地存储中的认证状态
function checkAuthStatus() {
const storedUser = localStorage.getItem('chatUser');
if (storedUser) {
try {
currentUser = JSON.parse(storedUser);
isAuthenticated = true;
} catch (e) {
console.error('Failed to parse stored user:', e);
logout();
}
}
}
// 更新用户信息显示
function updateUserInfoDisplay() {
// 检查元素是否存在
if (!elements.notLoggedInInfo || !elements.loggedInInfo || !elements.logoutBtn) {
return;
}
if (currentUser && isAuthenticated) {
// 用户已登录
if (elements.currentUsername) {
elements.currentUsername.textContent = currentUser.username;
}
if (elements.currentUserRole) {
elements.currentUserRole.textContent = '用户'; // 目前所有用户都显示为普通用户
}
// 显示登录状态,隐藏未登录状态
elements.notLoggedInInfo.style.display = 'none';
elements.loggedInInfo.style.display = 'flex';
elements.logoutBtn.style.display = 'block';
} else {
// 用户未登录
// 显示未登录状态,隐藏登录状态
elements.notLoggedInInfo.style.display = 'flex';
elements.loggedInInfo.style.display = 'none';
elements.logoutBtn.style.display = 'none';
}
}
// 保存用户认证状态到本地存储
function saveAuthStatus(user) {
currentUser = user;
isAuthenticated = true;
localStorage.setItem('chatUser', JSON.stringify(user));
updateUserInfoDisplay();
}
// 处理注销操作
function handleLogout() {
if (confirm('确定要注销吗?')) {
logout();
}
}
// 清除用户认证状态
function logout() {
currentUser = null;
isAuthenticated = false;
localStorage.removeItem('chatUser');
// 重置当前对话
currentConversationId = null;
// 清空消息容器
if (elements.messagesContainer) {
elements.messagesContainer.innerHTML = '';
}
// 重置聊天界面
if (elements.chatScreen) {
elements.chatScreen.style.display = 'none';
}
if (elements.welcomeScreen) {
elements.welcomeScreen.style.display = 'flex';
}
// 更新用户信息显示
updateUserInfoDisplay();
// 清空对话列表
if (elements.conversationsList) {
elements.conversationsList.innerHTML = '';
}
// 显示登录模态框
showLoginModal();
}
// 显示登录模态框
function showLoginModal() {
if (elements.loginModal) {
elements.loginModal.style.display = 'flex';
}
}
// 隐藏登录模态框
function hideLoginModal() {
if (elements.loginModal) {
elements.loginModal.style.display = 'none';
}
}
// 显示注册模态框
function showRegisterModal() {
if (elements.registerModal) {
elements.registerModal.style.display = 'flex';
}
}
// 隐藏注册模态框
function hideRegisterModal() {
if (elements.registerModal) {
elements.registerModal.style.display = 'none';
}
}
// 处理登录表单提交
async function handleLogin() {
if (!elements.loginUsername || !elements.loginPassword) return;
const username = elements.loginUsername.value.trim();
const password = elements.loginPassword.value.trim();
if (!username || !password) {
alert('请输入用户名和密码');
return;
}
try {
const response = await fetch(`${API_BASE_URL}login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || '登录失败');
}
const userData = await response.json();
saveAuthStatus(userData);
// 更新界面
if (elements.newChatLogin) {
elements.newChatLogin.value = userData.username;
}
hideLoginModal();
loadConversations();
alert('登录成功!');
} catch (error) {
console.error('Login error:', error);
alert('登录失败: ' + error.message);
}
}
// 处理注册表单提交
async function handleRegister() {
if (!elements.registerUsername || !elements.registerPassword || !elements.registerConfirmPassword) return;
const username = elements.registerUsername.value.trim();
const password = elements.registerPassword.value.trim();
const confirmPassword = elements.registerConfirmPassword.value.trim();
if (!username || !password) {
alert('请输入用户名和密码');
return;
}
if (password !== confirmPassword) {
alert('两次输入的密码不一致');
return;
}
try {
const response = await fetch(`${API_BASE_URL}register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, password })
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || '注册失败');
}
const userData = await response.json();
saveAuthStatus(userData);
// 更新界面
if (elements.newChatLogin) {
elements.newChatLogin.value = userData.username;
}
hideRegisterModal();
loadConversations();
alert('注册成功!');
} catch (error) {
console.error('Registration error:', error);
alert('注册失败: ' + error.message);
}
}
// 加载对话列表
async function loadConversations() {
try {
// 检查用户是否已登录
if (!isAuthenticated || !currentUser) {
// 如果未登录,清空对话列表并返回
console.log('User not logged in, not loading conversations');
if (elements.conversationsList) {
elements.conversationsList.innerHTML = '';
}
return;
}
// 如果已登录调用用户特定的对话API
const response = await fetch(`${API_BASE_URL}conversations/${currentUser.username}`);
if (!response.ok) {
throw new Error('Failed to load conversations');
}
const conversations = await response.json();
renderConversations(conversations);
// 如果有对话,默认选择第一个
if (conversations.length > 0 && !currentConversationId) {
selectConversation(conversations[0].id);
}
} catch (error) {
console.error('Error loading conversations:', error);
showNotification('加载对话失败', 'error');
}
}
// 渲染对话列表
function renderConversations(conversations) {
if (!elements.conversationsList) {
console.error('conversationsList element not found');
return;
}
elements.conversationsList.innerHTML = '';
conversations.forEach(conv => {
const convItem = document.createElement('div');
convItem.className = `conversation-item ${currentConversationId === conv.id ? 'active' : ''}`;
convItem.dataset.id = conv.id;
convItem.innerHTML = `
<div class="conversation-content">
<div class="conversation-title">${conv.title || '无标题对话'}</div>
<div class="conversation-time">${formatTime(conv.update_time)}</div>
</div>
<div class="conversation-actions">
<button class="delete-conv-btn" data-id="${conv.id}">×</button>
</div>
`;
// 添加点击事件
convItem.addEventListener('click', () => selectConversation(conv.id));
// 添加删除按钮事件
const deleteBtn = convItem.querySelector('.delete-conv-btn');
if (deleteBtn) {
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation(); // 阻止事件冒泡,避免触发对话选择
currentConversationId = conv.id;
showDeleteChatModal();
});
}
elements.conversationsList.appendChild(convItem);
});
}
// 选择对话
async function selectConversation(convId) {
if (convId === currentConversationId) return;
currentConversationId = convId;
// 更新对话列表选中状态
document.querySelectorAll('.conversation-item').forEach(item => {
item.classList.remove('active');
});
document.querySelector(`[data-id="${convId}"]`).classList.add('active');
// 加载对话内容
await loadConversationMessages(convId);
// 显示聊天界面,隐藏欢迎界面
elements.welcomeScreen.style.display = 'none';
elements.chatScreen.style.display = 'flex';
}
// 加载对话消息
async function loadConversationMessages(convId) {
try {
// 检查用户是否已登录
if (!isAuthenticated || !currentUser) {
throw new Error('用户未登录');
}
const response = await fetch(`${API_BASE_URL}conversations/${currentUser.username}/${convId}`);
if (!response.ok) {
throw new Error('Failed to load conversation messages');
}
const conversation = await response.json();
elements.chatTitle.textContent = conversation.title || '无标题对话';
// 渲染消息
renderMessages(conversation.messages);
} catch (error) {
console.error('Error loading conversation messages:', error);
showNotification('加载对话消息失败', 'error');
}
}
// 渲染消息列表
function renderMessages(messages) {
elements.messagesContainer.innerHTML = '';
messages.forEach(msg => {
addMessage(msg.role, msg.content, msg.time);
});
// 滚动到底部
scrollToBottom();
}
// 简单的本地 Markdown 渲染函数
function renderMarkdown(content) {
// 如果marked可用则使用marked解析
if (typeof marked !== 'undefined') {
try {
// 配置marked选项可选
marked.setOptions({
breaks: true, // 自动转换换行为<br>
gfm: true // 启用GitHub风格的Markdown
});
return marked.parse(content);
} catch (error) {
console.error('Marked解析错误:', error);
return simpleMarkdownFallback(content);
}
} else {
// 如果marked未加载使用降级方案
return simpleMarkdownFallback(content);
}
}
// 简单的Markdown降级处理函数
// 降级方案当marked不可用时使用
function simpleMarkdownFallback(content) {
let html = content;
// 按顺序处理,确保代码块优先
// 1. 处理代码块 (保持原样)
html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
// 2. 处理行内代码
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
// 3. 处理加粗(使用**text**语法)
html = html.replace(/\*\*([^*]+?)\*\*/g, '<strong>$1</strong>');
// 4. 处理斜体(使用*text*语法)
html = html.replace(/\*([^*]+?)\*/g, '<em>$1</em>');
// 5. 处理标题(# 标题)
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
// 6. 处理无序列表
html = html.replace(/^\s*-\s+(.+)$/gm, '<li>$1</li>');
html = html.replace(/(<li>[\s\S]*?<\/li>)/g, '<ul>$1</ul>');
// 7. 处理有序列表
html = html.replace(/^\s*\d+\.\s+(.+)$/gm, '<li>$1</li>');
html = html.replace(/(<li>[\s\S]*?<\/li>)/g, '<ol>$1</ol>');
// 8. 处理链接
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
// 9. 最后处理换行
html = html.replace(/\n/g, '<br>');
return html;
}
// 添加消息
function addMessage(role, content, time) {
// 使用本地的 Markdown 渲染函数
const formattedContent = renderMarkdown(content);
const messageDiv = document.createElement('div');
messageDiv.className = `message ${role}`;
const avatarEmoji = role === 'user' ? '👤' : '🤖';
messageDiv.innerHTML = `
<div class="message-avatar">
${avatarEmoji}
</div>
<div class="message-content">
<div class="message text">${formattedContent}</div>
<div class="message-time">${formatTime(time)}</div>
</div>
`;
elements.messagesContainer.appendChild(messageDiv);
scrollToBottom();
}
// 发送消息
async function sendMessage() {
// 检查用户是否已登录
if (!isAuthenticated || !currentUser) {
alert('请先登录才能发送消息');
showLoginModal();
return;
}
const content = elements.messageInput.value.trim();
if (!content || !currentConversationId || isStreaming) return;
// 清空输入框
elements.messageInput.value = '';
// 添加用户消息
const now = new Date().toISOString();
addMessage('user', content, now);
// 显示打字指示器
showTypingIndicator();
try {
isStreaming = true;
// 更新发送按钮为停止按钮
updateSendButtonState(true);
// 创建流式响应
const response = await fetch(`${API_BASE_URL}conversations/${currentConversationId}/stream`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
content: content,
user_login: currentUser.username
})
});
if (!response.ok) {
throw new Error('Failed to send message');
}
// 处理流式响应
await handleStreamResponse(response);
// 检查当前对话的消息数量
const messages = elements.messagesContainer.querySelectorAll('.message');
if (messages.length === 1) { // 如果只有一条消息(即刚刚发送的这条),则更新对话标题
// 截取用户输入的前5个字符作为标题
const newTitle = content.substring(0, 5) + (content.length > 5 ? '...' : '');
// 更新对话标题
try {
await fetch(`${API_BASE_URL}conversations/${currentConversationId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: newTitle
})
});
// 更新界面上的对话标题
if (elements.chatTitle) {
elements.chatTitle.textContent = newTitle;
}
// 重新加载对话列表(更新标题和时间)
loadConversations();
} catch (error) {
console.error('Error updating conversation title:', error);
}
} else {
// 重新加载对话列表(更新时间)
loadConversations();
}
} catch (error) {
console.error('Error sending message:', error);
addMessage('assistant', `发送消息失败: ${error.message}`, new Date().toISOString());
} finally {
isStreaming = false;
hideTypingIndicator();
// 重置发送按钮为发送状态
updateSendButtonState(false);
// 清理流式响应相关变量
currentStreamResponse = null;
currentStreamReader = null;
}
}
// 处理流式响应
async function handleStreamResponse(response) {
// 保存当前响应和阅读器对象,以便在需要时可以停止流式响应
currentStreamResponse = response;
const reader = response.body.getReader();
currentStreamReader = reader;
const decoder = new TextDecoder('utf-8');
let fullResponse = '';
let messageElement = null;
// 创建助手消息容器
const now = new Date().toISOString();
messageElement = createAssistantMessageContainer(now);
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// 解码接收到的字节
const chunk = decoder.decode(value, { stream: true });
fullResponse += chunk;
// 更新消息内容
updateAssistantMessageContent(messageElement, fullResponse);
}
// 保存完整消息
await saveAssistantMessage(fullResponse);
} catch (error) {
console.error('Error reading stream:', error);
updateAssistantMessageContent(messageElement, `流式响应错误: ${error.message}`);
} finally {
reader.releaseLock();
}
}
// 创建助手消息容器
function createAssistantMessageContainer(time) {
const messageDiv = document.createElement('div');
messageDiv.className = 'message assistant';
messageDiv.innerHTML = `
<div class="message-avatar">
🤖
</div>
<div class="message-content">
<div class="message text">
<div class="typing-dots" id="assistantTypingDots" style="display: flex;">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</div>
<div class="message-time">${formatTime(time)}</div>
</div>
`;
elements.messagesContainer.appendChild(messageDiv);
scrollToBottom();
return messageDiv;
}
// 更新助手消息内容
function updateAssistantMessageContent(messageElement, content) {
const textElement = messageElement.querySelector('.message.text');
const typingDots = textElement.querySelector('.typing-dots');
// 替换打字指示器为实际内容
if (typingDots) {
typingDots.remove();
}
// 使用本地的 Markdown 渲染函数
const formattedContent = renderMarkdown(content);
textElement.innerHTML = formattedContent;
scrollToBottom();
}
// 停止当前的流式响应
async function stopStreamResponse() {
if (!isStreaming) return;
try {
// 调用API的中止端点
await fetch(`${API_BASE_URL}conversations/${currentConversationId}/abort`, {
method: 'POST'
});
// 取消当前的流式响应
if (currentStreamReader) {
await currentStreamReader.cancel();
}
// 重置相关变量
isStreaming = false;
currentStreamResponse = null;
currentStreamReader = null;
// 隐藏打字指示器
hideTypingIndicator();
// 更新按钮状态
updateSendButtonState(false);
} catch (error) {
console.error('Error stopping stream:', error);
}
}
// 更新发送按钮的状态
function updateSendButtonState(isStreaming) {
if (!elements.sendBtn) return;
if (isStreaming) {
elements.sendBtn.textContent = '⏹️';
elements.sendBtn.title = '停止生成';
} else {
elements.sendBtn.textContent = '➤';
elements.sendBtn.title = '发送消息';
}
}
// 保存助手消息
async function saveAssistantMessage(content) {
// 这个函数主要是为了确保对话历史正确保存
// 在流式响应结束后API 应该已经将完整消息保存到数据库
}
// 显示/隐藏打字指示器 - 现在通过创建助手消息容器来显示,这里仅保留空函数以保持兼容性
function showTypingIndicator() {
// 打字指示器现在显示在助手消息容器中
}
function hideTypingIndicator() {
// 打字指示器现在显示在助手消息容器中
}
// 创建新对话
async function createNewConversation() {
// 检查用户是否已登录
if (!isAuthenticated || !currentUser) {
alert('请先登录');
showLoginModal();
return;
}
const userLogin = currentUser.username;
try {
const response = await fetch(`${API_BASE_URL}conversations`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
title: '新对话', // 默认标题,后续会根据第一条消息内容更新
user_login: userLogin
})
});
if (!response.ok) {
throw new Error('Failed to create conversation');
}
const newConv = await response.json();
// 隐藏模态框
// 加载对话列表并选择新对话
await loadConversations();
await selectConversation(newConv.id);
showNotification('新对话创建成功', 'success');
} catch (error) {
console.error('Error creating conversation:', error);
showNotification('创建对话失败', 'error');
}
}
// 显示删除对话模态框
function showDeleteChatModal() {
if (!currentConversationId) return;
elements.deleteChatModal.style.display = 'block';
}
// 隐藏删除对话模态框
function hideDeleteChatModal() {
elements.deleteChatModal.style.display = 'none';
}
// 删除当前对话
async function deleteCurrentConversation() {
if (!currentConversationId) return;
try {
// 检查用户是否已登录
if (!isAuthenticated || !currentUser) {
throw new Error('用户未登录');
}
const response = await fetch(`${API_BASE_URL}conversations/${currentUser.username}/${currentConversationId}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error('Failed to delete conversation');
}
// 隐藏模态框
hideDeleteChatModal();
// 重置当前对话
currentConversationId = null;
// 重新加载对话列表
await loadConversations();
// 显示欢迎界面
elements.chatScreen.style.display = 'none';
elements.welcomeScreen.style.display = 'flex';
showNotification('对话删除成功', 'success');
} catch (error) {
console.error('Error deleting conversation:', error);
showNotification('删除对话失败', 'error');
}
}
// 显示通知
function showNotification(message, type = 'info') {
// 创建简单的通知
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
// 样式
Object.assign(notification.style, {
position: 'fixed',
top: '20px',
right: '20px',
padding: '12px 20px',
borderRadius: '8px',
color: 'white',
fontWeight: '500',
zIndex: '10000',
animation: 'slideInRight 0.3s ease'
});
// 根据类型设置背景色
switch(type) {
case 'success':
notification.style.background = 'linear-gradient(135deg, #10b981 0%, #059669 100%)';
break;
case 'error':
notification.style.background = 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)';
break;
default:
notification.style.background = 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)';
}
// 添加到页面
document.body.appendChild(notification);
// 3秒后移除
setTimeout(() => {
notification.remove();
}, 3000);
}
// 工具函数
function formatTime(timeString) {
if (!timeString) return '';
const date = new Date(timeString);
const now = new Date();
// 今天的消息显示时间
if (date.toDateString() === now.toDateString()) {
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' });
}
// 昨天的消息显示 "昨天 HH:MM"
const yesterday = new Date(now);
yesterday.setDate(yesterday.getDate() - 1);
if (date.toDateString() === yesterday.toDateString()) {
return `昨天 ${date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}`;
}
// 今年的消息显示 "MM-DD HH:MM"
if (date.getFullYear() === now.getFullYear()) {
return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
}
// 其他显示完整日期时间
return date.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
}
function scrollToBottom() {
setTimeout(() => {
elements.messagesContainer.scrollTop = elements.messagesContainer.scrollHeight;
}, 100);
}
// 添加动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
`;
document.head.appendChild(style);