document.addEventListener('DOMContentLoaded', function() { setTimeout(() => { window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' }); }, 200); // Задержка в 100 миллисекунд }); class Chat { constructor(messagesContainerId, userInputId, inputAreaId, apiUrl, hiddenIconId, hiddenContainerId, modalId, modalTextId, closeBtnClass, themeSwitchId, themeIconId) { this.messagesContainer = document.getElementById(messagesContainerId); this.userInput = document.getElementById(userInputId); this.inputArea = document.getElementById(inputAreaId); this.API_URL = apiUrl; this.GEMINI_API_KEY = null; this.MAX_HISTORY = 10; this.messages = JSON.parse(localStorage.getItem('chatHistory')) || []; this.hiddenIcon = document.getElementById(hiddenIconId); this.hiddenContainer = document.getElementById(hiddenContainerId); this.modal = document.getElementById(modalId); this.modalText = document.getElementById(modalTextId); this.closeBtn = document.querySelector(closeBtnClass); this.themeSwitch = document.getElementById(themeSwitchId); this.themeIcon = document.getElementById(themeIconId); this.isDarkTheme = localStorage.getItem('theme') === 'dark'; this.init(); // Свойство для хранения экземпляра чата Chat.instance = this; } async init() { this.setupEventListeners(); this.setupTextareaHandling(); await this.initializeApiKey(); this.loadChatHistory(); this.scrollInputIntoView(); this.setInitialTheme(); } setInitialTheme() { if(this.isDarkTheme) { this.setDarkTheme(); } else { this.setLightTheme(); } } scrollInputIntoView() { if (this.inputArea) { this.inputArea.scrollIntoView({ behavior: 'smooth', block: 'end' }); } } setupEventListeners() { this.userInput.addEventListener('input', this.handleInput.bind(this)); this.userInput.addEventListener('keydown', this.handleEnter.bind(this)); this.themeSwitch.addEventListener('click', this.toggleTheme.bind(this)); } handleEnter(event) { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); this.sendMessage(); } } toggleTheme() { if (this.isDarkTheme) { this.setLightTheme(); localStorage.setItem('theme', 'light'); } else { this.setDarkTheme(); localStorage.setItem('theme', 'dark'); } this.isDarkTheme = !this.isDarkTheme; } setLightTheme() { document.body.classList.remove('dark-theme'); this.themeIcon.classList.remove('dark'); this.themeIcon.classList.add('light'); this.themeIcon.innerHTML = '☀'; // Иконка солнца } setDarkTheme() { document.body.classList.add('dark-theme'); this.themeIcon.classList.remove('light'); this.themeIcon.classList.add('dark'); this.themeIcon.innerHTML = '☽'; // Иконка луны } async initializeApiKey() { try { const response = await fetch(this.API_URL); const data = await response.json(); this.GEMINI_API_KEY = data.api_key || null; if (!this.GEMINI_API_KEY) throw new Error('API key not found'); } catch (error) { console.error('Ошибка получения ключа:', error); this.addMessage('Ошибка инициализации. Перезагрузите страницу.', 'assistant'); } } handleInput() { document.querySelector('button').disabled = !this.userInput.value.trim(); } setupTextareaHandling() { this.userInput.addEventListener('input', () => { const text = this.userInput.value; if (text.length > 400) { // Перемещаем весь текст в скрытый контейнер this.hiddenContainer.textContent += text + '\n'; // Очищаем поле ввода this.userInput.value = ''; // Показываем иконку this.hiddenIcon.style.display = 'block'; } }); // Открыть модальное окно с текстом this.hiddenIcon.addEventListener('click', () => { this.modalText.textContent = this.hiddenContainer.textContent; // Заполняем текст this.modal.style.display = 'flex'; }); // Закрыть модальное окно this.closeBtn.addEventListener('click', () => { this.modal.style.display = 'none'; }); // Закрытие окна при клике вне его window.addEventListener('click', (event) => { if (event.target === this.modal) { this.modal.style.display = 'none'; } }); } async sendMessage() { if (!this.GEMINI_API_KEY) { this.addMessage('Система не готова. Обновите страницу.', 'assistant'); return; } let userMessage = this.userInput.value.trim(); if (this.hiddenContainer.textContent.trim() !== '') { userMessage = userMessage + '\n\n' + this.hiddenContainer.textContent.trim() ; this.hiddenContainer.textContent = ''; this.hiddenIcon.style.display = 'none'; } if (!userMessage) return; this.addMessage(userMessage, "user"); this.userInput.value = ""; this.userInput.style.height = 'auto'; this.messages.push({ role: "user", content: userMessage }); this.saveChatHistory(); this.addMessage("Думаю над ответом...", "assistant", true); try { const response = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${this.GEMINI_API_KEY}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ contents: this.messages.slice(-this.MAX_HISTORY).map(msg => ({ role: msg.role, parts: [{ text: msg.content }] })) }) } ); const data = await response.json(); if (!response.ok) throw new Error(data.error?.message || 'API Error'); const botMessage = data.candidates?.[0]?.content?.parts?.[0]?.text || 'Не удалось получить ответ'; this.removeThinkingMessage(); this.addMessage(botMessage, "assistant", false, true); this.messages.push({ role: "assistant", content: botMessage }); this.saveChatHistory(); } catch (error) { console.error("Error:", error); this.removeThinkingMessage(); this.addMessage(`Ошибка: ${error.message}`, "assistant"); } } clearHiddenText(event) { event.stopPropagation(); this.hiddenContainer.textContent = ''; this.hiddenIcon.style.display = 'none'; } addMessage(text, role, isTemporary = false, animate = true) { const messageElement = document.createElement("div"); messageElement.className = `message ${role}`; // Начальные стили для анимации messageElement.style.transition = 'opacity 0.3s ease-out, transform 0.3s ease-out'; if (animate) { messageElement.style.opacity = '0'; messageElement.style.transform = 'translateY(-20px)'; } else { messageElement.style.opacity = '1'; messageElement.style.transform = 'translateY(0)'; } if (!isTemporary) { messageElement.dataset.content = text; // Кнопка удаления const deleteBtn = document.createElement('div'); deleteBtn.className = 'delete-btn'; deleteBtn.innerHTML = '×'; deleteBtn.onclick = () => { messageElement.remove(); this.updateMessagesFromDOM(); this.saveChatHistory(); }; messageElement.appendChild(deleteBtn); } if (isTemporary) { messageElement.classList.add("thinking"); messageElement.textContent = text; } else { if (role === "assistant") { const htmlContent = marked.parse(text); const contentDiv = document.createElement("div"); contentDiv.className = "markdown-content"; contentDiv.innerHTML = htmlContent; // Обработка блоков кода contentDiv.querySelectorAll('pre').forEach(pre => { const wrapper = document.createElement('div'); wrapper.className = 'code-block-wrapper'; const header = document.createElement('div'); header.className = 'code-block-header'; const copyButton = document.createElement('button'); copyButton.className = 'copy-button'; copyButton.textContent = 'Копировать'; copyButton.onclick = () => this.handleCopyClick(pre, copyButton); const downloadButton = document.createElement('button'); downloadButton.className = 'download-button'; downloadButton.textContent = 'Скачать'; downloadButton.onclick = () => this.handleDownloadClick(pre); header.appendChild(copyButton); header.appendChild(downloadButton); wrapper.append(header, pre.cloneNode(true)); pre.parentNode.replaceChild(wrapper, pre); }); messageElement.appendChild(contentDiv); setTimeout(() => Prism.highlightAllUnder(messageElement), 0); } else { const textNode = document.createElement('div'); textNode.textContent = text.replace(/\n/g, '\n'); // Явное сохранение переносов messageElement.appendChild(textNode); } } this.messagesContainer.appendChild(messageElement); // Активация анимации if (animate) { setTimeout(() => { messageElement.style.opacity = '1'; messageElement.style.transform = 'translateY(0)'; }, 10); } if(role === 'assistant') { messageElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); } else { // Плавная прокрутка вниз this.messagesContainer.scrollTo({ top: this.messagesContainer.scrollHeight, behavior: "smooth" }); } } handleCopyClick(pre, button) { const code = pre.textContent; if (this.copyToClipboard(code)) { button.textContent = 'Скопировано!'; button.classList.add('copied'); setTimeout(() => { button.textContent = 'Копировать'; button.classList.remove('copied'); }, 2000); } else { button.textContent = 'Ошибка!'; setTimeout(() => button.textContent = 'Копировать', 2000); } } handleDownloadClick(pre) { const code = pre.textContent; const blob = new Blob([code], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'code.html'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } copyToClipboard(text) { const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; document.body.appendChild(textarea); textarea.select(); try { return document.execCommand('copy'); } finally { document.body.removeChild(textarea); } } removeThinkingMessage() { const thinking = this.messagesContainer.querySelector('.thinking'); if (thinking) thinking.remove(); } saveChatHistory() { localStorage.setItem('chatHistory', JSON.stringify(this.messages.slice(-this.MAX_HISTORY))); } updateMessagesFromDOM() { this.messages = Array.from(this.messagesContainer.querySelectorAll('.message:not(.thinking)')) .map(el => ({ role: el.classList.contains('user') ? 'user' : 'assistant', content: el.dataset.content || el.textContent })); } loadChatHistory() { this.messages.forEach(msg => this.addMessage(msg.content, msg.role, false, false)); } clearChatHistory() { this.messages = []; localStorage.removeItem('chatHistory'); this.messagesContainer.innerHTML = ""; } } document.addEventListener('DOMContentLoaded', () => { const chat = new Chat('messages', 'user-input', 'input-area', 'http://mcmstest.atwebpages.com/apikey', 'hiddenIcon', 'hiddenContainer', 'myModal', 'modalText', '.close', 'theme-switch', 'theme-icon'); document.querySelector('button').addEventListener('click', () => chat.sendMessage()); // Устанавливаем обработчик клика на ссылку очистки истории document.getElementById('clear-history-link').addEventListener('click', function(event) { event.preventDefault(); if (Chat.instance) { Chat.instance.clearChatHistory(); } else { console.error('Экземпляр чата не инициализирован'); } }); }); // Настройки Markdown marked.setOptions({ headerIds: false, mangle: false, highlight: (code, lang) => { return Prism.languages[lang] ? Prism.highlight(code, Prism.languages[lang], lang) : code; } });