RAG/static/chat/script.js

1088 lines
34 KiB
JavaScript
Raw Permalink Normal View History

// 全局变量
let currentConversationId = null;
let currentUser = null; // 当前登录用户对象
let isStreaming = false;
let currentStreamResponse = null;
let currentStreamReader = null;
2026-01-27 17:26:17 +08:00
let currentAssistantMessageElement = 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();
}
2026-01-26 15:48:25 +08:00
// 简单的本地 Markdown 渲染函数
function renderMarkdown(content) {
2026-01-27 16:23:21 +08:00
// 如果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;
2026-01-26 15:48:25 +08:00
2026-01-27 16:23:21 +08:00
// 按顺序处理,确保代码块优先
// 1. 处理代码块 (保持原样)
2026-01-26 15:48:25 +08:00
html = html.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
2026-01-27 16:23:21 +08:00
// 2. 处理行内代码
2026-01-26 15:48:25 +08:00
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
2026-01-27 16:23:21 +08:00
// 3. 处理加粗(使用**text**语法)
html = html.replace(/\*\*([^*]+?)\*\*/g, '<strong>$1</strong>');
// 4. 处理斜体(使用*text*语法)
html = html.replace(/\*([^*]+?)\*/g, '<em>$1</em>');
2026-01-26 15:48:25 +08:00
2026-01-27 16:23:21 +08:00
// 5. 处理标题(# 标题)
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
2026-01-26 15:48:25 +08:00
2026-01-27 16:23:21 +08:00
// 6. 处理无序列表
html = html.replace(/^\s*-\s+(.+)$/gm, '<li>$1</li>');
2026-01-26 15:48:25 +08:00
html = html.replace(/(<li>[\s\S]*?<\/li>)/g, '<ul>$1</ul>');
2026-01-27 16:23:21 +08:00
// 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>');
2026-01-26 15:48:25 +08:00
return html;
}
// 添加消息
function addMessage(role, content, time) {
2026-01-26 15:48:25 +08:00
// 使用本地的 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">
2026-01-26 15:48:25 +08:00
<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);
2026-01-27 17:26:17 +08:00
currentAssistantMessageElement = messageElement; // 保存到全局变量
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);
2026-01-27 17:26:17 +08:00
// 检查是否是用户主动取消的错误
if (error.name === 'AbortError' || error.message.includes('cancel')) {
// 用户主动取消,不显示错误消息
} else {
// 其他错误,显示错误消息
updateAssistantMessageContent(messageElement, `流式响应错误: ${error.message}`);
}
} finally {
reader.releaseLock();
2026-01-27 17:26:17 +08:00
currentAssistantMessageElement = null; // 重置全局变量
}
}
// 创建助手消息容器
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">
2026-01-27 17:26:17 +08:00
<div class="typing-dots" id="assistantTypingDots" style="display: flex; align-items: center;">
<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();
}
2026-01-26 15:48:25 +08:00
// 使用本地的 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;
2026-01-27 17:26:17 +08:00
// 更新助手消息内容,替换打字指示器
let assistantMessageElement = currentAssistantMessageElement;
// 如果 currentAssistantMessageElement 为 null尝试查找最新的助手消息元素
if (!assistantMessageElement) {
const messageElements = elements.messagesContainer.querySelectorAll('.message.assistant');
if (messageElements.length > 0) {
assistantMessageElement = messageElements[messageElements.length - 1];
}
}
if (assistantMessageElement) {
updateAssistantMessageContent(assistantMessageElement, '流式响应已中止');
}
// 重置全局变量
currentAssistantMessageElement = null;
// 更新按钮状态
updateSendButtonState(false);
2026-01-27 17:26:17 +08:00
// 滚动到底部
scrollToBottom();
} 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);