// ============================================ // AUDIO TO TEXT - ЧИСТЫЙ СТАТИК // ============================================ const API_URL = 'https://api-inference.huggingface.co/models/openai/whisper-tiny'; // Элементы интерфейса const copyBtn = document.getElementById('copyBtn'); const copyFeedback = document.getElementById('copyFeedback'); const transcriptText = document.getElementById('transcriptText'); const dropZone = document.getElementById('dropZone'); const attachBtn = document.getElementById('attachBtn'); const transcriptArea = document.getElementById('transcriptArea'); // ========== УПРАВЛЕНИЕ ТОКЕНОМ ========== let HF_TOKEN = localStorage.getItem('hf_token'); function promptForToken() { const token = prompt('🔑 Введи свой Hugging Face токен:\n(получить: https://huggingface.co/settings/tokens)'); if (token) { localStorage.setItem('hf_token', token); HF_TOKEN = token; return true; } return false; } function resetToken() { localStorage.removeItem('hf_token'); HF_TOKEN = null; alert('🔄 Токен сброшен. Обнови страницу для ввода нового.'); } // Проверяем токен при загрузке if (!HF_TOKEN) { promptForToken(); } // ========== КОПИРОВАНИЕ ========== copyBtn.addEventListener('click', async () => { if (transcriptText.value) { await navigator.clipboard.writeText(transcriptText.value); copyFeedback.classList.add('show'); setTimeout(() => copyFeedback.classList.remove('show'), 2000); } }); // ========== DRAG & DROP ========== ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, preventDefaults, false); }); function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } ['dragenter', 'dragover'].forEach(eventName => { dropZone.addEventListener(eventName, () => { dropZone.classList.add('drag-over'); }); }); ['dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, () => { dropZone.classList.remove('drag-over'); }); }); dropZone.addEventListener('drop', (e) => { const files = e.dataTransfer.files; if (files.length) { handleFile(files[0]); } }); // ========== ВЫБОР ФАЙЛА ========== attachBtn.addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.accept = 'audio/*'; input.onchange = (e) => { if (e.target.files.length) { handleFile(e.target.files[0]); } }; input.click(); }); dropZone.addEventListener('click', (e) => { if (e.target !== attachBtn && !attachBtn.contains(e.target)) { attachBtn.click(); } }); // ========== ОСНОВНАЯ ФУНКЦИЯ ========== async function handleFile(file) { console.log('🎵 файл:', file.name); // Проверяем токен if (!HF_TOKEN) { if (!promptForToken()) { transcriptText.value = '❌ Без токена невозможно распознать речь'; return; } } dropZone.style.display = 'none'; transcriptArea.classList.add('active'); transcriptText.value = '🔄 Отправка на распознавание...'; const formData = new FormData(); formData.append('file', file); try { const arrayBuffer = await file.arrayBuffer(); const response = await fetch(API_URL, { method: 'POST', headers: { 'Authorization': `Bearer ${HF_TOKEN}`, 'Content-Type': file.type || 'audio/mpeg' }, body: arrayBuffer }); // Обработка ошибок авторизации if (response.status === 401 || response.status === 403) { localStorage.removeItem('hf_token'); HF_TOKEN = null; transcriptText.value = '❌ Токен недействителен. Обнови страницу и введи новый.'; return; } if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = await response.json(); transcriptText.value = data.text || '⚠️ Пустой ответ от API'; } catch (error) { transcriptText.value = `❌ Ошибка: ${error.message}`; console.error(error); } } // ========== ДОБАВЛЯЕМ КНОПКУ СБРОСА В ФУТЕР ========== // Этот код добавит ссылку для сброса токена в футер document.addEventListener('DOMContentLoaded', () => { const footer = document.querySelector('.footer'); if (footer) { const resetLink = document.createElement('span'); resetLink.innerHTML = ' · 🔄 сбросить токен'; footer.appendChild(resetLink); } }); // Делаем функцию глобальной window.resetToken = resetToken;