flen-crypto commited on
Commit
ba9e21b
·
verified ·
1 Parent(s): 87f079d

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1142 -1133
index.html CHANGED
@@ -1,1204 +1,1213 @@
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 Video Creator</title>
7
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
- <style>
9
- :root {
10
- --primary: #4a6bff;
11
- --primary-dark: #3a5bef;
12
- --secondary: #f8f9fa;
13
- --text: #333;
14
- --text-light: #666;
15
- --white: #ffffff;
16
- --error: #ff4444;
17
- --success: #28a745;
18
- --border: #e0e0e0;
19
- --shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
20
- --radius: 8px;
21
- }
22
-
23
- * {
24
- margin: 0;
25
- padding: 0;
26
- box-sizing: border-box;
27
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
28
- }
29
-
30
- body {
31
- background-color: #f5f7ff;
32
- color: var(--text);
33
- line-height: 1.6;
34
- padding: 20px;
35
- }
36
-
37
- .container {
38
- max-width: 1200px;
39
- margin: 0 auto;
40
- background: white;
41
- border-radius: var(--radius);
42
- box-shadow: var(--shadow);
43
- overflow: hidden;
44
- }
45
-
46
- header {
47
- background: linear-gradient(135deg, var(--primary), var(--primary-dark));
48
- color: white;
49
- padding: 20px 30px;
50
- display: flex;
51
- justify-content: space-between;
52
- align-items: center;
53
- }
54
-
55
- .logo {
56
- font-size: 24px;
57
- font-weight: bold;
58
- display: flex;
59
- align-items: center;
60
- gap: 10px;
61
- }
62
-
63
- .logo i {
64
- font-size: 30px;
65
- }
66
-
67
- .header-actions {
68
- display: flex;
69
- gap: 15px;
70
- align-items: center;
71
- }
72
-
73
- .btn {
74
- padding: 8px 16px;
75
- border: none;
76
- border-radius: var(--radius);
77
- cursor: pointer;
78
- font-weight: 500;
79
- transition: all 0.3s ease;
80
- display: inline-flex;
81
- align-items: center;
82
- gap: 8px;
83
- }
84
-
85
- .btn-primary {
86
- background-color: var(--primary);
87
- color: white;
88
- }
89
-
90
- .btn-primary:hover {
91
- background-color: var(--primary-dark);
92
- transform: translateY(-2px);
93
- }
94
-
95
- .btn-secondary {
96
- background-color: var(--secondary);
97
- color: var(--text);
98
- }
99
-
100
- .btn-secondary:hover {
101
- background-color: #e9ecef;
102
- }
103
-
104
- .btn-small {
105
- padding: 4px 12px;
106
- font-size: 14px;
107
- }
108
-
109
- .main-content {
110
- display: grid;
111
- grid-template-columns: 1fr 1fr;
112
- gap: 30px;
113
- padding: 30px;
114
- }
115
-
116
- .section {
117
- background: white;
118
- border-radius: var(--radius);
119
- padding: 20px;
120
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
121
- }
122
-
123
- .section-title {
124
- font-size: 18px;
125
- font-weight: 600;
126
- margin-bottom: 15px;
127
- color: var(--primary);
128
- display: flex;
129
- align-items: center;
130
- gap: 8px;
131
- }
132
 
