|
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' ? 'Вы' : 'Mistral 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);
|
|
}
|
|
}); |