const chatArea = document.getElementById('chatArea'); const messageInput = document.getElementById('messageInput'); const submitBtn = document.getElementById('submitBtn'); const newChatBtn = document.getElementById('newChatBtn'); const chatList = document.getElementById('chatList'); const welcomeMessage = document.getElementById('welcomeMessage'); const toggleSidebarBtn = document.getElementById('toggleSidebar'); const sidebar = document.getElementById('sidebar'); let currentEventSource = null; let currentChatId = null; // 获取知识库列表 document.addEventListener('DOMContentLoaded', function() { // 获取知识库列表 const loadRagOptions = () => { const ragSelect = document.getElementById('ragSelect'); fetch('http://localhost:8095/api/v1/rag/query_rag_tag_list') .then(response => response.json()) .then(data => { if (data.code === '0000' && data.data) { // 清空现有选项(保留第一个默认选项) while (ragSelect.options.length > 1) { ragSelect.remove(1); } // 添加新选项 data.data.forEach(tag => { const option = new Option(`Rag:${tag}`, tag); ragSelect.add(option); }); } }) .catch(error => { console.error('获取知识库列表失败:', error); }); }; // 初始化加载 loadRagOptions(); }); function createNewChat() { const chatId = Date.now().toString(); currentChatId = chatId; localStorage.setItem('currentChatId', chatId); // 修改数据结构为包含name和messages的对象 localStorage.setItem(`chat_${chatId}`, JSON.stringify({ name: '新聊天', messages: [] })); updateChatList(); clearChatArea(); } function deleteChat(chatId) { if (confirm('确定要删除这个聊天记录吗?')) { localStorage.removeItem(`chat_${chatId}`); // Remove the chat from localStorage if (currentChatId === chatId) { // If the current chat is being deleted createNewChat(); // Create a new chat } updateChatList(); // Update the chat list to reflect changes } } function updateChatList() { chatList.innerHTML = ''; const chats = Object.keys(localStorage) .filter(key => key.startsWith('chat_')); const currentChatIndex = chats.findIndex(key => key.split('_')[1] === currentChatId); if (currentChatIndex!== -1) { const currentChat = chats[currentChatIndex]; chats.splice(currentChatIndex, 1); chats.unshift(currentChat); } chats.forEach(chatKey => { let chatData = JSON.parse(localStorage.getItem(chatKey)); const chatId = chatKey.split('_')[1]; // 数据迁移:将旧数组格式转换为新对象格式 if (Array.isArray(chatData)) { chatData = { name: `聊天 ${new Date(parseInt(chatId)).toLocaleDateString()}`, messages: chatData }; localStorage.setItem(chatKey, JSON.stringify(chatData)); } const li = document.createElement('li'); li.className = `chat-item flex items-center justify-between p-2 hover:bg-gray-100 rounded-lg cursor-pointer transition-colors ${chatId === currentChatId? 'bg-blue-50' : ''}`; li.innerHTML = `
${chatData.name}
${new Date(parseInt(chatId)).toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' })}
`; li.addEventListener('click', (e) => { if (!e.target.closest('.chat-actions')) { loadChat(chatId); } }); li.addEventListener('mouseenter', () => { li.querySelector('.chat-actions').classList.remove('opacity-0'); }); li.addEventListener('mouseleave', () => { li.querySelector('.chat-actions').classList.add('opacity-0'); }); chatList.appendChild(li); }); } let currentContextMenu = null; // 优化后的上下文菜单 function showChatContextMenu(event, chatId) { event.stopPropagation(); closeContextMenu(); const buttonRect = event.target.closest('button').getBoundingClientRect(); const menu = document.createElement('div'); menu.className = 'context-menu'; menu.style.position = 'fixed'; menu.style.left = `${buttonRect.left}px`; menu.style.top = `${buttonRect.bottom + 4}px`; menu.innerHTML = `
重命名
删除
`; document.body.appendChild(menu); currentContextMenu = menu; // 点击外部关闭菜单 setTimeout(() => { document.addEventListener('click', closeContextMenu, { once: true }); }); } function closeContextMenu() { if (currentContextMenu) { currentContextMenu.remove(); currentContextMenu = null; } } function renameChat(chatId) { const chatKey = `chat_${chatId}`; const chatData = JSON.parse(localStorage.getItem(chatKey)); const currentName = chatData.name || `聊天 ${new Date(parseInt(chatId)).toLocaleString()}`; const newName = prompt('请输入新的聊天名称', currentName); if (newName) { chatData.name = newName; localStorage.setItem(chatKey, JSON.stringify(chatData)); updateChatList(); } } function loadChat(chatId) { currentChatId = chatId; localStorage.setItem('currentChatId', chatId); clearChatArea(); const chatData = JSON.parse(localStorage.getItem(`chat_${chatId}`) || { messages: [] }); chatData.messages.forEach(msg => { appendMessage(msg.content, msg.isAssistant, false); }); updateChatList() } function clearChatArea() { chatArea.innerHTML = ''; welcomeMessage.style.display = 'flex'; } function appendMessage(content, isAssistant = false, saveToStorage = true) { welcomeMessage.style.display = 'none'; const messageDiv = document.createElement('div'); messageDiv.className = `max-w-4xl mx-auto mb-4 p-4 rounded-lg ${isAssistant ? 'bg-gray-100' : 'bg-white border'} markdown-body relative`; const renderedContent = DOMPurify.sanitize(marked.parse(content)); messageDiv.innerHTML = renderedContent; // 添加复制按钮 const copyBtn = document.createElement('button'); copyBtn.className = 'absolute top-2 right-2 p-1 bg-gray-200 rounded-md text-xs'; copyBtn.textContent = '复制'; copyBtn.onclick = () => { navigator.clipboard.writeText(content).then(() => { copyBtn.textContent = '已复制'; setTimeout(() => copyBtn.textContent = '复制', 2000); }); }; messageDiv.appendChild(copyBtn); chatArea.appendChild(messageDiv); chatArea.scrollTop = chatArea.scrollHeight; // 仅在需要时保存到本地存储 if (saveToStorage && currentChatId) { // 确保读取和保存完整的数据结构 const chatData = JSON.parse(localStorage.getItem(`chat_${currentChatId}`) || '{"name": "新聊天", "messages": []}'); chatData.messages.push({ content, isAssistant }); localStorage.setItem(`chat_${currentChatId}`, JSON.stringify(chatData)); } } function startEventStream(message) { if (currentEventSource) { currentEventSource.close(); } // 选项值, // 组装01;http://localhost:8095/api/v1/ollama/generate_stream?message=Hello&model=deepseek-r1:1.5b // 组装02;http://localhost:8095/api/v1/openai/generate_stream?message=Hello&model=gpt-4o const ragTag = document.getElementById('ragSelect').value; const aiModelSelect = document.getElementById('aiModel'); const aiModelValue = aiModelSelect.value; // 获取选中的 aiModel 的 value const aiModelModel = aiModelSelect.options[aiModelSelect.selectedIndex].getAttribute('model'); // 获取选中的 aiModel 的 model 属性 let url; if (ragTag) { url = `http://localhost:8095/api/v1/${aiModelValue}/generate_stream_rag?message=${encodeURIComponent(message)}&ragTag=${encodeURIComponent(ragTag)}&model=${encodeURIComponent(aiModelModel)}`; } else { url = `http://localhost:8095/api/v1/${aiModelValue}/generate_stream?message=${encodeURIComponent(message)}&model=${encodeURIComponent(aiModelModel)}`; } currentEventSource = new EventSource(url); let accumulatedContent = ''; let tempMessageDiv = null; currentEventSource.onmessage = function(event) { try { const data = JSON.parse(event.data); if (data.result?.output?.content) { const newContent = data.result.output.content; accumulatedContent += newContent; // 首次创建临时消息容器 if (!tempMessageDiv) { tempMessageDiv = document.createElement('div'); tempMessageDiv.className = 'max-w-4xl mx-auto mb-4 p-4 rounded-lg bg-gray-100 markdown-body relative'; chatArea.appendChild(tempMessageDiv); welcomeMessage.style.display = 'none'; } // 直接更新文本内容(先不解析Markdown) tempMessageDiv.textContent = accumulatedContent; chatArea.scrollTop = chatArea.scrollHeight; } if (data.result?.output?.properties?.finishReason === 'STOP') { currentEventSource.close(); // 流式传输完成后进行最终渲染 const finalContent = accumulatedContent; tempMessageDiv.innerHTML = DOMPurify.sanitize(marked.parse(finalContent)); // 添加复制按钮 const copyBtn = document.createElement('button'); copyBtn.className = 'absolute top-2 right-2 p-1 bg-gray-200 rounded-md text-xs'; copyBtn.textContent = '复制'; copyBtn.onclick = () => { navigator.clipboard.writeText(finalContent).then(() => { copyBtn.textContent = '已复制'; setTimeout(() => copyBtn.textContent = '复制', 2000); }); }; tempMessageDiv.appendChild(copyBtn); // 保存到本地存储 if (currentChatId) { // 正确的数据结构应该是对象包含messages数组 const chatData = JSON.parse(localStorage.getItem(`chat_${currentChatId}`) || '{"name": "新聊天", "messages": []}'); chatData.messages.push({ content: finalContent, isAssistant: true }); localStorage.setItem(`chat_${currentChatId}`, JSON.stringify(chatData)); } } } catch (e) { console.error('Error parsing event data:', e); } }; currentEventSource.onerror = function(error) { console.error('EventSource error:', error); currentEventSource.close(); }; } submitBtn.addEventListener('click', () => { const message = messageInput.value.trim(); if (!message) return; if (!currentChatId) { createNewChat(); } appendMessage(message, false); messageInput.value = ''; startEventStream(message); }); messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submitBtn.click(); } }); newChatBtn.addEventListener('click', createNewChat); toggleSidebarBtn.addEventListener('click', () => { sidebar.classList.toggle('-translate-x-full'); updateSidebarIcon(); }); function updateSidebarIcon() { const iconPath = document.getElementById('sidebarIconPath'); if (sidebar.classList.contains('-translate-x-full')) { iconPath.setAttribute('d', 'M4 6h16M4 12h4m12 0h-4M4 18h16'); } else { iconPath.setAttribute('d', 'M4 6h16M4 12h16M4 18h16'); } } // Initialize updateChatList(); const savedChatId = localStorage.getItem('currentChatId'); if (savedChatId) { loadChat(savedChatId); } // Handle window resize for responsive design window.addEventListener('resize', () => { if (window.innerWidth > 768) { sidebar.classList.remove('-translate-x-full'); } else { sidebar.classList.add('-translate-x-full'); } }); // Initial check for mobile devices if (window.innerWidth <= 768) { sidebar.classList.add('-translate-x-full'); } updateSidebarIcon(); // 上传知识下拉菜单控制 const uploadMenuButton = document.getElementById('uploadMenuButton'); const uploadMenu = document.getElementById('uploadMenu'); // 切换菜单显示 uploadMenuButton.addEventListener('click', (e) => { e.stopPropagation(); uploadMenu.classList.toggle('hidden'); }); // 点击外部区域关闭菜单 document.addEventListener('click', (e) => { if (!uploadMenu.contains(e.target) && e.target !== uploadMenuButton) { uploadMenu.classList.add('hidden'); } }); // 菜单项点击后关闭菜单 document.querySelectorAll('#uploadMenu a').forEach(item => { item.addEventListener('click', () => { uploadMenu.classList.add('hidden'); }); });