getwavefromsuno / index.html
MySafeCode's picture
Update index.html
1bd91ff verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Suno API Audio Generator with Hugging Face Secrets">
<title>🎵 Suno AI Audio Generator</title>
<!-- Tailwind CSS for styling -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
}
.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.card {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.input-field {
transition: all 0.3s ease;
border: 2px solid #e5e7eb;
}
.input-field:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
width: 24px;
height: 24px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.copy-btn {
transition: all 0.2s ease;
}
.copy-btn:hover {
background-color: #f3f4f6;
}
.copy-btn.copied {
background-color: #10b981 !important;
color: white;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
}
</style>
</head>
<body class="flex items-center justify-center min-h-screen p-4">
<div class="card rounded-2xl shadow-2xl w-full max-w-2xl p-8">
<!-- Header -->
<div class="text-center mb-8">
<h1 class="text-3xl font-bold bg-gradient-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent mb-2">
🎵 Suno AI Audio Generator
</h1>
<p class="text-gray-600">
Generate audio using Suno API with secure secret management
</p>
<div class="mt-2">
<span id="configStatus" class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium">
<span class="w-2 h-2 rounded-full bg-yellow-500 mr-2"></span>
Loading configuration...
</span>
</div>
</div>
<!-- Configuration Section -->
<div id="configSection" class="mb-6 p-4 bg-blue-50 rounded-lg border border-blue-200 hidden">
<h3 class="font-semibold text-blue-800 mb-2">🔒 Configuration Status</h3>
<div class="space-y-2">
<div class="flex items-center">
<span id="apiKeyStatus" class="text-sm">API Key: <span class="font-mono">Loading...</span></span>
<button onclick="copyToClipboard('apiKey')"
class="copy-btn ml-2 px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200">
Copy
</button>
</div>
<div class="flex items-center">
<span id="callbackStatus" class="text-sm">Callback URL: <span class="font-mono">Loading...</span></span>
<button onclick="copyToClipboard('callbackUrl')"
class="copy-btn ml-2 px-2 py-1 text-xs bg-gray-100 rounded hover:bg-gray-200">
Copy
</button>
</div>
</div>
</div>
<!-- Form Section -->
<div class="space-y-6">
<div>
<label for="taskId" class="block text-sm font-medium text-gray-700 mb-2">
Task ID
</label>
<input type="text"
id="taskId"
placeholder="5c79****be8e"
value="5c79****be8e"
class="input-field w-full px-4 py-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<p class="text-xs text-gray-500 mt-1">Enter your Suno API task ID</p>
</div>
<div>
<label for="audioId" class="block text-sm font-medium text-gray-700 mb-2">
Audio ID
</label>
<input type="text"
id="audioId"
placeholder="e231****-****-****-****-****8cadc7dc"
value="e231****-****-****-****-****8cadc7dc"
class="input-field w-full px-4 py-3 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<p class="text-xs text-gray-500 mt-1">Enter your Suno API audio ID</p>
</div>
<!-- Generate Button -->
<button id="generateBtn"
onclick="generateAudio()"
class="btn-primary w-full py-3 rounded-lg text-white font-semibold flex items-center justify-center">
<span>🎵 Generate Audio</span>
</button>
<!-- Loading Indicator -->
<div id="loading" class="hidden flex-col items-center justify-center p-4">
<div class="spinner mb-3"></div>
<p class="text-gray-600">Generating audio... Please wait</p>
</div>
<!-- Status Messages -->
<div id="status" class="hidden p-4 rounded-lg"></div>
<!-- Result Section -->
<div id="result" class="hidden">
<h3 class="font-semibold text-gray-700 mb-3">📋 Response:</h3>
<div class="bg-gray-50 p-4 rounded-lg border border-gray-200">
<pre id="response" class="text-gray-800"></pre>
</div>
<div class="mt-4 flex justify-end">
<button onclick="copyToClipboard('response')"
class="copy-btn px-4 py-2 bg-gray-100 rounded-lg hover:bg-gray-200">
📋 Copy Response
</button>
</div>
</div>
</div>
<!-- Footer -->
<div class="mt-8 pt-6 border-t border-gray-200">
<p class="text-xs text-gray-500 text-center">
🔒 Secrets are loaded securely from Hugging Face Space environment variables
<br>
<span id="envInfo" class="font-mono text-gray-600"></span>
</p>
</div>
</div>
<!-- Inline JavaScript for configuration -->
<script>
// Configuration object - will be populated from environment
window.config = {
apiKey: '',
callbackUrl: '',
isConfigured: false,
envSource: 'Unknown',
timestamp: new Date().toISOString()
};
// Function to load configuration from multiple sources
function loadConfiguration() {
console.log('🔧 Loading configuration...');
// Method 1: Try to get from Hugging Face Space environment
// Note: In Hugging Face Spaces, environment variables might not be directly accessible in browser
// So we need to use fetch to a backend endpoint or URL parameters
// For this example, we'll use URL parameters which can be set in Hugging Face Space settings
const urlParams = new URLSearchParams(window.location.search);
// Method 2: Try URL parameters (set in Hugging Face Space as query string)
const apiKeyFromUrl = urlParams.get('api_key');
const callbackUrlFromUrl = urlParams.get('callback_url');
// Method 3: Try localStorage (for development/testing)
const apiKeyFromStorage = localStorage.getItem('suno_api_key');
const callbackUrlFromStorage = localStorage.getItem('suno_callback_url');
// Priority: URL params > localStorage > empty
if (apiKeyFromUrl && callbackUrlFromUrl) {
window.config.apiKey = apiKeyFromUrl;
window.config.callbackUrl = callbackUrlFromUrl;
window.config.envSource = 'URL Parameters';
window.config.isConfigured = true;
} else if (apiKeyFromStorage && callbackUrlFromStorage) {
window.config.apiKey = apiKeyFromStorage;
window.config.callbackUrl = callbackUrlFromStorage;
window.config.envSource = 'Local Storage (Dev Mode)';
window.config.isConfigured = true;
} else {
// Method 4: Try to fetch from a backend endpoint
fetch('/api/config')
.then(response => {
if (response.ok) return response.json();
throw new Error('No config endpoint');
})
.then(data => {
window.config.apiKey = data.apiKey || '';
window.config.callbackUrl = data.callbackUrl || '';
window.config.envSource = 'Backend API';
window.config.isConfigured = !!window.config.apiKey;
updateUI();
})
.catch(error => {
console.log('No backend config available');
// Show configuration form
showConfigForm();
});
}
updateUI();
console.log('Configuration loaded:', window.config);
}
// Update UI based on configuration status
function updateUI() {
const configStatus = document.getElementById('configStatus');
const configSection = document.getElementById('configSection');
const apiKeyStatus = document.getElementById('apiKeyStatus');
const callbackStatus = document.getElementById('callbackStatus');
const envInfo = document.getElementById('envInfo');
if (window.config.isConfigured) {
// Show success status
configStatus.innerHTML = '<span class="w-2 h-2 rounded-full bg-green-500 mr-2"></span> Configured';
configStatus.className = 'inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800';
// Update config display
apiKeyStatus.innerHTML = `API Key: <span class="font-mono">${maskString(window.config.apiKey, 8)}</span>`;
callbackStatus.innerHTML = `Callback URL: <span class="font-mono">${window.config.callbackUrl}</span>`;
envInfo.textContent = `Source: ${window.config.envSource}`;
// Show config section
configSection.classList.remove('hidden');
} else {
// Show warning status
configStatus.innerHTML = '<span class="w-2 h-2 rounded-full bg-red-500 mr-2"></span> Not Configured';
configStatus.className = 'inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800';
envInfo.textContent = 'Please configure secrets in Hugging Face Space settings';
}
}
// Helper function to mask sensitive strings
function maskString(str, visibleChars = 4) {
if (!str || str.length <= visibleChars * 2) return '••••••••';
const first = str.substring(0, visibleChars);
const last = str.substring(str.length - visibleChars);
return `${first}••••${last}`;
}
// Copy to clipboard helper
function copyToClipboard(elementId) {
let text = '';
if (elementId === 'apiKey') {
text = window.config.apiKey;
} else if (elementId === 'callbackUrl') {
text = window.config.callbackUrl;
} else if (elementId === 'response') {
text = document.getElementById('response').textContent;
}
navigator.clipboard.writeText(text).then(() => {
const btn = event.target;
const originalText = btn.innerHTML;
btn.innerHTML = '✅ Copied!';
btn.classList.add('copied');
setTimeout(() => {
btn.innerHTML = originalText;
btn.classList.remove('copied');
}, 2000);
});
}
// Show configuration form (for manual setup)
function showConfigForm() {
// Create a modal for manual configuration
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50';
modal.innerHTML = `
<div class="bg-white rounded-xl p-6 max-w-md w-full">
<h3 class="text-lg font-semibold text-gray-800 mb-4">⚙️ Manual Configuration</h3>
<p class="text-sm text-gray-600 mb-4">Enter your Suno API credentials:</p>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">API Key</label>
<input type="password"
id="manualApiKey"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Bearer token...">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Callback URL</label>
<input type="text"
id="manualCallbackUrl"
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="https://api.example.com/callback">
</div>
</div>
<div class="flex gap-2 mt-6">
<button onclick="saveManualConfig()" class="flex-1 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
Save
</button>
<button onclick="closeModal(this)" class="flex-1 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300">
Cancel
</button>
</div>
</div>
`;
document.body.appendChild(modal);
}
function saveManualConfig() {
const apiKey = document.getElementById('manualApiKey').value;
const callbackUrl = document.getElementById('manualCallbackUrl').value;
if (apiKey && callbackUrl) {
// Save to localStorage for development
localStorage.setItem('suno_api_key', apiKey);
localStorage.setItem('suno_callback_url', callbackUrl);
// Update global config
window.config.apiKey = apiKey;
window.config.callbackUrl = callbackUrl;
window.config.envSource = 'Manual Setup';
window.config.isConfigured = true;
updateUI();
closeModal();
} else {
alert('Please fill in both fields');
}
}
function closeModal(btn) {
const modal = btn.closest('.fixed');
if (modal) modal.remove();
}
// Initialize configuration when page loads
document.addEventListener('DOMContentLoaded', loadConfiguration);
</script>
<!-- External JavaScript for API calls -->
<script src="script.js"></script>
</body>
</html>