Opera8 commited on
Commit
ef7954e
·
verified ·
1 Parent(s): d5f8329

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +250 -248
index.html CHANGED
@@ -1,4 +1,4 @@
1
- <!-- START OF FILE index (20).html -->
2
  <!DOCTYPE html>
3
  <html lang="fa" dir="rtl">
4
  <head>
@@ -100,18 +100,26 @@
100
  font-family: inherit; font-size: 1rem; color: #2d3748;
101
  outline: none; transition: border-color 0.3s;
102
  }
103
-
104
- /* استایل اختصاصی برای فایل آپلود */
105
- input[type="file"] {
106
- width: 100%; background: #fff;
107
- border: 2px dashed var(--accent-primary);
108
- border-radius: 12px; padding: 10px;
109
- cursor: pointer; font-size: 0.9rem;
110
- }
111
-
112
  textarea { min-height: 120px; resize: vertical; }
113
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  /* دکمه اصلی */
116
  @keyframes move-gradient {
117
  0% { background-position: 0% 50%; }
@@ -160,13 +168,9 @@
160
  margin-bottom: 0; border: 1px solid var(--input-border); border-top: none;
161
  }
162
  .settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; padding: 15px 0; }
163
- .checkbox-wrapper { display: flex; align-items: center; gap: 10px; margin-top: 10px; padding: 10px 0; border-top: 1px solid #e2e8f0; }
164
  .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
165
-
166
- /* استایل خاص برای چک‌باکس اینسترومنتال */
167
- .inst-wrapper {
168
- background: #fff; padding: 10px; border-radius: 8px; border: 1px solid var(--input-border);
169
- }
170
 
171
  /* پلیر و متن */
172
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
@@ -183,30 +187,24 @@
183
  .audio-item { margin-bottom: 10px; }
184
  audio { width: 100%; height: 45px; border-radius: 20px; margin-top: 5px; }
185
 
186
- /* استایل متن کارائوکه */
187
  .lyrics-container {
188
  background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto;
189
- text-align: center; border: 1px solid var(--input-border); margin-top: 15px;
 
190
  scroll-behavior: smooth;
191
  }
192
- .lyric-line {
193
- padding: 8px 5px;
194
- font-size: 1rem;
195
- color: #718096;
196
- border-radius: 8px;
197
- transition: all 0.3s ease;
198
- line-height: 1.8;
199
- opacity: 0.7;
200
  }
201
- .lyric-line.active {
202
  color: var(--accent-primary);
203
  font-weight: 800;
204
- background-color: rgba(74, 108, 250, 0.08);
205
- transform: scale(1.02);
206
- opacity: 1;
207
- box-shadow: 0 2px 10px rgba(74, 108, 250, 0.05);
208
  }
209
- .lyrics-tag { color: var(--accent-secondary); font-size: 0.8rem; font-weight: 700; display: block; margin-top: 10px; margin-bottom: 5px; letter-spacing: 1px; }
210
 
211
  /* لودر */
212
  #loader { display: none; text-align: center; padding: 20px; }
@@ -317,16 +315,28 @@
317
  <div class="settings-group"><label>تعداد خروجی (آهنگ):</label><input type="number" id="batch_size" value="1" min="1" max="4"></div>
318
  <div class="settings-group"><label>تعداد گام (Steps):</label><input type="number" id="steps_input" value="20" min="4" max="50"></div>
319
  <div class="settings-group"><label>سید (Seed) تصادفی=-1:</label><input type="number" id="seed_input" value="-1"></div>
 
 
 
 
 
 
 
 
 
 
320
  <div class="settings-group"><label>مقیاس هدایت (CFG):</label><input type="number" id="cfg_input" value="7.0" step="0.5"></div>
321
- <div class="settings-group"><label>Audio Conditioning (فایل مرجع):</label><input type="file" id="audio_input" accept="audio/*"></div>
322
  </div>
323
 
324
- <div class="checkbox-wrapper inst-wrapper">
325
- <input type="checkbox" id="instrumental_checkbox">
326
- <label for="instrumental_checkbox" style="font-size: 0.95rem; cursor: pointer; color: #2d3748;"><b>حالت بیکلام (Instrumental)</b><br><span style="font-size: 0.8rem; color: #666;">اگر فعال باشد، فقط موزیک خالی ساخته می‌شود.</span></label>
 
 
 
 
 
327
  </div>
328
-
329
- <div class="checkbox-wrapper"><input type="checkbox" id="think_checkbox" checked><label for="think_checkbox" style="font-size: 0.9rem; cursor: pointer;"><b>فعال‌سازی تفکر مدل</b><br><span style="font-size: 0.8rem; color: #666;">افزایش کیفیت آهنگ</span></label></div>
330
  </div>
331
  <button id="processBtn" class="btn-main" style="margin-top: 15px;"><div class="btn-main-content">ساخت آهنگ <span>🎵</span></div></button>
332
  </div>
@@ -342,7 +352,7 @@
342
  <button id="mainDownloadBtn" class="download-btn-style">دانلود آهنگ 📥</button>
343
  </div>
344
  <div id="playerWrapper"></div>
345
- <div class="form-label" style="margin-top: 20px; justify-content: center; color: #718096;">متن آهنگ (همگام‌سازی شده)</div>
346
  <div class="lyrics-container" id="finalLyricsBox"></div>
347
  <button onclick="location.reload()" class="btn-main btn-outline">برگشت و ساخت آهنگ جدید</button>
348
  </div>
@@ -393,6 +403,8 @@
393
  let db;
394
  let songToDeleteId = null;
395
  let userSubscriptionStatus = 'free'; // پیش‌فرض رایگان
 
 
396
 
397
  // المان‌ها
398
  const ideaInput = document.getElementById('ideaInput');
@@ -411,13 +423,15 @@
411
  const statusBadge = document.getElementById('statusBadge');
412
  const upgradeModalTitle = document.getElementById('upgradeModalTitle');
413
  const upgradeModalText = document.getElementById('upgradeModalText');
414
- const audioInput = document.getElementById('audio_input');
415
- const instrumentalCheckbox = document.getElementById('instrumental_checkbox');
 
416
 
417
  const getVal = (id) => document.getElementById(id).value;
418
  const getNum = (id) => Number(document.getElementById(id).value);
419
  const getChk = (id) => document.getElementById(id).checked;
420
 
 
421
  const acc = document.getElementsByClassName("accordion");
422
  for (let i = 0; i < acc.length; i++) {
423
  acc[i].addEventListener("click", function() {
@@ -427,6 +441,15 @@
427
  });
428
  }
429
 
 
 
 
 
 
 
 
 
 
430
  // --- مدیریت اثر انگشت (Fingerprint) ---
431
  function getFingerprint() {
432
  let fp = localStorage.getItem('user_fingerprint');
@@ -437,7 +460,7 @@
437
  return fp;
438
  }
439
 
440
- // --- مدیریت اشتراک و ارتباط با آیفریم مادر ---
441
  function isUserPaid(userObject) {
442
  return userObject && userObject.isLogin && userObject.accessible_pages &&
443
  (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) ||
@@ -469,10 +492,9 @@
469
 
470
  parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
471
 
472
- // مدیریت کلیک دکمه دانلود (تابع امنیتی)
473
  function handleSecureDownload(url, e) {
474
  if (e) e.preventDefault();
475
-
476
  if (userSubscriptionStatus === 'free') {
477
  upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
478
  upgradeModalText.innerText = "برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.";
@@ -482,7 +504,6 @@
482
  type: 'INITIATE_DOWNLOAD_FROM_URL',
483
  payload: { audioUrl: url }
484
  }, '*');
485
-
486
  const btn = e ? e.target.closest('button') : null;
487
  if(btn) {
488
  const originalText = btn.innerHTML;
@@ -493,26 +514,17 @@
493
  }
494
 
495
  document.getElementById('btnGoPremium').addEventListener('click', () => {
496
- parent.postMessage({
497
- type: 'NAVIGATE_TO_PREMIUM',
498
- payload: { url: PREMIUM_URL }
499
- }, '*');
500
  });
501
 
502
- function closeUpgradeModal() {
503
- upgradeModal.classList.remove('open');
504
- }
505
 
506
- // --- مدیریت دیتابیس ---
507
  function initDB() {
508
- const request = indexedDB.open("alphaMusicDB", 2); // افزایش ورژن برای پشتیبانی بهتر
509
  request.onerror = (event) => console.error("IndexedDB error:", event);
510
  request.onsuccess = (event) => { db = event.target.result; loadHistory(); };
511
- request.onupgradeneeded = (event) => {
512
- if (!event.target.result.objectStoreNames.contains("songs")) {
513
- event.target.result.createObjectStore("songs", { keyPath: "id" });
514
- }
515
- };
516
  }
517
 
518
  async function saveToHistory(idea, lyrics, audioUrl) {
@@ -526,7 +538,6 @@
526
  const transaction = db.transaction(["songs"], "readwrite");
527
  const store = transaction.objectStore("songs");
528
  store.add(newItem);
529
-
530
  const countReq = store.count();
531
  countReq.onsuccess = () => {
532
  if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
@@ -560,77 +571,128 @@
560
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>
561
  </div>
562
  `;
563
- div.onclick = (e) => {
564
- if (!e.target.closest('.h-delete')) {
565
- openHistoryItem(item);
566
- }
567
- };
568
  historyList.appendChild(div);
569
  });
570
  };
571
  }
572
 
573
  function askToDelete(event, id) {
574
- event.stopPropagation();
575
- songToDeleteId = id;
576
- deleteModal.classList.add('open');
577
- }
578
-
579
- function closeDeleteModal() {
580
- deleteModal.classList.remove('open');
581
- songToDeleteId = null;
582
  }
583
-
584
  function confirmDelete() {
585
  if (songToDeleteId && db) {
586
  const transaction = db.transaction(["songs"], "readwrite");
587
  const store = transaction.objectStore("songs");
588
  store.delete(songToDeleteId);
589
- transaction.oncomplete = () => {
590
- loadHistory();
591
- closeDeleteModal();
592
- };
593
  }
594
  }
595
-
596
- deleteModal.addEventListener('click', (e) => {
597
- if (e.target === deleteModal) closeDeleteModal();
598
- });
599
- upgradeModal.addEventListener('click', (e) => {
600
- if (e.target === upgradeModal) closeUpgradeModal();
601
- });
602
 
603
  function openHistoryItem(item) {
604
- step1.style.display = 'none';
605
- historySection.style.display = 'none';
606
-
607
  const audioURL = URL.createObjectURL(item.audioBlob);
608
  const headerText = document.getElementById('resultHeaderText');
609
  headerText.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg> آهنگ آرشیو شده`;
610
  headerText.style.color = 'var(--accent-primary)';
611
-
612
  mainDownloadBtn.style.display = 'inline-flex';
613
  mainDownloadBtn.onclick = (e) => handleSecureDownload(audioURL, e);
614
-
615
- // در تاریخچه، چون فایل VTT نداریم، فقط متن را نمایش می‌دهیم
616
  playerWrapper.innerHTML = `<audio controls autoplay src="${audioURL}"></audio>`;
617
  finalLyricsBox.innerHTML = formatLyrics(item.lyrics);
618
-
619
  finalResult.style.display = 'block';
620
  window.scrollTo({ top: 0, behavior: 'smooth' });
621
  }
622
 
623
  initDB();
624
 
625
- // --- تابع آپلود فایل برای Audio Conditioning ---
626
- async function uploadFile(file) {
627
  const formData = new FormData();
628
  formData.append('files', file);
629
- // استفاده از آدرس مستقیم اسپیس برای آپلود
630
- const uploadRes = await fetch(`${ACE_SPACE_URL}upload`, { method: 'POST', body: formData });
631
- if (!uploadRes.ok) throw new Error("آپلود فایل صوتی ناموفق بود");
632
- const result = await uploadRes.json();
633
- return result[0]; // بازگشت مسیر فایل در سرور
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
634
  }
635
 
636
  // --- فرآیند اصلی ---
@@ -643,15 +705,22 @@
643
  loader.style.display = 'block';
644
 
645
  try {
646
- // 1. آپلود فایل صوتی اگر انتخاب شده باشد
647
- let uploadedPath = null;
648
- if (audioInput.files.length > 0) {
649
- loaderText.innerText = "در حال آپلود فایل صوتی...";
650
- uploadedPath = await uploadFile(audioInput.files[0]);
 
 
 
 
 
 
651
  }
652
 
653
- // 2. ساخت متن آهنگ (در صورت بی‌کلام بودن، متن خالی در نظر گرفته می‌شود برای ساخت اما لاگیک بک‌اند اجرا می‌شود)
654
  loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
 
655
 
656
  const response = await fetch('/api/refine', {
657
  method: 'POST',
@@ -659,16 +728,13 @@
659
  body: JSON.stringify({
660
  idea: ideaInput.value,
661
  fingerprint: getFingerprint(),
662
- is_premium: userSubscriptionStatus === 'paid'
 
663
  })
664
  });
665
 
666
  if (response.status === 429) {
667
- loader.style.display = 'none';
668
- step1.style.display = 'block';
669
- historySection.style.display = 'block';
670
- processBtn.disabled = false;
671
-
672
  upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
673
  upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت آهنگ‌های بیشتر و نامحدود، لطفا حساب خود را ارتقا دهید.";
674
  upgradeModal.classList.add('open');
@@ -680,32 +746,33 @@
680
 
681
  let { lyrics, musicPrompt } = data;
682
 
683
- // اگ�� کاربر "بی‌کلام" را انتخاب کرده باشد، متن ارسالی به مدل را خالی می‌کنیم
684
- const isInstrumental = getChk('instrumental_checkbox');
685
- const lyricsPayload = isInstrumental ? "" : lyrics;
 
686
 
687
  loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
688
 
689
- // ساخت پی‌لود مطابق با نسخه 1.5
690
- // ایندکس 2: فایل صوتی کاندیشنینگ (آپلود شده)
691
- // ایندکس 5: متن آهنگ (اگر اینسترومنتال باشد خالی است)
692
  const payload = [
693
- getVal('model_select'), // 0
694
- "custom", // 1
695
- uploadedPath, // 2: مسیر فایل صوتی برای Audio Conditioning
696
  "unknown", // 3
697
- musicPrompt, // 4
698
- lyricsPayload, // 5: متن آهنگ
699
- 0, "", "", "unknown",
700
- getNum('steps_input'), // 10
701
- getNum('cfg_input'), // 11
702
- true, // 12: احتمالا فعال‌سازی کاندیشنینگ یا پارامتر ثابت
703
- getNum('seed_input'), // 13
704
- null, -1,
 
705
  getNum('batch_size'), // 16
706
  null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
707
  1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
708
- getChk('think_checkbox'), 32, 0, 0.9, "NO USER INPUT", true, true, true, null, false, true, false, false, 0.5, 8, null, [], false, null, null, null, null
709
  ];
710
 
711
  const session_hash = Math.random().toString(36).substring(2);
@@ -719,156 +786,91 @@
719
  else if (msg.msg === 'process_completed') {
720
  eventSource.close();
721
  loader.style.display = 'none';
722
- handleAudioOutput(msg.output, lyrics, ideaInput.value);
723
  }
724
  };
725
  eventSource.onerror = () => { throw new Error('ارتباط با سرور قطع شد.'); };
726
 
727
  } catch (e) {
728
  alert("خطا: " + e.message);
729
- loader.style.display = 'none';
730
- step1.style.display = 'block';
731
- historySection.style.display = 'block';
732
- processBtn.disabled = false;
733
  }
734
  });
735
 
736
- // --- پردازش خروجی و کارائوکه ---
737
- async function handleAudioOutput(data, lyricsText, idea) {
738
- let audioUrl = null;
739
  let vttUrl = null;
740
 
 
741
  function traverse(obj) {
742
- if (typeof obj === 'string' && obj.includes('/file=')) {
743
- if (obj.endsWith('.mp3') || obj.endsWith('.wav')) audioUrl = obj;
744
- if (obj.endsWith('.vtt')) vttUrl = obj;
745
- }
746
- else if (obj && typeof obj === 'object') {
747
- if (obj.url) {
748
- if (obj.url.endsWith('.mp3') || obj.url.endsWith('.wav')) audioUrl = obj.url;
749
- if (obj.url.endsWith('.vtt')) vttUrl = obj.url;
750
- }
751
  Object.values(obj).forEach(traverse);
752
  }
753
  }
754
- traverse(data);
755
 
756
- if (audioUrl) {
757
- // تصحیح آدرس‌های نسبی
758
- if (!audioUrl.startsWith('http')) audioUrl = ACE_SPACE_URL.replace(/\/$/, '') + audioUrl;
759
- if (vttUrl && !vttUrl.startsWith('http')) vttUrl = ACE_SPACE_URL.replace(/\/$/, '') + vttUrl;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760
 
 
 
 
 
 
761
  const headerText = document.getElementById('resultHeaderText');
762
  headerText.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>آهنگ جدید آماده شد`;
763
  headerText.style.color = 'var(--success-color)';
764
-
765
  finalResult.style.display = 'block';
766
- mainDownloadBtn.style.display = 'inline-flex';
767
- mainDownloadBtn.onclick = (e) => handleSecureDownload(audioUrl, e);
768
 
769
- // ایجاد پلیر
770
- playerWrapper.innerHTML = '';
771
- const audioEl = document.createElement('audio');
772
- audioEl.controls = true;
773
- audioEl.autoplay = true;
774
- audioEl.src = audioUrl;
775
- playerWrapper.appendChild(audioEl);
776
-
777
- // پردازش متن و کارائوکه
778
- if (vttUrl && !getChk('instrumental_checkbox')) {
779
- try {
780
- const vttResp = await fetch(vttUrl);
781
- const vttText = await vttResp.text();
782
- setupKaraoke(vttText, audioEl);
783
- } catch(e) {
784
- console.error("خطا در دریافت زیرنویس", e);
785
- finalLyricsBox.innerHTML = formatLyrics(lyricsText);
786
- }
787
  } else {
788
- finalLyricsBox.innerHTML = formatLyrics(lyricsText);
 
 
 
 
 
789
  }
790
-
791
- saveToHistory(idea, lyricsText, audioUrl);
792
  window.scrollTo({ top: 0, behavior: 'smooth' });
793
  } else {
794
  alert("فایل صوتی یافت نشد!");
795
- step1.style.display = 'block';
796
- historySection.style.display = 'block';
797
- processBtn.disabled = false;
798
  }
799
  }
800
 
801
- // --- سیستم کارائوکه ---
802
- function parseVTTTime(timeStr) {
803
- // فرمت: 00:00:16.320
804
- const parts = timeStr.split(':');
805
- const secondsParts = parts[2].split('.');
806
- return (parseInt(parts[0]) * 3600) + (parseInt(parts[1]) * 60) + parseInt(secondsParts[0]) + (parseInt(secondsParts[1]) / 1000);
807
- }
808
-
809
- function setupKaraoke(vttData, audioElement) {
810
- finalLyricsBox.innerHTML = '';
811
- const lines = vttData.split('\n');
812
- const cues = [];
813
-
814
- // پارس کردن ساده VTT
815
- let currentCue = null;
816
- for(let line of lines) {
817
- line = line.trim();
818
- if(!line || line === 'WEBVTT') continue;
819
-
820
- if(line.includes('-->')) {
821
- const times = line.split('-->');
822
- currentCue = {
823
- start: parseVTTTime(times[0].trim()),
824
- end: parseVTTTime(times[1].trim()),
825
- text: ''
826
- };
827
- } else if (currentCue) {
828
- // اگر خط عدد خالی بود (شماره کیو) رد شو
829
- if (!isNaN(line) && line.length < 5) continue;
830
- currentCue.text = line;
831
- cues.push(currentCue);
832
- currentCue = null;
833
- }
834
- }
835
-
836
- // ساخت المان‌های HTML
837
- cues.forEach((cue, index) => {
838
- const p = document.createElement('div');
839
- p.className = 'lyric-line';
840
- p.innerText = cue.text;
841
- p.dataset.start = cue.start;
842
- p.dataset.end = cue.end;
843
- p.id = `cue-${index}`;
844
- // هندل کردن تگ‌ها در نمایش
845
- if (cue.text.startsWith('[')) {
846
- p.className += ' lyrics-tag';
847
- p.style.color = 'var(--accent-secondary)';
848
- }
849
- finalLyricsBox.appendChild(p);
850
- });
851
-
852
- // همگام‌سازی زمان پخش
853
- audioElement.addEventListener('timeupdate', () => {
854
- const time = audioElement.currentTime;
855
- cues.forEach((cue, index) => {
856
- const el = document.getElementById(`cue-${index}`);
857
- if (time >= cue.start && time <= cue.end) {
858
- if (!el.classList.contains('active')) {
859
- // حذف اکتیو قبلی‌ها
860
- document.querySelectorAll('.lyric-line.active').forEach(e => e.classList.remove('active'));
861
- el.classList.add('active');
862
- // اسکرول نرم به خط فعال
863
- el.scrollIntoView({ behavior: 'smooth', block: 'center' });
864
- }
865
- }
866
- });
867
- });
868
- }
869
-
870
  function formatLyrics(text) {
871
- if(!text) return "متن در دسترس نیست";
872
  return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>');
873
  }
874
 
 
1
+ <!-- START OF FILE index.html -->
2
  <!DOCTYPE html>
3
  <html lang="fa" dir="rtl">
4
  <head>
 
100
  font-family: inherit; font-size: 1rem; color: #2d3748;
101
  outline: none; transition: border-color 0.3s;
102
  }
 
 
 
 
 
 
 
 
 
103
  textarea { min-height: 120px; resize: vertical; }
104
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
105
 
106
+ /* فایل آپلود */
107
+ .file-upload-wrapper {
108
+ position: relative;
109
+ background: var(--input-bg);
110
+ border: 2px dashed var(--input-border);
111
+ border-radius: 12px;
112
+ padding: 20px;
113
+ text-align: center;
114
+ transition: 0.3s;
115
+ cursor: pointer;
116
+ }
117
+ .file-upload-wrapper:hover { border-color: var(--accent-primary); background: #fff; }
118
+ .file-upload-wrapper input[type="file"] {
119
+ position: absolute; width: 100%; height: 100%; top: 0; left: 0; opacity: 0; cursor: pointer;
120
+ }
121
+ .file-info { font-size: 0.9rem; color: var(--text-secondary); pointer-events: none; }
122
+
123
  /* دکمه اصلی */
124
  @keyframes move-gradient {
125
  0% { background-position: 0% 50%; }
 
168
  margin-bottom: 0; border: 1px solid var(--input-border); border-top: none;
169
  }
170
  .settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; padding: 15px 0; }
171
+ .checkbox-wrapper { display: flex; align-items: center; gap: 10px; margin-top: 5px; padding-bottom: 15px; border-top: 1px solid #e2e8f0; padding-top: 15px; }
172
  .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
173
+ .full-width { grid-column: span 2; }
 
 
 
 
174
 
175
  /* پلیر و متن */
176
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
 
187
  .audio-item { margin-bottom: 10px; }
188
  audio { width: 100%; height: 45px; border-radius: 20px; margin-top: 5px; }
189
 
190
+ /* استایل متن آهنگ (لیریکس) */
191
  .lyrics-container {
192
  background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto;
193
+ white-space: pre-wrap; line-height: 2.2; font-size: 1rem; color: #718096; text-align: center; border: 1px solid var(--input-border); margin-top: 15px;
194
+ position: relative;
195
  scroll-behavior: smooth;
196
  }
197
+ .lyrics-line {
198
+ display: block; margin-bottom: 12px; transition: all 0.3s ease; border-radius: 8px; padding: 2px 10px;
 
 
 
 
 
 
199
  }
200
+ .lyrics-line.active {
201
  color: var(--accent-primary);
202
  font-weight: 800;
203
+ transform: scale(1.05);
204
+ background-color: rgba(74, 108, 250, 0.05);
205
+ text-shadow: 0 0 1px rgba(74, 108, 250, 0.3);
 
206
  }
207
+ .lyrics-tag { color: var(--accent-secondary); font-size: 0.85em; margin-bottom: 5px; opacity: 0.8; }
208
 
209
  /* لودر */
210
  #loader { display: none; text-align: center; padding: 20px; }
 
315
  <div class="settings-group"><label>تعداد خروجی (آهنگ):</label><input type="number" id="batch_size" value="1" min="1" max="4"></div>
316
  <div class="settings-group"><label>تعداد گام (Steps):</label><input type="number" id="steps_input" value="20" min="4" max="50"></div>
317
  <div class="settings-group"><label>سید (Seed) تصادفی=-1:</label><input type="number" id="seed_input" value="-1"></div>
318
+
319
+ <!-- بخش جدید آپلود Audio Conditioning -->
320
+ <div class="settings-group full-width">
321
+ <label>Audio Conditioning (آپلود آهنگ نمونه - اختیاری):</label>
322
+ <div class="file-upload-wrapper" id="dropZone">
323
+ <input type="file" id="audio_ref_input" accept="audio/*">
324
+ <span id="file_info_text" class="file-info">برای آپلود کلیک کنید یا فایل را اینجا رها کنید (MP3/WAV)</span>
325
+ </div>
326
+ </div>
327
+
328
  <div class="settings-group"><label>مقیاس هدایت (CFG):</label><input type="number" id="cfg_input" value="7.0" step="0.5"></div>
 
329
  </div>
330
 
331
+ <div class="checkbox-wrapper">
332
+ <input type="checkbox" id="think_checkbox" checked>
333
+ <label for="think_checkbox" style="font-size: 0.9rem; cursor: pointer;"><b>فعال‌سازی تفکر مدل</b> <span style="font-size: 0.8rem; color: #666;">(افزایش کیفیت)</span></label>
334
+ </div>
335
+ <!-- بخش جدید Instrumental -->
336
+ <div class="checkbox-wrapper" style="border-top: none; padding-top: 0;">
337
+ <input type="checkbox" id="instrumental_mode">
338
+ <label for="instrumental_mode" style="font-size: 0.9rem; cursor: pointer;"><b>حالت بی‌کلام (Instrumental)</b> <span style="font-size: 0.8rem; color: #666;">(بدون خواننده)</span></label>
339
  </div>
 
 
340
  </div>
341
  <button id="processBtn" class="btn-main" style="margin-top: 15px;"><div class="btn-main-content">ساخت آهنگ <span>🎵</span></div></button>
342
  </div>
 
352
  <button id="mainDownloadBtn" class="download-btn-style">دانلود آهنگ 📥</button>
353
  </div>
354
  <div id="playerWrapper"></div>
355
+ <div class="form-label" style="margin-top: 20px; justify-content: center; color: #718096;">متن آهنگ</div>
356
  <div class="lyrics-container" id="finalLyricsBox"></div>
357
  <button onclick="location.reload()" class="btn-main btn-outline">برگشت و ساخت آهنگ جدید</button>
358
  </div>
 
403
  let db;
404
  let songToDeleteId = null;
405
  let userSubscriptionStatus = 'free'; // پیش‌فرض رایگان
406
+ let uploadedAudioPath = null; // مسیر فایل آپلود شده
407
+ let lyricsSyncData = []; // داده‌های زمانی متن آهنگ
408
 
409
  // المان‌ها
410
  const ideaInput = document.getElementById('ideaInput');
 
423
  const statusBadge = document.getElementById('statusBadge');
424
  const upgradeModalTitle = document.getElementById('upgradeModalTitle');
425
  const upgradeModalText = document.getElementById('upgradeModalText');
426
+ const audioRefInput = document.getElementById('audio_ref_input');
427
+ const fileInfoText = document.getElementById('file_info_text');
428
+ const instrumentalMode = document.getElementById('instrumental_mode');
429
 
430
  const getVal = (id) => document.getElementById(id).value;
431
  const getNum = (id) => Number(document.getElementById(id).value);
432
  const getChk = (id) => document.getElementById(id).checked;
433
 
434
+ // --- مدیریت رابط کاربری (UI) ---
435
  const acc = document.getElementsByClassName("accordion");
436
  for (let i = 0; i < acc.length; i++) {
437
  acc[i].addEventListener("click", function() {
 
441
  });
442
  }
443
 
444
+ // تغییر متن فایل انتخاب شده
445
+ audioRefInput.addEventListener('change', function() {
446
+ if (this.files && this.files[0]) {
447
+ fileInfoText.innerText = "فایل انتخاب شد: " + this.files[0].name;
448
+ fileInfoText.style.color = "var(--success-color)";
449
+ fileInfoText.style.fontWeight = "bold";
450
+ }
451
+ });
452
+
453
  // --- مدیریت اثر انگشت (Fingerprint) ---
454
  function getFingerprint() {
455
  let fp = localStorage.getItem('user_fingerprint');
 
460
  return fp;
461
  }
462
 
463
+ // --- مدیریت اشتراک ---
464
  function isUserPaid(userObject) {
465
  return userObject && userObject.isLogin && userObject.accessible_pages &&
466
  (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) ||
 
492
 
493
  parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
494
 
495
+ // --- توابع کمکی ---
496
  function handleSecureDownload(url, e) {
497
  if (e) e.preventDefault();
 
498
  if (userSubscriptionStatus === 'free') {
499
  upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
500
  upgradeModalText.innerText = "برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.";
 
504
  type: 'INITIATE_DOWNLOAD_FROM_URL',
505
  payload: { audioUrl: url }
506
  }, '*');
 
507
  const btn = e ? e.target.closest('button') : null;
508
  if(btn) {
509
  const originalText = btn.innerHTML;
 
514
  }
515
 
516
  document.getElementById('btnGoPremium').addEventListener('click', () => {
517
+ parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
 
 
 
518
  });
519
 
520
+ function closeUpgradeModal() { upgradeModal.classList.remove('open'); }
 
 
521
 
522
+ // --- دیتابیس و تاریخچه ---
523
  function initDB() {
524
+ const request = indexedDB.open("alphaMusicDB", 1);
525
  request.onerror = (event) => console.error("IndexedDB error:", event);
526
  request.onsuccess = (event) => { db = event.target.result; loadHistory(); };
527
+ request.onupgradeneeded = (event) => { event.target.result.createObjectStore("songs", { keyPath: "id" }); };
 
 
 
 
528
  }
529
 
530
  async function saveToHistory(idea, lyrics, audioUrl) {
 
538
  const transaction = db.transaction(["songs"], "readwrite");
539
  const store = transaction.objectStore("songs");
540
  store.add(newItem);
 
541
  const countReq = store.count();
542
  countReq.onsuccess = () => {
543
  if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
 
571
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>
572
  </div>
573
  `;
574
+ div.onclick = (e) => { if (!e.target.closest('.h-delete')) openHistoryItem(item); };
 
 
 
 
575
  historyList.appendChild(div);
576
  });
577
  };