133
- .form-group {
134
- margin-bottom: 15px;
135
- }
136
-
137
- .form-group label {
138
- display: block;
139
- margin-bottom: 5px;
140
- font-weight: 500;
141
- color: var(--text-light);
142
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
 
144
- .form-control {
145
- width: 100%;
146
- padding: 10px;
147
- border: 1px solid var(--border);
148
- border-radius: var(--radius);
149
- font-size: 14px;
150
- transition: border-color 0.3s;
151
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
- .form-control:focus {
154
- outline: none;
155
- border-color: var(--primary);
156
- box-shadow: 0 0 0 2px rgba(74, 107, 255, 0.2);
157
- }
158
 
159
- textarea.form-control {
160
- min-height: 100px;
161
- resize: vertical;
162
- }
163
 
164
- .checkbox-group {
165
- display: flex;
166
- align-items: center;
167
- gap: 10px;
168
- }
169
 
170
- .checkbox-group input {
171
- width: auto;
172
- }
 
173
 
174
- .output-section {
175
- background: #f8f9fa;
176
- border-radius: var(--radius);
177
- padding: 15px;
178
- min-height: 100px;
179
- font-family: monospace;
180
- font-size: 13px;
181
- white-space: pre-wrap;
182
- word-wrap: break-word;
183
- position: relative;
184
- }
185
 
186
- .output-actions {
187
- display: flex;
188
- gap: 10px;
189
- margin-top: 10px;
190
- }
191
 
192
- .thumb-grid {
193
- display: grid;
194
- grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
195
- gap: 10px;
196
- margin-top: 10px;
197
- }
198
 
199
- .thumb {
200
- width: 100%;
201
- height: 100px;
202
- object-fit: cover;
203
- border-radius: var(--radius);
204
- cursor: pointer;
205
- transition: transform 0.3s;
206
- }
207
 
208
- .thumb:hover {
209
- transform: scale(1.05);
210
- }
 
211
 
212
- .file-upload {
213
- border: 2px dashed var(--border);
214
- border-radius: var(--radius);
215
- padding: 20px;
216
- text-align: center;
217
- cursor: pointer;
218
- transition: all 0.3s;
219
- }
220
 
221
- .file-upload:hover {
222
- border-color: var(--primary);
223
- background-color: rgba(74, 107, 255, 0.05);
224
- }
225
 
226
- .file-info {
227
- display: flex;
228
- align-items: center;
229
- gap: 10px;
230
- margin-top: 10px;
231
- padding: 10px;
232
- background: #f8f9fa;
233
- border-radius: var(--radius);
234
- }
235
 
236
- .clip-item {
237
- display: flex;
238
- justify-content: space-between;
239
- align-items: center;
240
- padding: 8px;
241
- background: white;
242
- border-radius: var(--radius);
243
- margin-bottom: 5px;
244
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
245
- }
246
 
247
- .modal {
248
- display: none;
249
- position: fixed;
250
- top: 0;
251
- left: 0;
252
- width: 100%;
253
- height: 100%;
254
- background: rgba(0, 0, 0, 0.5);
255
- z-index: 1000;
256
- justify-content: center;
257
- align-items: center;
258
- }
259
 
260
- .modal-content {
261
- background: white;
262
- padding: 30px;
263
- border-radius: var(--radius);
264
- max-width: 500px;
265
- width: 90%;
266
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
267
- }
268
 
269
- .modal-header {
270
- display: flex;
271
- justify-content: space-between;
272
- align-items: center;
273
- margin-bottom: 20px;
274
- }
275
 
276
- .modal-title {
277
- font-size: 20px;
278
- font-weight: 600;
279
- color: var(--primary);
280
- }
281
 
282
- .close-btn {
283
- background: none;
284
- border: none;
285
- font-size: 24px;
286
- cursor: pointer;
287
- color: var(--text-light);
288
- }
289
 
290
- .overlay {
291
- display: none;
292
- position: fixed;
293
- top: 0;
294
- left: 0;
295
- width: 100%;
296
- height: 100%;
297
- background: rgba(0, 0, 0, 0.7);
298
- z-index: 999;
299
- justify-content: center;
300
- align-items: center;
301
- color: white;
302
- font-size: 18px;
303
- }
304
 
305
- .progress-bar {
306
- width: 200px;
307
- height: 4px;
308
- background: rgba(255, 255, 255, 0.3);
309
- border-radius: 2px;
310
- margin-top: 20px;
311
- overflow: hidden;
312
- }
313
 
314
- .progress {
315
- height: 100%;
316
- background: white;
317
- width: 0%;
318
- transition: width 0.3s;
319
- }
320
 
321
- .anycoder-link {
322
- position: absolute;
323
- top: 10px;
324
- right: 10px;
325
- color: white;
326
- text-decoration: none;
327
- font-size: 12px;
328
- opacity: 0.8;
329
- }
330
 
331
- .anycoder-link:hover {
332
- opacity: 1;
333
- text-decoration: underline;
334
- }
 
335
 
336
- @media (max-width: 768px) {
337
- .main-content {
338
- grid-template-columns: 1fr;
339
- padding: 15px;
340
- }
341
-
342
- header {
343
- flex-direction: column;
344
- gap: 15px;
345
- text-align: center;
346
- }
347
-
348
- .header-actions {
349
- flex-wrap: wrap;
350
- justify-content: center;
351
- }
352
- }
353
- </style>
354
- </head>
355
- <body>
356
- <div class="container">
357
- <header>
358
- <div class="logo">
359
- <i class="fas fa-music"></i>
360
- <span>Music Video Creator</span>
361
- </div>
362
- <div class="header-actions">
363
- <button class="btn btn-secondary btn-small" onclick="showApiKeyModal()">
364
- <i class="fas fa-key"></i> API Key
365
- </button>
366
- <button class="btn btn-secondary btn-small" onclick="saveDraft()">
367
- <i class="fas fa-save"></i> Save Draft
368
- </button>
369
- <button class="btn btn-secondary btn-small" onclick="loadDraft()">
370
- <i class="fas fa-folder-open"></i> Load Draft
371
- </button>
372
- <button class="btn btn-secondary btn-small" onclick="resetForm()">
373
- <i class="fas fa-undo"></i> Reset
374
- </button>
375
- </div>
376
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank">
377
- Built with anycoder
378
- </a>
379
- </header>
380
-
381
- <div class="main-content">
382
- <div class="section">
383
- <div class="section-title">
384
- <i class="fas fa-info-circle"></i>
385
- <span>Song Information</span>
386
- </div>
387
-
388
- <div class="form-group">
389
- <label for="songTitle">Song Title</label>
390
- <input type="text" id="songTitle" class="form-control" placeholder="Enter song title">
391
- </div>
392
-
393
- <div class="form-group">
394
- <label for="occasion">Occasion</label>
395
- <input type="text" id="occasion" class="form-control" placeholder="e.g., Birthday, Anniversary">
396
- </div>
397
-
398
- <div class="form-group">
399
- <label for="buyerName">Buyer Name</label>
400
- <input type="text" id="buyerName" class="form-control" placeholder="Your name">
401
- </div>
402
-
403
- <div class="form-group">
404
- <label for="recipientName">Recipient Name</label>
405
- <input type="text" id="recipientName" class="form-control" placeholder="Who is this for?">
406
- </div>
407
-
408
- <div class="form-group">
409
- <label for="relationship">Relationship</label>
410
- <input type="text" id="relationship" class="form-control" placeholder="e.g., Mother, Friend">
411
- </div>
412
-
413
- <div class="form-group">
414
- <label for="tone">Tone</label>
415
- <input type="text" id="tone" class="form-control" placeholder="e.g., Happy, Romantic">
416
- </div>
417
- </div>
418
-
419
- <div class="section">
420
- <div class="section-title">
421
- <i class="fas fa-music"></i>
422
- <span>Music Details</span>
423
- </div>
424
-
425
- <div class="form-group">
426
- <label for="mainGenre">Main Genre</label>
427
- <input type="text" id="mainGenre" class="form-control" placeholder="e.g., Pop, Rock">
428
- </div>
429
-
430
- <div class="form-group">
431
- <label for="secondaryGenres">Secondary Genres</label>
432
- <input type="text" id="secondaryGenres" class="form-control" placeholder="Additional genres">
433
- </div>
434
-
435
- <div class="form-group">
436
- <label for="tempo">Tempo</label>
437
- <input type="text" id="tempo" class="form-control" placeholder="e.g., Fast, Slow">
438
- </div>
439
-
440
- <div class="form-group">
441
- <label for="vocalStyle">Vocal Style</label>
442
- <input type="text" id="vocalStyle" class="form-control" placeholder="e.g., Male, Female">
443
- </div>
444
-
445
- <div class="form-group">
446
- <label for="instrumentationNotes">Instrumentation Notes</label>
447
- <textarea id="instrumentationNotes" class="form-control" placeholder="Any specific instruments?"></textarea>
448
- </div>
449
- </div>
450
-
451
- <div class="section">
452
- <div class="section-title">
453
- <i class="fas fa-story"></i>
454
- <span>Content Details</span>
455
- </div>
456
-
457
- <div class="form-group">
458
- <label for="keyFacts">Key Facts (one per line)</label>
459
- <textarea id="keyFacts" class="form-control" placeholder="Important facts about the recipient"></textarea>
460
- </div>
461
-
462
- <div class="form-group">
463
- <label for="stories">Stories (one per line)</label>
464
- <textarea id="stories" class="form-control" placeholder="Memorable stories to include"></textarea>
465
- </div>
466
-
467
- <div class="form-group">
468
- <label for="mainMessage">Main Message</label>
469
- <textarea id="mainMessage" class="form-control" placeholder="The core message of the song"></textarea>
470
- </div>
471
-
472
- <div class="form-group">
473
- <label for="namesPlaces">Names/Places (one per line)</label>
474
- <textarea id="namesPlaces" class="form-control" placeholder="Important names and places"></textarea>
475
- </div>
476
- </div>
477
-
478
- <div class="section">
479
- <div class="section-title">
480
- <i class="fas fa-palette"></i>
481
- <span>Visual Style</span>
482
- </div>
483
-
484
- <div class="form-group">
485
- <label for="visualStyle">Visual Style</label>
486
- <input type="text" id="visualStyle" class="form-control" placeholder="e.g., Cartoon, Realistic">
487
- </div>
488
-
489
- <div class="form-group">
490
- <label for="visualMood">Visual Mood</label>
491
- <input type="text" id="visualMood" class="form-control" placeholder="e.g., Bright, Moody">
492
- </div>
493
-
494
- <div class="form-group">
495
- <label for="visualPlacesThemes">Places/Themes (one per line)</label>
496
- <textarea id="visualPlacesThemes" class="form-control" placeholder="Visual themes to include"></textarea>
497
- </div>
498
-
499
- <div class="form-group">
500
- <label for="visualText">Visual Text</label>
501
- <input type="text" id="visualText" class="form-control" placeholder="Any text to include in visuals">
502
- </div>
503
- </div>
504
-
505
- <div class="section">
506
- <div class="section-title">
507
- <i class="fas fa-cog"></i>
508
- <span>Package Options</span>
509
- </div>
510
-
511
- <div class="form-group">
512
- <label for="languageLevel">Language Level</label>
513
- <select id="languageLevel" class="form-control">
514
  <option value="no limit">No Limit</option>
515
  <option value="simple">Simple</option>
516
  <option value="advanced">Advanced</option>
517
  </select>
518
- </div>
519
 
520
- <div class="form-group">
521
- <label for="packageLevel">Package Level</label>
522
- <select id="packageLevel" class="form-control">
523
  <option value="basic">Basic</option>
524
  <option value="premium">Premium</option>
525
  <option value="deluxe">Deluxe</option>
526
  </select>
527
- </div>
528
-
529
- <div class="form-group checkbox-group">
530
- <input type="checkbox" id="wantsArtwork">
531
- <label for="wantsArtwork">Include Artwork</label>
532
- </div>
533
-
534
- <div class="form-group checkbox-group">
535
- <input type="checkbox" id="wantsVideo">
536
- <label for="wantsVideo">Include Video</label>
537
- </div>
538
- </div>
539
-
540
- <div class="section">
541
- <div class="section-title">
542
- <i class="fas fa-robot"></i>
543
- <span>Generate Suno Block</span>
544
- </div>
545
-
546
- <button class="btn btn-primary" onclick="generateSunoBlock()">
547
  <i class="fas fa-magic"></i> Generate Suno Block
548
  </button>
549
 
550
- <div class="output-section" id="sunoBlockOut">
551
- Suno block will appear here...
552
- </div>
553
 
554
- <div class="output-actions">
555
- <button class="btn btn-secondary btn-small" onclick="copySunoBlock()">
556
  <i class="fas fa-copy"></i> Copy
557
  </button>
558
- <button class="btn btn-secondary btn-small" onclick="populateBuilders()">
559
  <i class="fas fa-arrow-right"></i> Populate Builders
560
  </button>
561
- </div>
562
-
563
- <div id="warningsDisplay" style="color: var(--error); margin-top: 10px; font-size: 13px;"></div>
564
- </div>
565
-
566
- <div class="section">
567
- <div class="section-title">
568
- <i class="fas fa-code"></i>
569
- <span>Builder Prompts</span>
570
- </div>
571
-
572
- <div class="form-group">
573
- <label for="songPrompt">Song Prompt</label>
574
- <textarea id="songPrompt" class="form-control" placeholder="Generated song prompt will appear here"></textarea>
575
- </div>
576
-
577
- <div class="form-group">
578
- <label for="artPrompt">Artwork Prompt</label>
579
- <textarea id="artPrompt" class="form-control" placeholder="Generated artwork prompt will appear here"></textarea>
580
- <button class="btn btn-primary btn-small" onclick="generateImage()" style="margin-top: 10px;">
581
- <i class="fas fa-image"></i> Generate Image
582
- </button>
583
- </div>
584
-
585
- <div class="form-group">
586
- <label for="videoPrompt">Video Prompt</label>
587
- <textarea id="videoPrompt" class="form-control" placeholder="Generated video prompt will appear here"></textarea>
588
- <button class="btn btn-primary btn-small" onclick="generateVideo()" style="margin-top: 10px;">
589
- <i class="fas fa-video"></i> Generate Video
590
- </button>
591
- </div>
592
- </div>
593
-
594
- <div class="section">
595
- <div class="section-title">
596
- <i class="fas fa-images"></i>
597
- <span>Generated Images</span>
598
- </div>
599
-
600
- <div id="imgGrid" class="thumb-grid">
601
- <!-- Images will appear here -->
602
- </div>
603
- </div>
604
-
605
- <div class="section">
606
- <div class="section-title">
607
- <i class="fas fa-file-video"></i>
608
- <span>Video Preview</span>
609
- </div>
610
-
611
- <div id="videoPreview" style="margin-bottom: 10px;">
612
- <!-- Video will appear here -->
613
- </div>
614
-
615
- <div id="videoOut" style="font-size: 13px; color: var(--text-light);">
616
- Video status...
617
- </div>
618
- </div>
619
-
620
- <div class="section">
621
- <div class="section-title">
622
- <i class="fas fa-file-audio"></i>
623
- <span>Song File Upload</span>
624
- </div>
625
-
626
- <div class="file-upload" onclick="document.getElementById('songFileInput').click()">
627
- <i class="fas fa-cloud-upload-alt" style="font-size: 40px; color: var(--primary); margin-bottom: 10px;"></i>
628
- <p>Click to upload song file</p>
629
- <input type="file" id="songFileInput" style="display: none;" accept="audio/*" onchange="handleSongFileUpload()">
630
- </div>
631
-
632
- <div id="songFileInfo" style="display: none;">
633
- <span id="songFileName"></span>
634
- <button class="btn btn-secondary btn-small" onclick="removeSongFile()">
635
- <i class="fas fa-trash"></i> Remove
636
- </button>
637
- </div>
638
-
639
- <div id="uploadOut" style="margin-top: 10px; font-size: 13px; color: var(--text-light);">
640
- Upload status...
641
- </div>
642
- </div>
643
-
644
- <div class="section">
645
- <div class="section-title">
646
- <i class="fas fa-film"></i>
647
- <span>Video Clips</span>
648
- </div>
649
-
650
- <div class="file-upload" onclick="document.getElementById('clipFilesInput').click()">
651
- <i class="fas fa-cloud-upload-alt" style="font-size: 40px; color: var(--primary); margin-bottom: 10px;"></i>
652
- <p>Click to upload video clips</p>
653
- <input type="file" id="clipFilesInput" style="display: none;" accept="video/*" multiple onchange="handleClipFilesUpload()">
654
- </div>
655
-
656
- <div id="clipQueueWrap" style="margin-top: 15px;">
657
- <div class="small" style="margin-top:6px">No clips queued.</div>
658
- </div>
659
-
660
- <div style="margin-top: 15px; display: flex; gap: 10px;">
661
- <button class="btn btn-primary btn-small" onclick="stitchClips()">
662
- <i class="fas fa-link"></i> Stitch Clips
663
- </button>
664
- <button class="btn btn-secondary btn-small" onclick="clearClips()">
665
- <i class="fas fa-trash"></i> Clear All
666
- </button>
667
- </div>
668
- </div>
669
-
670
- <div class="section">
671
- <div class="section-title">
672
- <i class="fas fa-tools"></i>
673
- <span>Advanced Tools</span>
674
- </div>
675
-
676
- <button class="btn btn-secondary" onclick="randomizeForm()" style="margin-bottom: 10px;">
677
- <i class="fas fa-random"></i> Randomize Form
678
- </button>
679
 
680
- <button class="btn btn-secondary" onclick="runDiagnostics()">
681
- <i class="fas fa-stethoscope"></i> Run Diagnostics
682
- </button>
683
 
684
- <div id="diagOut" style="display: none; margin-top: 15px; font-family: monospace; font-size: 12px; background: #f8f9fa; padding: 10px; border-radius: var(--radius);"></div>
685
- </div>
 
 
686
  </div>
687
- </div>
688
 
689
- <!-- Modal for API Key -->
690
- <div id="apiKeyModal" class="modal">
691
- <div class="modal-content">
692
- <div class="modal-header">
693
- <h2 class="modal-title">API Key Settings</h2>
694
- <button class="close-btn" onclick="closeApiKeyModal()">&times;</button>
695
- </div>
696
- <div class="form-group">
697
- <label for="apiKeyInput">OpenAI API Key</label>
698
- <input type="password" id="apiKeyInput" class="form-control" placeholder="Enter your OpenAI API key">
699
- </div>
700
- <button class="btn btn-primary" onclick="saveApiKey()">
701
- <i class="fas fa-save"></i> Save API Key
702
- </button>
703
  </div>
704
- </div>
705
 
706
- <!-- General Modal -->
707
- <div id="generalModal" class="modal">
708
- <div class="modal-content">
709
- <div class="modal-header">
710
- <h2 class="modal-title">Notification</h2>
711
- <button class="close-btn" onclick="closeModal()">&times;</button>
712
- </div>
713
- <div id="modalContent"></div>
714
  </div>
715
- </div>
716
 
717
- <!-- Overlay -->
718
- <div id="overlay" class="overlay">
719
- <div>
720
- <div id="overlayText">Processing...</div>
721
- <div class="progress-bar">
722
- <div class="progress" id="progressBar"></div>
723
- </div>
724
  </div>
725
- </div>
726
-
727
- <script>
728
- // Configuration
729
- const API_BASE = 'https://api.example.com'; // Replace with your actual API base
730
- let apiKey = localStorage.getItem('openaiApiKey') || '';
731
- let songFile = null;
732
- let videoClips = [];
733
-
734
- // DOM Elements
735
- const sunoBlockOut = document.getElementById('sunoBlockOut');
736
- const warningsDisplay = document.getElementById('warningsDisplay');
737
- const imgGrid = document.getElementById('imgGrid');
738
- const videoPreview = document.getElementById('videoPreview');
739
- const videoOut = document.getElementById('videoOut');
740
- const songFileInput = document.getElementById('songFileInput');
741
- const songFileName = document.getElementById('songFileName');
742
- const songFileInfo = document.getElementById('songFileInfo');
743
- const clipFilesInput = document.getElementById('clipFilesInput');
744
- const clipQueueWrap = document.getElementById('clipQueueWrap');
745
- const diagOut = document.getElementById('diagOut');
746
- const uploadOut = document.getElementById('uploadOut');
747
-
748
- // Initialize
749
- function init() {
750
- // Check if API key exists
751
- if (apiKey) {
752
- document.getElementById('apiKeyInput').value = '••••••••••••••••';
753
- }
754
-
755
- // Set up event listeners
756
- document.addEventListener('keydown', function(e) {
757
- if (e.key === 'Escape') {
758
- closeModal();
759
- closeApiKeyModal();
760
- }
761
- });
762
- }
763
-
764
- // Modal functions
765
- function showModal(content) {
766
- const modal = document.getElementById('generalModal');
767
- const modalContent = document.getElementById('modalContent');
768
- modalContent.innerHTML = content;
769
- modal.style.display = 'flex';
770
- }
771
-
772
- function closeModal() {
773
- document.getElementById('generalModal').style.display = 'none';
774
- }
775
-
776
- function showApiKeyModal() {
777
- document.getElementById('apiKeyModal').style.display = 'flex';
778
- }
779
-
780
- function closeApiKeyModal() {
781
- document.getElementById('apiKeyModal').style.display = 'none';
782
- }
783
-
784
- function saveApiKey() {
785
- const keyInput = document.getElementById('apiKeyInput');
786
- apiKey = keyInput.value.trim();
787
-
788
- if (apiKey) {
789
- localStorage.setItem('openaiApiKey', apiKey);
790
- keyInput.value = '••••••••••••••••';
791
- showModal('<p class="small">API key saved successfully ✅</p>');
792
- closeApiKeyModal();
793
- } else {
794
- showModal('<p class="small">Please enter a valid API key</p>');
795
- }
796
- }
797
-
798
- // Overlay functions
799
- function showOverlay(text, progress = 0) {
800
- const overlay = document.getElementById('overlay');
801
- const overlayText = document.getElementById('overlayText');
802
- const progressBar = document.getElementById('progressBar');
803
-
804
- overlayText.textContent = text;
805
- progressBar.style.width = `${progress}%`;
806
- overlay.style.display = 'flex';
807
- }
808
-
809
- function hideOverlay() {
810
- document.getElementById('overlay').style.display = 'none';
811
- }
812
 
813
- function updateOverlayProgress(progress) {
814
- document.getElementById('progressBar').style.width = `${progress}%`;
815
- }
816
-
817
- // Utility functions
818
- function copyToClipboard(text) {
819
- navigator.clipboard.writeText(text).then(() => {
820
- showModal('<p class="small">Copied to clipboard ✅</p>');
821
- }).catch(err => {
822
- showModal('<p class="small">Failed to copy: ' + err.message + '</p>');
823
- });
824
- }
825
-
826
- function splitLines(text) {
827
- return text.split('\n')
828
- .map(line => line.trim())
829
- .filter(Boolean);
830
- }
831
-
832
- function getFormData() {
833
- return {
834
- songTitle: document.getElementById('songTitle').value.trim(),
835
- occasion: document.getElementById('occasion').value.trim(),
836
- buyerName: document.getElementById('buyerName').value.trim(),
837
- recipientName: document.getElementById('recipientName').value.trim(),
838
- relationship: document.getElementById('relationship').value.trim(),
839
- tone: document.getElementById('tone').value.trim(),
840
- mainGenre: document.getElementById('mainGenre').value.trim(),
841
- secondaryGenres: document.getElementById('secondaryGenres').value.trim(),
842
- tempo: document.getElementById('tempo').value.trim(),
843
- vocalStyle: document.getElementById('vocalStyle').value.trim(),
844
- instrumentationNotes: document.getElementById('instrumentationNotes').value.trim(),
845
- languageLevel: document.getElementById('languageLevel').value,
846
- packageLevel: document.getElementById('packageLevel').value,
847
- wantsArtwork: document.getElementById('wantsArtwork').checked,
848
- wantsVideo: document.getElementById('wantsVideo').checked,
849
- keyFacts: splitLines(document.getElementById('keyFacts').value),
850
- stories: splitLines(document.getElementById('stories').value),
851
- mainMessage: document.getElementById('mainMessage').value.trim(),
852
- namesPlaces: splitLines(document.getElementById('namesPlaces').value),
853
- visualStyle: document.getElementById('visualStyle').value.trim(),
854
- visualMood: document.getElementById('visualMood').value.trim(),
855
- visualPlacesThemes: splitLines(document.getElementById('visualPlacesThemes').value),
856
- visualText: document.getElementById('visualText').value.trim()
857
- };
858
- }
859
-
860
- // Fixed image generation function with better error handling and timeout
861
- async function generateImage() {
862
- const prompt = document.getElementById('artPrompt').value.trim();
863
- if (!prompt) {
864
- return showModal('<p class="small">Please populate the artwork prompt first.</p>');
865
- }
866
-
867
- try {
868
- showOverlay('Generating image...', 10);
869
-
870
- // Create a controller for aborting the request
871
- const controller = new AbortController();
872
- const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout
873
-
874
- const response = await fetch(`${API_BASE}/generate-image`, {
875
- method: 'POST',
876
- headers: {
877
- 'Content-Type': 'application/json',
878
- 'X-OpenAI-API-Key': apiKey
879
- },
880
- body: JSON.stringify({ prompt }),
881
- signal: controller.signal
882
- });
883
-
884
- clearTimeout(timeoutId);
885
-
886
- if (!response.ok) {
887
- const errorData = await response.json().catch(() => ({}));
888
- throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
889
- }
890
-
891
- const data = await response.json();
892
- hideOverlay();
893
-
894
- if (data.imageUrls && data.imageUrls.length > 0) {
895
- imgGrid.innerHTML = '';
896
- data.imageUrls.forEach(url => {
897
- const img = document.createElement('img');
898
- img.src = url;
899
- img.className = 'thumb';
900
- imgGrid.appendChild(img);
901
- });
902
- showModal('<p class="small">Image generated successfully ✅</p>');
903
- } else {
904
- throw new Error('No image URLs returned from the API');
905
- }
906
- } catch (error) {
907
- hideOverlay();
908
- console.error('Image generation error:', error);
909
-
910
- let errorMessage = 'Failed to generate image';
911
- if (error.name === 'AbortError') {
912
- errorMessage = 'Image generation timed out. Please try again.';
913
- } else if (error.message.includes('socket hang up')) {
914
- errorMessage = 'Connection to image generation service was interrupted. Please check your internet connection and try again.';
915
- } else if (error.message) {
916
- errorMessage = error.message;
917
- }
918
-
919
- showModal(`<p class="small">Error: ${errorMessage}</p>`);
920
- }
921
- }
922
-
923
- // Other functions remain the same as in your original code
924
- async function generateSunoBlock() {
925
- if (!apiKey) {
926
- return showModal('<p class="small">Please save your OpenAI API key first.</p>');
927
- }
928
-
929
- const formData = getFormData();
930
- if (!formData.buyerName || !formData.recipientName) {
931
- return showModal('<p class="small">Please fill in at least buyer and recipient names.</p>');
932
- }
933
-
934
- try {
935
- showOverlay('Generating Suno block...', 30);
936
- const response = await fetch(`${API_BASE}/generate-suno-block`, {
937
- method: 'POST',
938
- headers: {
939
- 'Content-Type': 'application/json',
940
- 'X-OpenAI-API-Key': apiKey
941
- },
942
- body: JSON.stringify(formData)
943
- });
944
-
945
- const data = await safeJson(response);
946
- hideOverlay();
947
-
948
- if (response.ok) {
949
- sunoBlockOut.textContent = data.sunoBlock;
950
- warningsDisplay.textContent = data.warnings || '';
951
- showModal('<p class="small">Suno block generated successfully ✅</p>');
952
- } else {
953
- showModal(`<p class="small">Error: ${data.error || 'Failed to generate Suno block'}</p>`);
954
- }
955
- } catch (error) {
956
- hideOverlay();
957
- showModal(`<p class="small">Error: ${error.message}</p>`);
958
- console.error('Generate error:', error);
959
- }
960
- }
961
 
962
- async function populateBuilders() {
963
- if (!sunoBlockOut.textContent.trim()) {
964
- return showModal('<p class="small">Generate a Suno block first.</p>');
965
- }
966
-
967
- try {
968
- showOverlay('Populating builders...', 30);
969
- const response = await fetch(`${API_BASE}/populate-builders`, {
970
- method: 'POST',
971
- headers: {
972
- 'Content-Type': 'application/json',
973
- 'X-OpenAI-API-Key': apiKey
974
- },
975
- body: JSON.stringify({
976
- sunoBlock: sunoBlockOut.textContent.trim(),
977
- formData: getFormData()
978
- })
979
- });
980
-
981
- const data = await safeJson(response);
982
- hideOverlay();
983
-
984
- if (response.ok) {
985
- document.getElementById('songPrompt').value = data.songPrompt || '';
986
- document.getElementById('artPrompt').value = data.artPrompt || '';
987
- document.getElementById('videoPrompt').value = data.videoPrompt || '';
988
- showModal('<p class="small">Builders populated successfully ✅</p>');
989
- } else {
990
- showModal(`<p class="small">Error: ${data.error || 'Failed to populate builders'}</p>`);
991
- }
992
- } catch (error) {
993
- hideOverlay();
994
- showModal(`<p class="small">Error: ${error.message}</p>`);
995
- console.error('Populate error:', error);
996
- }
997
- }
998
 
999
- async function generateVideo() {
1000
- const prompt = document.getElementById('videoPrompt').value.trim();
1001
- if (!prompt) {
1002
- return showModal('<p class="small">Please populate the video prompt first.</p>');
1003
- }
1004
-
1005
- try {
1006
- showOverlay('Generating video...', 30);
1007
- const response = await fetch(`${API_BASE}/generate-video`, {
1008
- method: 'POST',
1009
- headers: {
1010
- 'Content-Type': 'application/json',
1011
- 'X-OpenAI-API-Key': apiKey
1012
- },
1013
- body: JSON.stringify({ prompt })
1014
- });
1015
-
1016
- const data = await safeJson(response);
1017
- hideOverlay();
1018
-
1019
- if (response.ok) {
1020
- videoOut.textContent = 'Video generated successfully';
1021
- videoPreview.innerHTML = `
1022
- <video controls class="thumb" style="width:100%">
1023
- <source src="${data.videoUrl}" type="video/mp4">
1024
- Your browser does not support the video tag.
1025
- </video>
1026
- `;
1027
- showModal('<p class="small">Video generated successfully ✅</p>');
1028
- } else {
1029
- showModal(`<p class="small">Error: ${data.error || 'Failed to generate video'}</p>`);
1030
- }
1031
- } catch (error) {
1032
- hideOverlay();
1033
- showModal(`<p class="small">Error: ${error.message}</p>`);
1034
- console.error('Video generation error:', error);
1035
- }
1036
- }
1037
 
1038
- // File handling functions
1039
- function handleSongFileUpload() {
1040
- const file = songFileInput.files[0];
1041
- if (!file) return;
1042
 
1043
- songFile = file;
1044
- songFileName.textContent = file.name;
1045
- songFileInfo.style.display = 'flex';
1046
- uploadOut.textContent = 'Song file ready for upload';
1047
- }
1048
 
1049
- function removeSongFile() {
1050
- songFile = null;
1051
- songFileInput.value = '';
1052
- songFileInfo.style.display = 'none';
1053
- uploadOut.textContent = 'Upload status...';
1054
- }
1055
 
1056
- function handleClipFilesUpload() {
1057
- const files = Array.from(clipFilesInput.files);
1058
- if (!files.length) return;
 
 
1059
 
1060
- videoClips = files.map((file, i) => ({
1061
- id: Date.now() + i,
1062
- file,
1063
- name: file.name
1064
- }));
 
1065
 
1066
- renderClipQueue();
1067
- }
 
 
1068
 
1069
- function renderClipQueue() {
1070
- if (!videoClips.length) {
1071
- clipQueueWrap.innerHTML = '<div class="small" style="margin-top:6px">No clips queued.</div>';
1072
- return;
1073
- }
1074
-
1075
- clipQueueWrap.innerHTML = '';
1076
- videoClips.forEach((clip, idx) => {
1077
- const item = document.createElement('div');
1078
- item.className = 'clipItem';
1079
- item.innerHTML = `
1080
- <span class="clipName">${clip.name}</span>
1081
- <div class="clipBtns">
1082
- <button class="miniBtn" data-id="${clip.id}" data-action="move-up">↑</button>
1083
- <button class="miniBtn" data-id="${clip.id}" data-action="move-down">↓</button>
1084
- <button class="miniBtn" data-id="${clip.id}" data-action="remove">×</button>
1085
- </div>
1086
- `;
1087
- clipQueueWrap.appendChild(item);
1088
- });
1089
-
1090
- clipQueueWrap.querySelectorAll('.clipBtns button').forEach(btn => {
1091
- btn.addEventListener('click', (e) => {
1092
- const id = parseInt(e.target.getAttribute('data-id'));
1093
- const action = e.target.getAttribute('data-action');
1094
- handleClipAction(id, action);
1095
- });
1096
- });
1097
- }
1098
 
1099
- function handleClipAction(id, action) {
1100
- const idx = videoClips.findIndex(c => c.id === id);
1101
- if (idx === -1) return;
1102
-
1103
- if (action === 'remove') {
1104
- videoClips.splice(idx, 1);
1105
- } else if (action === 'move-up' && idx > 0) {
1106
- [videoClips[idx], videoClips[idx - 1]] = [videoClips[idx - 1], videoClips[idx]];
1107
- } else if (action === 'move-down' && idx < videoClips.length - 1) {
1108
- [videoClips[idx], videoClips[idx + 1]] = [videoClips[idx + 1], videoClips[idx]];
1109
- }
1110
- renderClipQueue();
1111
- }
1112
 
1113
- async function stitchClips() {
1114
- if (!songFile) {
1115
- return showModal('<p class="small">Please upload a song file first.</p>');
1116
- }
1117
-
1118
- if (!videoClips.length) {
1119
- return showModal('<p class="small">Please upload at least one video clip.</p>');
1120
- }
1121
-
1122
- try {
1123
- showOverlay('Stitching clips...', 30);
1124
-
1125
- // Simulate progress
1126
- await new Promise(resolve => setTimeout(resolve, 2000));
1127
- updateOverlayProgress(60);
1128
- await new Promise(resolve => setTimeout(resolve, 2000));
1129
- updateOverlayProgress(90);
1130
- await new Promise(resolve => setTimeout(resolve, 1000));
1131
-
1132
- hideOverlay();
1133
- showModal('<p class="small">Clips stitched successfully! ✅</p>');
1134
- uploadOut.textContent = 'Stitching complete. Download: musicVID.mp4';
1135
- } catch (error) {
1136
- hideOverlay();
1137
- showModal(`<p class="small">Error: ${error.message}</p>`);
1138
- console.error('Stitch error:', error);
1139
- }
1140
- }
1141
 
1142
- function clearClips() {
1143
- videoClips = [];
1144
- clipFilesInput.value = '';
1145
- renderClipQueue();
1146
- uploadOut.textContent = 'Upload status...';
1147
- }
 
 
 
1148
 
1149
- // Utility functions
1150
- async function safeJson(response) {
1151
- try {
1152
- return await response.json();
1153
- } catch {
1154
- return { error: 'Invalid server response' };
1155
- }
1156
- }
1157
 
1158
- function copySunoBlock() {
1159
- copyToClipboard(sunoBlockOut.textContent);
1160
- }
1161
 
1162
- function saveDraft() {
1163
- const draft = {
1164
- formData: getFormData(),
1165
- sunoBlock: sunoBlockOut.textContent,
1166
- songPrompt: document.getElementById('songPrompt').value,
1167
- artPrompt: document.getElementById('artPrompt').value,
1168
- videoPrompt: document.getElementById('videoPrompt').value
1169
- };
1170
- localStorage.setItem('fiverrMusicDraft', JSON.stringify(draft));
1171
- showModal('<p class="small">Draft saved successfully ✅</p>');
1172
- }
1173
 
1174
- function loadDraft() {
1175
- const saved = localStorage.getItem('fiverrMusicDraft');
1176
- if (!saved) return showModal('<p class="small">No saved draft found.</p>');
1177
-
1178
- try {
1179
- const draft = JSON.parse(saved);
1180
-
1181
- // Restore form
1182
- document.getElementById('songTitle').value = draft.formData.songTitle || '';
1183
- document.getElementById('occasion').value = draft.formData.occasion || '';
1184
- document.getElementById('buyerName').value = draft.formData.buyerName || '';
1185
- document.getElementById('recipientName').value = draft.formData.recipientName || '';
1186
- document.getElementById('relationship').value = draft.formData.relationship || '';
1187
- document.getElementById('tone').value = draft.formData.tone || '';
1188
- document.getElementById('mainGenre').value = draft.formData.mainGenre || '';
1189
- document.getElementById('secondaryGenres').value = draft.formData.secondaryGenres || '';
1190
- document.getElementById('tempo').value = draft.formData.tempo || '';
1191
- document.getElementById('vocalStyle').value = draft.formData.vocalStyle || '';
1192
- document.getElementById('instrumentationNotes').value = draft.formData.instrumentationNotes || '';
1193
- document.getElementById('languageLevel').value = draft.formData.languageLevel || 'no limit';
1194
- document.getElementById('packageLevel').value = draft.formData.packageLevel || 'premium';
1195
- document.getElementById('wantsArtwork').checked = draft.formData.wantsArtwork || false;
1196
- document.getElementById('wantsVideo').checked = draft.formData.wantsVideo || false;
1197
- document.getElementById('keyFacts').value = draft.formData.keyFacts?.join('\n') || '';
1198
- document.getElementById('stories').value = draft.formData.stories?.join('\n') || '';
1199
- document.getElementById('mainMessage').value = draft.formData.mainMessage || '';
1200
- document.getElementById('namesPlaces').value = draft.formData.namesPlaces?.join('\n') || '';
1201
- document.getElementById('visualStyle').value = draft.formData.visualStyle || '';
1202
- document.getElementById('visualMood').value = draft.formData.visualMood || '';
1203
- document.getElementById('visualPlacesThemes').value = draft.formData.visualPlacesThemes?.join('\n') || '';
1204
- document.getElementById
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!DOCTYPE html>
2
  <html lang="en">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Music Video Creator</title>
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: #4a6bff;
12
+ --primary-dark: #3a5bef;
13
+ --secondary: #f8f9fa;
14
+ --text: #333;
15
+ --text-light: #666;
16
+ --white: #ffffff;
17
+ --error: #ff4444;
18
+ --success: #28a745;
19
+ --border: #e0e0e0;
20
+ --shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
21
+ --radius: 8px;
22
+ }
23
+
24
+ * {
25
+ margin: 0;
26
+ padding: 0;
27
+ box-sizing: border-box;
28
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
29
+ }
30
+
31
+ body {
32
+ background-color: #f5f7ff;
33
+ color: var(--text);
34
+ line-height: 1.6;
35
+ padding: 20px;
36
+ }
37
+
38
+ .container {
39
+ max-width: 1200px;
40
+ margin: 0 auto;
41
+ background: white;
42
+ border-radius: var(--radius);
43
+ box-shadow: var(--shadow);
44
+ overflow: hidden;
45
+ }
46
+
47
+ header {
48
+ background: linear-gradient(135deg, var(--primary), var(--primary-dark));
49
+ color: white;
50
+ padding: 20px 30px;
51
+ display: flex;
52
+ justify-content: space-between;
53
+ align-items: center;
54
+ }
55
+
56
+ .logo {
57
+ font-size: 24px;
58
+ font-weight: bold;
59
+ display: flex;
60
+ align-items: center;
61
+ gap: 10px;
62
+ }
63
+
64
+ .logo i {
65
+ font-size: 30px;
66
+ }
67
+
68
+ .header-actions {
69
+ display: flex;
70
+ gap: 15px;
71
+ align-items: center;
72
+ }
73
+
74
+ .btn {
75
+ padding: 8px 16px;
76
+ border: none;
77
+ border-radius: var(--radius);
78
+ cursor: pointer;
79
+ font-weight: 500;
80
+ transition: all 0.3s ease;
81
+ display: inline-flex;
82
+ align-items: center;
83
+ gap: 8px;
84
+ }
85
+
86
+ .btn-primary {
87
+ background-color: var(--primary);
88
+ color: white;
89
+ }
90
+
91
+ .btn-primary:hover {
92
+ background-color: var(--primary-dark);
93
+ transform: translateY(-2px);
94
+ }
95
+
96
+ .btn-secondary {
97
+ background-color: var(--secondary);
98
+ color: var(--text);
99
+ }
100
+
101
+ .btn-secondary:hover {
102
+ background-color: #e9ecef;
103
+ }
104
+
105
+ .btn-small {
106
+ padding: 4px 12px;
107
+ font-size: 14px;
108
+ }
109
+
110
+ .main-content {
111
+ display: grid;
112
+ grid-template-columns: 1fr 1fr;
113
+ gap: 30px;
114
+ padding: 30px;
115
+ }
116
+
117
+ .section {
118
+ background: white;
119
+ border-radius: var(--radius);
120
+ padding: 20px;
121
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
122
+ }
123
+
124
+ .section-title {
125
+ font-size: 18px;
126
+ font-weight: 600;
127
+ margin-bottom: 15px;
128
+ color: var(--primary);
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 8px;
132
+ }
133
+
134
+ .form-group {
135
+ margin-bottom: 15px;
136
+ }
137
+
138
+ .form-group label {
139
+ display: block;
140
+ margin-bottom: 5px;
141
+ font-weight: 500;
142
+ color: var(--text-light);
143
+ }
144
+
145
+ .form-control {
146
+ width: 100%;
147
+ padding: 10px;
148
+ border: 1px solid var(--border);
149
+ border-radius: var(--radius);
150
+ font-size: 14px;
151
+ transition: border-color 0.3s;
152
+ }
153
+
154
+ .form-control:focus {
155
+ outline: none;
156
+ border-color: var(--primary);
157
+ box-shadow: 0 0 0 2px rgba(74, 107, 255, 0.2);
158
+ }
159
+
160
+ textarea.form-control {
161
+ min-height: 100px;
162
+ resize: vertical;
163
+ }
164
+
165
+ .checkbox-group {
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 10px;
169
+ }
170
+
171
+ .checkbox-group input {
172
+ width: auto;
173
+ }
174
+
175
+ .output-section {
176
+ background: #f8f9fa;
177
+ border-radius: var(--radius);
178
+ padding: 15px;
179
+ min-height: 100px;
180
+ font-family: monospace;
181
+ font-size: 13px;
182
+ white-space: pre-wrap;
183
+ word-wrap: break-word;
184
+ position: relative;
185
+ }
186
+
187
+ .output-actions {
188
+ display: flex;
189
+ gap: 10px;
190
+ margin-top: 10px;
191
+ }
192
+
193
+ .thumb-grid {
194
+ display: grid;
195
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
196
+ gap: 10px;
197
+ margin-top: 10px;
198
+ }
199
+
200
+ .thumb {
201
+ width: 100%;
202
+ height: 100px;
203
+ object-fit: cover;
204
+ border-radius: var(--radius);
205
+ cursor: pointer;
206
+ transition: transform 0.3s;
207
+ }
208
+
209
+ .thumb:hover {
210
+ transform: scale(1.05);
211
+ }
212
+
213
+ .file-upload {
214
+ border: 2px dashed var(--border);
215
+ border-radius: var(--radius);
216
+ padding: 20px;
217
+ text-align: center;
218
+ cursor: pointer;
219
+ transition: all 0.3s;
220
+ }
221
+
222
+ .file-upload:hover {
223
+ border-color: var(--primary);
224
+ background-color: rgba(74, 107, 255, 0.05);
225
+ }
226
+
227
+ .file-info {
228
+ display: flex;
229
+ align-items: center;
230
+ gap: 10px;
231
+ margin-top: 10px;
232
+ padding: 10px;
233
+ background: #f8f9fa;
234
+ border-radius: var(--radius);
235
+ }
236
+
237
+ .clip-item {
238
+ display: flex;
239
+ justify-content: space-between;
240
+ align-items: center;
241
+ padding: 8px;
242
+ background: white;
243
+ border-radius: var(--radius);
244
+ margin-bottom: 5px;
245
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
246
+ }
247
+
248
+ .modal {
249
+ display: none;
250
+ position: fixed;
251
+ top: 0;
252
+ left: 0;
253
+ width: 100%;
254
+ height: 100%;
255
+ background: rgba(0, 0, 0, 0.5);
256
+ z-index: 1000;
257
+ justify-content: center;
258
+ align-items: center;
259
+ }
260
+
261
+ .modal-content {
262
+ background: white;
263
+ padding: 30px;
264
+ border-radius: var(--radius);
265
+ max-width: 500px;
266
+ width: 90%;
267
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
268
+ }
269
+
270
+ .modal-header {
271
+ display: flex;
272
+ justify-content: space-between;
273
+ align-items: center;
274
+ margin-bottom: 20px;
275
+ }
276
+
277
+ .modal-title {
278
+ font-size: 20px;
279
+ font-weight: 600;
280
+ color: var(--primary);
281
+ }
282
+
283
+ .close-btn {
284
+ background: none;
285
+ border: none;
286
+ font-size: 24px;
287
+ cursor: pointer;
288
+ color: var(--text-light);
289
+ }
290
+
291
+ .overlay {
292
+ display: none;
293
+ position: fixed;
294
+ top: 0;
295
+ left: 0;
296
+ width: 100%;
297
+ height: 100%;
298
+ background: rgba(0, 0, 0, 0.7);
299
+ z-index: 999;
300
+ justify-content: center;
301
+ align-items: center;
302
+ color: white;
303
+ font-size: 18px;
304
+ }
305
+
306
+ .progress-bar {
307
+ width: 200px;
308
+ height: 4px;
309
+ background: rgba(255, 255, 255, 0.3);
310
+ border-radius: 2px;
311
+ margin-top: 20px;
312
+ overflow: hidden;
313
+ }
314
+
315
+ .progress {
316
+ height: 100%;
317
+ background: white;
318
+ width: 0%;
319
+ transition: width 0.3s;
320
+ }
321
+
322
+ .anycoder-link {
323
+ position: absolute;
324
+ top: 10px;
325
+ right: 10px;
326
+ color: white;
327
+ text-decoration: none;
328
+ font-size: 12px;
329
+ opacity: 0.8;
330
+ }
331
+
332
+ .anycoder-link:hover {
333
+ opacity: 1;
334
+ text-decoration: underline;
335
+ }
336
+
337
+ @media (max-width: 768px) {
338
+ .main-content {
339
+ grid-template-columns: 1fr;
340
+ padding: 15px;
341
+ }
342
+
343
+ header {
344
+ flex-direction: column;
345
+ gap: 15px;
346
+ text-align: center;
347
+ }
348
+
349
+ .header-actions {
350
+ flex-wrap: wrap;
351
+ justify-content: center;
352
+ }
353
+ }
354
+ </style>
355
+ </head>
356
 
357
+ <body>
358
+ <div class="container">
359
+ <header>
360
+ <div class="logo">
361
+ <i class="fas fa-music"></i>
362
+ <span>Music Video Creator</span>
363
+ </div>
364
+ <div class="header-actions">
365
+ <button class="btn btn-secondary btn-small" onclick="showApiKeyModal()">
366
+ <i class="fas fa-key"></i> API Key
367
+ </button>
368
+ <button class="btn btn-secondary btn-small" onclick="saveDraft()">
369
+ <i class="fas fa-save"></i> Save Draft
370
+ </button>
371
+ <button class="btn btn-secondary btn-small" onclick="loadDraft()">
372
+ <i class="fas fa-folder-open"></i> Load Draft
373
+ </button>
374
+ <button class="btn btn-secondary btn-small" onclick="resetForm()">
375
+ <i class="fas fa-undo"></i> Reset
376
+ </button>
377
+ </div>
378
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank">
379
+ Built with anycoder
380
+ </a>
381
+ </header>
382
+
383
+ <div class="main-content">
384
+ <div class="section">
385
+ <div class="section-title">
386
+ <i class="fas fa-info-circle"></i>
387
+ <span>Song Information</span>
388
+ </div>
389
 
390
+ <div class="form-group">
391
+ <label for="songTitle">Song Title</label>
392
+ <input type="text" id="songTitle" class="form-control" placeholder="Enter song title">
393
+ </div>
 
394
 
395
+ <div class="form-group">
396
+ <label for="occasion">Occasion</label>
397
+ <input type="text" id="occasion" class="form-control" placeholder="e.g., Birthday, Anniversary">
398
+ </div>
399
 
400
+ <div class="form-group">
401
+ <label for="buyerName">Buyer Name</label>
402
+ <input type="text" id="buyerName" class="form-control" placeholder="Your name">
403
+ </div>
 
404
 
405
+ <div class="form-group">
406
+ <label for="recipientName">Recipient Name</label>
407
+ <input type="text" id="recipientName" class="form-control" placeholder="Who is this for?">
408
+ </div>
409
 
410
+ <div class="form-group">
411
+ <label for="relationship">Relationship</label>
412
+ <input type="text" id="relationship" class="form-control" placeholder="e.g., Mother, Friend">
413
+ </div>
 
 
 
 
 
 
 
414
 
415
+ <div class="form-group">
416
+ <label for="tone">Tone</label>
417
+ <input type="text" id="tone" class="form-control" placeholder="e.g., Happy, Romantic">
418
+ </div>
419
+ </div>
420
 
421
+ <div class="section">
422
+ <div class="section-title">
423
+ <i class="fas fa-music"></i>
424
+ <span>Music Details</span>
425
+ </div>
 
426
 
427
+ <div class="form-group">
428
+ <label for="mainGenre">Main Genre</label>
429
+ <input type="text" id="mainGenre" class="form-control" placeholder="e.g., Pop, Rock">
430
+ </div>
 
 
 
 
431
 
432
+ <div class="form-group">
433
+ <label for="secondaryGenres">Secondary Genres</label>
434
+ <input type="text" id="secondaryGenres" class="form-control" placeholder="Additional genres">
435
+ </div>
436
 
437
+ <div class="form-group">
438
+ <label for="tempo">Tempo</label>
439
+ <input type="text" id="tempo" class="form-control" placeholder="e.g., Fast, Slow">
440
+ </div>
 
 
 
 
441
 
442
+ <div class="form-group">
443
+ <label for="vocalStyle">Vocal Style</label>
444
+ <input type="text" id="vocalStyle" class="form-control" placeholder="e.g., Male, Female">
445
+ </div>
446
 
447
+ <div class="form-group">
448
+ <label for="instrumentationNotes">Instrumentation Notes</label>
449
+ <textarea id="instrumentationNotes" class="form-control" placeholder="Any specific instruments?"></textarea>
450
+ </div>
451
+ </div>
 
 
 
 
452
 
453
+ <div class="section">
454
+ <div class="section-title">
455
+ <i class="fas fa-story"></i>
456
+ <span>Content Details</span>
457
+ </div>
 
 
 
 
 
458
 
459
+ <div class="form-group">
460
+ <label for="keyFacts">Key Facts (one per line)</label>
461
+ <textarea id="keyFacts" class="form-control" placeholder="Important facts about the recipient"></textarea>
462
+ </div>
 
 
 
 
 
 
 
 
463
 
464
+ <div class="form-group">
465
+ <label for="stories">Stories (one per line)</label>
466
+ <textarea id="stories" class="form-control" placeholder="Memorable stories to include"></textarea>
467
+ </div>
 
 
 
 
468
 
469
+ <div class="form-group">
470
+ <label for="mainMessage">Main Message</label>
471
+ <textarea id="mainMessage" class="form-control" placeholder="The core message of the song"></textarea>
472
+ </div>
 
 
473
 
474
+ <div class="form-group">
475
+ <label for="namesPlaces">Names/Places (one per line)</label>
476
+ <textarea id="namesPlaces" class="form-control" placeholder="Important names and places"></textarea>
477
+ </div>
478
+ </div>
479
 
480
+ <div class="section">
481
+ <div class="section-title">
482
+ <i class="fas fa-palette"></i>
483
+ <span>Visual Style</span>
484
+ </div>
 
 
485
 
486
+ <div class="form-group">
487
+ <label for="visualStyle">Visual Style</label>
488
+ <input type="text" id="visualStyle" class="form-control" placeholder="e.g., Cartoon, Realistic">
489
+ </div>
 
 
 
 
 
 
 
 
 
 
490
 
491
+ <div class="form-group">
492
+ <label for="visualMood">Visual Mood</label>
493
+ <input type="text" id="visualMood" class="form-control" placeholder="e.g., Bright, Moody">
494
+ </div>
 
 
 
 
495
 
496
+ <div class="form-group">
497
+ <label for="visualPlacesThemes">Places/Themes (one per line)</label>
498
+ <textarea id="visualPlacesThemes" class="form-control" placeholder="Visual themes to include"></textarea>
499
+ </div>
 
 
500
 
501
+ <div class="form-group">
502
+ <label for="visualText">Visual Text</label>
503
+ <input type="text" id="visualText" class="form-control" placeholder="Any text to include in visuals">
504
+ </div>
505
+ </div>
 
 
 
 
506
 
507
+ <div class="section">
508
+ <div class="section-title">
509
+ <i class="fas fa-cog"></i>
510
+ <span>Package Options</span>
511
+ </div>
512
 
513
+ <div class="form-group">
514
+ <label for="languageLevel">Language Level</label>
515
+ <select id="languageLevel" class="form-control">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
  <option value="no limit">No Limit</option>
517
  <option value="simple">Simple</option>
518
  <option value="advanced">Advanced</option>
519
  </select>
520
+ </div>
521
 
522
+ <div class="form-group">
523
+ <label for="packageLevel">Package Level</label>
524
+ <select id="packageLevel" class="form-control">
525
  <option value="basic">Basic</option>
526
  <option value="premium">Premium</option>
527
  <option value="deluxe">Deluxe</option>
528
  </select>
529
+ </div>
530
+
531
+ <div class="form-group checkbox-group">
532
+ <input type="checkbox" id="wantsArtwork">
533
+ <label for="wantsArtwork">Include Artwork</label>
534
+ </div>
535
+
536
+ <div class="form-group checkbox-group">
537
+ <input type="checkbox" id="wantsVideo">
538
+ <label for="wantsVideo">Include Video</label>
539
+ </div>
540
+ </div>
541
+
542
+ <div class="section">
543
+ <div class="section-title">
544
+ <i class="fas fa-robot"></i>
545
+ <span>Generate Suno Block</span>
546
+ </div>
547
+
548
+ <button class="btn btn-primary" onclick="generateSunoBlock()">
549
  <i class="fas fa-magic"></i> Generate Suno Block
550
  </button>
551
 
552
+ <div class="output-section" id="sunoBlockOut">
553
+ Suno block will appear here...
554
+ </div>
555
 
556
+ <div class="output-actions">
557
+ <button class="btn btn-secondary btn-small" onclick="copySunoBlock()">
558
  <i class="fas fa-copy"></i> Copy
559
  </button>
560
+ <button class="btn btn-secondary btn-small" onclick="populateBuilders()">
561
  <i class="fas fa-arrow-right"></i> Populate Builders
562
  </button>
563
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
 
565
+ <div id="warningsDisplay" style="color: var(--error); margin-top: 10px; font-size: 13px;"></div>
566
+ </div>
 
567
 
568
+ <div class="section">
569
+ <div class="section-title">
570
+ <i class="fas fa-code"></i>
571
+ <span>Builder Prompts</span>
572
  </div>
 
573
 
574
+ <div class="form-group">
575
+ <label for="songPrompt">Song Prompt</label>
576
+ <textarea id="songPrompt" class="form-control" placeholder="Generated song prompt will appear here"></textarea>
 
 
 
 
 
 
 
 
 
 
 
577
  </div>
 
578
 
579
+ <div class="form-group">
580
+ <label for="artPrompt">Artwork Prompt</label>
581
+ <textarea id="artPrompt" class="form-control" placeholder="Generated artwork prompt will appear here"></textarea>
582
+ <button class="btn btn-primary btn-small" onclick="generateImage()" style="margin-top: 10px;">
583
+ <i class="fas fa-image"></i> Generate Image
584
+ </button>
 
 
585
  </div>
 
586
 
587
+ <div class="form-group">
588
+ <label for="videoPrompt">Video Prompt</label>
589
+ <textarea id="videoPrompt" class="form-control" placeholder="Generated video prompt will appear here"></textarea>
590
+ <button class="btn btn-primary btn-small" onclick="generateVideo()" style="margin-top: 10px;">
591
+ <i class="fas fa-video"></i> Generate Video
592
+ </button>
 
593
  </div>
594
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
595
 
596
+ <div class="section">
597
+ <div class="section-title">
598
+ <i class="fas fa-images"></i>
599
+ <span>Generated Images</span>
600
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
601
 
602
+ <div id="imgGrid" class="thumb-grid">
603
+ <!-- Images will appear here -->
604
+ </div>
605
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
606
 
607
+ <div class="section">
608
+ <div class="section-title">
609
+ <i class="fas fa-file-video"></i>
610
+ <span>Video Preview</span>
611
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
 
613
+ <div id="videoPreview" style="margin-bottom: 10px;">
614
+ <!-- Video will appear here -->
615
+ </div>
 
616
 
617
+ <div id="videoOut" style="font-size: 13px; color: var(--text-light);">
618
+ Video status...
619
+ </div>
620
+ </div>
 
621
 
622
+ <div class="section">
623
+ <div class="section-title">
624
+ <i class="fas fa-file-audio"></i>
625
+ <span>Song File Upload</span>
626
+ </div>
 
627
 
628
+ <div class="file-upload" onclick="document.getElementById('songFileInput').click()">
629
+ <i class="fas fa-cloud-upload-alt" style="font-size: 40px; color: var(--primary); margin-bottom: 10px;"></i>
630
+ <p>Click to upload song file</p>
631
+ <input type="file" id="songFileInput" style="display: none;" accept="audio/*" onchange="handleSongFileUpload()">
632
+ </div>
633
 
634
+ <div id="songFileInfo" style="display: none;">
635
+ <span id="songFileName"></span>
636
+ <button class="btn btn-secondary btn-small" onclick="removeSongFile()">
637
+ <i class="fas fa-trash"></i> Remove
638
+ </button>
639
+ </div>
640
 
641
+ <div id="uploadOut" style="margin-top: 10px; font-size: 13px; color: var(--text-light);">
642
+ Upload status...
643
+ </div>
644
+ </div>
645
 
646
+ <div class="section">
647
+ <div class="section-title">
648
+ <i class="fas fa-film"></i>
649
+ <span>Video Clips</span>
650
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
 
652
+ <div class="file-upload" onclick="document.getElementById('clipFilesInput').click()">
653
+ <i class="fas fa-cloud-upload-alt" style="font-size: 40px; color: var(--primary); margin-bottom: 10px;"></i>
654
+ <p>Click to upload video clips</p>
655
+ <input type="file" id="clipFilesInput" style="display: none;" accept="video/*" multiple onchange="handleClipFilesUpload()">
656
+ </div>
 
 
 
 
 
 
 
 
657
 
658
+ <div id="clipQueueWrap" style="margin-top: 15px;">
659
+ <div class="small" style="margin-top:6px">No clips queued.</div>
660
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
661
 
662
+ <div style="margin-top: 15px; display: flex; gap: 10px;">
663
+ <button class="btn btn-primary btn-small" onclick="stitchClips()">
664
+ <i class="fas fa-link"></i> Stitch Clips
665
+ </button>
666
+ <button class="btn btn-secondary btn-small" onclick="clearClips()">
667
+ <i class="fas fa-trash"></i> Clear All
668
+ </button>
669
+ </div>
670
+ </div>
671
 
672
+ <div class="section">
673
+ <div class="section-title">
674
+ <i class="fas fa-tools"></i>
675
+ <span>Advanced Tools</span>
676
+ </div>
 
 
 
677
 
678
+ <button class="btn btn-secondary" onclick="randomizeForm()" style="margin-bottom: 10px;">
679
+ <i class="fas fa-random"></i> Randomize Form
680
+ </button>
681
 
682
+ <button class="btn btn-secondary" onclick="runDiagnostics()">
683
+ <i class="fas fa-stethoscope"></i> Run Diagnostics
684
+ </button>
 
 
 
 
 
 
 
 
685
 
686
+ <div id="diagOut"
687
+ style="display: none; margin-top: 15px; font-family: monospace; font-size: 12px; background: #f8f9fa; padding: 10px; border-radius: var(--radius);">
688
+ </div>
689
+ </div>
690
+ </div>
691
+ </div>
692
+
693
+ <!-- Modal for API Key -->
694
+ <div id="apiKeyModal" class="modal">
695
+ <div class="modal-content">
696
+ <div class="modal-header">
697
+ <h2 class="modal-title">API Key Settings</h2>
698
+ <button class="close-btn" onclick="closeApiKeyModal()">&times;</button>
699
+ </div>
700
+ <div class="form-group">
701
+ <label for="apiKeyInput">OpenAI API Key</label>
702
+ <input type="password" id="apiKeyInput" class="form-control" placeholder="Enter your OpenAI API key">
703
+ </div>
704
+ <button class="btn btn-primary" onclick="saveApiKey()">
705
+ <i class="fas fa-save"></i> Save API Key
706
+ </button>
707
+ </div>
708
+ </div>
709
+
710
+ <!-- General Modal -->
711
+ <div id="generalModal" class="modal">
712
+ <div class="modal-content">
713
+ <div class="modal-header">
714
+ <h2 class="modal-title">Notification</h2>
715
+ <button class="close-btn" onclick="closeModal()">&times;</button>
716
+ </div>
717
+ <div id="modalContent"></div>
718
+ </div>
719
+ </div>
720
+
721
+ <!-- Overlay -->
722
+ <div id="overlay" class="overlay">
723
+ <div>
724
+ <div id="overlayText">Processing...</div>
725
+ <div class="progress-bar">
726
+ <div class="progress" id="progressBar"></div>
727
+ </div>
728
+ </div>
729
+ </div>
730
+
731
+ <script>
732
+ // Configuration
733
+ const API_BASE = 'https://api.example.com'; // Replace with your actual API base
734
+ let apiKey = localStorage.getItem('openaiApiKey') || '';
735
+ let songFile = null;
736
+ let videoClips = [];
737
+
738
+ // DOM Elements
739
+ const sunoBlockOut = document.getElementById('sunoBlockOut');
740
+ const warningsDisplay = document.getElementById('warningsDisplay');
741
+ const imgGrid = document.getElementById('imgGrid');
742
+ const videoPreview = document.getElementById('videoPreview');
743
+ const videoOut = document.getElementById('videoOut');
744
+ const songFileInput = document.getElementById('songFileInput');
745
+ const songFileName = document.getElementById('songFileName');
746
+ const songFileInfo = document.getElementById('songFileInfo');
747
+ const clipFilesInput = document.getElementById('clipFilesInput');
748
+ const clipQueueWrap = document.getElementById('clipQueueWrap');
749
+ const diagOut = document.getElementById('diagOut');
750
+ const uploadOut = document.getElementById('uploadOut');
751
+
752
+ // Initialize
753
+ function init() {
754
+ // Check if API key exists
755
+ if (apiKey) {
756
+ document.getElementById('apiKeyInput').value = '••••••••••••••••';
757
+ }
758
+
759
+ // Set up event listeners
760
+ document.addEventListener('keydown', function(e) {
761
+ if (e.key === 'Escape') {
762
+ closeModal();
763
+ closeApiKeyModal();
764
+ }
765
+ });
766
+ }
767
+
768
+ // Modal functions
769
+ function showModal(content) {
770
+ const modal = document.getElementById('generalModal');
771
+ const modalContent = document.getElementById('modalContent');
772
+ modalContent.innerHTML = content;
773
+ modal.style.display = 'flex';
774
+ }
775
+
776
+ function closeModal() {
777
+ document.getElementById('generalModal').style.display = 'none';
778
+ }
779
+
780
+ function showApiKeyModal() {
781
+ document.getElementById('apiKeyModal').style.display = 'flex';
782
+ }
783
+
784
+ function closeApiKeyModal() {
785
+ document.getElementById('apiKeyModal').style.display = 'none';
786
+ }
787
+
788
+ function saveApiKey() {
789
+ const keyInput = document.getElementById('apiKeyInput');
790
+ apiKey = keyInput.value.trim();
791
+
792
+ if (apiKey) {
793
+ localStorage.setItem('openaiApiKey', apiKey);
794
+ keyInput.value = '••••••••••••••••';
795
+ showModal('<p class="small">API key saved successfully ✅</p>');
796
+ closeApiKeyModal();
797
+ } else {
798
+ showModal('<p class="small">Please enter a valid API key</p>');
799
+ }
800
+ }
801
+
802
+ // Overlay functions
803
+ function showOverlay(text, progress = 0) {
804
+ const overlay = document.getElementById('overlay');
805
+ const overlayText = document.getElementById('overlayText');
806
+ const progressBar = document.getElementById('progressBar');
807
+
808
+ overlayText.textContent = text;
809
+ progressBar.style.width = `${progress}%`;
810
+ overlay.style.display = 'flex';
811
+ }
812
+
813
+ function hideOverlay() {
814
+ document.getElementById('overlay').style.display = 'none';
815
+ }
816
+
817
+ function updateOverlayProgress(progress) {
818
+ document.getElementById('progressBar').style.width = `${progress}%`;
819
+ }
820
+
821
+ // Utility functions
822
+ function copyToClipboard(text) {
823
+ navigator.clipboard.writeText(text).then(() => {
824
+ showModal('<p class="small">Copied to clipboard ✅</p>');
825
+ }).catch(err => {
826
+ showModal('<p class="small">Failed to copy: ' + err.message + '</p>');
827
+ });
828
+ }
829
+
830
+ function splitLines(text) {
831
+ return text.split('\n')
832
+ .map(line => line.trim())
833
+ .filter(Boolean);
834
+ }
835
+
836
+ function getFormData() {
837
+ return {
838
+ songTitle: document.getElementById('songTitle').value.trim(),
839
+ occasion: document.getElementById('occasion').value.trim(),
840
+ buyerName: document.getElementById('buyerName').value.trim(),
841
+ recipientName: document.getElementById('recipientName').value.trim(),
842
+ relationship: document.getElementById('relationship').value.trim(),
843
+ tone: document.getElementById('tone').value.trim(),
844
+ mainGenre: document.getElementById('mainGenre').value.trim(),
845
+ secondaryGenres: document.getElementById('secondaryGenres').value.trim(),
846
+ tempo: document.getElementById('tempo').value.trim(),
847
+ vocalStyle: document.getElementById('vocalStyle').value.trim(),
848
+ instrumentationNotes: document.getElementById('instrumentationNotes').value.trim(),
849
+ languageLevel: document.getElementById('languageLevel').value,
850
+ packageLevel: document.getElementById('packageLevel').value,
851
+ wantsArtwork: document.getElementById('wantsArtwork').checked,
852
+ wantsVideo: document.getElementById('wantsVideo').checked,
853
+ keyFacts: splitLines(document.getElementById('keyFacts').value),
854
+ stories: splitLines(document.getElementById('stories').value),
855
+ mainMessage: document.getElementById('mainMessage').value.trim(),
856
+ namesPlaces: splitLines(document.getElementById('namesPlaces').value),
857
+ visualStyle: document.getElementById('visualStyle').value.trim(),
858
+ visualMood: document.getElementById('visualMood').value.trim(),
859
+ visualPlacesThemes: splitLines(document.getElementById('visualPlacesThemes').value),
860
+ visualText: document.getElementById('visualText').value.trim()
861
+ };
862
+ }
863
+
864
+ // Fixed image generation function with better error handling and timeout
865
+ async function generateImage() {
866
+ const prompt = document.getElementById('artPrompt').value.trim();
867
+ if (!prompt) {
868
+ return showModal('<p class="small">Please populate the artwork prompt first.</p>');
869
+ }
870
+
871
+ try {
872
+ showOverlay('Generating image...', 10);
873
+
874
+ // Create a controller for aborting the request
875
+ const controller = new AbortController();
876
+ const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout
877
+
878
+ const response = await fetch(`${API_BASE}/generate-image`, {
879
+ method: 'POST',
880
+ headers: {
881
+ 'Content-Type': 'application/json',
882
+ 'X-OpenAI-API-Key': apiKey
883
+ },
884
+ body: JSON.stringify({ prompt }),
885
+ signal: controller.signal
886
+ });
887
+
888
+ clearTimeout(timeoutId);
889
+
890
+ if (!response.ok) {
891
+ const errorData = await response.json().catch(() => ({}));
892
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
893
+ }
894
+
895
+ const data = await response.json();
896
+ hideOverlay();
897
+
898
+ if (data.imageUrls && data.imageUrls.length > 0) {
899
+ imgGrid.innerHTML = '';
900
+ data.imageUrls.forEach(url => {
901
+ const img = document.createElement('img');
902
+ img.src = url;
903
+ img.className = 'thumb';
904
+ imgGrid.appendChild(img);
905
+ });
906
+ showModal('<p class="small">Image generated successfully ✅</p>');
907
+ } else {
908
+ throw new Error('No image URLs returned from the API');
909
+ }
910
+ } catch (error) {
911
+ hideOverlay();
912
+ console.error('Image generation error:', error);
913
+
914
+ let errorMessage = 'Failed to generate image';
915
+ if (error.name === 'AbortError') {
916
+ errorMessage = 'Image generation timed out. Please try again.';
917
+ } else if (error.message.includes('socket hang up')) {
918
+ errorMessage = 'Connection to image generation service was interrupted. Please check your internet connection and try again.';
919
+ } else if (error.message) {
920
+ errorMessage = error.message;
921
+ }
922
+
923
+ showModal(`<p class="small">Error: ${errorMessage}</p>`);
924
+ }
925
+ }
926
+
927
+ // Other functions
928
+ async function generateSunoBlock() {
929
+ if (!apiKey) {
930
+ return showModal('<p class="small">Please save your OpenAI API key first.</p>');
931
+ }
932
+
933
+ const formData = getFormData();
934
+ if (!formData.buyerName || !formData.recipientName) {
935
+ return showModal('<p class="small">Please fill in at least buyer and recipient names.</p>');
936
+ }
937
+
938
+ try {
939
+ showOverlay('Generating Suno block...', 30);
940
+ const response = await fetch(`${API_BASE}/generate-suno-block`, {
941
+ method: 'POST',
942
+ headers: {
943
+ 'Content-Type': 'application/json',
944
+ 'X-OpenAI-API-Key': apiKey
945
+ },
946
+ body: JSON.stringify(formData)
947
+ });
948
+
949
+ const data = await safeJson(response);
950
+ hideOverlay();
951
+
952
+ if (response.ok) {
953
+ sunoBlockOut.textContent = data.sunoBlock;
954
+ warningsDisplay.textContent = data.warnings || '';
955
+ showModal('<p class="small">Suno block generated successfully ✅</p>');
956
+ } else {
957
+ showModal(`<p class="small">Error: ${data.error || 'Failed to generate Suno block'}</p>`);
958
+ }
959
+ } catch (error) {
960
+ hideOverlay();
961
+ showModal(`<p class="small">Error: ${error.message}</p>`);
962
+ console.error('Generate error:', error);
963
+ }
964
+ }
965
+
966
+ async function populateBuilders() {
967
+ if (!sunoBlockOut.textContent.trim()) {
968
+ return showModal('<p class="small">Generate a Suno block first.</p>');
969
+ }
970
+
971
+ try {
972
+ showOverlay('Populating builders...', 30);
973
+ const response = await fetch(`${API_BASE}/populate-builders`, {
974
+ method: 'POST',
975
+ headers: {
976
+ 'Content-Type': 'application/json',
977
+ 'X-OpenAI-API-Key': apiKey
978
+ },
979
+ body: JSON.stringify({
980
+ sunoBlock: sunoBlockOut.textContent.trim(),
981
+ formData: getFormData()
982
+ })
983
+ });
984
+
985
+ const data = await safeJson(response);
986
+ hideOverlay();
987
+
988
+ if (response.ok) {
989
+ document.getElementById('songPrompt').value = data.songPrompt || '';
990
+ document.getElementById('artPrompt').value = data.artPrompt || '';
991
+ document.getElementById('videoPrompt').value = data.videoPrompt || '';
992
+ showModal('<p class="small">Builders populated successfully ✅</p>');
993
+ } else {
994
+ showModal(`<p class="small">Error: ${data.error || 'Failed to populate builders'}</p>`);
995
+ }
996
+ } catch (error) {
997
+ hideOverlay();
998
+ showModal(`<p class="small">Error: ${error.message}</p>`);
999
+ console.error('Populate error:', error);
1000
+ }
1001
+ }
1002
+
1003
+ async function generateVideo() {
1004
+ const prompt = document.getElementById('videoPrompt').value.trim();
1005
+ if (!prompt) {
1006
+ return showModal('<p class="small">Please populate the video prompt first.</p>');
1007
+ }
1008
+
1009
+ try {
1010
+ showOverlay('Generating video...', 30);
1011
+ const response = await fetch(`${API_BASE}/generate-video`, {
1012
+ method: 'POST',
1013
+ headers: {
1014
+ 'Content-Type': 'application/json',
1015
+ 'X-OpenAI-API-Key': apiKey
1016
+ },
1017
+ body: JSON.stringify({ prompt })
1018
+ });
1019
+
1020
+ const data = await safeJson(response);
1021
+ hideOverlay();
1022
+
1023
+ if (response.ok) {
1024
+ videoOut.textContent = 'Video generated successfully';
1025
+ videoPreview.innerHTML = `
1026
+ <video controls class="thumb" style="width:100%">
1027
+ <source src="${data.videoUrl}" type="video/mp4">
1028
+ Your browser does not support the video tag.
1029
+ </video>
1030
+ `;
1031
+ showModal('<p class="small">Video generated successfully ✅</p>');
1032
+ } else {
1033
+ showModal(`<p class="small">Error: ${data.error || 'Failed to generate video'}</p>`);
1034
+ }
1035
+ } catch (error) {
1036
+ hideOverlay();
1037
+ showModal(`<p class="small">Error: ${error.message}</p>`);
1038
+ console.error('Video generation error:', error);
1039
+ }
1040
+ }
1041
+
1042
+ // File handling functions
1043
+ function handleSongFileUpload() {
1044
+ const file = songFileInput.files[0];
1045
+ if (!file) return;
1046
+
1047
+ songFile = file;
1048
+ songFileName.textContent = file.name;
1049
+ songFileInfo.style.display = 'flex';
1050
+ uploadOut.textContent = 'Song file ready for upload';
1051
+ }
1052
+
1053
+ function removeSongFile() {
1054
+ songFile = null;
1055
+ songFileInput.value = '';
1056
+ songFileInfo.style.display = 'none';
1057
+ uploadOut.textContent = 'Upload status...';
1058
+ }
1059
+
1060
+ function handleClipFilesUpload() {
1061
+ const files = Array.from(clipFilesInput.files);
1062
+ if (!files.length) return;
1063
+
1064
+ videoClips = files.map((file, i) => ({
1065
+ id: Date.now() + i,
1066
+ file,
1067
+ name: file.name
1068
+ }));
1069
+
1070
+ renderClipQueue();
1071
+ }
1072
+
1073
+ function renderClipQueue() {
1074
+ if (!videoClips.length) {
1075
+ clipQueueWrap.innerHTML = '<div class="small" style="margin-top:6px">No clips queued.</div>';
1076
+ return;
1077
+ }
1078
+
1079
+ clipQueueWrap.innerHTML = '';
1080
+ videoClips.forEach((clip, idx) => {
1081
+ const item = document.createElement('div');
1082
+ item.className = 'clip-item';
1083
+ item.innerHTML = `
1084
+ <span>${clip.name}</span>
1085
+ <div>
1086
+ <button class="btn btn-secondary btn-small" onclick="moveClipUp(${clip.id})">↑</button>
1087
+ <button class="btn btn-secondary btn-small" onclick="moveClipDown(${clip.id})">↓</button>
1088
+ <button class="btn btn-secondary btn-small" onclick="removeClip(${clip.id})">×</button>
1089
+ </div>
1090
+ `;
1091
+ clipQueueWrap.appendChild(item);
1092
+ });
1093
+ }
1094
+
1095
+ function moveClipUp(id) {
1096
+ const idx = videoClips.findIndex(c => c.id === id);
1097
+ if (idx > 0) {
1098
+ [videoClips[idx], videoClips[idx - 1]] = [videoClips[idx - 1], videoClips[idx]];
1099
+ renderClipQueue();
1100
+ }
1101
+ }
1102
+
1103
+ function moveClipDown(id) {
1104
+ const idx = videoClips.findIndex(c => c.id === id);
1105
+ if (idx < videoClips.length - 1) {
1106
+ [videoClips[idx], videoClips[idx + 1]] = [videoClips[idx + 1], videoClips[idx]];
1107
+ renderClipQueue();
1108
+ }
1109
+ }
1110
+
1111
+ function removeClip(id) {
1112
+ videoClips = videoClips.filter(c => c.id !== id);
1113
+ renderClipQueue();
1114
+ }
1115
+
1116
+ async function stitchClips() {
1117
+ if (!songFile) {
1118
+ return showModal('<p class="small">Please upload a song file first.</p>');
1119
+ }
1120
+
1121
+ if (!videoClips.length) {
1122
+ return showModal('<p class="small">Please upload at least one video clip.</p>');
1123
+ }
1124
+
1125
+ try {
1126
+ showOverlay('Stitching clips...', 30);
1127
+
1128
+ // Simulate progress
1129
+ await new Promise(resolve => setTimeout(resolve, 2000));
1130
+ updateOverlayProgress(60);
1131
+ await new Promise(resolve => setTimeout(resolve, 2000));
1132
+ updateOverlayProgress(90);
1133
+ await new Promise(resolve => setTimeout(resolve, 1000));
1134
+
1135
+ hideOverlay();
1136
+ showModal('<p class="small">Clips stitched successfully! ✅</p>');
1137
+ uploadOut.textContent = 'Stitching complete. Download: musicVID.mp4';
1138
+ } catch (error) {
1139
+ hideOverlay();
1140
+ showModal(`<p class="small">Error: ${error.message}</p>`);
1141
+ console.error('Stitch error:', error);
1142
+ }
1143
+ }
1144
+
1145
+ function clearClips() {
1146
+ videoClips = [];
1147
+ clipFilesInput.value = '';
1148
+ renderClipQueue();
1149
+ uploadOut.textContent = 'Upload status...';
1150
+ }
1151
+
1152
+ // Utility functions
1153
+ async function safeJson(response) {
1154
+ try {
1155
+ return await response.json();
1156
+ } catch {
1157
+ return { error: 'Invalid server response' };
1158
+ }
1159
+ }
1160
+
1161
+ function copySunoBlock() {
1162
+ copyToClipboard(sunoBlockOut.textContent);
1163
+ }
1164
+
1165
+ function saveDraft() {
1166
+ const draft = {
1167
+ formData: getFormData(),
1168
+ sunoBlock: sunoBlockOut.textContent,
1169
+ songPrompt: document.getElementById('songPrompt').value,
1170
+ artPrompt: document.getElementById('artPrompt').value,
1171
+ videoPrompt: document.getElementById('videoPrompt').value
1172
+ };
1173
+ localStorage.setItem('fiverrMusicDraft', JSON.stringify(draft));
1174
+ showModal('<p class="small">Draft saved successfully ✅</p>');
1175
+ }
1176
+
1177
+ function loadDraft() {
1178
+ const saved = localStorage.getItem('fiverrMusicDraft');
1179
+ if (!saved) return showModal('<p class="small">No saved draft found.</p>');
1180
+
1181
+ try {
1182
+ const draft = JSON.parse(saved);
1183
+
1184
+ // Restore form
1185
+ document.getElementById('songTitle').value = draft.formData.songTitle || '';
1186
+ document.getElementById('occasion').value = draft.formData.occasion || '';
1187
+ document.getElementById('buyerName').value = draft.formData.buyerName || '';
1188
+ document.getElementById('recipientName').value = draft.formData.recipientName || '';
1189
+ document.getElementById('relationship').value = draft.formData.relationship || '';
1190
+ document.getElementById('tone').value = draft.formData.tone || '';
1191
+ document.getElementById('mainGenre').value = draft.formData.mainGenre || '';
1192
+ document.getElementById('secondaryGenres').value = draft.formData.secondaryGenres || '';
1193
+ document.getElementById('tempo').value = draft.formData.tempo || '';
1194
+ document.getElementById('vocalStyle').value = draft.formData.vocalStyle || '';
1195
+ document.getElementById('instrumentationNotes').value = draft.formData.instrumentationNotes || '';
1196
+ document.getElementById('languageLevel').value = draft.formData.languageLevel || 'no limit';
1197
+ document.getElementById('packageLevel').value = draft.formData.packageLevel || 'premium';
1198
+ document.getElementById('wantsArtwork').checked = draft.formData.wantsArtwork || false;
1199
+ document.getElementById('wantsVideo').checked = draft.formData.wantsVideo || false;
1200
+ document.getElementById('keyFacts').value = draft.formData.keyFacts?.join('\n') || '';
1201
+ document.getElementById('stories').value = draft.formData.stories?.join('\n') || '';
1202
+ document.getElementById('mainMessage').value = draft.formData.mainMessage || '';
1203
+ document.getElementById('namesPlaces').value = draft.formData.namesPlaces?.join('\n') || '';
1204
+ document.getElementById('visualStyle').value = draft.formData.visualStyle || '';
1205
+ document.getElementById('visualMood').value = draft.formData.visualMood || '';
1206
+ document.getElementById('visualPlacesThemes').value = draft.formData.visualPlacesThemes?.join('\n') || '';
1207
+ document.getElementById('visualText').value = draft.formData.visualText || '';
1208
+
1209
+ // Restore outputs
1210
+ sunoBlockOut.textContent = draft.sunoBlock || '';
1211
+ document.getElementById('songPrompt').value = draft.songPrompt || '';
1212
+ document.getElementById('artPrompt').value = draft.artPrompt || '';
1213
+ document.getElementById('videoPrompt').value = draft.videoPrompt || '';