|
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); |
|
} |
|
}); |