578
  }
579
 
580
  function askToDelete(event, id) {
581
+ event.stopPropagation(); songToDeleteId = id; deleteModal.classList.add('open');
 
 
 
 
 
 
 
582
  }
583
+ function closeDeleteModal() { deleteModal.classList.remove('open'); songToDeleteId = null; }
584
  function confirmDelete() {
585
  if (songToDeleteId && db) {
586
  const transaction = db.transaction(["songs"], "readwrite");
587
  const store = transaction.objectStore("songs");
588
  store.delete(songToDeleteId);
589
+ transaction.oncomplete = () => { loadHistory(); closeDeleteModal(); };
 
 
 
590
  }
591
  }
592
+ deleteModal.addEventListener('click', (e) => { if (e.target === deleteModal) closeDeleteModal(); });
593
+ upgradeModal.addEventListener('click', (e) => { if (e.target === upgradeModal) closeUpgradeModal(); });
 
 
 
 
 
594
 
595
  function openHistoryItem(item) {
596
+ step1.style.display = 'none'; historySection.style.display = 'none';
 
 
597
  const audioURL = URL.createObjectURL(item.audioBlob);
598
  const headerText = document.getElementById('resultHeaderText');
599
  headerText.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg> آهنگ آرشیو شده`;
600
  headerText.style.color = 'var(--accent-primary)';
 
601
  mainDownloadBtn.style.display = 'inline-flex';
602
  mainDownloadBtn.onclick = (e) => handleSecureDownload(audioURL, e);
 
 
603
  playerWrapper.innerHTML = `<audio controls autoplay src="${audioURL}"></audio>`;
604
  finalLyricsBox.innerHTML = formatLyrics(item.lyrics);
 
605
  finalResult.style.display = 'block';
606
  window.scrollTo({ top: 0, behavior: 'smooth' });
607
  }
608
 
609
  initDB();
610
 
611
+ // --- توابع آپلود و پردازش صدا ---
612
+ async function uploadAudioFile(file) {
613
  const formData = new FormData();
614
  formData.append('files', file);
615
+ // تولید شناسه آپلود تصادفی
616
+ const uploadId = Math.random().toString(36).substring(2);
617
+
618
+ const response = await fetch(`${ACE_SPACE_URL}upload?upload_id=${uploadId}`, {
619
+ method: 'POST',
620
+ body: formData
621
+ });
622
+
623
+ if (!response.ok) throw new Error("آپلود فایل صوتی با خطا مواجه شد.");
624
+
625
+ const data = await response.json();
626
+ // خروجی به صورت ["/tmp/gradio/..."] است
627
+ return data[0];
628
+ }
629
+
630
+ // --- پردازش VTT برای متن هماهنگ ---
631
+ async function fetchAndParseVTT(vttUrl) {
632
+ try {
633
+ // اگر URL نسبی است، دامنه را اضافه کن
634
+ const fullUrl = vttUrl.startsWith('http') ? vttUrl : ACE_SPACE_URL.replace(/\/$/, '') + vttUrl;
635
+ const response = await fetch(fullUrl);
636
+ const text = await response.text();
637
+
638
+ lyricsSyncData = [];
639
+ // رگکس ساده برای پارس کردن فرمت WEBVTT
640
+ // 00:00:16.320 --> 00:00:20.640
641
+ const regex = /(\d{2}:\d{2}:\d{2}\.\d{3})\s-->\s(\d{2}:\d{2}:\d{2}\.\d{3})\s*\n(.*?)(?=\n\n|\n\d|$)/gs;
642
+ let match;
643
+
644
+ while ((match = regex.exec(text)) !== null) {
645
+ const start = parseTime(match[1]);
646
+ const end = parseTime(match[2]);
647
+ const content = match[3].trim();
648
+ if (content) {
649
+ lyricsSyncData.push({ start, end, text: content });
650
+ }
651
+ }
652
+
653
+ // رندر کردن مجدد متن آهنگ با قابلیت سینک
654
+ renderSyncedLyrics();
655
+ } catch (e) {
656
+ console.error("Error parsing subtitles:", e);
657
+ }
658
+ }
659
+
660
+ function parseTime(timeStr) {
661
+ const parts = timeStr.split(':');
662
+ const seconds = parts[2].split('.');
663
+ return (parseInt(parts[0]) * 3600) + (parseInt(parts[1]) * 60) + parseInt(seconds[0]) + (parseInt(seconds[1]) / 1000);
664
+ }
665
+
666
+ function renderSyncedLyrics() {
667
+ finalLyricsBox.innerHTML = '';
668
+ if (lyricsSyncData.length === 0) return;
669
+
670
+ lyricsSyncData.forEach((line, index) => {
671
+ const div = document.createElement('div');
672
+ div.className = 'lyrics-line';
673
+ div.id = `lyric-${index}`;
674
+ div.innerHTML = line.text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>');
675
+ finalLyricsBox.appendChild(div);
676
+ });
677
+ }
678
+
679
+ function syncLyricsToAudio(currentTime) {
680
+ if (!lyricsSyncData.length) return;
681
+
682
+ // پیدا کردن خط فعلی
683
+ const currentLineIndex = lyricsSyncData.findIndex(line => currentTime >= line.start && currentTime <= line.end);
684
+
685
+ // حذف کلاس active از همه
686
+ document.querySelectorAll('.lyrics-line').forEach(el => el.classList.remove('active'));
687
+
688
+ if (currentLineIndex !== -1) {
689
+ const activeEl = document.getElementById(`lyric-${currentLineIndex}`);
690
+ if (activeEl) {
691
+ activeEl.classList.add('active');
692
+ // اسکرول نرم به خط فعال
693
+ activeEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
694
+ }
695
+ }
696
  }
697
 
698
  // --- فرآیند اصلی ---
 
705
  loader.style.display = 'block';
706
 
707
  try {
708
+ // 1. آپلود فایل اگر وجود داشته باشد
709
+ if (audioRefInput.files.length > 0) {
710
+ loaderText.innerText = "در حال آپلود فایل صوتی شما...";
711
+ try {
712
+ uploadedAudioPath = await uploadAudioFile(audioRefInput.files[0]);
713
+ } catch (e) {
714
+ alert("خطا در آپلود فایل: " + e.message);
715
+ throw e;
716
+ }
717
+ } else {
718
+ uploadedAudioPath = null;
719
  }
720
 
721
+ // 2. دریافت پرامپت و متن از Gemini
722
  loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
723
+ const isInstrumental = getChk('instrumental_mode');
724
 
725
  const response = await fetch('/api/refine', {
726
  method: 'POST',
 
728
  body: JSON.stringify({
729
  idea: ideaInput.value,
730
  fingerprint: getFingerprint(),
731
+ is_premium: userSubscriptionStatus === 'paid',
732
+ is_instrumental: isInstrumental // ارسال وضعیت اینسترومنتال
733
  })
734
  });
735
 
736
  if (response.status === 429) {
737
+ loader.style.display = 'none'; step1.style.display = 'block'; historySection.style.display = 'block'; processBtn.disabled = false;
 
 
 
 
738
  upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
739
  upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت آهنگ‌های بیشتر و نامحدود، لطفا حساب خود را ارتقا دهید.";
740
  upgradeModal.classList.add('open');
 
746
 
747
  let { lyrics, musicPrompt } = data;
748
 
749
+ // اگر کاربر Instrumental خواسته، متن را خالی رد می‌کنیم تا مدل نخواند
750
+ if (isInstrumental) {
751
+ lyrics = "";
752
+ }
753
 
754
  loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
755
 
756
+ // ساخت Payload برای نسخه ACE-Step-v1.5
757
+ // جایگذاری uploadedAudioPath در ایندکس 14 ایگزین null)
 
758
  const payload = [
759
+ getVal('model_select'), // 0: Model
760
+ "custom", // 1: Prompt type
761
+ null, // 2
762
  "unknown", // 3
763
+ musicPrompt, // 4: Music Prompt
764
+ lyrics, // 5: Lyrics
765
+ 0, "", "", "unknown", // 6-9
766
+ getNum('steps_input'), // 10: Steps
767
+ getNum('cfg_input'), // 11: CFG
768
+ true, // 12
769
+ getNum('seed_input'), // 13: Seed
770
+ uploadedAudioPath, // 14: Audio Conditioning (null if empty)
771
+ -1, // 15
772
  getNum('batch_size'), // 16
773
  null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
774
  1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
775
+ getChk('think_checkbox'), 2, 0, 0.9, "NO USER INPUT", true, true, true, null, false, true, false, false, 0.5, 8, null, [], false, null, null, null, null
776
  ];
777
 
778
  const session_hash = Math.random().toString(36).substring(2);
 
786
  else if (msg.msg === 'process_completed') {
787
  eventSource.close();
788
  loader.style.display = 'none';
789
+ handleAudioOutput(msg.output, lyrics, ideaInput.value, isInstrumental);
790
  }
791
  };
792
  eventSource.onerror = () => { throw new Error('ارتباط با سرور قطع شد.'); };
793
 
794
  } catch (e) {
795
  alert("خطا: " + e.message);
796
+ loader.style.display = 'none'; step1.style.display = 'block'; historySection.style.display = 'block'; processBtn.disabled = false;
 
 
 
797
  }
798
  });
799
 
800
+ function handleAudioOutput(data, lyrics, idea, isInstrumental) {
801
+ const processedUrls = new Set();
802
+ let hasResult = false;
803
  let vttUrl = null;
804
 
805
+ // تابع بازگشتی برای پیدا کردن فایل‌ها
806
  function traverse(obj) {
807
+ if (typeof obj === 'string') {
808
+ if (obj.includes('/file=') && obj.endsWith('.mp3')) addAudio(obj);
809
+ if ((obj.includes('/file=') || obj.endsWith('.vtt')) && obj.includes('subtitles')) vttUrl = obj;
810
+ } else if (obj && typeof obj === 'object') {
811
+ if (obj.url && obj.url.endsWith('.mp3')) addAudio(obj.url);
812
+ if (obj.url && obj.url.endsWith('.vtt')) vttUrl = obj.url;
 
 
 
813
  Object.values(obj).forEach(traverse);
814
  }
815
  }
 
816
 
817
+ function addAudio(url) {
818
+ const fullUrl = url.startsWith('http') ? url : ACE_SPACE_URL.replace(/\/$/, '') + url;
819
+ if (processedUrls.has(fullUrl)) return;
820
+ processedUrls.add(fullUrl);
821
+ hasResult = true;
822
+
823
+ if (processedUrls.size === 1) {
824
+ mainDownloadBtn.style.display = 'inline-flex';
825
+ mainDownloadBtn.onclick = (e) => handleSecureDownload(fullUrl, e);
826
+ saveToHistory(idea, lyrics, fullUrl);
827
+ }
828
+
829
+ // ایجاد پلیر و اتصال رویداد timeupdate برای سینک کردن متن
830
+ const div = document.createElement('div');
831
+ div.className = 'audio-item';
832
+ const audio = document.createElement('audio');
833
+ audio.controls = true;
834
+ audio.autoplay = true;
835
+ audio.src = fullUrl;
836
+
837
+ // اگر فایل زیرنویس پیدا شده، رویداد سینک را وصل کن
838
+ audio.ontimeupdate = () => syncLyricsToAudio(audio.currentTime);
839
+
840
+ div.appendChild(audio);
841
+ playerWrapper.appendChild(div);
842
+ }
843
 
844
+ // پاک کردن پلیر قبلی
845
+ playerWrapper.innerHTML = '';
846
+ traverse(data);
847
+
848
+ if (hasResult) {
849
  const headerText = document.getElementById('resultHeaderText');
850
  headerText.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>آهنگ جدید آماده شد`;
851
  headerText.style.color = 'var(--success-color)';
 
852
  finalResult.style.display = 'block';
 
 
853
 
854
+ if (isInstrumental) {
855
+ finalLyricsBox.innerHTML = '<div style="margin-top:20px; font-weight:bold;">(موسیقی بی‌کلام / Instrumental)</div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
856
  } else {
857
+ // نمایش اولیه متن (اگر VTT پیدا نشد، فرمت ساده)
858
+ finalLyricsBox.innerHTML = formatLyrics(lyrics);
859
+ // اگر فایل VTT پیدا شد، آن را لود و جایگزین کن
860
+ if (vttUrl) {
861
+ fetchAndParseVTT(vttUrl);
862
+ }
863
  }
864
+
 
865
  window.scrollTo({ top: 0, behavior: 'smooth' });
866
  } else {
867
  alert("فایل صوتی یافت نشد!");
868
+ step1.style.display = 'block'; historySection.style.display = 'block';
 
 
869
  }
870
  }
871
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
872
  function formatLyrics(text) {
873
+ // فرمت ساده برای وقتی که VTT هنوز لود نشده یا وجود ندارد
874
  return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>');
875
  }
876