Spaces:
Paused
Paused
| // Use the current host for API requests | |
| const OPENAI_API_URL = `${window.location.protocol}//${window.location.host}/v1/audio/speech`; | |
| const processingStatus = document.getElementById('processing-status'); | |
| const activeRequests = document.getElementById('queue-size'); | |
| const lastUpdate = document.getElementById('last-update'); | |
| const maxQueueSize = document.getElementById('max-queue-size'); | |
| const queueProgressBar = document.getElementById('queue-progress-bar'); | |
| const statusIndicator = document.getElementById('status-indicator'); | |
| const queueLoadText = document.getElementById('queue-load-text'); | |
| // Track active requests | |
| let currentActiveRequests = 0; | |
| // Initialize current language | |
| let currentLang = 'en'; | |
| // Language translations | |
| const translations = { | |
| en: { | |
| title: "OpenAI TTS API Documentation", | |
| subtitle: "Text-to-Speech API with Multiple Voice Options", | |
| tryItOut: "Try It Out", | |
| textToConvert: "Text to Convert", | |
| voice: "Voice", | |
| instructions: "Instructions (Optional)", | |
| generateSpeech: "Generate Speech", | |
| quickStart: "Quick Start", | |
| availableVoices: "Available Voices", | |
| apiReference: "API Reference", | |
| queueStatus: "Queue Status", | |
| activeRequests: "Active Requests", | |
| maxCapacity: "Maximum Capacity", | |
| noLoad: "No Load", | |
| lowLoad: "Low Load", | |
| mediumLoad: "Medium Load", | |
| highLoad: "High Load" | |
| }, | |
| zh: { | |
| title: "OpenAI TTS API 文档", | |
| subtitle: "支持多种语音的文本转语音 API", | |
| tryItOut: "立即体验", | |
| textToConvert: "要转换的文本", | |
| voice: "语音", | |
| instructions: "指令(可选)", | |
| generateSpeech: "生成语音", | |
| quickStart: "快速开始", | |
| availableVoices: "可用语音", | |
| apiReference: "API 参考", | |
| queueStatus: "队列状态", | |
| activeRequests: "活动请求", | |
| maxCapacity: "最大容量", | |
| noLoad: "无负载", | |
| lowLoad: "低负载", | |
| mediumLoad: "中负载", | |
| highLoad: "高负载" | |
| } | |
| }; | |
| // Language switching functionality | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const langButtons = document.querySelectorAll('.lang-btn'); | |
| // Set initial language based on current page | |
| const isChinesePage = window.location.pathname.includes('_zh.html'); | |
| currentLang = isChinesePage ? 'zh' : 'en'; | |
| // Update active state of language buttons | |
| langButtons.forEach(btn => { | |
| if (btn.getAttribute('data-lang') === currentLang) { | |
| btn.classList.add('active'); | |
| } else { | |
| btn.classList.remove('active'); | |
| } | |
| }); | |
| // Initial queue size update | |
| updateQueueSize(); | |
| }); | |
| function updateProcessingStatus(requestCount) { | |
| if (requestCount > 0) { | |
| processingStatus.textContent = 'Processing'; | |
| processingStatus.className = 'processing'; | |
| } else { | |
| processingStatus.textContent = 'Idle'; | |
| processingStatus.className = 'idle'; | |
| } | |
| } | |
| function updateLastUpdate() { | |
| const now = new Date(); | |
| if (lastUpdate) { | |
| lastUpdate.textContent = now.toLocaleTimeString(); | |
| } | |
| } | |
| // Function to update queue size with visual indicators | |
| async function updateQueueSize() { | |
| try { | |
| const response = await fetch('/api/queue-size'); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| // Update text values | |
| document.getElementById('queue-size').textContent = data.queue_size; | |
| document.getElementById('max-queue-size').textContent = data.max_queue_size; | |
| // Calculate load percentage | |
| const loadPercentage = (data.queue_size / data.max_queue_size) * 100; | |
| // Update progress bar width | |
| queueProgressBar.style.width = `${Math.min(loadPercentage, 100)}%`; | |
| // Update status indicators based on load | |
| updateLoadStatus(loadPercentage); | |
| } catch (error) { | |
| console.error('Error fetching queue size:', error); | |
| // Show error state in UI | |
| document.getElementById('queue-size').textContent = '?'; | |
| document.getElementById('max-queue-size').textContent = '?'; | |
| queueProgressBar.style.width = '0%'; | |
| statusIndicator.classList.remove('indicator-low', 'indicator-medium', 'indicator-high'); | |
| statusIndicator.classList.add('indicator-error'); | |
| queueProgressBar.classList.remove('progress-low', 'progress-medium', 'progress-high'); | |
| queueLoadText.classList.remove('low-load', 'medium-load', 'high-load'); | |
| queueLoadText.textContent = 'Error'; | |
| } | |
| } | |
| // Function to update load status indicators | |
| function updateLoadStatus(loadPercentage) { | |
| // Remove all existing classes | |
| statusIndicator.classList.remove('indicator-low', 'indicator-medium', 'indicator-high'); | |
| queueProgressBar.classList.remove('progress-low', 'progress-medium', 'progress-high'); | |
| queueLoadText.classList.remove('low-load', 'medium-load', 'high-load'); | |
| // Apply appropriate classes based on load percentage | |
| if (loadPercentage >= 75) { | |
| // High load (75-100%) | |
| statusIndicator.classList.add('indicator-high'); | |
| queueProgressBar.classList.add('progress-high'); | |
| queueLoadText.classList.add('high-load'); | |
| queueLoadText.textContent = translations[currentLang].highLoad; | |
| } else if (loadPercentage >= 40) { | |
| // Medium load (40-75%) | |
| statusIndicator.classList.add('indicator-medium'); | |
| queueProgressBar.classList.add('progress-medium'); | |
| queueLoadText.classList.add('medium-load'); | |
| queueLoadText.textContent = translations[currentLang].mediumLoad; | |
| } else { | |
| // Low load (0-40%) | |
| statusIndicator.classList.add('indicator-low'); | |
| queueProgressBar.classList.add('progress-low'); | |
| queueLoadText.classList.add('low-load'); | |
| queueLoadText.textContent = loadPercentage > 0 ? translations[currentLang].lowLoad : translations[currentLang].noLoad; | |
| } | |
| } | |
| // Update queue size every 2 seconds | |
| setInterval(updateQueueSize, 2000); | |
| // Initial update | |
| updateQueueSize(); | |
| // Function to copy code blocks | |
| function copyCode(button) { | |
| const codeBlock = button.closest('.code-block').querySelector('code'); | |
| const text = codeBlock.textContent; | |
| navigator.clipboard.writeText(text).then(() => { | |
| // Visual feedback | |
| const originalIcon = button.innerHTML; | |
| button.innerHTML = '<i class="fas fa-check"></i>'; | |
| button.style.color = '#4CAF50'; | |
| // Reset after 2 seconds | |
| setTimeout(() => { | |
| button.innerHTML = originalIcon; | |
| button.style.color = ''; | |
| }, 2000); | |
| }).catch(err => { | |
| console.error('Failed to copy text:', err); | |
| // Visual feedback for error | |
| button.style.color = '#f44336'; | |
| setTimeout(() => { | |
| button.style.color = ''; | |
| }, 2000); | |
| }); | |
| } | |
| // Playground functionality | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const submitButton = document.getElementById('playground-submit'); | |
| const textInput = document.getElementById('playground-text'); | |
| const voiceSelect = document.getElementById('playground-voice'); | |
| const instructionsInput = document.getElementById('playground-instructions'); | |
| const formatSelect = document.getElementById('playground-format'); | |
| const statusDiv = document.getElementById('playground-status'); | |
| const audioDiv = document.getElementById('playground-audio'); | |
| submitButton.addEventListener('click', async function() { | |
| const text = textInput.value.trim(); | |
| const voice = voiceSelect.value; | |
| const instructions = instructionsInput.value.trim(); | |
| const format = formatSelect.value; | |
| if (!text) { | |
| showStatus('Please enter some text to convert', 'error'); | |
| return; | |
| } | |
| // Disable the submit button and show loading state | |
| submitButton.disabled = true; | |
| submitButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating...'; | |
| showStatus('Generating speech...', 'success'); | |
| audioDiv.innerHTML = ''; | |
| try { | |
| const response = await fetch(OPENAI_API_URL, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ | |
| input: text, | |
| voice: voice, | |
| instructions: instructions || undefined, | |
| response_format: format | |
| }) | |
| }); | |
| if (!response.ok) { | |
| const error = await response.json(); | |
| throw new Error(error.error || 'Failed to generate speech'); | |
| } | |
| const blob = await response.blob(); | |
| const audioUrl = URL.createObjectURL(blob); | |
| // Create audio element | |
| const audio = document.createElement('audio'); | |
| audio.controls = true; | |
| audio.src = audioUrl; | |
| // Clear previous audio and add new one | |
| audioDiv.innerHTML = ''; | |
| audioDiv.appendChild(audio); | |
| showStatus('Speech generated successfully!', 'success'); | |
| } catch (error) { | |
| showStatus(error.message || 'Failed to generate speech', 'error'); | |
| } finally { | |
| // Re-enable the submit button and restore original text | |
| submitButton.disabled = false; | |
| submitButton.innerHTML = '<i class="fas fa-play"></i> Generate Speech'; | |
| } | |
| }); | |
| function showStatus(message, type) { | |
| statusDiv.textContent = message; | |
| statusDiv.className = `playground-status ${type}`; | |
| } | |
| }); | |
| // Voice sample functionality | |
| let currentSampleAudio = null; | |
| // Function to load and play voice sample | |
| async function loadVoiceSample(voice) { | |
| const previewAudioDiv = document.getElementById('preview-audio'); | |
| try { | |
| // Create new audio element | |
| const response = await fetch(`/api/voice-sample/${voice}`); | |
| if (!response.ok) { | |
| throw new Error(`Failed to load voice sample: ${response.statusText}`); | |
| } | |
| const blob = await response.blob(); | |
| const audioUrl = URL.createObjectURL(blob); | |
| // Create and configure audio element | |
| currentSampleAudio = document.createElement('audio'); | |
| currentSampleAudio.controls = true; | |
| currentSampleAudio.src = audioUrl; | |
| // Clear previous audio and add new one | |
| previewAudioDiv.innerHTML = ''; | |
| previewAudioDiv.appendChild(currentSampleAudio); | |
| } catch (error) { | |
| console.error('Error loading voice sample:', error); | |
| // Show error in status | |
| const statusDiv = document.getElementById('playground-status'); | |
| statusDiv.innerHTML = `<div class="error-message">Error loading voice sample: ${error.message}</div>`; | |
| } | |
| } | |
| // Add voice selection change handler | |
| document.getElementById('playground-voice').addEventListener('change', function() { | |
| loadVoiceSample(this.value); | |
| }); | |
| // Load initial voice sample when page loads | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const voiceSelect = document.getElementById('playground-voice'); | |
| if (voiceSelect) { | |
| loadVoiceSample(voiceSelect.value); | |
| } | |
| }); | |
| // Update the example code blocks | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Update Python example | |
| const pythonExample = document.querySelector('.language-python'); | |
| if (pythonExample) { | |
| pythonExample.textContent = `import requests | |
| url = "https://ttsapi.site/v1/audio/speech" | |
| headers = { | |
| "Content-Type": "application/json" | |
| } | |
| data = { | |
| "input": "Hello, this is a test.", | |
| "voice": "alloy", | |
| "instructions": "Speak in a cheerful and upbeat tone.", # Optional | |
| "response_format": "mp3" # Optional, supported formats: mp3, opus, aac, flac, wav, pcm | |
| } | |
| response = requests.post(url, json=data, headers=headers) | |
| if response.status_code == 200: | |
| # Save the audio file in the requested format | |
| with open("output.${format}", "wb") as f: | |
| f.write(response.content) | |
| print(f"Audio saved as output.${format}") | |
| else: | |
| print(f"Error: {response.status_code}, {response.json()}")`; | |
| } | |
| // Update JavaScript example | |
| const javascriptExample = document.querySelector('.language-javascript'); | |
| if (javascriptExample) { | |
| javascriptExample.textContent = `async function generateSpeech() { | |
| const response = await fetch('https://ttsapi.site/v1/audio/speech', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ | |
| input: 'Hello, this is a test.', | |
| voice: 'alloy', | |
| instructions: 'Speak in a cheerful and upbeat tone.', // Optional | |
| response_format: 'mp3' // Optional, supported formats: mp3, opus, aac, flac, wav, pcm | |
| }) | |
| }); | |
| if (response.ok) { | |
| const blob = await response.blob(); | |
| const audio = new Audio(URL.createObjectURL(blob)); | |
| audio.play(); | |
| } else { | |
| const error = await response.json(); | |
| console.error('Error:', error); | |
| } | |
| }`; | |
| } | |
| }); |