Spaces:
Runtime error
Runtime error
| document.addEventListener("DOMContentLoaded", () => { | |
| let mediaRecorder, audioChunks = [], audioStream, currentChatId = null; | |
| const recordBtn = document.getElementById("record-btn"); | |
| const stopBtn = document.getElementById("stop-btn"); | |
| const sendBtn = document.getElementById("send-btn"); | |
| const userInput = document.getElementById("user-input"); | |
| const chatBox = document.getElementById("chat-box"); | |
| const audioFileInput = document.getElementById("audio-file"); | |
| const newChatBtn = document.getElementById("new-chat-btn"); | |
| const chatList = document.getElementById("chat-list"); | |
| const currentChatTitle = document.getElementById("current-chat-title"); | |
| const fileInfo = document.getElementById("file-info"); | |
| const fileName = document.getElementById("file-name"); | |
| const clearFileBtn = document.getElementById("clear-file"); | |
| // Emotion Map (скопирован из profile.html) | |
| const emotionMap = { | |
| 'joy': '😊 Радость', | |
| 'neutral': '😐 Нейтрально', | |
| 'anger': '😠 Злость', | |
| 'sadness': '😢 Грусть', | |
| 'surprise': '😲 Удивление' | |
| }; | |
| // Инициализация при загрузке | |
| initializeChats(); | |
| function initializeChats() { | |
| const savedChatId = localStorage.getItem('currentChatId'); | |
| fetch("/get_chats") | |
| .then(response => response.json()) | |
| .then(chats => { | |
| renderChatList(chats); | |
| if (savedChatId && chats.some(c => c.chat_id === savedChatId)) { | |
| loadChat(savedChatId); | |
| } else if (chats.length > 0) { | |
| loadChat(chats[0].chat_id); | |
| } else { | |
| showEmptyChatUI(); | |
| } | |
| }) | |
| .catch(error => { | |
| console.error("Ошибка загрузки чатов:", error); | |
| showEmptyChatUI(); | |
| }); | |
| } | |
| function showEmptyChatUI() { | |
| if (chatBox) chatBox.innerHTML = '<div class="empty-chat">Нет активного чата</div>'; | |
| } | |
| function renderChatList(chats) { | |
| if (!chatList) return; | |
| chatList.innerHTML = ''; | |
| chats.forEach(chat => { | |
| const chatItem = document.createElement("div"); | |
| chatItem.className = "chat-item"; | |
| chatItem.dataset.chatId = chat.chat_id; | |
| chatItem.innerHTML = ` | |
| <div class="chat-item-main"> | |
| <i class="fas fa-comment chat-icon"></i> | |
| <div class="chat-item-content"> | |
| <span class="chat-title">${chat.title}</span> | |
| <span class="chat-date">${formatDate(chat.created_at)}</span> | |
| </div> | |
| </div> | |
| <button class="delete-chat-btn" title="Удалить чат"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| `; | |
| chatItem.querySelector('.chat-item-main').addEventListener('click', () => { | |
| loadChat(chat.chat_id); | |
| localStorage.setItem('currentChatId', chat.chat_id); | |
| }); | |
| chatItem.querySelector('.delete-chat-btn').addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| deleteChat(chat.chat_id); | |
| }); | |
| chatList.appendChild(chatItem); | |
| }); | |
| } | |
| function formatDate(dateString) { | |
| if (!dateString) return ''; | |
| const date = new Date(dateString); | |
| return date.toLocaleDateString('ru-RU'); | |
| } | |
| async function deleteChat(chatId) { | |
| if (!confirm('Вы точно хотите удалить этот чат? Это действие нельзя отменить.')) return; | |
| try { | |
| const response = await fetch(`/delete_chat/${chatId}`, { | |
| method: 'DELETE', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'X-CSRFToken': getCSRFToken() | |
| } | |
| }); | |
| const result = await response.json(); | |
| if (result.success) { | |
| if (currentChatId === chatId) { | |
| chatBox.innerHTML = '<div class="empty-chat">Чат удалён</div>'; | |
| currentChatId = null; | |
| } | |
| initializeChats(); // Перезагружаем список чатов | |
| } else { | |
| throw new Error(result.error || 'Ошибка при удалении чата'); | |
| } | |
| } catch (error) { | |
| console.error('Delete chat error:', error); | |
| appendMessage('bot', `❌ Ошибка при удалении: ${error.message}`); | |
| } | |
| } | |
| function getCSRFToken() { | |
| const meta = document.querySelector('meta[name="csrf-token"]'); | |
| return meta ? meta.content : ''; | |
| } | |
| newChatBtn?.addEventListener("click", startNewChat); | |
| function startNewChat() { | |
| fetch("/start_chat", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| currentChatId = data.chat_id; | |
| if (currentChatTitle) { | |
| currentChatTitle.textContent = data.title; | |
| } | |
| chatBox.innerHTML = '<div class="message bot-message">Привет! Отправьте текст или голосовое сообщение для анализа эмоций.</div>'; | |
| initializeChats(); | |
| localStorage.setItem('currentChatId', data.chat_id); | |
| }) | |
| .catch(console.error); | |
| } | |
| function loadChat(chatId) { | |
| fetch(`/load_chat/${chatId}`) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.error) throw new Error(data.error); | |
| currentChatId = chatId; | |
| currentChatTitle.textContent = data.title; | |
| updateActiveChat(chatId); | |
| chatBox.innerHTML = ""; | |
| data.messages.forEach(msg => { | |
| appendMessage(msg.sender, msg.content); | |
| }); | |
| localStorage.setItem('currentChatId', chatId); | |
| }) | |
| .catch(error => { | |
| console.error("Ошибка загрузки чата:", error); | |
| appendMessage("bot", `❌ Ошибка: ${error.message}`); | |
| }); | |
| } | |
| function updateActiveChat(chatId) { | |
| document.querySelectorAll(".chat-item").forEach(item => { | |
| item.classList.toggle("active", item.dataset.chatId === chatId); | |
| }); | |
| } | |
| sendBtn?.addEventListener("click", sendMessage); | |
| userInput?.addEventListener("keypress", (e) => { | |
| if (e.key === "Enter") sendMessage(); | |
| }); | |
| async function sendMessage() { | |
| const text = userInput?.value.trim(); | |
| if (!text || !currentChatId) return; | |
| appendAndSaveMessage("user", text); | |
| userInput.value = ""; | |
| try { | |
| const response = await fetch("/analyze", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ text, chat_id: currentChatId }) | |
| }); | |
| const data = await response.json(); | |
| appendAndSaveMessage("bot", `Эмоция: ${data.emotion} (${(data.confidence * 100).toFixed(1)}%)`); | |
| } catch (error) { | |
| console.error("Ошибка:", error); | |
| appendAndSaveMessage("bot", `❌ Ошибка: ${error.message}`); | |
| } | |
| } | |
| // Обработчики аудио | |
| if (audioFileInput) { | |
| audioFileInput.addEventListener("change", handleAudioUpload); | |
| } | |
| if (clearFileBtn) { | |
| clearFileBtn.addEventListener("click", clearAudioFile); | |
| } | |
| function handleAudioUpload() { | |
| const file = audioFileInput?.files[0]; | |
| if (file) { | |
| fileName.textContent = file.name; | |
| fileInfo.style.display = 'flex'; | |
| sendAudioFile(file); | |
| } | |
| } | |
| function clearAudioFile() { | |
| audioFileInput.value = ''; | |
| fileInfo.style.display = 'none'; | |
| } | |
| async function sendAudioFile(file) { | |
| if (!currentChatId) return; | |
| appendAndSaveMessage("user", "Загружен аудиофайл..."); | |
| try { | |
| const formData = new FormData(); | |
| formData.append("audio", file); | |
| formData.append("chat_id", currentChatId); | |
| const response = await fetch("/analyze_audio", { | |
| method: "POST", | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (data.transcribed_text) { | |
| appendAndSaveMessage("user", `Распознанный текст: ${data.transcribed_text}`); | |
| } | |
| appendAndSaveMessage("bot", `Эмоция: ${data.emotion} (${(data.confidence * 100).toFixed(1)}%)`); | |
| clearAudioFile(); | |
| } catch (error) { | |
| console.error("Ошибка:", error); | |
| appendAndSaveMessage("bot", `❌ Ошибка: ${error.message}`); | |
| } | |
| } | |
| // Запись голоса | |
| if (recordBtn) recordBtn.addEventListener("click", startRecording); | |
| if (stopBtn) stopBtn.addEventListener("click", stopRecording); | |
| async function startRecording() { | |
| try { | |
| audioStream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
| mediaRecorder = new MediaRecorder(audioStream); | |
| audioChunks = []; | |
| mediaRecorder.ondataavailable = e => audioChunks.push(e.data); | |
| mediaRecorder.onstop = async () => { | |
| const audioBlob = new Blob(audioChunks, { type: "audio/wav" }); | |
| sendAudioBlob(audioBlob); | |
| }; | |
| mediaRecorder.start(); | |
| if (recordBtn) recordBtn.disabled = true; | |
| if (stopBtn) stopBtn.disabled = false; | |
| appendMessage("user", "Запись начата..."); | |
| } catch (error) { | |
| console.error("Ошибка записи:", error); | |
| appendMessage("bot", "❌ Не удалось получить доступ к микрофону"); | |
| } | |
| } | |
| function stopRecording() { | |
| if (mediaRecorder?.state === "recording") { | |
| mediaRecorder.stop(); | |
| if (recordBtn) recordBtn.disabled = false; | |
| if (stopBtn) stopBtn.disabled = true; | |
| if (audioStream) audioStream.getTracks().forEach(track => track.stop()); | |
| } | |
| } | |
| async function sendAudioBlob(audioBlob) { | |
| if (!currentChatId) return; | |
| appendAndSaveMessage("user", "Отправлено голосовое сообщение..."); | |
| try { | |
| const formData = new FormData(); | |
| formData.append("audio", audioBlob, "recording.wav"); | |
| formData.append("chat_id", currentChatId); | |
| const response = await fetch("/analyze_audio", { | |
| method: "POST", | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (data.transcribed_text) { | |
| appendAndSaveMessage("user", `Распознанный текст: ${data.transcribed_text}`); | |
| } | |
| appendAndSaveMessage("bot", `Эмоция: ${data.emotion} (${(data.confidence * 100).toFixed(1)}%)`); | |
| } catch (error) { | |
| console.error("Ошибка:", error); | |
| appendAndSaveMessage("bot", `❌ Ошибка: ${error.message}`); | |
| } | |
| } | |
| function appendMessage(sender, text) { | |
| const message = document.createElement("div"); | |
| message.className = `message ${sender}-message`; | |
| message.innerHTML = text; | |
| if (chatBox) { | |
| chatBox.appendChild(message); | |
| chatBox.scrollTop = chatBox.scrollHeight; | |
| } | |
| } | |
| function appendAndSaveMessage(sender, text) { | |
| appendMessage(sender, text); | |
| if (currentChatId) { | |
| fetch("/save_message", { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| "X-CSRFToken": getCSRFToken() | |
| }, | |
| body: JSON.stringify({ | |
| chat_id: currentChatId, | |
| sender: sender, | |
| content: text | |
| }) | |
| }).catch(console.error); | |
| } | |
| } | |
| // Telegram анализ | |
| document.getElementById('telegram-upload-form')?.addEventListener('submit', async function(e) { | |
| e.preventDefault(); | |
| const fileInput = document.getElementById('telegram-file'); | |
| const file = fileInput.files[0]; | |
| if (!file) { | |
| alert('Пожалуйста, выберите файл'); | |
| return; | |
| } | |
| try { | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| const response = await fetch('/analyze_telegram_chat', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const result = await response.json(); | |
| if (result.error) { | |
| throw new Error(result.error); | |
| } | |
| alert('Анализ завершен успешно!'); | |
| updateTelegramAnalytics(); // Обновляем графики | |
| } catch (error) { | |
| console.error('Ошибка загрузки файла:', error); | |
| alert(`Ошибка: ${error.message}`); | |
| } | |
| }); | |
| // Функция скользящего среднего | |
| function movingAverage(data, windowSize) { | |
| const result = []; | |
| for (let i = 0; i < data.length; i++) { | |
| const start = Math.max(0, i - windowSize + 1); | |
| const slice = data.slice(start, i + 1); | |
| const avg = slice.reduce((sum, val) => sum + val, 0) / slice.length; | |
| result.push(avg); | |
| } | |
| return result; | |
| } | |
| // Цвета эмоций | |
| function getEmotionColor(emotion) { | |
| const colors = { | |
| '😊 Радость': '#00b894', | |
| '😢 Грусть': '#0984e3', | |
| '😠 Злость': '#d63031', | |
| '😲 Удивление': '#fdcb6e', | |
| '😨 Страх': '#a29bfe', | |
| '😐 Нейтрально': '#636e72' | |
| }; | |
| return colors[emotion] || '#4a4ae8'; | |
| } | |
| // Иконки эмоций | |
| function getEmotionIcon(emotion) { | |
| const icons = { | |
| '😊 Радость': 'fa-smile', | |
| '😢 Грусть': 'fa-sad-tear', | |
| '😠 Злость': 'fa-angry', | |
| '😲 Удивление': 'fa-surprise', | |
| '😨 Страх': 'fa-flushed', | |
| '😐 Нейтрально': 'fa-meh' | |
| }; | |
| return icons[emotion] || 'fa-comment'; | |
| } | |
| // Основная функция анализа Telegram | |
| // Основная функция анализа Telegram | |
| async function updateTelegramAnalytics(range = 'month') { | |
| try { | |
| const response = await fetch('/get_telegram_analysis'); | |
| const analyses = await response.json(); | |
| if (!analyses || analyses.length === 0) { | |
| document.getElementById('emotion-timeline').innerHTML = | |
| '<div class="empty-state"><i class="fas fa-comment-slash"></i><p>Нет данных для отображения</p></div>'; | |
| document.getElementById('emotion-distribution').innerHTML = ''; | |
| return; | |
| } | |
| const allData = analyses.flatMap(a => JSON.parse(a.data)); | |
| const emotionMap = { | |
| 'joy': '😊 Радость', | |
| 'sadness': '😢 Грусть', | |
| 'anger': '😠 Злость', | |
| 'surprise': '😲 Удивление', | |
| 'fear': '😨 Страх', | |
| 'no_emotion': '😐 Нейтрально' | |
| }; | |
| // Фильтрация по пользователю | |
| const userSelect = document.getElementById('user-select'); | |
| const selectedUser = userSelect?.value; | |
| let filteredData = allData; | |
| if (selectedUser && selectedUser !== 'all') { | |
| filteredData = allData.filter(d => d.from === selectedUser); | |
| } | |
| const processedData = filteredData.map(d => ({ | |
| emotion: emotionMap[d.emotion] || d.emotion, | |
| from: d.from, | |
| text: d.text, | |
| date: new Date(d.timestamp), | |
| confidence: d.confidence | |
| })); | |
| // --- Блок подготовки графиков --- | |
| const groupByTime = (date, range) => { | |
| const d = new Date(date); | |
| if (range === 'week') { | |
| d.setHours(0, 0, 0, 0); | |
| d.setDate(d.getDate() - d.getDay()); | |
| return d; | |
| } else if (range === 'month') { | |
| return new Date(d.getFullYear(), d.getMonth(), 1); | |
| } else if (range === 'year') { | |
| return new Date(d.getFullYear(), 0, 1); | |
| } | |
| return new Date(d.getFullYear(), d.getMonth(), d.getDate()); | |
| }; | |
| const groupedData = {}; | |
| processedData.forEach(d => { | |
| const timeKey = groupByTime(d.date, range).getTime(); | |
| if (!groupedData[timeKey]) { | |
| groupedData[timeKey] = { | |
| date: new Date(timeKey), | |
| emotions: {} | |
| }; | |
| } | |
| if (!groupedData[timeKey].emotions[d.emotion]) { | |
| groupedData[timeKey].emotions[d.emotion] = { | |
| count: 0, | |
| totalConfidence: 0 | |
| }; | |
| } | |
| groupedData[timeKey].emotions[d.emotion].count++; | |
| groupedData[timeKey].emotions[d.emotion].totalConfidence += d.confidence; | |
| }); | |
| const timeKeys = Object.keys(groupedData).sort(); | |
| const emotions = [...new Set(processedData.map(d => d.emotion))]; | |
| const traces = emotions.map(emotion => { | |
| const x = []; | |
| const y = []; | |
| const customdata = []; | |
| timeKeys.forEach(key => { | |
| const dataPoint = groupedData[key]; | |
| if (dataPoint.emotions[emotion]) { | |
| x.push(dataPoint.date); | |
| y.push(dataPoint.emotions[emotion].count); | |
| customdata.push({ | |
| emotion: emotion, | |
| avgConfidence: (dataPoint.emotions[emotion].totalConfidence / | |
| dataPoint.emotions[emotion].count).toFixed(2) | |
| }); | |
| } else { | |
| x.push(dataPoint.date); | |
| y.push(0); | |
| customdata.push(null); | |
| } | |
| }); | |
| return { | |
| x: x, | |
| y: y, | |
| name: emotion, | |
| type: 'scatter', | |
| mode: 'lines+markers', | |
| line: { shape: 'spline' }, | |
| marker: { color: getEmotionColor(emotion), size: 6 }, | |
| fill: 'tonexty', | |
| fillcolor: `${getEmotionColor(emotion)}7F`, | |
| customdata: customdata, | |
| hovertemplate: | |
| '<b>%{x|%d %b %Y}</b><br>' + | |
| 'Эмоция: %{fullData.name}<br>' + | |
| 'Сообщений: %{y}<br>' + | |
| 'Средняя уверенность: %{customdata.avgConfidence}<extra></extra>' | |
| }; | |
| }); | |
| Plotly.newPlot('emotion-timeline', traces, { | |
| title: false, | |
| plot_bgcolor: 'rgba(0,0,0,0)', | |
| paper_bgcolor: 'rgba(0,0,0,0)', | |
| font: { color: 'white' }, | |
| xaxis: { | |
| title: 'Дата', | |
| tickformat: range === 'year' ? '%Y' : range === 'month' ? '%b %Y' : '%d %b', | |
| gridcolor: 'rgba(255,255,255,0.1)' | |
| }, | |
| yaxis: { | |
| title: 'Количество сообщений', | |
| gridcolor: 'rgba(255,255,255,0.1)' | |
| }, | |
| hovermode: 'closest', | |
| legend: { | |
| orientation: 'h', | |
| y: -0.2 | |
| }, | |
| margin: { t: 0, b: 80 } | |
| }); | |
| // Тепловая карта | |
| const dates = [...new Set(processedData.map(d => d.date.toDateString()))]; | |
| const emotionLabels = Object.values(emotionMap); | |
| const z = emotionLabels.map(e => dates.map(d => | |
| processedData.filter(msg => msg.date.toDateString() === d && msg.emotion === e).length | |
| )); | |
| Plotly.newPlot('calendar-heatmap', [{ | |
| type: 'heatmap', | |
| z: z, | |
| x: dates, | |
| y: emotionLabels, | |
| colorscale: [ | |
| [0, '#2d3436'], // Темный фон | |
| [0.5, '#6c5ce7'], | |
| [1, '#00b894'] | |
| ], | |
| showscale: true, | |
| colorbar: { | |
| title: 'Частота', | |
| titleside: 'top', | |
| tickmode: 'array', | |
| tickvals: [0, Math.max(...z.flat())], | |
| ticktext: ['Мало', 'Много'], | |
| ticks: 'outside' | |
| } | |
| }], { | |
| title: 'Тепловая карта эмоций по дням', | |
| xaxis: { | |
| title: 'Дата', | |
| tickangle: -45 | |
| }, | |
| yaxis: { | |
| title: 'Эмоции', | |
| automargin: true | |
| }, | |
| margin: { t: 30, r: 30, l: 80, b: 80 } | |
| }); | |
| // Автоматический анализ | |
| const totalMessages = processedData.length; | |
| const emotionCounts = {}; | |
| processedData.forEach(d => { | |
| emotionCounts[d.emotion] = (emotionCounts[d.emotion] || 0) + 1; | |
| }); | |
| const sorted = Object.entries(emotionCounts).sort((a, b) => b[1] - a[1]); | |
| const dominant = sorted[0]; | |
| const sadnessPeaks = processedData | |
| .filter(d => d.emotion === '😢 Грусть') | |
| .reduce((acc, d) => { | |
| const key = d.date.toDateString(); | |
| acc[key] = (acc[key] || 0) + 1; | |
| return acc; | |
| }, {}); | |
| const sadPeak = Object.entries(sadnessPeaks).sort((a, b) => b[1] - a[1])[0]; | |
| document.getElementById('summary-content').innerHTML = ` | |
| <ul style="color: white;"> | |
| <li>💡 Преобладает: ${dominant[0]} (${((dominant[1]/totalMessages)*100).toFixed(1)}%)</li> | |
| <li>📉 Пик грусти: ${sadPeak[0]} (${sadPeak[1]} сообщений)</li> | |
| </ul> | |
| `; | |
| // Круговая диаграмма и статистика | |
| const pieLabels = Object.keys(emotionCounts); | |
| const pieValues = Object.values(emotionCounts); | |
| Plotly.newPlot('emotion-distribution-pie', [{ | |
| labels: pieLabels, | |
| values: pieValues, | |
| type: 'pie', | |
| textinfo: 'label+percent', | |
| hoverinfo: 'label+value+percent', | |
| marker: { | |
| colors: pieLabels.map(e => getEmotionColor(e)) | |
| }, | |
| textfont: { | |
| color: 'white' | |
| }, | |
| hole: 0.4, | |
| rotation: 45 | |
| }], { | |
| title: false, | |
| plot_bgcolor: 'rgba(0,0,0,0)', | |
| paper_bgcolor: 'rgba(0,0,0,0)', | |
| font: { color: 'white' }, | |
| showlegend: false, | |
| margin: { t: 0, b: 0, l: 0, r: 0 } | |
| }); | |
| // Статистика по эмоциям — 2 строки по 3 эмоции | |
| const statsHTML = pieLabels.slice(0, 6).map((emotion, i) => { | |
| const percentage = ((pieValues[i] / totalMessages) * 100).toFixed(1); | |
| return ` | |
| <div class="emotion-stat"> | |
| <div class="emotion-label" style="color: ${getEmotionColor(emotion)}"> | |
| <span>${emotion.replace(/[\u{1F600}-\u{1F64F}]/gu, '')}</span> | |
| </div> | |
| <div class="confidence-bar"> | |
| <div class="confidence-fill" | |
| style="width: ${percentage}%; | |
| background: ${getEmotionColor(emotion)};"></div> | |
| </div> | |
| <div class="confidence-value">${percentage}%</div> | |
| </div>`; | |
| }).join(''); | |
| document.getElementById('emotion-distribution').innerHTML = statsHTML; | |
| // Адаптация сетки на фронтенде | |
| const statsContainer = document.getElementById('emotion-distribution'); | |
| if (statsContainer) { | |
| statsContainer.style.display = 'grid'; | |
| statsContainer.style.gridTemplateColumns = 'repeat(3, 1fr)'; | |
| statsContainer.style.gap = '15px'; | |
| } | |
| // --- Выбор пользователя --- | |
| populateUserSelect(processedData); | |
| } catch (error) { | |
| console.error('Ошибка обновления аналитики:', error); | |
| document.getElementById('emotion-timeline').innerHTML = | |
| `<div class="empty-state"><i class="fas fa-exclamation-triangle"></i><p>Ошибка загрузки данных: ${error.message}</p></div>`; | |
| } | |
| } | |
| // Глобальная переменная для хранения списка пользователей | |
| let telegramUsers = []; | |
| function populateUserSelect(processedData) { | |
| const userSelect = document.getElementById('user-select'); | |
| if (!userSelect) return; | |
| const users = [...new Set(processedData.map(d => d.from))]; | |
| // Если пользователи не изменились — ничего не делаем | |
| if (JSON.stringify(users.sort()) === JSON.stringify(telegramUsers.sort())) return; | |
| telegramUsers = users; | |
| userSelect.innerHTML = '<option value="all">Все участники</option>'; | |
| users.forEach(user => { | |
| const option = document.createElement('option'); | |
| option.value = user; | |
| option.textContent = user; | |
| userSelect.appendChild(option); | |
| }); | |
| // Добавляем обработчик только один раз | |
| if (!userSelect.dataset.listenerAdded) { | |
| userSelect.addEventListener('change', () => { | |
| updateTelegramAnalytics(document.querySelector('.time-btn.active')?.dataset.range || 'month'); | |
| }); | |
| userSelect.dataset.listenerAdded = 'true'; | |
| } | |
| } | |
| document.querySelectorAll('.time-btn').forEach(button => { | |
| button.addEventListener('click', function () { | |
| // Удаляем класс 'active' у всех кнопок | |
| document.querySelectorAll('.time-btn').forEach(btn => btn.classList.remove('active')); | |
| // Добавляем класс 'active' текущей кнопке | |
| this.classList.add('active'); | |
| // Получаем диапазон времени из атрибута data-range | |
| const range = this.getAttribute('data-range'); | |
| // Вызываем функцию обновления графиков с новым диапазоном | |
| updateTelegramAnalytics(range); | |
| }); | |
| }); | |
| // Проверяем, есть ли сохранённый активный временной интервал в localStorage или просто ставим 'month' | |
| window.addEventListener('load', () => { | |
| const activeTimeBtn = document.querySelector('.time-btn.active'); | |
| if (activeTimeBtn) { | |
| const range = activeTimeBtn.dataset.range || 'month'; | |
| updateTelegramAnalytics(range); | |
| } else { | |
| updateTelegramAnalytics('month'); | |
| } | |
| }); | |
| // Инициализация при загрузке страницы | |
| if (window.location.pathname.includes('/profile')) { | |
| updateTelegramAnalytics(); | |
| } | |
| }); |