| document.addEventListener('DOMContentLoaded', function() { |
| |
| const userInput = document.getElementById('user-input'); |
| const sendBtn = document.getElementById('send-btn'); |
| const messagesContainer = document.getElementById('messages'); |
| const chatContainer = document.getElementById('chat-container'); |
| const welcomeScreen = document.getElementById('welcome-screen'); |
| const newChatBtn = document.getElementById('new-chat'); |
| const chatHistory = document.getElementById('chat-history'); |
| const toggleModeBtn = document.getElementById('toggle-mode'); |
| const examples = document.querySelectorAll('.example'); |
| |
| |
| let currentChatId = generateId(); |
| let chats = {}; |
| let isWaitingForResponse = false; |
| |
| |
| initTheme(); |
| loadChats(); |
| |
| |
| sendBtn.addEventListener('click', sendMessage); |
| userInput.addEventListener('keydown', handleKeyDown); |
| newChatBtn.addEventListener('click', createNewChat); |
| toggleModeBtn.addEventListener('click', toggleDarkMode); |
| |
| |
| examples.forEach(example => { |
| example.addEventListener('click', () => { |
| const prompt = example.getAttribute('data-prompt'); |
| if (prompt) { |
| hideWelcomeScreen(); |
| userInput.value = prompt; |
| sendMessage(); |
| } |
| }); |
| }); |
| |
| |
| userInput.addEventListener('input', function() { |
| this.style.height = 'auto'; |
| this.style.height = (this.scrollHeight) + 'px'; |
| |
| |
| sendBtn.disabled = this.value.trim() === '' || isWaitingForResponse; |
| }); |
| |
| |
| function sendMessage() { |
| const message = userInput.value.trim(); |
| if (message === '' || isWaitingForResponse) return; |
| |
| |
| addMessage('user', message); |
| |
| |
| userInput.value = ''; |
| userInput.style.height = 'auto'; |
| sendBtn.disabled = true; |
| |
| |
| showTypingIndicator(); |
| |
| |
| isWaitingForResponse = true; |
| |
| |
| saveChat(message); |
| |
| |
| fetch('/api/chat', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify({ prompt: message }) |
| }) |
| .then(response => response.json()) |
| .then(data => { |
| |
| removeTypingIndicator(); |
| |
| |
| if (data.error) { |
| addMessage('ai', `Ошибка: ${data.error}`); |
| } else { |
| addMessage('ai', data.response); |
| } |
| |
| |
| saveChat(null, data.response || `Ошибка: ${data.error}`); |
| |
| |
| isWaitingForResponse = false; |
| |
| |
| sendBtn.disabled = userInput.value.trim() === ''; |
| }) |
| .catch(error => { |
| console.error('Ошибка:', error); |
| removeTypingIndicator(); |
| addMessage('ai', 'Произошла ошибка при обработке запроса. Пожалуйста, попробуйте еще раз.'); |
| isWaitingForResponse = false; |
| sendBtn.disabled = userInput.value.trim() === ''; |
| }); |
| } |
| |
| function handleKeyDown(e) { |
| if (e.key === 'Enter' && !e.shiftKey) { |
| e.preventDefault(); |
| sendMessage(); |
| } |
| } |
| |
| function addMessage(sender, text) { |
| |
| hideWelcomeScreen(); |
| |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `message ${sender}`; |
| |
| const avatarDiv = document.createElement('div'); |
| avatarDiv.className = `message-avatar ${sender}`; |
| avatarDiv.innerHTML = sender === 'user' ? '<i class="fas fa-user"></i>' : '<i class="fas fa-robot"></i>'; |
| |
| const contentDiv = document.createElement('div'); |
| contentDiv.className = 'message-content'; |
| |
| const senderDiv = document.createElement('div'); |
| senderDiv.className = 'message-sender'; |
| senderDiv.textContent = sender === 'user' ? 'Вы' : 'AI'; |
| |
| const textDiv = document.createElement('div'); |
| textDiv.className = 'message-text'; |
| |
| |
| if (sender === 'ai') { |
| textDiv.innerHTML = marked.parse(text); |
| |
| |
| textDiv.querySelectorAll('pre code').forEach((block) => { |
| hljs.highlightElement(block); |
| }); |
| |
| |
| const actionsDiv = document.createElement('div'); |
| actionsDiv.className = 'message-actions'; |
| |
| const copyBtn = document.createElement('button'); |
| copyBtn.className = 'action-btn'; |
| copyBtn.innerHTML = '<i class="fas fa-copy"></i> Копировать'; |
| copyBtn.addEventListener('click', () => copyToClipboard(text)); |
| |
| actionsDiv.appendChild(copyBtn); |
| contentDiv.appendChild(actionsDiv); |
| } else { |
| textDiv.textContent = text; |
| } |
| |
| contentDiv.appendChild(senderDiv); |
| contentDiv.appendChild(textDiv); |
| |
| messageDiv.appendChild(avatarDiv); |
| messageDiv.appendChild(contentDiv); |
| |
| messagesContainer.appendChild(messageDiv); |
| |
| |
| messageDiv.scrollIntoView({ behavior: 'smooth' }); |
| } |
| |
| function showTypingIndicator() { |
| const indicatorDiv = document.createElement('div'); |
| indicatorDiv.className = 'message ai typing'; |
| indicatorDiv.innerHTML = ` |
| <div class="message-avatar ai"> |
| <i class="fas fa-robot"></i> |
| </div> |
| <div class="message-content"> |
| <div class="message-sender">Mistral AI</div> |
| <div class="typing-indicator"> |
| <div class="typing-dot"></div> |
| <div class="typing-dot"></div> |
| <div class="typing-dot"></div> |
| </div> |
| </div> |
| `; |
| |
| messagesContainer.appendChild(indicatorDiv); |
| indicatorDiv.scrollIntoView({ behavior: 'smooth' }); |
| } |
| |
| function removeTypingIndicator() { |
| const typingIndicator = document.querySelector('.message.typing'); |
| if (typingIndicator) { |
| typingIndicator.remove(); |
| } |
| } |
| |
| function hideWelcomeScreen() { |
| if (welcomeScreen.style.display !== 'none') { |
| welcomeScreen.style.display = 'none'; |
| } |
| } |
| |
| function createNewChat() { |
| |
| saveChats(); |
| |
| |
| currentChatId = generateId(); |
| chats[currentChatId] = { |
| id: currentChatId, |
| title: 'Новый чат', |
| messages: [], |
| timestamp: Date.now() |
| }; |
| |
| |
| messagesContainer.innerHTML = ''; |
| |
| |
| welcomeScreen.style.display = 'flex'; |
| |
| |
| updateChatHistory(); |
| } |
| |
| function saveChat(userMessage, aiResponse) { |
| if (!chats[currentChatId]) { |
| chats[currentChatId] = { |
| id: currentChatId, |
| title: userMessage ? userMessage.substring(0, 30) : 'Новый чат', |
| messages: [], |
| timestamp: Date.now() |
| }; |
| } |
| |
| if (userMessage) { |
| chats[currentChatId].messages.push({ |
| sender: 'user', |
| text: userMessage, |
| timestamp: Date.now() |
| }); |
| |
| |
| if (chats[currentChatId].messages.length === 1) { |
| chats[currentChatId].title = userMessage.substring(0, 30) + (userMessage.length > 30 ? '...' : ''); |
| } |
| } |
| |
| if (aiResponse) { |
| chats[currentChatId].messages.push({ |
| sender: 'ai', |
| text: aiResponse, |
| timestamp: Date.now() |
| }); |
| } |
| |
| |
| saveChats(); |
| |
| |
| updateChatHistory(); |
| } |
| |
| function saveChats() { |
| localStorage.setItem('mistral_chats', JSON.stringify(chats)); |
| } |
| |
| function loadChats() { |
| const savedChats = localStorage.getItem('mistral_chats'); |
| if (savedChats) { |
| chats = JSON.parse(savedChats); |
| updateChatHistory(); |
| |
| |
| const chatIds = Object.keys(chats); |
| if (chatIds.length > 0) { |
| |
| const sortedIds = chatIds.sort((a, b) => chats[b].timestamp - chats[a].timestamp); |
| loadChat(sortedIds[0]); |
| } |
| } |
| } |
| |
| function updateChatHistory() { |
| chatHistory.innerHTML = ''; |
| |
| |
| const sortedChats = Object.values(chats).sort((a, b) => b.timestamp - a.timestamp); |
| |
| sortedChats.forEach(chat => { |
| const chatItem = document.createElement('div'); |
| chatItem.className = `chat-item ${chat.id === currentChatId ? 'active' : ''}`; |
| chatItem.setAttribute('data-id', chat.id); |
| |
| chatItem.innerHTML = ` |
| <i class="fas fa-comment"></i> |
| <span>${chat.title}</span> |
| `; |
| |
| chatItem.addEventListener('click', () => loadChat(chat.id)); |
| |
| chatHistory.appendChild(chatItem); |
| }); |
| } |
| |
| function loadChat(chatId) { |
| if (!chats[chatId]) return; |
| |
| currentChatId = chatId; |
| messagesContainer.innerHTML = ''; |
| welcomeScreen.style.display = 'none'; |
| |
| |
| chats[chatId].messages.forEach(msg => { |
| addMessage(msg.sender, msg.text); |
| }); |
| |
| |
| updateChatHistory(); |
| } |
| |
| function toggleDarkMode() { |
| const isDarkMode = document.body.classList.toggle('dark-mode'); |
| localStorage.setItem('dark_mode', isDarkMode ? 'true' : 'false'); |
| |
| |
| toggleModeBtn.innerHTML = isDarkMode ? |
| '<i class="fas fa-sun"></i>' : |
| '<i class="fas fa-moon"></i>'; |
| } |
| |
| function initTheme() { |
| const savedTheme = localStorage.getItem('dark_mode'); |
| if (savedTheme === 'true') { |
| document.body.classList.add('dark-mode'); |
| toggleModeBtn.innerHTML = '<i class="fas fa-sun"></i>'; |
| } |
| } |
| |
| function copyToClipboard(text) { |
| navigator.clipboard.writeText(text).then(() => { |
| |
| const notification = document.createElement('div'); |
| notification.className = 'copy-notification'; |
| notification.textContent = 'Скопировано в буфер обмена'; |
| document.body.appendChild(notification); |
| |
| setTimeout(() => { |
| notification.remove(); |
| }, 2000); |
| }).catch(err => { |
| console.error('Ошибка при копировании: ', err); |
| }); |
| } |
| |
| function generateId() { |
| return Date.now().toString(36) + Math.random().toString(36).substring(2); |
| } |
| }); |