Opera8 commited on
Commit
3df8f9b
·
verified ·
1 Parent(s): e6fdeb9

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +46 -128
index.html CHANGED
@@ -66,7 +66,6 @@
66
  }
67
  .subtitle { font-size: 0.9rem; color: var(--text-secondary); margin-top: 5px; margin-bottom: 10px; }
68
 
69
- /* استایل بج وضعیت اشتراک */
70
  .status-badge {
71
  display: inline-block; padding: 6px 16px; border-radius: 20px;
72
  font-size: 0.85rem; font-weight: 700; margin-top: 5px;
@@ -102,10 +101,8 @@
102
  textarea { min-height: 120px; resize: vertical; }
103
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
104
 
105
- /* استایل اختصاصی فایل آپلود */
106
  input[type="file"] { padding: 10px; font-size: 0.9rem; background: #fff; }
107
 
108
- /* دکمه اصلی */
109
  @keyframes move-gradient {
110
  0% { background-position: 0% 50%; }
111
  50% { background-position: 100% 50%; }
@@ -137,7 +134,6 @@
137
  }
138
  .btn-outline:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
139
 
140
- /* تنظیمات پیشرفته */
141
  .accordion {
142
  background-color: var(--input-bg); color: var(--text-primary); cursor: pointer; padding: 15px; width: 100%; border: none;
143
  text-align: right; outline: none; font-size: 0.95rem; font-weight: 600; border-radius: 12px;
@@ -157,7 +153,6 @@
157
  .checkbox-wrapper:last-child { border-bottom: none; }
158
  .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
159
 
160
- /* پلیر و متن */
161
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
162
 
163
  .download-btn-style {
@@ -168,44 +163,24 @@
168
  .download-btn-style.locked { background-color: #f1f5f9; color: #94a3b8; }
169
  .download-btn-style.locked:hover { background-color: #e2e8f0; }
170
 
171
- .audio-item { margin-bottom: 10px; width: 100%; }
172
  audio { width: 100%; height: 50px; border-radius: 25px; margin-top: 5px; }
173
 
174
  /* استایل باکس لیریک (برگشت به حالت اول) */
175
  .lyrics-container {
176
- background: var(--input-bg);
177
- border-radius: 16px;
178
- padding: 20px;
179
- max-height: 350px;
180
- overflow-y: auto;
181
- border: 1px solid var(--input-border);
182
- margin-top: 15px;
183
- text-align: center;
184
- scroll-behavior: smooth; /* اسکرول نرم */
185
  }
186
 
187
- /* استایل خطوط متن */
188
- .lyric-line {
189
- margin: 0;
190
- padding: 8px 0;
191
- font-size: 1rem;
192
- color: #4a5568; /* رنگ خاکستری تیره اصلی */
193
- transition: all 0.3s ease;
194
- cursor: default;
195
- line-height: 1.8;
196
- font-weight: 400;
197
- }
198
 
199
  /* استایل خط فعال */
200
  .lyric-line.active {
201
- color: var(--accent-primary); /* آبی اصلی */
202
- font-weight: 800; /* بولد */
203
- transform: scale(1.05); /* کمی بزرگتر */
204
- background: rgba(74, 108, 250, 0.05); /* پس زمینه خیلی محو */
205
- border-radius: 8px;
206
  }
207
 
208
- /* لودر */
209
  #loader { display: none; text-align: center; padding: 20px; }
210
  .wave-bars { display: flex; justify-content: center; gap: 4px; height: 30px; align-items: flex-end; }
211
  .bar { width: 5px; background: var(--accent-primary); animation: jump 1s infinite; border-radius: 2px; }
@@ -214,7 +189,6 @@
214
  .bar:nth-child(4) { animation-delay: 0.3s; height: 50%; }
215
  @keyframes jump { 0%, 100% { height: 20%; } 50% { height: 100%; } }
216
 
217
- /* --- استایل‌های تاریخچه و حذف --- */
218
  #historySection { margin-top: 30px; width: 100%; }
219
  .history-title { font-size: 1.2rem; font-weight: 800; color: var(--text-primary); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
220
  .history-list { display: grid; gap: 12px; }
@@ -239,7 +213,6 @@
239
  .history-card:hover .h-delete { opacity: 1; transform: scale(1); }
240
  .h-delete:hover { background: var(--danger-color); color: white; transform: scale(1.1); }
241
 
242
- /* مودال‌ها */
243
  .modal-overlay {
244
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
245
  background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(8px);
@@ -265,7 +238,6 @@
265
  .btn-confirm { background: var(--danger-color); color: white; box-shadow: 0 5px 15px rgba(229, 62, 62, 0.3); }
266
  .btn-confirm:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(229, 62, 62, 0.4); }
267
 
268
- /* مودال ارتقا */
269
  @keyframes pulse-gold { 0% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); } 70% { box-shadow: 0 0 0 15px rgba(255, 193, 7, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); } }
270
  .upgrade-icon {
271
  width: 70px; height: 70px;
@@ -391,7 +363,6 @@
391
 
392
  <script>
393
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
394
- // مشخصات نسخه پولی
395
  const PREMIUM_PAGE_ID = '1149636';
396
  const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTU0MjI0ZWVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
397
 
@@ -399,7 +370,6 @@
399
  let songToDeleteId = null;
400
  let userSubscriptionStatus = 'free';
401
 
402
- // المان‌ها
403
  const ideaInput = document.getElementById('ideaInput');
404
  const processBtn = document.getElementById('processBtn');
405
  const step1 = document.getElementById('step1');
@@ -416,8 +386,6 @@
416
  const statusBadge = document.getElementById('statusBadge');
417
  const upgradeModalTitle = document.getElementById('upgradeModalTitle');
418
  const upgradeModalText = document.getElementById('upgradeModalText');
419
-
420
- // المان‌های جدید
421
  const audioInput = document.getElementById('audioInput');
422
  const instrumentalChk = document.getElementById('instrumental_chk');
423
 
@@ -453,7 +421,6 @@
453
  if (status === 'paid') {
454
  statusBadge.textContent = 'نسخه نامحدود';
455
  statusBadge.className = 'status-badge visible badge-premium';
456
- mainDownloadBtn.classList.remove('locked');
457
  } else {
458
  statusBadge.textContent = 'نسخه رایگان';
459
  statusBadge.className = 'status-badge visible badge-free';
@@ -476,17 +443,12 @@
476
 
477
  function handleSecureDownload(url, e) {
478
  if (e) e.preventDefault();
479
-
480
  if (userSubscriptionStatus === 'free') {
481
  upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
482
- upgradeModalText.innerText = "برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.";
483
  upgradeModal.classList.add('open');
484
  } else {
485
- parent.postMessage({
486
- type: 'INITIATE_DOWNLOAD_FROM_URL',
487
- payload: { audioUrl: url }
488
- }, '*');
489
-
490
  const btn = e ? e.target.closest('button') : null;
491
  if(btn) {
492
  const originalText = btn.innerHTML;
@@ -497,17 +459,11 @@
497
  }
498
 
499
  document.getElementById('btnGoPremium').addEventListener('click', () => {
500
- parent.postMessage({
501
- type: 'NAVIGATE_TO_PREMIUM',
502
- payload: { url: PREMIUM_URL }
503
- }, '*');
504
  });
505
 
506
- function closeUpgradeModal() {
507
- upgradeModal.classList.remove('open');
508
- }
509
 
510
- // --- مدیریت دیتابیس ---
511
  function initDB() {
512
  const request = indexedDB.open("alphaMusicDB", 1);
513
  request.onerror = (event) => console.error("IndexedDB error:", event);
@@ -519,20 +475,19 @@
519
  try {
520
  const response = await fetch(audioUrl);
521
  const audioBlob = await response.blob();
522
-
523
  const newItem = {
524
  id: Date.now(), idea, lyrics, audioBlob, lrcData,
525
  date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' })
526
  };
527
- const transaction = db.transaction(["songs"], "readwrite");
528
- const store = transaction.objectStore("songs");
529
  store.add(newItem);
530
 
531
  const countReq = store.count();
532
  countReq.onsuccess = () => {
533
  if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
534
  };
535
- transaction.oncomplete = () => loadHistory();
536
  } catch (error) { console.error("Error saving:", error); }
537
  }
538
 
@@ -584,10 +539,9 @@
584
 
585
  function confirmDelete() {
586
  if (songToDeleteId && db) {
587
- const transaction = db.transaction(["songs"], "readwrite");
588
- const store = transaction.objectStore("songs");
589
- store.delete(songToDeleteId);
590
- transaction.oncomplete = () => {
591
  loadHistory();
592
  closeDeleteModal();
593
  };
@@ -617,7 +571,6 @@
617
 
618
  initDB();
619
 
620
- // --- آپلود فایل به گرادیو ---
621
  async function uploadFileToGradio(file) {
622
  const formData = new FormData();
623
  formData.append('files', file);
@@ -632,18 +585,13 @@
632
  return data[0];
633
  }
634
 
635
- // --- تابع کمکی برای درخواست به Gradio ---
636
  async function runGradioFunction(fnIndex, payloadData) {
637
  const session_hash = Math.random().toString(36).substring(2);
638
 
639
  const joinResp = await fetch(`${ACE_SPACE_URL}gradio_api/queue/join`, {
640
  method: 'POST',
641
  headers: { 'Content-Type': 'application/json' },
642
- body: JSON.stringify({
643
- data: payloadData,
644
- fn_index: fnIndex,
645
- session_hash
646
- })
647
  });
648
 
649
  if (!joinResp.ok) throw new Error(`Gradio Call Failed: ${fnIndex}`);
@@ -656,8 +604,6 @@
656
  if (msg.msg === 'process_completed') {
657
  eventSource.close();
658
  resolve(msg.output);
659
- } else if (msg.msg === 'close') {
660
- eventSource.close();
661
  }
662
  };
663
 
@@ -668,7 +614,6 @@
668
  });
669
  }
670
 
671
- // --- فرآیند اصلی ---
672
  processBtn.addEventListener('click', async () => {
673
  if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
674
 
@@ -681,7 +626,6 @@
681
  let uploadedAudioPath = null;
682
  const isInstrumental = instrumentalChk.checked;
683
 
684
- // 1. آپلود فایل در صورت وجود
685
  if (audioInput.files.length > 0) {
686
  loaderText.innerText = "در حال آپلود فایل نمونه...";
687
  uploadedAudioPath = await uploadFileToGradio(audioInput.files[0]);
@@ -690,50 +634,31 @@
690
  let lyrics = "";
691
  let musicPrompt = ideaInput.value;
692
 
693
- // 2. تولید متن و پرامپت (اگر بی‌کلام نباشد)
694
  if (!isInstrumental) {
695
  loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
696
 
697
  const response = await fetch('/api/refine', {
698
  method: 'POST',
699
  headers: {'Content-Type': 'application/json'},
700
- body: JSON.stringify({
701
- idea: ideaInput.value,
702
- fingerprint: getFingerprint(),
703
- is_premium: userSubscriptionStatus === 'paid'
704
- })
705
  });
706
 
707
- if (response.status === 429) {
708
- throw { isLimit: true };
709
- }
710
-
711
  const data = await response.json();
712
  if (data.error) throw new Error(data.error);
713
-
714
  lyrics = data.lyrics;
715
  musicPrompt = data.music_prompt;
716
  } else {
717
- loaderText.innerText = "تنظیم آهنگ بی‌کلام...";
718
  lyrics = "";
719
  }
720
 
721
- // 3. ساخت آهنگ (فانکشن 77)
722
  loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
723
 
724
  const genPayload = [
725
- getVal('model_select'),
726
- "custom",
727
- uploadedAudioPath,
728
- "unknown",
729
- musicPrompt,
730
- lyrics,
731
- 0, "", "", "unknown",
732
  getNum('steps_input'), getNum('cfg_input'), true, getNum('seed_input'), null, -1,
733
  getNum('batch_size'), null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
734
- 1,
735
- uploadedAudioPath ? "audio2music" : "text2music",
736
- false, 0, 1, 3, "ode", "", "mp3", 0.85,
737
  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
738
  ];
739
 
@@ -742,22 +667,19 @@
742
 
743
  if(!audioFileObj) throw new Error("Audio generation failed");
744
 
745
- // 4. دریافت LRC (سینک کردن) - فانکشن 84 طبق لاگ شما
746
- // لاگ شما نشان داد فانکشن 84 فایل VTT تولید میکند
747
  let lrcData = null;
748
  if (!isInstrumental) {
749
  loaderText.innerText = "در حال هماهنگ‌سازی متن و آهنگ...";
750
  try {
751
  const lrcOutput = await runGradioFunction(84, [audioFileObj]);
752
- if (lrcOutput && lrcOutput.data && lrcOutput.data[0]) {
753
- // دریافت محتوای فایل VTT
754
  const vttFile = lrcOutput.data[0];
755
- const vttUrl = vttFile.url ? vttFile.url : `${ACE_SPACE_URL}gradio_api/file=${vttFile.path}`;
756
  const resp = await fetch(vttUrl);
757
  lrcData = await resp.text();
758
  }
759
  } catch (e) {
760
- console.warn("LRC generation failed, using plain lyrics", e);
761
  }
762
  }
763
 
@@ -767,7 +689,7 @@
767
  } catch (e) {
768
  if (e.isLimit) {
769
  upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
770
- upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت آهنگ‌های بیشتر و نامحدود، لطفا حساب خود را ارتقا دهید.";
771
  upgradeModal.classList.add('open');
772
  } else {
773
  alert("خطا: " + e.message);
@@ -782,6 +704,7 @@
782
  function findAudioFile(data) {
783
  let found = null;
784
  function traverse(obj) {
 
785
  if (obj && typeof obj === 'object') {
786
  if (obj.url && (obj.url.endsWith('.mp3') || obj.url.endsWith('.wav'))) {
787
  found = obj;
@@ -810,23 +733,27 @@
810
 
811
  window.scrollTo({ top: 0, behavior: 'smooth' });
812
  }
 
 
 
 
 
813
 
814
- // --- پارسر VTT ---
815
  function parseVTT(vttText) {
816
  const lines = [];
817
- // Regex برای فرمت VTT: 00:00:16.320 --> 00:00:20.640
818
- const regex = /(\d{2}:\d{2}:\d{2}\.\d{3})\s-->\s(\d{2}:\d{2}:\d{2}\.\d{3})\n(.*)/;
819
 
820
  const blocks = vttText.split('\n\n');
821
  for(let block of blocks) {
822
- const match = block.match(regex);
823
- if(match) {
824
- const startTime = timeToSeconds(match[1]);
825
- const endTime = timeToSeconds(match[2]);
826
- const text = match[3].trim();
827
- if(text && text !== '[Intro]' && text !== '[Chorus]' && text !== '[Verse]') {
828
- lines.push({ startTime, endTime, text });
829
- }
 
830
  }
831
  }
832
  return lines;
@@ -847,15 +774,14 @@
847
  audio.src = audioSrc;
848
  playerWrapper.appendChild(audio);
849
 
850
- // اگر دیتای VTT موجود باشد (سینک شده)
851
  if (lrcData && typeof lrcData === 'string' && lrcData.includes('WEBVTT')) {
852
  const parsedLyrics = parseVTT(lrcData);
853
 
854
  parsedLyrics.forEach((line, index) => {
855
- const p = document.createElement('p');
856
  p.className = 'lyric-line';
857
  p.id = `line-${index}`;
858
- p.innerText = line.text;
859
  finalLyricsBox.appendChild(p);
860
  });
861
 
@@ -863,7 +789,6 @@
863
  const currentTime = audio.currentTime;
864
  let activeIndex = -1;
865
 
866
- // پیدا کردن خطی که در بازه زمانی فعلی است
867
  for (let i = 0; i < parsedLyrics.length; i++) {
868
  if (currentTime >= parsedLyrics[i].startTime && currentTime < parsedLyrics[i].endTime) {
869
  activeIndex = i;
@@ -883,17 +808,10 @@
883
  });
884
 
885
  } else {
886
- // حالت متن ساده (بدون سینک)
887
  if (rawLyrics) {
888
- const lines = rawLyrics.split('\n').filter(l => l.trim() !== '');
889
- lines.forEach(line => {
890
- const p = document.createElement('p');
891
- p.className = 'lyric-line';
892
- p.innerText = line;
893
- finalLyricsBox.appendChild(p);
894
- });
895
  } else {
896
- finalLyricsBox.innerHTML = '<span style="color:#a0aec0; display:block; padding-top:100px;">(متن آهنگی موجود نیست یا آهنگ بی‌کلام است)</span>';
897
  }
898
  }
899
  }
 
66
  }
67
  .subtitle { font-size: 0.9rem; color: var(--text-secondary); margin-top: 5px; margin-bottom: 10px; }
68
 
 
69
  .status-badge {
70
  display: inline-block; padding: 6px 16px; border-radius: 20px;
71
  font-size: 0.85rem; font-weight: 700; margin-top: 5px;
 
101
  textarea { min-height: 120px; resize: vertical; }
102
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
103
 
 
104
  input[type="file"] { padding: 10px; font-size: 0.9rem; background: #fff; }
105
 
 
106
  @keyframes move-gradient {
107
  0% { background-position: 0% 50%; }
108
  50% { background-position: 100% 50%; }
 
134
  }
135
  .btn-outline:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
136
 
 
137
  .accordion {
138
  background-color: var(--input-bg); color: var(--text-primary); cursor: pointer; padding: 15px; width: 100%; border: none;
139
  text-align: right; outline: none; font-size: 0.95rem; font-weight: 600; border-radius: 12px;
 
153
  .checkbox-wrapper:last-child { border-bottom: none; }
154
  .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
155
 
 
156
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
157
 
158
  .download-btn-style {
 
163
  .download-btn-style.locked { background-color: #f1f5f9; color: #94a3b8; }
164
  .download-btn-style.locked:hover { background-color: #e2e8f0; }
165
 
 
166
  audio { width: 100%; height: 50px; border-radius: 25px; margin-top: 5px; }
167
 
168
  /* استایل باکس لیریک (برگشت به حالت اول) */
169
  .lyrics-container {
170
+ background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto;
171
+ white-space: pre-wrap; line-height: 2; font-size: 1rem; color: #4a5568; text-align: center; border: 1px solid var(--input-border); margin-top: 15px;
172
+ scroll-behavior: smooth;
 
 
 
 
 
 
173
  }
174
 
175
+ /* استایل تگ‌های انگلیسی */
176
+ .lyrics-tag { color: var(--accent-primary); font-weight: 800; display: block; margin-top: 20px; margin-bottom: 5px; font-size: 0.9em; letter-spacing: 1px; text-transform: uppercase; }
 
 
 
 
 
 
 
 
 
177
 
178
  /* استایل خط فعال */
179
  .lyric-line.active {
180
+ color: var(--accent-primary);
181
+ font-weight: 800;
 
 
 
182
  }
183
 
 
184
  #loader { display: none; text-align: center; padding: 20px; }
185
  .wave-bars { display: flex; justify-content: center; gap: 4px; height: 30px; align-items: flex-end; }
186
  .bar { width: 5px; background: var(--accent-primary); animation: jump 1s infinite; border-radius: 2px; }
 
189
  .bar:nth-child(4) { animation-delay: 0.3s; height: 50%; }
190
  @keyframes jump { 0%, 100% { height: 20%; } 50% { height: 100%; } }
191
 
 
192
  #historySection { margin-top: 30px; width: 100%; }
193
  .history-title { font-size: 1.2rem; font-weight: 800; color: var(--text-primary); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
194
  .history-list { display: grid; gap: 12px; }
 
213
  .history-card:hover .h-delete { opacity: 1; transform: scale(1); }
214
  .h-delete:hover { background: var(--danger-color); color: white; transform: scale(1.1); }
215
 
 
216
  .modal-overlay {
217
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
218
  background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(8px);
 
238
  .btn-confirm { background: var(--danger-color); color: white; box-shadow: 0 5px 15px rgba(229, 62, 62, 0.3); }
239
  .btn-confirm:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(229, 62, 62, 0.4); }
240
 
 
241
  @keyframes pulse-gold { 0% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); } 70% { box-shadow: 0 0 0 15px rgba(255, 193, 7, 0); } 100% { box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); } }
242
  .upgrade-icon {
243
  width: 70px; height: 70px;
 
363
 
364
  <script>
365
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
 
366
  const PREMIUM_PAGE_ID = '1149636';
367
  const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTU0MjI0ZWVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
368
 
 
370
  let songToDeleteId = null;
371
  let userSubscriptionStatus = 'free';
372
 
 
373
  const ideaInput = document.getElementById('ideaInput');
374
  const processBtn = document.getElementById('processBtn');
375
  const step1 = document.getElementById('step1');
 
386
  const statusBadge = document.getElementById('statusBadge');
387
  const upgradeModalTitle = document.getElementById('upgradeModalTitle');
388
  const upgradeModalText = document.getElementById('upgradeModalText');
 
 
389
  const audioInput = document.getElementById('audioInput');
390
  const instrumentalChk = document.getElementById('instrumental_chk');
391
 
 
421
  if (status === 'paid') {
422
  statusBadge.textContent = 'نسخه نامحدود';
423
  statusBadge.className = 'status-badge visible badge-premium';
 
424
  } else {
425
  statusBadge.textContent = 'نسخه رایگان';
426
  statusBadge.className = 'status-badge visible badge-free';
 
443
 
444
  function handleSecureDownload(url, e) {
445
  if (e) e.preventDefault();
 
446
  if (userSubscriptionStatus === 'free') {
447
  upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
448
+ upgradeModalText.innerText = "برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی، لطفا حساب خود را ارتقا دهید.";
449
  upgradeModal.classList.add('open');
450
  } else {
451
+ parent.postMessage({ type: 'INITIATE_DOWNLOAD_FROM_URL', payload: { audioUrl: url } }, '*');
 
 
 
 
452
  const btn = e ? e.target.closest('button') : null;
453
  if(btn) {
454
  const originalText = btn.innerHTML;
 
459
  }
460
 
461
  document.getElementById('btnGoPremium').addEventListener('click', () => {
462
+ parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
 
 
 
463
  });
464
 
465
+ function closeUpgradeModal() { upgradeModal.classList.remove('open'); }
 
 
466
 
 
467
  function initDB() {
468
  const request = indexedDB.open("alphaMusicDB", 1);
469
  request.onerror = (event) => console.error("IndexedDB error:", event);
 
475
  try {
476
  const response = await fetch(audioUrl);
477
  const audioBlob = await response.blob();
 
478
  const newItem = {
479
  id: Date.now(), idea, lyrics, audioBlob, lrcData,
480
  date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' })
481
  };
482
+ const tx = db.transaction(["songs"], "readwrite");
483
+ const store = tx.objectStore("songs");
484
  store.add(newItem);
485
 
486
  const countReq = store.count();
487
  countReq.onsuccess = () => {
488
  if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
489
  };
490
+ tx.oncomplete = () => loadHistory();
491
  } catch (error) { console.error("Error saving:", error); }
492
  }
493
 
 
539
 
540
  function confirmDelete() {
541
  if (songToDeleteId && db) {
542
+ const tx = db.transaction(["songs"], "readwrite");
543
+ tx.objectStore("songs").delete(songToDeleteId);
544
+ tx.oncomplete = () => {
 
545
  loadHistory();
546
  closeDeleteModal();
547
  };
 
571
 
572
  initDB();
573
 
 
574
  async function uploadFileToGradio(file) {
575
  const formData = new FormData();
576
  formData.append('files', file);
 
585
  return data[0];
586
  }
587
 
 
588
  async function runGradioFunction(fnIndex, payloadData) {
589
  const session_hash = Math.random().toString(36).substring(2);
590
 
591
  const joinResp = await fetch(`${ACE_SPACE_URL}gradio_api/queue/join`, {
592
  method: 'POST',
593
  headers: { 'Content-Type': 'application/json' },
594
+ body: JSON.stringify({ data: payloadData, fn_index: fnIndex, session_hash })
 
 
 
 
595
  });
596
 
597
  if (!joinResp.ok) throw new Error(`Gradio Call Failed: ${fnIndex}`);
 
604
  if (msg.msg === 'process_completed') {
605
  eventSource.close();
606
  resolve(msg.output);
 
 
607
  }
608
  };
609
 
 
614
  });
615
  }
616
 
 
617
  processBtn.addEventListener('click', async () => {
618
  if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
619
 
 
626
  let uploadedAudioPath = null;
627
  const isInstrumental = instrumentalChk.checked;
628
 
 
629
  if (audioInput.files.length > 0) {
630
  loaderText.innerText = "در حال آپلود فایل نمونه...";
631
  uploadedAudioPath = await uploadFileToGradio(audioInput.files[0]);
 
634
  let lyrics = "";
635
  let musicPrompt = ideaInput.value;
636
 
 
637
  if (!isInstrumental) {
638
  loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
639
 
640
  const response = await fetch('/api/refine', {
641
  method: 'POST',
642
  headers: {'Content-Type': 'application/json'},
643
+ body: JSON.stringify({ idea: ideaInput.value, fingerprint: getFingerprint(), is_premium: userSubscriptionStatus === 'paid' })
 
 
 
 
644
  });
645
 
646
+ if (response.status === 429) throw { isLimit: true };
 
 
 
647
  const data = await response.json();
648
  if (data.error) throw new Error(data.error);
 
649
  lyrics = data.lyrics;
650
  musicPrompt = data.music_prompt;
651
  } else {
 
652
  lyrics = "";
653
  }
654
 
 
655
  loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
656
 
657
  const genPayload = [
658
+ getVal('model_select'), "custom", uploadedAudioPath, "unknown", musicPrompt, lyrics, 0, "", "", "unknown",
 
 
 
 
 
 
659
  getNum('steps_input'), getNum('cfg_input'), true, getNum('seed_input'), null, -1,
660
  getNum('batch_size'), null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
661
+ 1, uploadedAudioPath ? "audio2music" : "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
 
 
662
  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
663
  ];
664
 
 
667
 
668
  if(!audioFileObj) throw new Error("Audio generation failed");
669
 
 
 
670
  let lrcData = null;
671
  if (!isInstrumental) {
672
  loaderText.innerText = "در حال هماهنگ‌سازی متن و آهنگ...";
673
  try {
674
  const lrcOutput = await runGradioFunction(84, [audioFileObj]);
675
+ if (lrcOutput && lrcOutput.data && lrcOutput.data[0] && lrcOutput.data[0].path) {
 
676
  const vttFile = lrcOutput.data[0];
677
+ const vttUrl = vttFile.url || `${ACE_SPACE_URL.replace(/\/$/,'')}/gradio_api/file=${vttFile.path}`;
678
  const resp = await fetch(vttUrl);
679
  lrcData = await resp.text();
680
  }
681
  } catch (e) {
682
+ console.warn("LRC generation failed, using plain lyrics.", e);
683
  }
684
  }
685
 
 
689
  } catch (e) {
690
  if (e.isLimit) {
691
  upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
692
+ upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت نامحدود، حساب خود را ارتقا دهید.";
693
  upgradeModal.classList.add('open');
694
  } else {
695
  alert("خطا: " + e.message);
 
704
  function findAudioFile(data) {
705
  let found = null;
706
  function traverse(obj) {
707
+ if (found) return;
708
  if (obj && typeof obj === 'object') {
709
  if (obj.url && (obj.url.endsWith('.mp3') || obj.url.endsWith('.wav'))) {
710
  found = obj;
 
733
 
734
  window.scrollTo({ top: 0, behavior: 'smooth' });
735
  }
736
+
737
+ // --- توابع پردازش متن و زمانبندی ---
738
+ function formatLyricsHTML(text) {
739
+ return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>');
740
+ }
741
 
 
742
  function parseVTT(vttText) {
743
  const lines = [];
744
+ const regex = /(\d{2}:\d{2}:\d{2}\.\d{3})\s-->\s(\d{2}:\d{2}:\d{2}\.\d{3})((.|\n)*)/;
 
745
 
746
  const blocks = vttText.split('\n\n');
747
  for(let block of blocks) {
748
+ if(block.includes('-->')) {
749
+ const parts = block.split('\n');
750
+ const timeMatch = parts[0].match(/(\d{2}:\d{2}:\d{2}\.\d{3})\s-->\s(\d{2}:\d{2}:\d{2}\.\d{3})/);
751
+ if(timeMatch) {
752
+ const startTime = timeToSeconds(timeMatch[1]);
753
+ const endTime = timeToSeconds(timeMatch[2]);
754
+ const text = parts.slice(1).join('\n').trim();
755
+ if (text) lines.push({ startTime, endTime, text });
756
+ }
757
  }
758
  }
759
  return lines;
 
774
  audio.src = audioSrc;
775
  playerWrapper.appendChild(audio);
776
 
 
777
  if (lrcData && typeof lrcData === 'string' && lrcData.includes('WEBVTT')) {
778
  const parsedLyrics = parseVTT(lrcData);
779
 
780
  parsedLyrics.forEach((line, index) => {
781
+ const p = document.createElement('div');
782
  p.className = 'lyric-line';
783
  p.id = `line-${index}`;
784
+ p.innerHTML = formatLyricsHTML(line.text.replace(/\n/g, '<br>'));
785
  finalLyricsBox.appendChild(p);
786
  });
787
 
 
789
  const currentTime = audio.currentTime;
790
  let activeIndex = -1;
791
 
 
792
  for (let i = 0; i < parsedLyrics.length; i++) {
793
  if (currentTime >= parsedLyrics[i].startTime && currentTime < parsedLyrics[i].endTime) {
794
  activeIndex = i;
 
808
  });
809
 
810
  } else {
 
811
  if (rawLyrics) {
812
+ finalLyricsBox.innerHTML = formatLyricsHTML(rawLyrics);
 
 
 
 
 
 
813
  } else {
814
+ finalLyricsBox.innerHTML = '<div style="color:#a0aec0; padding-top:100px;">(متن آهنگی موجود نیست)</div>';
815
  }
816
  }
817
  }