Spaces:
Sleeping
Sleeping
| // ============================================ | |||
| // AUDIO TO TEXT - ЧИСТЫЙ СТАТИК | |||
| // ============================================ | |||
| const API_URL = '/static-proxy?url=https%3A%2F%2Fapi-inference.huggingface.co%2Fmodels%2Fopenai%2Fwhisper-tiny%26%23x27%3B%3C%2Fspan%3E%3B%3C!----%3E%3C%2Ftd%3E%3C%2Ftr%3E%3Ctr id="L6"> | |||
| // Элементы интерфейса | |||
| 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 = ' · <a href="#" onclick="resetToken(); return false;">🔄 сбросить токен</a>'; | |||
| footer.appendChild(resetLink); | |||
| } | |||
| }); | |||
| // Делаем функцию глобальной | |||
| window.resetToken = resetToken; | |||