flen-crypto commited on
Commit
b132d53
·
verified ·
1 Parent(s): 73d219e

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1169 -498
index.html CHANGED
@@ -1,498 +1,1169 @@
1
- .getElementById('tempo').value,
2
- vocal_style: document.getElementById('vocalStyle').value,
3
- instrumentation_notes: document.getElementById('instrumentationNotes').value,
4
- language_level: document.getElementById('languageLevel').value,
5
- package_level: document.getElementById('packageLevel').value,
6
- key_facts: document.getElementById('keyFacts').value,
7
- stories: document.getElementById('stories').value,
8
- main_message: document.getElementById('mainMessage').value,
9
- names_places: document.getElementById('namesPlaces').value,
10
- visual_style: document.getElementById('visualStyle').value,
11
- visual_mood: document.getElementById('visualMood').value,
12
- visual_places_themes: document.getElementById('visualPlacesThemes').value,
13
- visual_text: document.getElementById('visualText').value,
14
- wants_artwork: document.getElementById('wantsArtwork').checked,
15
- wants_video: document.getElementById('wantsVideo').checked
16
- };
17
- }
18
-
19
- // Randomize form
20
- async function randomizeForm() {
21
- try {
22
- const response = await fetch(`${API_BASE}/randomize`);
23
- if (!response.ok) throw new Error('Failed to randomize');
24
-
25
- const randomData = await response.json();
26
-
27
- // Fill form fields with random data
28
- document.getElementById('songTitle').value = randomData.songTitle;
29
- document.getElementById('occasion').value = randomData.occasion;
30
- document.getElementById('buyerName').value = randomData.buyerName;
31
- document.getElementById('recipientName').value = randomData.recipientName;
32
- document.getElementById('relationship').value = randomData.relationship;
33
- document.getElementById('tone').value = randomData.tone;
34
- document.getElementById('mainGenre').value = randomData.mainGenre;
35
- document.getElementById('secondaryGenres').value = randomData.secondaryGenres;
36
- document.getElementById('tempo').value = randomData.tempo;
37
- document.getElementById('vocalStyle').value = randomData.vocalStyle;
38
- document.getElementById('instrumentationNotes').value = randomData.instrumentationNotes;
39
- document.getElementById('keyFacts').value = randomData.keyFacts.join('\n');
40
- document.getElementById('stories').value = randomData.stories.join('\n');
41
- document.getElementById('mainMessage').value = randomData.mainMessage;
42
- document.getElementById('namesPlaces').value = randomData.namesPlaces;
43
- document.getElementById('visualStyle').value = randomData.visualStyle;
44
- document.getElementById('visualMood').value = randomData.visualMood;
45
- document.getElementById('visualPlacesThemes').value = randomData.visualPlacesThemes;
46
- document.getElementById('visualText').value = randomData.visualText;
47
-
48
- warningsDisplay.textContent = 'Form randomized with API data!';
49
- } catch (error) {
50
- warningsDisplay.textContent = `Error randomizing: ${error.message}`;
51
- console.error('Randomization error:', error);
52
- }
53
- }
54
-
55
- // Populate builders
56
- function populateBuilders() {
57
- if (!currentOrderId) {
58
- warningsDisplay.textContent = 'Please generate a Suno block first!';
59
- return;
60
- }
61
-
62
- // This would normally populate from the generated data
63
- // For now, we'll just show a message
64
- warningsDisplay.textContent = 'Builders populated from generated data!';
65
- }
66
-
67
- // Copy Suno block to clipboard
68
- function copySunoBlock() {
69
- copyToClipboard(sunoBlockOut.textContent);
70
- warningsDisplay.textContent = 'Suno block copied to clipboard!';
71
- }
72
-
73
- // Save draft
74
- function saveDraft() {
75
- const draft = {
76
- formData: getFormData(),
77
- sunoBlock: sunoBlockOut.textContent,
78
- songPrompt: document.getElementById('songPrompt').value,
79
- artPrompt: document.getElementById('artPrompt').value,
80
- videoPrompt: document.getElementById('videoPrompt').value,
81
- orderId: currentOrderId
82
- };
83
-
84
- localStorage.setItem('fiverrMusicDraft', JSON.stringify(draft));
85
- warningsDisplay.textContent = 'Draft saved to browser storage!';
86
- }
87
-
88
- // Load draft
89
- function loadDraft() {
90
- const draftData = localStorage.getItem('fiverrMusicDraft');
91
- if (!draftData) {
92
- warningsDisplay.textContent = 'No saved draft found!';
93
- return;
94
- }
95
-
96
- try {
97
- const draft = JSON.parse(draftData);
98
-
99
- // Restore form data
100
- document.getElementById('songTitle').value = draft.formData.songTitle;
101
- document.getElementById('occasion').value = draft.formData.occasion;
102
- document.getElementById('buyerName').value = draft.formData.customerName;
103
- document.getElementById('recipientName').value = draft.formData.recipient_name;
104
- document.getElementById('relationship').value = draft.formData.relationship;
105
- document.getElementById('tone').value = draft.formData.tone_vibe;
106
- document.getElementById('mainGenre').value = draft.formData.main_genre;
107
- document.getElementById('secondaryGenres').value = draft.formData.secondary_influences;
108
- document.getElementById('tempo').value = draft.formData.tempo_energy;
109
- document.getElementById('vocalStyle').value = draft.formData.vocal_style;
110
- document.getElementById('instrumentationNotes').value = draft.formData.instrumentation_notes;
111
- document.getElementById('languageLevel').value = draft.formData.language_level;
112
- document.getElementById('packageLevel').value = draft.formData.package_level;
113
- document.getElementById('keyFacts').value = draft.formData.key_facts;
114
- document.getElementById('stories').value = draft.formData.stories;
115
- document.getElementById('mainMessage').value = draft.formData.main_message;
116
- document.getElementById('namesPlaces').value = draft.formData.names_places;
117
- document.getElementById('visualStyle').value = draft.formData.visual_style;
118
- document.getElementById('visualMood').value = draft.formData.visual_mood;
119
- document.getElementById('visualPlacesThemes').value = draft.formData.visual_places_themes;
120
- document.getElementById('visualText').value = draft.formData.visual_text;
121
- document.getElementById('wantsArtwork').checked = draft.formData.wants_artwork;
122
- document.getElementById('wantsVideo').checked = draft.formData.wants_video;
123
-
124
- // Restore generated content
125
- sunoBlockOut.textContent = draft.sunoBlock;
126
- document.getElementById('songPrompt').value = draft.songPrompt;
127
- document.getElementById('artPrompt').value = draft.artPrompt;
128
- document.getElementById('videoPrompt').value = draft.videoPrompt;
129
- currentOrderId = draft.orderId;
130
- orderIdDisplay.textContent = currentOrderId;
131
-
132
- warningsDisplay.textContent = 'Draft loaded successfully!';
133
- } catch (error) {
134
- warningsDisplay.textContent = `Error loading draft: ${error.message}`;
135
- console.error('Draft load error:', error);
136
- }
137
- }
138
-
139
- // Reset form
140
- function resetForm() {
141
- if (confirm('Are you sure you want to reset the form? All data will be lost.')) {
142
- document.querySelectorAll('input, textarea, select').forEach(el => {
143
- if (el.type === 'checkbox') {
144
- el.checked = false;
145
- } else {
146
- el.value = '';
147
- }
148
- });
149
-
150
- sunoBlockOut.textContent = 'Suno block will appear here...';
151
- warningsDisplay.textContent = '';
152
- diagOut.textContent = '';
153
- currentOrderId = null;
154
- orderIdDisplay.textContent = 'none';
155
- document.getElementById('songPrompt').value = '';
156
- document.getElementById('artPrompt').value = '';
157
- document.getElementById('videoPrompt').value = '';
158
- document.getElementById('imgGrid').innerHTML = '';
159
- document.getElementById('videoPreview').innerHTML = '';
160
- document.getElementById('videoOut').textContent = 'Video status...';
161
-
162
- warningsDisplay.textContent = 'Form reset complete!';
163
- }
164
- }
165
-
166
- // Run diagnostics
167
- async function runDiagnostics() {
168
- try {
169
- const response = await fetch(`${API_BASE}/diagnostics`);
170
- if (!response.ok) throw new Error('Diagnostics failed');
171
-
172
- const diagData = await response.json();
173
- diagOut.textContent = JSON.stringify(diagData, null, 2);
174
- diagOut.style.display = 'block';
175
-
176
- warningsDisplay.textContent = 'Diagnostics complete!';
177
- } catch (error) {
178
- diagOut.textContent = `Error running diagnostics: ${error.message}`;
179
- diagOut.style.display = 'block';
180
- console.error('Diagnostics error:', error);
181
- }
182
- }
183
-
184
- // Generate image
185
- async function generateImage() {
186
- if (!apiKey) {
187
- warningsDisplay.textContent = 'Please save your OpenAI API key first!';
188
- return;
189
- }
190
-
191
- const prompt = document.getElementById('artPrompt').value;
192
- if (!prompt) {
193
- warningsDisplay.textContent = 'Please generate an artwork prompt first!';
194
- return;
195
- }
196
-
197
- try {
198
- showProcessing('Generating image...');
199
-
200
- const response = await fetch(`${API_BASE}/generate/image`, {
201
- method: 'POST',
202
- headers: {
203
- 'Content-Type': 'application/json',
204
- 'X-API-Key': apiKey
205
- },
206
- body: JSON.stringify({ prompt })
207
- });
208
-
209
- if (!response.ok) {
210
- const errorData = await response.json();
211
- throw new Error(errorData.error || 'Image generation failed');
212
- }
213
-
214
- const result = await response.json();
215
- displayGeneratedImages(result.images);
216
-
217
- warningsDisplay.textContent = 'Image generation complete!';
218
- } catch (error) {
219
- warningsDisplay.textContent = `Error generating image: ${error.message}`;
220
- console.error('Image generation error:', error);
221
- } finally {
222
- hideProcessing();
223
- }
224
- }
225
-
226
- // Display generated images
227
- function displayGeneratedImages(images) {
228
- const imgGrid = document.getElementById('imgGrid');
229
- imgGrid.innerHTML = '';
230
-
231
- images.forEach((imgUrl, index) => {
232
- const imgContainer = document.createElement('div');
233
- imgContainer.className = 'thumb-container';
234
-
235
- const img = document.createElement('img');
236
- img.src = imgUrl;
237
- img.className = 'thumb';
238
- img.alt = `Generated image ${index + 1}`;
239
-
240
- const downloadBtn = document.createElement('button');
241
- downloadBtn.className = 'miniBtn';
242
- downloadBtn.textContent = 'Download';
243
- downloadBtn.addEventListener('click', () => {
244
- downloadImage(imgUrl, `generated-image-${index + 1}.png`);
245
- });
246
-
247
- imgContainer.appendChild(img);
248
- imgContainer.appendChild(downloadBtn);
249
- imgGrid.appendChild(imgContainer);
250
- });
251
- }
252
-
253
- // Generate video
254
- async function generateVideo() {
255
- if (!apiKey) {
256
- warningsDisplay.textContent = 'Please save your OpenAI API key first!';
257
- return;
258
- }
259
-
260
- const prompt = document.getElementById('videoPrompt').value;
261
- if (!prompt) {
262
- warningsDisplay.textContent = 'Please generate a video prompt first!';
263
- return;
264
- }
265
-
266
- try {
267
- showProcessing('Generating video...');
268
-
269
- const response = await fetch(`${API_BASE}/generate/video`, {
270
- method: 'POST',
271
- headers: {
272
- 'Content-Type': 'application/json',
273
- 'X-API-Key': apiKey
274
- },
275
- body: JSON.stringify({ prompt })
276
- });
277
-
278
- if (!response.ok) {
279
- const errorData = await response.json();
280
- throw new Error(errorData.error || 'Video generation failed');
281
- }
282
-
283
- const result = await response.json();
284
- displayVideoPreview(result.videoUrl);
285
-
286
- warningsDisplay.textContent = 'Video generation complete!';
287
- } catch (error) {
288
- warningsDisplay.textContent = `Error generating video: ${error.message}`;
289
- console.error('Video generation error:', error);
290
- } finally {
291
- hideProcessing();
292
- }
293
- }
294
-
295
- // Display video preview
296
- function displayVideoPreview(videoUrl) {
297
- const videoPreview = document.getElementById('videoPreview');
298
- videoPreview.innerHTML = '';
299
-
300
- const video = document.createElement('video');
301
- video.src = videoUrl;
302
- video.controls = true;
303
- video.style.width = '100%';
304
- video.style.borderRadius = '12px';
305
-
306
- const downloadBtn = document.createElement('button');
307
- downloadBtn.className = 'miniBtn';
308
- downloadBtn.textContent = 'Download Video';
309
- downloadBtn.style.marginTop = '10px';
310
- downloadBtn.addEventListener('click', () => {
311
- downloadFile(videoUrl, 'generated-video.mp4');
312
- });
313
-
314
- videoPreview.appendChild(video);
315
- videoPreview.appendChild(downloadBtn);
316
-
317
- document.getElementById('videoOut').textContent = 'Video ready for download!';
318
- }
319
-
320
- // Handle song file upload
321
- function handleSongFileUpload() {
322
- const file = songFileInput.files[0];
323
- if (!file) return;
324
-
325
- if (!['audio/mp3', 'audio/wav', 'audio/m4a'].includes(file.type)) {
326
- warningsDisplay.textContent = 'Please upload a valid audio file (MP3, WAV, or M4A)';
327
- return;
328
- }
329
-
330
- songFile = file;
331
- songFileName.textContent = file.name;
332
- songFileInfo.style.display = 'flex';
333
- warningsDisplay.textContent = 'Song file uploaded successfully!';
334
- }
335
-
336
- // Remove song file
337
- function removeSongFile() {
338
- songFile = null;
339
- songFileInput.value = '';
340
- songFileInfo.style.display = 'none';
341
- warningsDisplay.textContent = 'Song file removed!';
342
- }
343
-
344
- // Handle clip files upload
345
- function handleClipFilesUpload() {
346
- const files = Array.from(clipFilesInput.files);
347
- if (files.length === 0) return;
348
-
349
- const invalidFiles = files.filter(file => file.type !== 'video/mp4');
350
- if (invalidFiles.length > 0) {
351
- warningsDisplay.textContent = 'Only MP4 files are allowed for video clips!';
352
- return;
353
- }
354
-
355
- videoClips = [...videoClips, ...files];
356
- updateClipQueue();
357
- warningsDisplay.textContent = `${files.length} video clip(s) added to queue!`;
358
- }
359
-
360
- // Update clip queue display
361
- function updateClipQueue() {
362
- if (videoClips.length === 0) {
363
- clipQueueWrap.innerHTML = '<div class="small" style="margin-top:6px">No clips queued.</div>';
364
- return;
365
- }
366
-
367
- let queueHTML = '<div class="small" style="margin-top:6px">Clips in queue:</div>';
368
- queueHTML += '<div class="clip-list">';
369
-
370
- videoClips.forEach((clip, index) => {
371
- queueHTML += `
372
- <div class="clipItem">
373
- <div class="clipName">${clip.name}</div>
374
- <div class="clipBtns">
375
- <button class="miniBtn" onclick="removeClip(${index})">Remove</button>
376
- </div>
377
- </div>
378
- `;
379
- });
380
-
381
- queueHTML += '</div>';
382
- clipQueueWrap.innerHTML = queueHTML;
383
- }
384
-
385
- // Remove a specific clip
386
- function removeClip(index) {
387
- videoClips.splice(index, 1);
388
- updateClipQueue();
389
- warningsDisplay.textContent = 'Clip removed from queue!';
390
- }
391
-
392
- // Clear all clips
393
- function clearClips() {
394
- videoClips = [];
395
- clipFilesInput.value = '';
396
- updateClipQueue();
397
- warningsDisplay.textContent = 'All clips cleared!';
398
- }
399
-
400
- // Stitch clips
401
- async function stitchClips() {
402
- if (!songFile) {
403
- warningsDisplay.textContent = 'Please upload a song file first!';
404
- return;
405
- }
406
-
407
- if (videoClips.length === 0) {
408
- warningsDisplay.textContent = 'Please upload at least one video clip!';
409
- return;
410
- }
411
-
412
- try {
413
- showProcessing('Stitching video clips...');
414
-
415
- const formData = new FormData();
416
- formData.append('song', songFile);
417
- videoClips.forEach((clip, index) => {
418
- formData.append('clips', clip);
419
- });
420
-
421
- const response = await fetch(`${API_BASE}/stitch`, {
422
- method: 'POST',
423
- headers: {
424
- 'X-API-Key': apiKey
425
- },
426
- body: formData
427
- });
428
-
429
- if (!response.ok) {
430
- const errorData = await response.json();
431
- throw new Error(errorData.error || 'Stitching failed');
432
- }
433
-
434
- const result = await response.json();
435
- const downloadLink = document.createElement('a');
436
- downloadLink.href = result.videoUrl;
437
- downloadLink.textContent = 'Download final video';
438
- downloadLink.style.color = '#2dd4bf';
439
- downloadLink.style.marginLeft = '10px';
440
-
441
- document.getElementById('uploadOut').textContent = 'Stitching complete! ';
442
- document.getElementById('uploadOut').appendChild(downloadLink);
443
-
444
- warningsDisplay.textContent = 'Video stitching successful!';
445
- } catch (error) {
446
- document.getElementById('uploadOut').textContent = `Error stitching video: ${error.message}`;
447
- console.error('Stitching error:', error);
448
- } finally {
449
- hideProcessing();
450
- }
451
- }
452
-
453
- // Show processing overlay
454
- function showProcessing(message) {
455
- processingMessage.textContent = message;
456
- processingOverlay.style.display = 'flex';
457
- }
458
-
459
- // Hide processing overlay
460
- function hideProcessing() {
461
- processingOverlay.style.display = 'none';
462
- }
463
-
464
- // Copy to clipboard
465
- function copyToClipboard(text) {
466
- navigator.clipboard.writeText(text).then(() => {
467
- console.log('Copied to clipboard');
468
- }).catch(err => {
469
- console.error('Failed to copy:', err);
470
- });
471
- }
472
-
473
- // Download image
474
- function downloadImage(url, filename) {
475
- const a = document.createElement('a');
476
- a.href = url;
477
- a.download = filename;
478
- document.body.appendChild(a);
479
- a.click();
480
- document.body.removeChild(a);
481
- }
482
-
483
- // Download file
484
- function downloadFile(url, filename) {
485
- const a = document.createElement('a');
486
- a.href = url;
487
- a.download = filename;
488
- document.body.appendChild(a);
489
- a.click();
490
- document.body.removeChild(a);
491
- }
492
-
493
- // Initialize the app when DOM is loaded
494
- document.addEventListener('DOMContentLoaded', init);
495
- </script>
496
- </body>
497
-
498
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Music Generation Tool</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+ <style>
10
+ :root {
11
+ --primary: #2dd4bf;
12
+ --primary-dark: #1da896;
13
+ --secondary: #f8fafc;
14
+ --accent: #fbbf24;
15
+ --text: #1e293b;
16
+ --text-light: #64748b;
17
+ --border: #e2e8f0;
18
+ --error: #ef4444;
19
+ --success: #22c55e;
20
+ --warning: #f59e0b;
21
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
22
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
23
+ }
24
+
25
+ * {
26
+ margin: 0;
27
+ padding: 0;
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ body {
32
+ font-family: 'Inter', sans-serif;
33
+ background-color: #f1f5f9;
34
+ color: var(--text);
35
+ line-height: 1.6;
36
+ }
37
+
38
+ .container {
39
+ max-width: 1200px;
40
+ margin: 0 auto;
41
+ padding: 20px;
42
+ }
43
+
44
+ header {
45
+ background-color: white;
46
+ padding: 15px 0;
47
+ box-shadow: var(--shadow);
48
+ margin-bottom: 20px;
49
+ border-radius: 12px;
50
+ }
51
+
52
+ .header-content {
53
+ display: flex;
54
+ justify-content: space-between;
55
+ align-items: center;
56
+ }
57
+
58
+ .logo {
59
+ font-size: 1.5rem;
60
+ font-weight: 700;
61
+ color: var(--primary);
62
+ }
63
+
64
+ .built-with {
65
+ font-size: 0.875rem;
66
+ color: var(--text-light);
67
+ }
68
+
69
+ .built-with a {
70
+ color: var(--primary);
71
+ text-decoration: none;
72
+ }
73
+
74
+ .main-grid {
75
+ display: grid;
76
+ grid-template-columns: 1fr 1fr;
77
+ gap: 20px;
78
+ margin-bottom: 20px;
79
+ }
80
+
81
+ .card {
82
+ background-color: white;
83
+ border-radius: 12px;
84
+ padding: 20px;
85
+ box-shadow: var(--shadow);
86
+ }
87
+
88
+ h2 {
89
+ font-size: 1.25rem;
90
+ font-weight: 600;
91
+ margin-bottom: 15px;
92
+ color: var(--primary);
93
+ border-bottom: 2px solid var(--primary);
94
+ padding-bottom: 10px;
95
+ }
96
+
97
+ .form-group {
98
+ margin-bottom: 15px;
99
+ }
100
+
101
+ label {
102
+ display: block;
103
+ margin-bottom: 5px;
104
+ font-weight: 500;
105
+ color: var(--text);
106
+ }
107
+
108
+ input[type="text"],
109
+ input[type="email"],
110
+ input[type="password"],
111
+ textarea,
112
+ select {
113
+ width: 100%;
114
+ padding: 10px;
115
+ border: 1px solid var(--border);
116
+ border-radius: 8px;
117
+ font-family: inherit;
118
+ font-size: 0.875rem;
119
+ transition: border-color 0.2s;
120
+ }
121
+
122
+ input:focus,
123
+ textarea:focus,
124
+ select:focus {
125
+ outline: none;
126
+ border-color: var(--primary);
127
+ box-shadow: 0 0 0 3px rgba(45, 212, 191, 0.1);
128
+ }
129
+
130
+ textarea {
131
+ min-height: 100px;
132
+ resize: vertical;
133
+ }
134
+
135
+ .btn {
136
+ padding: 10px 15px;
137
+ border: none;
138
+ border-radius: 8px;
139
+ font-weight: 500;
140
+ cursor: pointer;
141
+ transition: all 0.2s;
142
+ font-size: 0.875rem;
143
+ }
144
+
145
+ .btn-primary {
146
+ background-color: var(--primary);
147
+ color: white;
148
+ }
149
+
150
+ .btn-primary:hover {
151
+ background-color: var(--primary-dark);
152
+ }
153
+
154
+ .btn-secondary {
155
+ background-color: var(--secondary);
156
+ color: var(--text);
157
+ border: 1px solid var(--border);
158
+ }
159
+
160
+ .btn-secondary:hover {
161
+ background-color: #e2e8f0;
162
+ }
163
+
164
+ .btn-danger {
165
+ background-color: var(--error);
166
+ color: white;
167
+ }
168
+
169
+ .btn-danger:hover {
170
+ background-color: #dc2626;
171
+ }
172
+
173
+ .btn-group {
174
+ display: flex;
175
+ gap: 10px;
176
+ margin-bottom: 15px;
177
+ }
178
+
179
+ .output-area {
180
+ background-color: #f8fafc;
181
+ border: 1px solid var(--border);
182
+ border-radius: 8px;
183
+ padding: 15px;
184
+ min-height: 100px;
185
+ font-family: 'Courier New', monospace;
186
+ font-size: 0.875rem;
187
+ white-space: pre-wrap;
188
+ word-wrap: break-word;
189
+ margin-bottom: 15px;
190
+ }
191
+
192
+ .status-message {
193
+ padding: 10px;
194
+ border-radius: 8px;
195
+ margin-bottom: 15px;
196
+ font-size: 0.875rem;
197
+ }
198
+
199
+ .status-success {
200
+ background-color: rgba(34, 197, 94, 0.1);
201
+ color: var(--success);
202
+ }
203
+
204
+ .status-error {
205
+ background-color: rgba(239, 68, 68, 0.1);
206
+ color: var(--error);
207
+ }
208
+
209
+ .status-warning {
210
+ background-color: rgba(245, 158, 11, 0.1);
211
+ color: var(--warning);
212
+ }
213
+
214
+ .checkbox-group {
215
+ display: flex;
216
+ align-items: center;
217
+ gap: 10px;
218
+ margin-bottom: 10px;
219
+ }
220
+
221
+ .checkbox-group input[type="checkbox"] {
222
+ width: auto;
223
+ accent-color: var(--primary);
224
+ }
225
+
226
+ .file-upload {
227
+ border: 2px dashed var(--border);
228
+ border-radius: 8px;
229
+ padding: 20px;
230
+ text-align: center;
231
+ cursor: pointer;
232
+ transition: all 0.2s;
233
+ }
234
+
235
+ .file-upload:hover {
236
+ border-color: var(--primary);
237
+ background-color: rgba(45, 212, 191, 0.05);
238
+ }
239
+
240
+ .file-upload input[type="file"] {
241
+ display: none;
242
+ }
243
+
244
+ .file-info {
245
+ display: flex;
246
+ align-items: center;
247
+ gap: 10px;
248
+ margin-top: 10px;
249
+ padding: 10px;
250
+ background-color: #f8fafc;
251
+ border-radius: 8px;
252
+ }
253
+
254
+ .thumb-container {
255
+ position: relative;
256
+ margin-bottom: 10px;
257
+ }
258
+
259
+ .thumb {
260
+ width: 100%;
261
+ border-radius: 8px;
262
+ box-shadow: var(--shadow);
263
+ }
264
+
265
+ .miniBtn {
266
+ position: absolute;
267
+ bottom: 10px;
268
+ right: 10px;
269
+ padding: 5px 10px;
270
+ background-color: rgba(0, 0, 0, 0.7);
271
+ color: white;
272
+ border: none;
273
+ border-radius: 4px;
274
+ font-size: 0.75rem;
275
+ cursor: pointer;
276
+ }
277
+
278
+ .processing-overlay {
279
+ position: fixed;
280
+ top: 0;
281
+ left: 0;
282
+ width: 100%;
283
+ height: 100%;
284
+ background-color: rgba(0, 0, 0, 0.5);
285
+ display: none;
286
+ justify-content: center;
287
+ align-items: center;
288
+ z-index: 1000;
289
+ }
290
+
291
+ .processing-content {
292
+ background-color: white;
293
+ padding: 30px;
294
+ border-radius: 12px;
295
+ text-align: center;
296
+ max-width: 400px;
297
+ }
298
+
299
+ .spinner {
300
+ border: 4px solid rgba(0, 0, 0, 0.1);
301
+ border-radius: 50%;
302
+ border-top: 4px solid var(--primary);
303
+ width: 40px;
304
+ height: 40px;
305
+ animation: spin 1s linear infinite;
306
+ margin: 0 auto 15px;
307
+ }
308
+
309
+ @keyframes spin {
310
+ 0% { transform: rotate(0deg); }
311
+ 100% { transform: rotate(360deg); }
312
+ }
313
+
314
+ @media (max-width: 768px) {
315
+ .main-grid {
316
+ grid-template-columns: 1fr;
317
+ }
318
+
319
+ .header-content {
320
+ flex-direction: column;
321
+ gap: 10px;
322
+ }
323
+ }
324
+ </style>
325
+ </head>
326
+ <body>
327
+ <header>
328
+ <div class="container">
329
+ <div class="header-content">
330
+ <div class="logo">Music Generation Tool</div>
331
+ <div class="built-with">
332
+ Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
333
+ </div>
334
+ </div>
335
+ </div>
336
+ </header>
337
+
338
+ <div class="container">
339
+ <div class="main-grid">
340
+ <div class="card">
341
+ <h2>API Configuration</h2>
342
+ <div class="form-group">
343
+ <label for="apiKey">OpenAI API Key</label>
344
+ <input type="password" id="apiKey" placeholder="Enter your OpenAI API key">
345
+ </div>
346
+ <div class="btn-group">
347
+ <button class="btn btn-primary" id="saveKeyBtn">Save Key</button>
348
+ <button class="btn btn-secondary" id="testKeyBtn">Test Key</button>
349
+ </div>
350
+ <div id="apiStatus" class="status-message" style="display: none;"></div>
351
+
352
+ <h2 style="margin-top: 25px;">Server Controls</h2>
353
+ <div class="btn-group">
354
+ <button class="btn btn-secondary" id="checkHealthBtn">Check Server Health</button>
355
+ <button class="btn btn-secondary" id="runDiagnosticsBtn">Run Diagnostics</button>
356
+ </div>
357
+ <div id="healthStatus" class="status-message" style="display: none;"></div>
358
+ <div id="diagOut" class="output-area" style="display: none;"></div>
359
+ </div>
360
+
361
+ <div class="card">
362
+ <h2>Form Controls</h2>
363
+ <div class="btn-group">
364
+ <button class="btn btn-secondary" id="randomizeBtn">Randomize Form</button>
365
+ <button class="btn btn-secondary" id="resetFormBtn">Reset Form</button>
366
+ </div>
367
+ <div class="btn-group">
368
+ <button class="btn btn-secondary" id="saveDraftBtn">Save Draft</button>
369
+ <button class="btn btn-secondary" id="loadDraftBtn">Load Draft</button>
370
+ </div>
371
+ <div id="warningsDisplay" class="status-message" style="display: none;"></div>
372
+ </div>
373
+ </div>
374
+
375
+ <div class="card">
376
+ <h2>Main Form</h2>
377
+ <div class="form-group">
378
+ <label for="songTitle">Song Title</label>
379
+ <input type="text" id="songTitle" placeholder="Enter song title">
380
+ </div>
381
+
382
+ <div class="form-group">
383
+ <label for="occasion">Occasion</label>
384
+ <input type="text" id="occasion" placeholder="e.g., Birthday, Anniversary">
385
+ </div>
386
+
387
+ <div class="form-group">
388
+ <label for="buyerName">Buyer Name</label>
389
+ <input type="text" id="buyerName" placeholder="Enter buyer name">
390
+ </div>
391
+
392
+ <div class="form-group">
393
+ <label for="recipientName">Recipient Name</label>
394
+ <input type="text" id="recipientName" placeholder="Enter recipient name">
395
+ </div>
396
+
397
+ <div class="form-group">
398
+ <label for="relationship">Relationship</label>
399
+ <input type="text" id="relationship" placeholder="e.g., Mother, Friend">
400
+ </div>
401
+
402
+ <div class="form-group">
403
+ <label for="tone">Tone/Vibe</label>
404
+ <input type="text" id="tone" placeholder="e.g., Happy, Romantic">
405
+ </div>
406
+
407
+ <div class="form-group">
408
+ <label for="mainGenre">Main Genre</label>
409
+ <input type="text" id="mainGenre" placeholder="e.g., Pop, Rock">
410
+ </div>
411
+
412
+ <div class="form-group">
413
+ <label for="secondaryGenres">Secondary Genres</label>
414
+ <input type="text" id="secondaryGenres" placeholder="e.g., Jazz, Electronic">
415
+ </div>
416
+
417
+ <div class="form-group">
418
+ <label for="tempo">Tempo/Energy</label>
419
+ <input type="text" id="tempo" placeholder="e.g., Fast, Slow">
420
+ </div>
421
+
422
+ <div class="form-group">
423
+ <label for="vocalStyle">Vocal Style</label>
424
+ <input type="text" id="vocalStyle" placeholder="e.g., Male, Female, Choir">
425
+ </div>
426
+
427
+ <div class="form-group">
428
+ <label for="instrumentationNotes">Instrumentation Notes</label>
429
+ <textarea id="instrumentationNotes" placeholder="Any specific instruments or arrangements"></textarea>
430
+ </div>
431
+
432
+ <div class="form-group">
433
+ <label for="keyFacts">Key Facts</label>
434
+ <textarea id="keyFacts" placeholder="Important facts to include in the song"></textarea>
435
+ </div>
436
+
437
+ <div class="form-group">
438
+ <label for="stories">Stories</label>
439
+ <textarea id="stories" placeholder="Any stories to include"></textarea>
440
+ </div>
441
+
442
+ <div class="form-group">
443
+ <label for="mainMessage">Main Message</label>
444
+ <textarea id="mainMessage" placeholder="The main message of the song"></textarea>
445
+ </div>
446
+
447
+ <div class="form-group">
448
+ <label for="namesPlaces">Names/Places</label>
449
+ <input type="text" id="namesPlaces" placeholder="Any specific names or places to mention">
450
+ </div>
451
+
452
+ <div class="form-group">
453
+ <label for="visualStyle">Visual Style</label>
454
+ <input type="text" id="visualStyle" placeholder="e.g., Realistic, Cartoon">
455
+ </div>
456
+
457
+ <div class="form-group">
458
+ <label for="visualMood">Visual Mood</label>
459
+ <input type="text" id="visualMood" placeholder="e.g., Bright, Dark">
460
+ </div>
461
+
462
+ <div class="form-group">
463
+ <label for="visualPlacesThemes">Visual Places/Themes</label>
464
+ <input type="text" id="visualPlacesThemes" placeholder="e.g., Beach, Mountains">
465
+ </div>
466
+
467
+ <div class="form-group">
468
+ <label for="visualText">Visual Text</label>
469
+ <input type="text" id="visualText" placeholder="Any text to include in visuals">
470
+ </div>
471
+
472
+ <div class="checkbox-group">
473
+ <input type="checkbox" id="wantsArtwork">
474
+ <label for="wantsArtwork">Wants Artwork</label>
475
+ </div>
476
+
477
+ <div class="checkbox-group">
478
+ <input type="checkbox" id="wantsVideo">
479
+ <label for="wantsVideo">Wants Video</label>
480
+ </div>
481
+
482
+ <div class="btn-group">
483
+ <button class="btn btn-primary" id="generateBtn">Generate Suno Block</button>
484
+ <button class="btn btn-secondary" id="copySunoBtn">Copy Suno Block</button>
485
+ </div>
486
+
487
+ <div class="form-group">
488
+ <label>Suno Block Output</label>
489
+ <div id="sunoBlockOut" class="output-area">Suno block will appear here...</div>
490
+ </div>
491
+ </div>
492
+
493
+ <div class="card">
494
+ <h2>Generated Prompts</h2>
495
+ <div class="form-group">
496
+ <label for="songPrompt">Song Prompt</label>
497
+ <textarea id="songPrompt" readonly></textarea>
498
+ </div>
499
+
500
+ <div class="form-group">
501
+ <label for="artPrompt">Artwork Prompt</label>
502
+ <textarea id="artPrompt" readonly></textarea>
503
+ </div>
504
+
505
+ <div class="form-group">
506
+ <label for="videoPrompt">Video Prompt</label>
507
+ <textarea id="videoPrompt" readonly></textarea>
508
+ </div>
509
+ </div>
510
+
511
+ <div class="card">
512
+ <h2>Image Generation</h2>
513
+ <div class="btn-group">
514
+ <button class="btn btn-primary" id="generateImageBtn">Generate Image</button>
515
+ </div>
516
+ <div id="imgGrid" class="output-area" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px;"></div>
517
+ </div>
518
+
519
+ <div class="card">
520
+ <h2>Video Generation</h2>
521
+ <div class="btn-group">
522
+ <button class="btn btn-primary" id="generateVideoBtn">Generate Video</button>
523
+ </div>
524
+ <div id="videoPreview" class="output-area"></div>
525
+ <div id="videoOut" class="status-message"></div>
526
+ </div>
527
+
528
+ <div class="card">
529
+ <h2>File Upload</h2>
530
+ <div class="form-group">
531
+ <label>Song File</label>
532
+ <div class="file-upload" id="songFileUpload">
533
+ <i class="fas fa-upload fa-2x"></i>
534
+ <p>Click to upload song file (MP3, WAV, M4A)</p>
535
+ <input type="file" id="songFileInput" accept="audio/mp3,audio/wav,audio/m4a">
536
+ </div>
537
+ <div id="songFileInfo" class="file-info" style="display: none;">
538
+ <i class="fas fa-file-audio"></i>
539
+ <span id="songFileName"></span>
540
+ <button class="btn btn-danger" id="removeSongBtn" style="padding: 5px 10px;">Remove</button>
541
+ </div>
542
+ </div>
543
+
544
+ <div class="form-group">
545
+ <label>Video Clips (MP4)</label>
546
+ <div class="file-upload" id="clipFilesUpload">
547
+ <i class="fas fa-upload fa-2x"></i>
548
+ <p>Click to upload video clips (MP4)</p>
549
+ <input type="file" id="clipFilesInput" accept="video/mp4" multiple>
550
+ </div>
551
+ <div id="clipQueueWrap" class="file-info">
552
+ <div class="small" style="margin-top:6px">No clips queued.</div>
553
+ </div>
554
+ <div class="btn-group">
555
+ <button class="btn btn-danger" id="clearClipsBtn">Clear All Clips</button>
556
+ <button class="btn btn-primary" id="stitchClipsBtn">Stitch Clips</button>
557
+ </div>
558
+ </div>
559
+ <div id="uploadOut" class="status-message"></div>
560
+ </div>
561
+ </div>
562
+
563
+ <div class="processing-overlay" id="processingOverlay">
564
+ <div class="processing-content">
565
+ <div class="spinner"></div>
566
+ <div id="processingMessage">Processing...</div>
567
+ </div>
568
+ </div>
569
+
570
+ <script>
571
+ // Constants
572
+ const API_BASE = 'https://api.example.com'; // Replace with your actual API base URL
573
+ let apiKey = '';
574
+ let currentOrderId = null;
575
+ let songFile = null;
576
+ let videoClips = [];
577
+
578
+ // DOM Elements
579
+ const warningsDisplay = document.getElementById('warningsDisplay');
580
+ const apiStatus = document.getElementById('apiStatus');
581
+ const healthStatus = document.getElementById('healthStatus');
582
+ const diagOut = document.getElementById('diagOut');
583
+ const sunoBlockOut = document.getElementById('sunoBlockOut');
584
+ const orderIdDisplay = document.getElementById('orderIdDisplay');
585
+ const processingOverlay = document.getElementById('processingOverlay');
586
+ const processingMessage = document.getElementById('processingMessage');
587
+ const songFileInput = document.getElementById('songFileInput');
588
+ const songFileUpload = document.getElementById('songFileUpload');
589
+ const songFileInfo = document.getElementById('songFileInfo');
590
+ const songFileName = document.getElementById('songFileName');
591
+ const removeSongBtn = document.getElementById('removeSongBtn');
592
+ const clipFilesInput = document.getElementById('clipFilesInput');
593
+ const clipFilesUpload = document.getElementById('clipFilesUpload');
594
+ const clipQueueWrap = document.getElementById('clipQueueWrap');
595
+ const clearClipsBtn = document.getElementById('clearClipsBtn');
596
+ const stitchClipsBtn = document.getElementById('stitchClipsBtn');
597
+ const uploadOut = document.getElementById('uploadOut');
598
+
599
+ // Initialize the app
600
+ function init() {
601
+ // Load saved API key
602
+ const savedKey = localStorage.getItem('openaiApiKey');
603
+ if (savedKey) {
604
+ apiKey = savedKey;
605
+ document.getElementById('apiKey').value = '••••••••••••••••';
606
+ }
607
+
608
+ // Set up event listeners
609
+ document.getElementById('saveKeyBtn').addEventListener('click', saveApiKey);
610
+ document.getElementById('testKeyBtn').addEventListener('click', testApiKey);
611
+ document.getElementById('checkHealthBtn').addEventListener('click', checkServerHealth);
612
+ document.getElementById('runDiagnosticsBtn').addEventListener('click', runDiagnostics);
613
+ document.getElementById('randomizeBtn').addEventListener('click', randomizeForm);
614
+ document.getElementById('resetFormBtn').addEventListener('click', resetForm);
615
+ document.getElementById('saveDraftBtn').addEventListener('click', saveDraft);
616
+ document.getElementById('loadDraftBtn').addEventListener('click', loadDraft);
617
+ document.getElementById('generateBtn').addEventListener('click', generateSunoBlock);
618
+ document.getElementById('copySunoBtn').addEventListener('click', copySunoBlock);
619
+ document.getElementById('generateImageBtn').addEventListener('click', generateImage);
620
+ document.getElementById('generateVideoBtn').addEventListener('click', generateVideo);
621
+ songFileUpload.addEventListener('click', () => songFileInput.click());
622
+ songFileInput.addEventListener('change', handleSongFileUpload);
623
+ removeSongBtn.addEventListener('click', removeSongFile);
624
+ clipFilesUpload.addEventListener('click', () => clipFilesInput.click());
625
+ clipFilesInput.addEventListener('change', handleClipFilesUpload);
626
+ clearClipsBtn.addEventListener('click', clearClips);
627
+ stitchClipsBtn.addEventListener('click', stitchClips);
628
+
629
+ // Initialize file upload areas
630
+ updateFileUploadAreas();
631
+ }
632
+
633
+ // Update file upload areas
634
+ function updateFileUploadAreas() {
635
+ // Song file upload
636
+ songFileUpload.addEventListener('dragover', (e) => {
637
+ e.preventDefault();
638
+ songFileUpload.style.borderColor = var(--primary);
639
+ });
640
+
641
+ songFileUpload.addEventListener('dragleave', () => {
642
+ songFileUpload.style.borderColor = var(--border);
643
+ });
644
+
645
+ songFileUpload.addEventListener('drop', (e) => {
646
+ e.preventDefault();
647
+ songFileUpload.style.borderColor = var(--border);
648
+ songFileInput.files = e.dataTransfer.files;
649
+ handleSongFileUpload();
650
+ });
651
+
652
+ // Clip files upload
653
+ clipFilesUpload.addEventListener('dragover', (e) => {
654
+ e.preventDefault();
655
+ clipFilesUpload.style.borderColor = var(--primary);
656
+ });
657
+
658
+ clipFilesUpload.addEventListener('dragleave', () => {
659
+ clipFilesUpload.style.borderColor = var(--border);
660
+ });
661
+
662
+ clipFilesUpload.addEventListener('drop', (e) => {
663
+ e.preventDefault();
664
+ clipFilesUpload.style.borderColor = var(--border);
665
+ clipFilesInput.files = e.dataTransfer.files;
666
+ handleClipFilesUpload();
667
+ });
668
+ }
669
+
670
+ // Save API key
671
+ function saveApiKey() {
672
+ const keyInput = document.getElementById('apiKey');
673
+ const keyValue = keyInput.value.trim();
674
+
675
+ if (!keyValue) {
676
+ showStatus(apiStatus, 'Please enter an API key', 'error');
677
+ return;
678
+ }
679
+
680
+ // Simple validation - should start with 'sk-' and be at least 40 characters
681
+ if (!keyValue.startsWith('sk-') || keyValue.length < 40) {
682
+ showStatus(apiStatus, 'Invalid API key format', 'error');
683
+ return;
684
+ }
685
+
686
+ apiKey = keyValue;
687
+ localStorage.setItem('openaiApiKey', apiKey);
688
+ keyInput.value = '••••••••••••••••';
689
+ showStatus(apiStatus, 'API key saved successfully!', 'success');
690
+ }
691
+
692
+ // Test API key
693
+ async function testApiKey() {
694
+ if (!apiKey) {
695
+ showStatus(apiStatus, 'Please save your API key first!', 'error');
696
+ return;
697
+ }
698
+
699
+ try {
700
+ showProcessing('Testing API key...');
701
+
702
+ // Mock API call - replace with actual API endpoint
703
+ const response = await fetch(`${API_BASE}/test-key`, {
704
+ method: 'POST',
705
+ headers: {
706
+ 'Content-Type': 'application/json',
707
+ 'Authorization': `Bearer ${apiKey}`
708
+ }
709
+ });
710
+
711
+ if (!response.ok) {
712
+ const errorData = await response.json();
713
+ throw new Error(errorData.error || 'API key test failed');
714
+ }
715
+
716
+ const result = await response.json();
717
+ showStatus(apiStatus, 'API key is valid!', 'success');
718
+ } catch (error) {
719
+ showStatus(apiStatus, `API key test failed: ${error.message}`, 'error');
720
+ console.error('API key test error:', error);
721
+ } finally {
722
+ hideProcessing();
723
+ }
724
+ }
725
+
726
+ // Check server health
727
+ async function checkServerHealth() {
728
+ try {
729
+ showProcessing('Checking server health...');
730
+
731
+ const response = await fetch(`${API_BASE}/health`);
732
+ if (!response.ok) throw new Error('Server health check failed');
733
+
734
+ const healthData = await response.json();
735
+ showStatus(healthStatus, `Server is healthy! (${healthData.status})`, 'success');
736
+ } catch (error) {
737
+ showStatus(healthStatus, `Server health check failed: ${error.message}`, 'error');
738
+ console.error('Health check error:', error);
739
+ } finally {
740
+ hideProcessing();
741
+ }
742
+ }
743
+
744
+ // Show status message
745
+ function showStatus(element, message, type) {
746
+ element.textContent = message;
747
+ element.className = `status-message status-${type}`;
748
+ element.style.display = 'block';
749
+
750
+ // Hide after 5 seconds
751
+ setTimeout(() => {
752
+ element.style.display = 'none';
753
+ }, 5000);
754
+ }
755
+
756
+ // Get form data
757
+ function getFormData() {
758
+ return {
759
+ songTitle: document.getElementById('songTitle').value,
760
+ occasion: document.getElementById('occasion').value,
761
+ customerName: document.getElementById('buyerName').value,
762
+ recipient_name: document.getElementById('recipientName').value,
763
+ relationship: document.getElementById('relationship').value,
764
+ tone_vibe: document.getElementById('tone').value,
765
+ main_genre: document.getElementById('mainGenre').value,
766
+ secondary_influences: document.getElementById('secondaryGenres').value,
767
+ tempo_energy: document.getElementById('tempo').value,
768
+ vocal_style: document.getElementById('vocalStyle').value,
769
+ instrumentation_notes: document.getElementById('instrumentationNotes').value,
770
+ language_level: document.getElementById('languageLevel').value,
771
+ package_level: document.getElementById('packageLevel').value,
772
+ key_facts: document.getElementById('keyFacts').value,
773
+ stories: document.getElementById('stories').value,
774
+ main_message: document.getElementById('mainMessage').value,
775
+ names_places: document.getElementById('namesPlaces').value,
776
+ visual_style: document.getElementById('visualStyle').value,
777
+ visual_mood: document.getElementById('visualMood').value,
778
+ visual_places_themes: document.getElementById('visualPlacesThemes').value,
779
+ visual_text: document.getElementById('visualText').value,
780
+ wants_artwork: document.getElementById('wantsArtwork').checked,
781
+ wants_video: document.getElementById('wantsVideo').checked
782
+ };
783
+ }
784
+
785
+ // Generate Suno block
786
+ async function generateSunoBlock() {
787
+ const formData = getFormData();
788
+
789
+ // Basic validation
790
+ if (!formData.songTitle || !formData.customerName || !formData.recipient_name) {
791
+ showStatus(warningsDisplay, 'Please fill in required fields (Song Title, Buyer Name, Recipient Name)', 'error');
792
+ return;
793
+ }
794
+
795
+ try {
796
+ showProcessing('Generating Suno block...');
797
+
798
+ // Mock API call - replace with actual API endpoint
799
+ const response = await fetch(`${API_BASE}/generate`, {
800
+ method: 'POST',
801
+ headers: {
802
+ 'Content-Type': 'application/json',
803
+ 'X-API-Key': apiKey
804
+ },
805
+ body: JSON.stringify(formData)
806
+ });
807
+
808
+ if (!response.ok) {
809
+ const errorData = await response.json();
810
+ throw new Error(errorData.error || 'Generation failed');
811
+ }
812
+
813
+ const result = await response.json();
814
+ currentOrderId = result.orderId;
815
+ orderIdDisplay.textContent = currentOrderId;
816
+
817
+ // Display the Suno block
818
+ sunoBlockOut.textContent = result.sunoBlock;
819
+
820
+ // Generate prompts
821
+ document.getElementById('songPrompt').value = result.songPrompt;
822
+ document.getElementById('artPrompt').value = result.artPrompt;
823
+ document.getElementById('videoPrompt').value = result.videoPrompt;
824
+
825
+ showStatus(warningsDisplay, 'Suno block generated successfully!', 'success');
826
+ } catch (error) {
827
+ showStatus(warningsDisplay, `Error generating Suno block: ${error.message}`, 'error');
828
+ console.error('Generation error:', error);
829
+ } finally {
830
+ hideProcessing();
831
+ }
832
+ }
833
+
834
+ // Randomize form
835
+ async function randomizeForm() {
836
+ try {
837
+ showProcessing('Randomizing form...');
838
+
839
+ // Mock API call - replace with actual API endpoint
840
+ const response = await fetch(`${API_BASE}/randomize`);
841
+ if (!response.ok) throw new Error('Failed to randomize');
842
+
843
+ const randomData = await response.json();
844
+
845
+ // Fill form fields with random data
846
+ document.getElementById('songTitle').value = randomData.songTitle;
847
+ document.getElementById('occasion').value = randomData.occasion;
848
+ document.getElementById('buyerName').value = randomData.buyerName;
849
+ document.getElementById('recipientName').value = randomData.recipientName;
850
+ document.getElementById('relationship').value = randomData.relationship;
851
+ document.getElementById('tone').value = randomData.tone;
852
+ document.getElementById('mainGenre').value = randomData.mainGenre;
853
+ document.getElementById('secondaryGenres').value = randomData.secondaryGenres;
854
+ document.getElementById('tempo').value = randomData.tempo;
855
+ document.getElementById('vocalStyle').value = randomData.vocalStyle;
856
+ document.getElementById('instrumentationNotes').value = randomData.instrumentationNotes;
857
+ document.getElementById('keyFacts').value = randomData.keyFacts.join('\n');
858
+ document.getElementById('stories').value = randomData.stories.join('\n');
859
+ document.getElementById('mainMessage').value = randomData.mainMessage;
860
+ document.getElementById('namesPlaces').value = randomData.namesPlaces;
861
+ document.getElementById('visualStyle').value = randomData.visualStyle;
862
+ document.getElementById('visualMood').value = randomData.visualMood;
863
+ document.getElementById('visualPlacesThemes').value = randomData.visualPlacesThemes;
864
+ document.getElementById('visualText').value = randomData.visualText;
865
+
866
+ showStatus(warningsDisplay, 'Form randomized with API data!', 'success');
867
+ } catch (error) {
868
+ showStatus(warningsDisplay, `Error randomizing: ${error.message}`, 'error');
869
+ console.error('Randomization error:', error);
870
+ } finally {
871
+ hideProcessing();
872
+ }
873
+ }
874
+
875
+ // Reset form
876
+ function resetForm() {
877
+ if (confirm('Are you sure you want to reset the form? All data will be lost.')) {
878
+ document.querySelectorAll('input, textarea, select').forEach(el => {
879
+ if (el.type === 'checkbox') {
880
+ el.checked = false;
881
+ } else {
882
+ el.value = '';
883
+ }
884
+ });
885
+
886
+ sunoBlockOut.textContent = 'Suno block will appear here...';
887
+ warningsDisplay.textContent = '';
888
+ diagOut.textContent = '';
889
+ currentOrderId = null;
890
+ orderIdDisplay.textContent = 'none';
891
+ document.getElementById('songPrompt').value = '';
892
+ document.getElementById('artPrompt').value = '';
893
+ document.getElementById('videoPrompt').value = '';
894
+ document.getElementById('imgGrid').innerHTML = '';
895
+ document.getElementById('videoPreview').innerHTML = '';
896
+ document.getElementById('videoOut').textContent = 'Video status...';
897
+
898
+ showStatus(warningsDisplay, 'Form reset complete!', 'success');
899
+ }
900
+ }
901
+
902
+ // Save draft
903
+ function saveDraft() {
904
+ const draft = {
905
+ formData: getFormData(),
906
+ sunoBlock: sunoBlockOut.textContent,
907
+ songPrompt: document.getElementById('songPrompt').value,
908
+ artPrompt: document.getElementById('artPrompt').value,
909
+ videoPrompt: document.getElementById('videoPrompt').value,
910
+ orderId: currentOrderId
911
+ };
912
+
913
+ localStorage.setItem('fiverrMusicDraft', JSON.stringify(draft));
914
+ showStatus(warningsDisplay, 'Draft saved to browser storage!', 'success');
915
+ }
916
+
917
+ // Load draft
918
+ function loadDraft() {
919
+ const draftData = localStorage.getItem('fiverrMusicDraft');
920
+ if (!draftData) {
921
+ showStatus(warningsDisplay, 'No saved draft found!', 'warning');
922
+ return;
923
+ }
924
+
925
+ try {
926
+ const draft = JSON.parse(draftData);
927
+
928
+ // Restore form data
929
+ document.getElementById('songTitle').value = draft.formData.songTitle;
930
+ document.getElementById('occasion').value = draft.formData.occasion;
931
+ document.getElementById('buyerName').value = draft.formData.customerName;
932
+ document.getElementById('recipientName').value = draft.formData.recipient_name;
933
+ document.getElementById('relationship').value = draft.formData.relationship;
934
+ document.getElementById('tone').value = draft.formData.tone_vibe;
935
+ document.getElementById('mainGenre').value = draft.formData.main_genre;
936
+ document.getElementById('secondaryGenres').value = draft.formData.secondary_influences;
937
+ document.getElementById('tempo').value = draft.formData.tempo_energy;
938
+ document.getElementById('vocalStyle').value = draft.formData.vocal_style;
939
+ document.getElementById('instrumentationNotes').value = draft.formData.instrumentation_notes;
940
+ document.getElementById('languageLevel').value = draft.formData.language_level;
941
+ document.getElementById('packageLevel').value = draft.formData.package_level;
942
+ document.getElementById('keyFacts').value = draft.formData.key_facts;
943
+ document.getElementById('stories').value = draft.formData.stories;
944
+ document.getElementById('mainMessage').value = draft.formData.main_message;
945
+ document.getElementById('namesPlaces').value = draft.formData.names_places;
946
+ document.getElementById('visualStyle').value = draft.formData.visual_style;
947
+ document.getElementById('visualMood').value = draft.formData.visual_mood;
948
+ document.getElementById('visualPlacesThemes').value = draft.formData.visual_places_themes;
949
+ document.getElementById('visualText').value = draft.formData.visual_text;
950
+ document.getElementById('wantsArtwork').checked = draft.formData.wants_artwork;
951
+ document.getElementById('wantsVideo').checked = draft.formData.wants_video;
952
+
953
+ // Restore generated content
954
+ sunoBlockOut.textContent = draft.sunoBlock;
955
+ document.getElementById('songPrompt').value = draft.songPrompt;
956
+ document.getElementById('artPrompt').value = draft.artPrompt;
957
+ document.getElementById('videoPrompt').value = draft.videoPrompt;
958
+ currentOrderId = draft.orderId;
959
+ orderIdDisplay.textContent = currentOrderId;
960
+
961
+ showStatus(warningsDisplay, 'Draft loaded successfully!', 'success');
962
+ } catch (error) {
963
+ showStatus(warningsDisplay, `Error loading draft: ${error.message}`, 'error');
964
+ console.error('Draft load error:', error);
965
+ }
966
+ }
967
+
968
+ // Copy Suno block to clipboard
969
+ function copySunoBlock() {
970
+ copyToClipboard(sunoBlockOut.textContent);
971
+ showStatus(warningsDisplay, 'Suno block copied to clipboard!', 'success');
972
+ }
973
+
974
+ // Run diagnostics
975
+ async function runDiagnostics() {
976
+ try {
977
+ showProcessing('Running diagnostics...');
978
+
979
+ const response = await fetch(`${API_BASE}/diagnostics`);
980
+ if (!response.ok) throw new Error('Diagnostics failed');
981
+
982
+ const diagData = await response.json();
983
+ diagOut.textContent = JSON.stringify(diagData, null, 2);
984
+ diagOut.style.display = 'block';
985
+
986
+ showStatus(warningsDisplay, 'Diagnostics complete!', 'success');
987
+ } catch (error) {
988
+ diagOut.textContent = `Error running diagnostics: ${error.message}`;
989
+ diagOut.style.display = 'block';
990
+ console.error('Diagnostics error:', error);
991
+ } finally {
992
+ hideProcessing();
993
+ }
994
+ }
995
+
996
+ // Generate image
997
+ async function generateImage() {
998
+ if (!apiKey) {
999
+ showStatus(warningsDisplay, 'Please save your OpenAI API key first!', 'error');
1000
+ return;
1001
+ }
1002
+
1003
+ const prompt = document.getElementById('artPrompt').value;
1004
+ if (!prompt) {
1005
+ showStatus(warningsDisplay, 'Please generate an artwork prompt first!', 'error');
1006
+ return;
1007
+ }
1008
+
1009
+ try {
1010
+ showProcessing('Generating image...');
1011
+
1012
+ const response = await fetch(`${API_BASE}/generate/image`, {
1013
+ method: 'POST',
1014
+ headers: {
1015
+ 'Content-Type': 'application/json',
1016
+ 'X-API-Key': apiKey
1017
+ },
1018
+ body: JSON.stringify({ prompt })
1019
+ });
1020
+
1021
+ if (!response.ok) {
1022
+ const errorData = await response.json();
1023
+ throw new Error(errorData.error || 'Image generation failed');
1024
+ }
1025
+
1026
+ const result = await response.json();
1027
+ displayGeneratedImages(result.images);
1028
+
1029
+ showStatus(warningsDisplay, 'Image generation complete!', 'success');
1030
+ } catch (error) {
1031
+ showStatus(warningsDisplay, `Error generating image: ${error.message}`, 'error');
1032
+ console.error('Image generation error:', error);
1033
+ } finally {
1034
+ hideProcessing();
1035
+ }
1036
+ }
1037
+
1038
+ // Display generated images
1039
+ function displayGeneratedImages(images) {
1040
+ const imgGrid = document.getElementById('imgGrid');
1041
+ imgGrid.innerHTML = '';
1042
+
1043
+ images.forEach((imgUrl, index) => {
1044
+ const imgContainer = document.createElement('div');
1045
+ imgContainer.className = 'thumb-container';
1046
+
1047
+ const img = document.createElement('img');
1048
+ img.src = imgUrl;
1049
+ img.className = 'thumb';
1050
+ img.alt = `Generated image ${index + 1}`;
1051
+
1052
+ const downloadBtn = document.createElement('button');
1053
+ downloadBtn.className = 'miniBtn';
1054
+ downloadBtn.textContent = 'Download';
1055
+ downloadBtn.addEventListener('click', () => {
1056
+ downloadImage(imgUrl, `generated-image-${index + 1}.png`);
1057
+ });
1058
+
1059
+ imgContainer.appendChild(img);
1060
+ imgContainer.appendChild(downloadBtn);
1061
+ imgGrid.appendChild(imgContainer);
1062
+ });
1063
+ }
1064
+
1065
+ // Generate video
1066
+ async function generateVideo() {
1067
+ if (!apiKey) {
1068
+ showStatus(warningsDisplay, 'Please save your OpenAI API key first!', 'error');
1069
+ return;
1070
+ }
1071
+
1072
+ const prompt = document.getElementById('videoPrompt').value;
1073
+ if (!prompt) {
1074
+ showStatus(warningsDisplay, 'Please generate a video prompt first!', 'error');
1075
+ return;
1076
+ }
1077
+
1078
+ try {
1079
+ showProcessing('Generating video...');
1080
+
1081
+ const response = await fetch(`${API_BASE}/generate/video`, {
1082
+ method: 'POST',
1083
+ headers: {
1084
+ 'Content-Type': 'application/json',
1085
+ 'X-API-Key': apiKey
1086
+ },
1087
+ body: JSON.stringify({ prompt })
1088
+ });
1089
+
1090
+ if (!response.ok) {
1091
+ const errorData = await response.json();
1092
+ throw new Error(errorData.error || 'Video generation failed');
1093
+ }
1094
+
1095
+ const result = await response.json();
1096
+ displayVideoPreview(result.videoUrl);
1097
+
1098
+ showStatus(warningsDisplay, 'Video generation complete!', 'success');
1099
+ } catch (error) {
1100
+ showStatus(warningsDisplay, `Error generating video: ${error.message}`, 'error');
1101
+ console.error('Video generation error:', error);
1102
+ } finally {
1103
+ hideProcessing();
1104
+ }
1105
+ }
1106
+
1107
+ // Display video preview
1108
+ function displayVideoPreview(videoUrl) {
1109
+ const videoPreview = document.getElementById('videoPreview');
1110
+ videoPreview.innerHTML = '';
1111
+
1112
+ const video = document.createElement('video');
1113
+ video.src = videoUrl;
1114
+ video.controls = true;
1115
+ video.style.width = '100%';
1116
+ video.style.borderRadius = '12px';
1117
+
1118
+ const downloadBtn = document.createElement('button');
1119
+ downloadBtn.className = 'miniBtn';
1120
+ downloadBtn.textContent = 'Download Video';
1121
+ downloadBtn.style.marginTop = '10px';
1122
+ downloadBtn.addEventListener('click', () => {
1123
+ downloadFile(videoUrl, 'generated-video.mp4');
1124
+ });
1125
+
1126
+ videoPreview.appendChild(video);
1127
+ videoPreview.appendChild(downloadBtn);
1128
+
1129
+ document.getElementById('videoOut').textContent = 'Video ready for download!';
1130
+ }
1131
+
1132
+ // Handle song file upload
1133
+ function handleSongFileUpload() {
1134
+ const file = songFileInput.files[0];
1135
+ if (!file) return;
1136
+
1137
+ if (!['audio/mp3', 'audio/wav', 'audio/m4a'].includes(file.type)) {
1138
+ showStatus(warningsDisplay, 'Please upload a valid audio file (MP3, WAV, or M4A)', 'error');
1139
+ return;
1140
+ }
1141
+
1142
+ songFile = file;
1143
+ songFileName.textContent = file.name;
1144
+ songFileInfo.style.display = 'flex';
1145
+ showStatus(warningsDisplay, 'Song file uploaded successfully!', 'success');
1146
+ }
1147
+
1148
+ // Remove song file
1149
+ function removeSongFile() {
1150
+ songFile = null;
1151
+ songFileInput.value = '';
1152
+ songFileInfo.style.display = 'none';
1153
+ showStatus(warningsDisplay, 'Song file removed!', 'success');
1154
+ }
1155
+
1156
+ // Handle clip files upload
1157
+ function handleClipFilesUpload() {
1158
+ const files = Array.from(clipFilesInput.files);
1159
+ if (files.length === 0) return;
1160
+
1161
+ const invalidFiles = files.filter(file => file.type !== 'video/mp4');
1162
+ if (invalidFiles.length > 0) {
1163
+ showStatus(warningsDisplay, 'Only MP4 files are allowed for video clips!', 'error');
1164
+ return;
1165
+ }
1166
+
1167
+ videoClips = [...videoClips, ...files];
1168
+ updateClipQueue();
1169
+ showStatus(warnings