anycoder-881a72f0 / index.html
flen-crypto's picture
Upload folder using huggingface_hub
b132d53 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Music Generation Tool</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary: #2dd4bf;
--primary-dark: #1da896;
--secondary: #f8fafc;
--accent: #fbbf24;
--text: #1e293b;
--text-light: #64748b;
--border: #e2e8f0;
--error: #ef4444;
--success: #22c55e;
--warning: #f59e0b;
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background-color: #f1f5f9;
color: var(--text);
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: white;
padding: 15px 0;
box-shadow: var(--shadow);
margin-bottom: 20px;
border-radius: 12px;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary);
}
.built-with {
font-size: 0.875rem;
color: var(--text-light);
}
.built-with a {
color: var(--primary);
text-decoration: none;
}
.main-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.card {
background-color: white;
border-radius: 12px;
padding: 20px;
box-shadow: var(--shadow);
}
h2 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 15px;
color: var(--primary);
border-bottom: 2px solid var(--primary);
padding-bottom: 10px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: var(--text);
}
input[type="text"],
input[type="email"],
input[type="password"],
textarea,
select {
width: 100%;
padding: 10px;
border: 1px solid var(--border);
border-radius: 8px;
font-family: inherit;
font-size: 0.875rem;
transition: border-color 0.2s;
}
input:focus,
textarea:focus,
select:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(45, 212, 191, 0.1);
}
textarea {
min-height: 100px;
resize: vertical;
}
.btn {
padding: 10px 15px;
border: none;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
font-size: 0.875rem;
}
.btn-primary {
background-color: var(--primary);
color: white;
}
.btn-primary:hover {
background-color: var(--primary-dark);
}
.btn-secondary {
background-color: var(--secondary);
color: var(--text);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background-color: #e2e8f0;
}
.btn-danger {
background-color: var(--error);
color: white;
}
.btn-danger:hover {
background-color: #dc2626;
}
.btn-group {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.output-area {
background-color: #f8fafc;
border: 1px solid var(--border);
border-radius: 8px;
padding: 15px;
min-height: 100px;
font-family: 'Courier New', monospace;
font-size: 0.875rem;
white-space: pre-wrap;
word-wrap: break-word;
margin-bottom: 15px;
}
.status-message {
padding: 10px;
border-radius: 8px;
margin-bottom: 15px;
font-size: 0.875rem;
}
.status-success {
background-color: rgba(34, 197, 94, 0.1);
color: var(--success);
}
.status-error {
background-color: rgba(239, 68, 68, 0.1);
color: var(--error);
}
.status-warning {
background-color: rgba(245, 158, 11, 0.1);
color: var(--warning);
}
.checkbox-group {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.checkbox-group input[type="checkbox"] {
width: auto;
accent-color: var(--primary);
}
.file-upload {
border: 2px dashed var(--border);
border-radius: 8px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
}
.file-upload:hover {
border-color: var(--primary);
background-color: rgba(45, 212, 191, 0.05);
}
.file-upload input[type="file"] {
display: none;
}
.file-info {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
padding: 10px;
background-color: #f8fafc;
border-radius: 8px;
}
.thumb-container {
position: relative;
margin-bottom: 10px;
}
.thumb {
width: 100%;
border-radius: 8px;
box-shadow: var(--shadow);
}
.miniBtn {
position: absolute;
bottom: 10px;
right: 10px;
padding: 5px 10px;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border: none;
border-radius: 4px;
font-size: 0.75rem;
cursor: pointer;
}
.processing-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
}
.processing-content {
background-color: white;
padding: 30px;
border-radius: 12px;
text-align: center;
max-width: 400px;
}
.spinner {
border: 4px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
border-top: 4px solid var(--primary);
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@media (max-width: 768px) {
.main-grid {
grid-template-columns: 1fr;
}
.header-content {
flex-direction: column;
gap: 10px;
}
}
</style>
</head>
<body>
<header>
<div class="container">
<div class="header-content">
<div class="logo">Music Generation Tool</div>
<div class="built-with">
Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
</div>
</div>
</div>
</header>
<div class="container">
<div class="main-grid">
<div class="card">
<h2>API Configuration</h2>
<div class="form-group">
<label for="apiKey">OpenAI API Key</label>
<input type="password" id="apiKey" placeholder="Enter your OpenAI API key">
</div>
<div class="btn-group">
<button class="btn btn-primary" id="saveKeyBtn">Save Key</button>
<button class="btn btn-secondary" id="testKeyBtn">Test Key</button>
</div>
<div id="apiStatus" class="status-message" style="display: none;"></div>
<h2 style="margin-top: 25px;">Server Controls</h2>
<div class="btn-group">
<button class="btn btn-secondary" id="checkHealthBtn">Check Server Health</button>
<button class="btn btn-secondary" id="runDiagnosticsBtn">Run Diagnostics</button>
</div>
<div id="healthStatus" class="status-message" style="display: none;"></div>
<div id="diagOut" class="output-area" style="display: none;"></div>
</div>
<div class="card">
<h2>Form Controls</h2>
<div class="btn-group">
<button class="btn btn-secondary" id="randomizeBtn">Randomize Form</button>
<button class="btn btn-secondary" id="resetFormBtn">Reset Form</button>
</div>
<div class="btn-group">
<button class="btn btn-secondary" id="saveDraftBtn">Save Draft</button>
<button class="btn btn-secondary" id="loadDraftBtn">Load Draft</button>
</div>
<div id="warningsDisplay" class="status-message" style="display: none;"></div>
</div>
</div>
<div class="card">
<h2>Main Form</h2>
<div class="form-group">
<label for="songTitle">Song Title</label>
<input type="text" id="songTitle" placeholder="Enter song title">
</div>
<div class="form-group">
<label for="occasion">Occasion</label>
<input type="text" id="occasion" placeholder="e.g., Birthday, Anniversary">
</div>
<div class="form-group">
<label for="buyerName">Buyer Name</label>
<input type="text" id="buyerName" placeholder="Enter buyer name">
</div>
<div class="form-group">
<label for="recipientName">Recipient Name</label>
<input type="text" id="recipientName" placeholder="Enter recipient name">
</div>
<div class="form-group">
<label for="relationship">Relationship</label>
<input type="text" id="relationship" placeholder="e.g., Mother, Friend">
</div>
<div class="form-group">
<label for="tone">Tone/Vibe</label>
<input type="text" id="tone" placeholder="e.g., Happy, Romantic">
</div>
<div class="form-group">
<label for="mainGenre">Main Genre</label>
<input type="text" id="mainGenre" placeholder="e.g., Pop, Rock">
</div>
<div class="form-group">
<label for="secondaryGenres">Secondary Genres</label>
<input type="text" id="secondaryGenres" placeholder="e.g., Jazz, Electronic">
</div>
<div class="form-group">
<label for="tempo">Tempo/Energy</label>
<input type="text" id="tempo" placeholder="e.g., Fast, Slow">
</div>
<div class="form-group">
<label for="vocalStyle">Vocal Style</label>
<input type="text" id="vocalStyle" placeholder="e.g., Male, Female, Choir">
</div>
<div class="form-group">
<label for="instrumentationNotes">Instrumentation Notes</label>
<textarea id="instrumentationNotes" placeholder="Any specific instruments or arrangements"></textarea>
</div>
<div class="form-group">
<label for="keyFacts">Key Facts</label>
<textarea id="keyFacts" placeholder="Important facts to include in the song"></textarea>
</div>
<div class="form-group">
<label for="stories">Stories</label>
<textarea id="stories" placeholder="Any stories to include"></textarea>
</div>
<div class="form-group">
<label for="mainMessage">Main Message</label>
<textarea id="mainMessage" placeholder="The main message of the song"></textarea>
</div>
<div class="form-group">
<label for="namesPlaces">Names/Places</label>
<input type="text" id="namesPlaces" placeholder="Any specific names or places to mention">
</div>
<div class="form-group">
<label for="visualStyle">Visual Style</label>
<input type="text" id="visualStyle" placeholder="e.g., Realistic, Cartoon">
</div>
<div class="form-group">
<label for="visualMood">Visual Mood</label>
<input type="text" id="visualMood" placeholder="e.g., Bright, Dark">
</div>
<div class="form-group">
<label for="visualPlacesThemes">Visual Places/Themes</label>
<input type="text" id="visualPlacesThemes" placeholder="e.g., Beach, Mountains">
</div>
<div class="form-group">
<label for="visualText">Visual Text</label>
<input type="text" id="visualText" placeholder="Any text to include in visuals">
</div>
<div class="checkbox-group">
<input type="checkbox" id="wantsArtwork">
<label for="wantsArtwork">Wants Artwork</label>
</div>
<div class="checkbox-group">
<input type="checkbox" id="wantsVideo">
<label for="wantsVideo">Wants Video</label>
</div>
<div class="btn-group">
<button class="btn btn-primary" id="generateBtn">Generate Suno Block</button>
<button class="btn btn-secondary" id="copySunoBtn">Copy Suno Block</button>
</div>
<div class="form-group">
<label>Suno Block Output</label>
<div id="sunoBlockOut" class="output-area">Suno block will appear here...</div>
</div>
</div>
<div class="card">
<h2>Generated Prompts</h2>
<div class="form-group">
<label for="songPrompt">Song Prompt</label>
<textarea id="songPrompt" readonly></textarea>
</div>
<div class="form-group">
<label for="artPrompt">Artwork Prompt</label>
<textarea id="artPrompt" readonly></textarea>
</div>
<div class="form-group">
<label for="videoPrompt">Video Prompt</label>
<textarea id="videoPrompt" readonly></textarea>
</div>
</div>
<div class="card">
<h2>Image Generation</h2>
<div class="btn-group">
<button class="btn btn-primary" id="generateImageBtn">Generate Image</button>
</div>
<div id="imgGrid" class="output-area" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px;"></div>
</div>
<div class="card">
<h2>Video Generation</h2>
<div class="btn-group">
<button class="btn btn-primary" id="generateVideoBtn">Generate Video</button>
</div>
<div id="videoPreview" class="output-area"></div>
<div id="videoOut" class="status-message"></div>
</div>
<div class="card">
<h2>File Upload</h2>
<div class="form-group">
<label>Song File</label>
<div class="file-upload" id="songFileUpload">
<i class="fas fa-upload fa-2x"></i>
<p>Click to upload song file (MP3, WAV, M4A)</p>
<input type="file" id="songFileInput" accept="audio/mp3,audio/wav,audio/m4a">
</div>
<div id="songFileInfo" class="file-info" style="display: none;">
<i class="fas fa-file-audio"></i>
<span id="songFileName"></span>
<button class="btn btn-danger" id="removeSongBtn" style="padding: 5px 10px;">Remove</button>
</div>
</div>
<div class="form-group">
<label>Video Clips (MP4)</label>
<div class="file-upload" id="clipFilesUpload">
<i class="fas fa-upload fa-2x"></i>
<p>Click to upload video clips (MP4)</p>
<input type="file" id="clipFilesInput" accept="video/mp4" multiple>
</div>
<div id="clipQueueWrap" class="file-info">
<div class="small" style="margin-top:6px">No clips queued.</div>
</div>
<div class="btn-group">
<button class="btn btn-danger" id="clearClipsBtn">Clear All Clips</button>
<button class="btn btn-primary" id="stitchClipsBtn">Stitch Clips</button>
</div>
</div>
<div id="uploadOut" class="status-message"></div>
</div>
</div>
<div class="processing-overlay" id="processingOverlay">
<div class="processing-content">
<div class="spinner"></div>
<div id="processingMessage">Processing...</div>
</div>
</div>
<script>
// Constants
const API_BASE = 'https://api.example.com'; // Replace with your actual API base URL
let apiKey = '';
let currentOrderId = null;
let songFile = null;
let videoClips = [];
// DOM Elements
const warningsDisplay = document.getElementById('warningsDisplay');
const apiStatus = document.getElementById('apiStatus');
const healthStatus = document.getElementById('healthStatus');
const diagOut = document.getElementById('diagOut');
const sunoBlockOut = document.getElementById('sunoBlockOut');
const orderIdDisplay = document.getElementById('orderIdDisplay');
const processingOverlay = document.getElementById('processingOverlay');
const processingMessage = document.getElementById('processingMessage');
const songFileInput = document.getElementById('songFileInput');
const songFileUpload = document.getElementById('songFileUpload');
const songFileInfo = document.getElementById('songFileInfo');
const songFileName = document.getElementById('songFileName');
const removeSongBtn = document.getElementById('removeSongBtn');
const clipFilesInput = document.getElementById('clipFilesInput');
const clipFilesUpload = document.getElementById('clipFilesUpload');
const clipQueueWrap = document.getElementById('clipQueueWrap');
const clearClipsBtn = document.getElementById('clearClipsBtn');
const stitchClipsBtn = document.getElementById('stitchClipsBtn');
const uploadOut = document.getElementById('uploadOut');
// Initialize the app
function init() {
// Load saved API key
const savedKey = localStorage.getItem('openaiApiKey');
if (savedKey) {
apiKey = savedKey;
document.getElementById('apiKey').value = '••••••••••••••••';
}
// Set up event listeners
document.getElementById('saveKeyBtn').addEventListener('click', saveApiKey);
document.getElementById('testKeyBtn').addEventListener('click', testApiKey);
document.getElementById('checkHealthBtn').addEventListener('click', checkServerHealth);
document.getElementById('runDiagnosticsBtn').addEventListener('click', runDiagnostics);
document.getElementById('randomizeBtn').addEventListener('click', randomizeForm);
document.getElementById('resetFormBtn').addEventListener('click', resetForm);
document.getElementById('saveDraftBtn').addEventListener('click', saveDraft);
document.getElementById('loadDraftBtn').addEventListener('click', loadDraft);
document.getElementById('generateBtn').addEventListener('click', generateSunoBlock);
document.getElementById('copySunoBtn').addEventListener('click', copySunoBlock);
document.getElementById('generateImageBtn').addEventListener('click', generateImage);
document.getElementById('generateVideoBtn').addEventListener('click', generateVideo);
songFileUpload.addEventListener('click', () => songFileInput.click());
songFileInput.addEventListener('change', handleSongFileUpload);
removeSongBtn.addEventListener('click', removeSongFile);
clipFilesUpload.addEventListener('click', () => clipFilesInput.click());
clipFilesInput.addEventListener('change', handleClipFilesUpload);
clearClipsBtn.addEventListener('click', clearClips);
stitchClipsBtn.addEventListener('click', stitchClips);
// Initialize file upload areas
updateFileUploadAreas();
}
// Update file upload areas
function updateFileUploadAreas() {
// Song file upload
songFileUpload.addEventListener('dragover', (e) => {
e.preventDefault();
songFileUpload.style.borderColor = var(--primary);
});
songFileUpload.addEventListener('dragleave', () => {
songFileUpload.style.borderColor = var(--border);
});
songFileUpload.addEventListener('drop', (e) => {
e.preventDefault();
songFileUpload.style.borderColor = var(--border);
songFileInput.files = e.dataTransfer.files;
handleSongFileUpload();
});
// Clip files upload
clipFilesUpload.addEventListener('dragover', (e) => {
e.preventDefault();
clipFilesUpload.style.borderColor = var(--primary);
});
clipFilesUpload.addEventListener('dragleave', () => {
clipFilesUpload.style.borderColor = var(--border);
});
clipFilesUpload.addEventListener('drop', (e) => {
e.preventDefault();
clipFilesUpload.style.borderColor = var(--border);
clipFilesInput.files = e.dataTransfer.files;
handleClipFilesUpload();
});
}
// Save API key
function saveApiKey() {
const keyInput = document.getElementById('apiKey');
const keyValue = keyInput.value.trim();
if (!keyValue) {
showStatus(apiStatus, 'Please enter an API key', 'error');
return;
}
// Simple validation - should start with 'sk-' and be at least 40 characters
if (!keyValue.startsWith('sk-') || keyValue.length < 40) {
showStatus(apiStatus, 'Invalid API key format', 'error');
return;
}
apiKey = keyValue;
localStorage.setItem('openaiApiKey', apiKey);
keyInput.value = '••••••••••••••••';
showStatus(apiStatus, 'API key saved successfully!', 'success');
}
// Test API key
async function testApiKey() {
if (!apiKey) {
showStatus(apiStatus, 'Please save your API key first!', 'error');
return;
}
try {
showProcessing('Testing API key...');
// Mock API call - replace with actual API endpoint
const response = await fetch(`${API_BASE}/test-key`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
}
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'API key test failed');
}
const result = await response.json();
showStatus(apiStatus, 'API key is valid!', 'success');
} catch (error) {
showStatus(apiStatus, `API key test failed: ${error.message}`, 'error');
console.error('API key test error:', error);
} finally {
hideProcessing();
}
}
// Check server health
async function checkServerHealth() {
try {
showProcessing('Checking server health...');
const response = await fetch(`${API_BASE}/health`);
if (!response.ok) throw new Error('Server health check failed');
const healthData = await response.json();
showStatus(healthStatus, `Server is healthy! (${healthData.status})`, 'success');
} catch (error) {
showStatus(healthStatus, `Server health check failed: ${error.message}`, 'error');
console.error('Health check error:', error);
} finally {
hideProcessing();
}
}
// Show status message
function showStatus(element, message, type) {
element.textContent = message;
element.className = `status-message status-${type}`;
element.style.display = 'block';
// Hide after 5 seconds
setTimeout(() => {
element.style.display = 'none';
}, 5000);
}
// Get form data
function getFormData() {
return {
songTitle: document.getElementById('songTitle').value,
occasion: document.getElementById('occasion').value,
customerName: document.getElementById('buyerName').value,
recipient_name: document.getElementById('recipientName').value,
relationship: document.getElementById('relationship').value,
tone_vibe: document.getElementById('tone').value,
main_genre: document.getElementById('mainGenre').value,
secondary_influences: document.getElementById('secondaryGenres').value,
tempo_energy: document.getElementById('tempo').value,
vocal_style: document.getElementById('vocalStyle').value,
instrumentation_notes: document.getElementById('instrumentationNotes').value,
language_level: document.getElementById('languageLevel').value,
package_level: document.getElementById('packageLevel').value,
key_facts: document.getElementById('keyFacts').value,
stories: document.getElementById('stories').value,
main_message: document.getElementById('mainMessage').value,
names_places: document.getElementById('namesPlaces').value,
visual_style: document.getElementById('visualStyle').value,
visual_mood: document.getElementById('visualMood').value,
visual_places_themes: document.getElementById('visualPlacesThemes').value,
visual_text: document.getElementById('visualText').value,
wants_artwork: document.getElementById('wantsArtwork').checked,
wants_video: document.getElementById('wantsVideo').checked
};
}
// Generate Suno block
async function generateSunoBlock() {
const formData = getFormData();
// Basic validation
if (!formData.songTitle || !formData.customerName || !formData.recipient_name) {
showStatus(warningsDisplay, 'Please fill in required fields (Song Title, Buyer Name, Recipient Name)', 'error');
return;
}
try {
showProcessing('Generating Suno block...');
// Mock API call - replace with actual API endpoint
const response = await fetch(`${API_BASE}/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey
},
body: JSON.stringify(formData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Generation failed');
}
const result = await response.json();
currentOrderId = result.orderId;
orderIdDisplay.textContent = currentOrderId;
// Display the Suno block
sunoBlockOut.textContent = result.sunoBlock;
// Generate prompts
document.getElementById('songPrompt').value = result.songPrompt;
document.getElementById('artPrompt').value = result.artPrompt;
document.getElementById('videoPrompt').value = result.videoPrompt;
showStatus(warningsDisplay, 'Suno block generated successfully!', 'success');
} catch (error) {
showStatus(warningsDisplay, `Error generating Suno block: ${error.message}`, 'error');
console.error('Generation error:', error);
} finally {
hideProcessing();
}
}
// Randomize form
async function randomizeForm() {
try {
showProcessing('Randomizing form...');
// Mock API call - replace with actual API endpoint
const response = await fetch(`${API_BASE}/randomize`);
if (!response.ok) throw new Error('Failed to randomize');
const randomData = await response.json();
// Fill form fields with random data
document.getElementById('songTitle').value = randomData.songTitle;
document.getElementById('occasion').value = randomData.occasion;
document.getElementById('buyerName').value = randomData.buyerName;
document.getElementById('recipientName').value = randomData.recipientName;
document.getElementById('relationship').value = randomData.relationship;
document.getElementById('tone').value = randomData.tone;
document.getElementById('mainGenre').value = randomData.mainGenre;
document.getElementById('secondaryGenres').value = randomData.secondaryGenres;
document.getElementById('tempo').value = randomData.tempo;
document.getElementById('vocalStyle').value = randomData.vocalStyle;
document.getElementById('instrumentationNotes').value = randomData.instrumentationNotes;
document.getElementById('keyFacts').value = randomData.keyFacts.join('\n');
document.getElementById('stories').value = randomData.stories.join('\n');
document.getElementById('mainMessage').value = randomData.mainMessage;
document.getElementById('namesPlaces').value = randomData.namesPlaces;
document.getElementById('visualStyle').value = randomData.visualStyle;
document.getElementById('visualMood').value = randomData.visualMood;
document.getElementById('visualPlacesThemes').value = randomData.visualPlacesThemes;
document.getElementById('visualText').value = randomData.visualText;
showStatus(warningsDisplay, 'Form randomized with API data!', 'success');
} catch (error) {
showStatus(warningsDisplay, `Error randomizing: ${error.message}`, 'error');
console.error('Randomization error:', error);
} finally {
hideProcessing();
}
}
// Reset form
function resetForm() {
if (confirm('Are you sure you want to reset the form? All data will be lost.')) {
document.querySelectorAll('input, textarea, select').forEach(el => {
if (el.type === 'checkbox') {
el.checked = false;
} else {
el.value = '';
}
});
sunoBlockOut.textContent = 'Suno block will appear here...';
warningsDisplay.textContent = '';
diagOut.textContent = '';
currentOrderId = null;
orderIdDisplay.textContent = 'none';
document.getElementById('songPrompt').value = '';
document.getElementById('artPrompt').value = '';
document.getElementById('videoPrompt').value = '';
document.getElementById('imgGrid').innerHTML = '';
document.getElementById('videoPreview').innerHTML = '';
document.getElementById('videoOut').textContent = 'Video status...';
showStatus(warningsDisplay, 'Form reset complete!', 'success');
}
}
// Save draft
function saveDraft() {
const draft = {
formData: getFormData(),
sunoBlock: sunoBlockOut.textContent,
songPrompt: document.getElementById('songPrompt').value,
artPrompt: document.getElementById('artPrompt').value,
videoPrompt: document.getElementById('videoPrompt').value,
orderId: currentOrderId
};
localStorage.setItem('fiverrMusicDraft', JSON.stringify(draft));
showStatus(warningsDisplay, 'Draft saved to browser storage!', 'success');
}
// Load draft
function loadDraft() {
const draftData = localStorage.getItem('fiverrMusicDraft');
if (!draftData) {
showStatus(warningsDisplay, 'No saved draft found!', 'warning');
return;
}
try {
const draft = JSON.parse(draftData);
// Restore form data
document.getElementById('songTitle').value = draft.formData.songTitle;
document.getElementById('occasion').value = draft.formData.occasion;
document.getElementById('buyerName').value = draft.formData.customerName;
document.getElementById('recipientName').value = draft.formData.recipient_name;
document.getElementById('relationship').value = draft.formData.relationship;
document.getElementById('tone').value = draft.formData.tone_vibe;
document.getElementById('mainGenre').value = draft.formData.main_genre;
document.getElementById('secondaryGenres').value = draft.formData.secondary_influences;
document.getElementById('tempo').value = draft.formData.tempo_energy;
document.getElementById('vocalStyle').value = draft.formData.vocal_style;
document.getElementById('instrumentationNotes').value = draft.formData.instrumentation_notes;
document.getElementById('languageLevel').value = draft.formData.language_level;
document.getElementById('packageLevel').value = draft.formData.package_level;
document.getElementById('keyFacts').value = draft.formData.key_facts;
document.getElementById('stories').value = draft.formData.stories;
document.getElementById('mainMessage').value = draft.formData.main_message;
document.getElementById('namesPlaces').value = draft.formData.names_places;
document.getElementById('visualStyle').value = draft.formData.visual_style;
document.getElementById('visualMood').value = draft.formData.visual_mood;
document.getElementById('visualPlacesThemes').value = draft.formData.visual_places_themes;
document.getElementById('visualText').value = draft.formData.visual_text;
document.getElementById('wantsArtwork').checked = draft.formData.wants_artwork;
document.getElementById('wantsVideo').checked = draft.formData.wants_video;
// Restore generated content
sunoBlockOut.textContent = draft.sunoBlock;
document.getElementById('songPrompt').value = draft.songPrompt;
document.getElementById('artPrompt').value = draft.artPrompt;
document.getElementById('videoPrompt').value = draft.videoPrompt;
currentOrderId = draft.orderId;
orderIdDisplay.textContent = currentOrderId;
showStatus(warningsDisplay, 'Draft loaded successfully!', 'success');
} catch (error) {
showStatus(warningsDisplay, `Error loading draft: ${error.message}`, 'error');
console.error('Draft load error:', error);
}
}
// Copy Suno block to clipboard
function copySunoBlock() {
copyToClipboard(sunoBlockOut.textContent);
showStatus(warningsDisplay, 'Suno block copied to clipboard!', 'success');
}
// Run diagnostics
async function runDiagnostics() {
try {
showProcessing('Running diagnostics...');
const response = await fetch(`${API_BASE}/diagnostics`);
if (!response.ok) throw new Error('Diagnostics failed');
const diagData = await response.json();
diagOut.textContent = JSON.stringify(diagData, null, 2);
diagOut.style.display = 'block';
showStatus(warningsDisplay, 'Diagnostics complete!', 'success');
} catch (error) {
diagOut.textContent = `Error running diagnostics: ${error.message}`;
diagOut.style.display = 'block';
console.error('Diagnostics error:', error);
} finally {
hideProcessing();
}
}
// Generate image
async function generateImage() {
if (!apiKey) {
showStatus(warningsDisplay, 'Please save your OpenAI API key first!', 'error');
return;
}
const prompt = document.getElementById('artPrompt').value;
if (!prompt) {
showStatus(warningsDisplay, 'Please generate an artwork prompt first!', 'error');
return;
}
try {
showProcessing('Generating image...');
const response = await fetch(`${API_BASE}/generate/image`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey
},
body: JSON.stringify({ prompt })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Image generation failed');
}
const result = await response.json();
displayGeneratedImages(result.images);
showStatus(warningsDisplay, 'Image generation complete!', 'success');
} catch (error) {
showStatus(warningsDisplay, `Error generating image: ${error.message}`, 'error');
console.error('Image generation error:', error);
} finally {
hideProcessing();
}
}
// Display generated images
function displayGeneratedImages(images) {
const imgGrid = document.getElementById('imgGrid');
imgGrid.innerHTML = '';
images.forEach((imgUrl, index) => {
const imgContainer = document.createElement('div');
imgContainer.className = 'thumb-container';
const img = document.createElement('img');
img.src = imgUrl;
img.className = 'thumb';
img.alt = `Generated image ${index + 1}`;
const downloadBtn = document.createElement('button');
downloadBtn.className = 'miniBtn';
downloadBtn.textContent = 'Download';
downloadBtn.addEventListener('click', () => {
downloadImage(imgUrl, `generated-image-${index + 1}.png`);
});
imgContainer.appendChild(img);
imgContainer.appendChild(downloadBtn);
imgGrid.appendChild(imgContainer);
});
}
// Generate video
async function generateVideo() {
if (!apiKey) {
showStatus(warningsDisplay, 'Please save your OpenAI API key first!', 'error');
return;
}
const prompt = document.getElementById('videoPrompt').value;
if (!prompt) {
showStatus(warningsDisplay, 'Please generate a video prompt first!', 'error');
return;
}
try {
showProcessing('Generating video...');
const response = await fetch(`${API_BASE}/generate/video`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey
},
body: JSON.stringify({ prompt })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Video generation failed');
}
const result = await response.json();
displayVideoPreview(result.videoUrl);
showStatus(warningsDisplay, 'Video generation complete!', 'success');
} catch (error) {
showStatus(warningsDisplay, `Error generating video: ${error.message}`, 'error');
console.error('Video generation error:', error);
} finally {
hideProcessing();
}
}
// Display video preview
function displayVideoPreview(videoUrl) {
const videoPreview = document.getElementById('videoPreview');
videoPreview.innerHTML = '';
const video = document.createElement('video');
video.src = videoUrl;
video.controls = true;
video.style.width = '100%';
video.style.borderRadius = '12px';
const downloadBtn = document.createElement('button');
downloadBtn.className = 'miniBtn';
downloadBtn.textContent = 'Download Video';
downloadBtn.style.marginTop = '10px';
downloadBtn.addEventListener('click', () => {
downloadFile(videoUrl, 'generated-video.mp4');
});
videoPreview.appendChild(video);
videoPreview.appendChild(downloadBtn);
document.getElementById('videoOut').textContent = 'Video ready for download!';
}
// Handle song file upload
function handleSongFileUpload() {
const file = songFileInput.files[0];
if (!file) return;
if (!['audio/mp3', 'audio/wav', 'audio/m4a'].includes(file.type)) {
showStatus(warningsDisplay, 'Please upload a valid audio file (MP3, WAV, or M4A)', 'error');
return;
}
songFile = file;
songFileName.textContent = file.name;
songFileInfo.style.display = 'flex';
showStatus(warningsDisplay, 'Song file uploaded successfully!', 'success');
}
// Remove song file
function removeSongFile() {
songFile = null;
songFileInput.value = '';
songFileInfo.style.display = 'none';
showStatus(warningsDisplay, 'Song file removed!', 'success');
}
// Handle clip files upload
function handleClipFilesUpload() {
const files = Array.from(clipFilesInput.files);
if (files.length === 0) return;
const invalidFiles = files.filter(file => file.type !== 'video/mp4');
if (invalidFiles.length > 0) {
showStatus(warningsDisplay, 'Only MP4 files are allowed for video clips!', 'error');
return;
}
videoClips = [...videoClips, ...files];
updateClipQueue();
showStatus(warnings