Opera8 commited on
Commit
89d021e
·
verified ·
1 Parent(s): 6d8f979

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +152 -92
index.html CHANGED
@@ -360,7 +360,7 @@
360
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
361
  // مشخصات نسخه پولی
362
  const PREMIUM_PAGE_ID = '1149636';
363
- const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBL29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTU0MjI0ZWVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
364
 
365
  let db;
366
  let songToDeleteId = null;
@@ -401,6 +401,7 @@
401
  function getFingerprint() {
402
  let fp = localStorage.getItem('user_fingerprint');
403
  if (!fp) {
 
404
  fp = 'user_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
405
  localStorage.setItem('user_fingerprint', fp);
406
  }
@@ -425,6 +426,7 @@
425
  }
426
  }
427
 
 
428
  window.addEventListener('message', (event) => {
429
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
430
  try {
@@ -437,16 +439,26 @@
437
  }
438
  });
439
 
 
440
  parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
441
 
 
442
  function handleSecureDownload(url, e) {
443
  if (e) e.preventDefault();
 
444
  if (userSubscriptionStatus === 'free') {
 
445
  upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
446
  upgradeModalText.innerText = "برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.";
447
  upgradeModal.classList.add('open');
448
  } else {
449
- parent.postMessage({ type: 'INITIATE_DOWNLOAD_FROM_URL', payload: { audioUrl: url } }, '*');
 
 
 
 
 
 
450
  const btn = e ? e.target.closest('button') : null;
451
  if(btn) {
452
  const originalText = btn.innerHTML;
@@ -456,8 +468,12 @@
456
  }
457
  }
458
 
 
459
  document.getElementById('btnGoPremium').addEventListener('click', () => {
460
- parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
 
 
 
461
  });
462
 
463
  function closeUpgradeModal() {
@@ -474,6 +490,7 @@
474
 
475
  async function saveToHistory(idea, lyrics, audioUrl) {
476
  try {
 
477
  const response = await fetch(audioUrl);
478
  const audioBlob = await response.blob();
479
  const newItem = {
@@ -484,6 +501,7 @@
484
  const store = transaction.objectStore("songs");
485
  store.add(newItem);
486
 
 
487
  const countReq = store.count();
488
  countReq.onsuccess = () => {
489
  if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
@@ -518,7 +536,9 @@
518
  <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>
519
  </div>
520
  `;
 
521
  div.onclick = (e) => {
 
522
  if (!e.target.closest('.h-delete')) {
523
  openHistoryItem(item);
524
  }
@@ -530,7 +550,7 @@
530
 
531
  // --- توابع حذف ---
532
  function askToDelete(event, id) {
533
- event.stopPropagation();
534
  songToDeleteId = id;
535
  deleteModal.classList.add('open');
536
  }
@@ -552,6 +572,7 @@
552
  }
553
  }
554
 
 
555
  deleteModal.addEventListener('click', (e) => {
556
  if (e.target === deleteModal) closeDeleteModal();
557
  });
@@ -568,6 +589,7 @@
568
  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> آهنگ آرشیو شده`;
569
  headerText.style.color = 'var(--accent-primary)';
570
 
 
571
  mainDownloadBtn.style.display = 'inline-flex';
572
  mainDownloadBtn.onclick = (e) => handleSecureDownload(audioURL, e);
573
 
@@ -580,36 +602,122 @@
580
 
581
  initDB();
582
 
583
- // --- توابع تبدیل و آپلود ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
  async function convertAudioToWav(file) {
585
  return new Promise((resolve, reject) => {
586
  const reader = new FileReader();
587
- reader.onload = (e) => {
588
- const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
589
- audioCtx.decodeAudioData(e.target.result)
590
- .then(buffer => {
591
- const wavBlob = bufferToWave(buffer, buffer.length);
592
- const wavFile = new File([wavBlob], file.name.replace(/\.[^/.]+$/, "") + ".wav", { type: "audio/wav" });
593
- resolve(wavFile);
594
- })
595
- .catch(err => reject(`خطا در دیکد کردن فایل: ${err.message}`));
 
 
596
  };
597
- reader.onerror = (e) => reject(`خطا در خواندن فایل: ${e.message}`);
598
  reader.readAsArrayBuffer(file);
599
  });
600
  }
601
 
 
602
  async function uploadAudioFile(file) {
603
  const formData = new FormData();
604
  formData.append("files", file);
605
  try {
606
- const response = await fetch(`${ACE_SPACE_URL}gradio_api/upload`, { method: "POST", body: formData });
 
 
 
607
  const data = await response.json();
608
  if (data && data.length > 0) {
609
  return {
610
  "path": data[0],
611
  "url": `${ACE_SPACE_URL}gradio_api/file=${data[0]}`,
612
- "orig_name": file.name, "size": file.size, "mime_type": file.type,
 
 
613
  "meta": {"_type": "gradio.FileData"}
614
  };
615
  }
@@ -630,43 +738,53 @@
630
  loader.style.display = 'block';
631
 
632
  try {
 
633
  const audioInput = document.getElementById('audio_reference');
634
  let uploadedAudioObj = null;
635
- let mode = "custom"; // حالت پیش‌فرض
636
 
637
  if (audioInput.files.length > 0) {
638
  let fileToUpload = audioInput.files[0];
 
 
639
  if (fileToUpload.type !== 'audio/wav' && !fileToUpload.name.toLowerCase().endsWith('.wav')) {
640
  loaderText.innerText = "در حال تبدیل فرمت فایل صوتی به WAV...";
641
- fileToUpload = await convertAudioToWav(fileToUpload);
 
 
 
 
 
 
 
642
  }
643
 
644
  loaderText.innerText = "در حال آپلود فایل نمونه...";
645
  uploadedAudioObj = await uploadAudioFile(fileToUpload);
646
-
647
- if (uploadedAudioObj) {
648
- mode = "Continuation";
649
- }
650
  }
651
 
 
652
  loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
653
  const isInstrumental = getChk('instrumental_chk');
654
 
 
655
  const response = await fetch('/api/refine', {
656
  method: 'POST',
657
  headers: {'Content-Type': 'application/json'},
658
  body: JSON.stringify({
659
- idea: ideaInput.value, fingerprint: getFingerprint(),
 
660
  is_premium: userSubscriptionStatus === 'paid',
661
  is_instrumental: isInstrumental
662
  })
663
  });
664
 
 
665
  if (response.status === 429) {
666
  loader.style.display = 'none';
667
  step1.style.display = 'block';
668
  historySection.style.display = 'block';
669
  processBtn.disabled = false;
 
670
  upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
671
  upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت آهنگ‌های بیشتر و نامحدود، لطفا حساب خود را ارتقا دهید.";
672
  upgradeModal.classList.add('open');
@@ -681,9 +799,10 @@
681
 
682
  const finalLyrics = isInstrumental ? "" : lyrics;
683
 
 
684
  const payload = [
685
  getVal('model_select'),
686
- mode,
687
  uploadedAudioObj,
688
  getVal('duration_select'),
689
  musicPrompt,
@@ -720,7 +839,7 @@
720
  }
721
  });
722
 
723
- function handleAudioOutput(outputData, lyrics, idea) {
724
  const processedUrls = new Set();
725
  let hasResult = false;
726
 
@@ -739,24 +858,14 @@
739
  playerWrapper.innerHTML += `<div class="audio-item"><audio controls autoplay src="${fullUrl}"></audio></div>`;
740
  }
741
 
742
- // **اصلاح کلیدی:** جستجو را از داخل آرایه data در آبجکت خروجی شروع می‌کنیم
743
  function traverse(obj) {
744
- if (typeof obj === 'string' && obj.includes('/file=') && obj.endsWith('.mp3')) {
745
- addAudio(obj);
746
- } else if (Array.isArray(obj)) {
747
- obj.forEach(traverse);
748
- } else if (obj && typeof obj === 'object') {
749
- if (obj.url && obj.url.endsWith('.mp3')) {
750
- addAudio(obj.url);
751
- } else {
752
- Object.values(obj).forEach(traverse);
753
- }
754
  }
755
  }
756
-
757
- if (outputData && outputData.data) {
758
- traverse(outputData.data);
759
- }
760
 
761
  if (hasResult) {
762
  const headerText = document.getElementById('resultHeaderText');
@@ -766,17 +875,13 @@
766
  finalLyricsBox.innerHTML = formatLyrics(lyrics);
767
  window.scrollTo({ top: 0, behavior: 'smooth' });
768
  } else {
769
- console.error("No audio file URL found in the response:", outputData);
770
- alert("فایل صوتی یافت نشد! (ممکن است خطایی در سرور رخ داده باشد)");
771
  step1.style.display = 'block';
772
  historySection.style.display = 'block';
773
  }
774
  }
775
 
776
- function formatLyrics(text) {
777
- if(!text) return "";
778
- return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>');
779
- }
780
 
781
  const canvas = document.getElementById('music-canvas');
782
  const ctx = canvas.getContext('2d');
@@ -798,51 +903,6 @@
798
  requestAnimationFrame(anim);
799
  }
800
  anim();
801
-
802
- // --- توابع کمکی برای تبدیل به WAV ---
803
- function bufferToWave(aBuffer, len) {
804
- let numOfChan = aBuffer.numberOfChannels,
805
- length = len * numOfChan * 2 + 44,
806
- buffer = new ArrayBuffer(length),
807
- view = new DataView(buffer),
808
- channels = [], i, sample,
809
- offset = 0,
810
- pos = 0;
811
-
812
- writeString(view, 0, 'RIFF');
813
- view.setUint32(4, 36 + len * numOfChan * 2, true);
814
- writeString(view, 8, 'WAVE');
815
- writeString(view, 12, 'fmt ');
816
- view.setUint32(16, 16, true);
817
- view.setUint16(20, 1, true);
818
- view.setUint16(22, numOfChan, true);
819
- view.setUint32(24, aBuffer.sampleRate, true);
820
- view.setUint32(28, aBuffer.sampleRate * 2 * numOfChan, true);
821
- view.setUint16(32, numOfChan * 2, true);
822
- view.setUint16(34, 16, true);
823
- writeString(view, 36, 'data');
824
- view.setUint32(40, len * numOfChan * 2, true);
825
-
826
- for (i = 0; i < aBuffer.numberOfChannels; i++)
827
- channels.push(aBuffer.getChannelData(i));
828
-
829
- while (pos < len) {
830
- for (i = 0; i < numOfChan; i++) {
831
- sample = Math.max(-1, Math.min(1, channels[i][pos]));
832
- sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0;
833
- view.setInt16(offset, sample, true);
834
- offset += 2;
835
- }
836
- pos++;
837
- }
838
- return new Blob([view], { type: 'audio/wav' });
839
- }
840
-
841
- function writeString(view, offset, string) {
842
- for (var i = 0; i < string.length; i++) {
843
- view.setUint8(offset + i, string.charCodeAt(i));
844
- }
845
- }
846
  </script>
847
  </body>
848
  </html>
 
360
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
361
  // مشخصات نسخه پولی
362
  const PREMIUM_PAGE_ID = '1149636';
363
+ const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTU0MjI0ZWVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
364
 
365
  let db;
366
  let songToDeleteId = null;
 
401
  function getFingerprint() {
402
  let fp = localStorage.getItem('user_fingerprint');
403
  if (!fp) {
404
+ // تولید یک شناسه تصادفی و ذخیره آن
405
  fp = 'user_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
406
  localStorage.setItem('user_fingerprint', fp);
407
  }
 
426
  }
427
  }
428
 
429
+ // شنونده پیام از آیفریم مادر
430
  window.addEventListener('message', (event) => {
431
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
432
  try {
 
439
  }
440
  });
441
 
442
+ // درخواست وضعیت کاربر در شروع برنامه
443
  parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
444
 
445
+ // مدیریت کلیک دکمه دانلود (تابع امنیتی)
446
  function handleSecureDownload(url, e) {
447
  if (e) e.preventDefault();
448
+
449
  if (userSubscriptionStatus === 'free') {
450
+ // تنظیم متن مودال برای دانلود
451
  upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
452
  upgradeModalText.innerText = "برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.";
453
  upgradeModal.classList.add('open');
454
  } else {
455
+ // ارسال درخواست دانلود به آیفریم مادر
456
+ parent.postMessage({
457
+ type: 'INITIATE_DOWNLOAD_FROM_URL',
458
+ payload: { audioUrl: url }
459
+ }, '*');
460
+
461
+ // تغییر موقت متن دکمه برای فیدبک
462
  const btn = e ? e.target.closest('button') : null;
463
  if(btn) {
464
  const originalText = btn.innerHTML;
 
468
  }
469
  }
470
 
471
+ // دکمه ارتقا در مودال
472
  document.getElementById('btnGoPremium').addEventListener('click', () => {
473
+ parent.postMessage({
474
+ type: 'NAVIGATE_TO_PREMIUM',
475
+ payload: { url: PREMIUM_URL }
476
+ }, '*');
477
  });
478
 
479
  function closeUpgradeModal() {
 
490
 
491
  async function saveToHistory(idea, lyrics, audioUrl) {
492
  try {
493
+ // برای ذخیره در DB باید فایل را فچ کنیم
494
  const response = await fetch(audioUrl);
495
  const audioBlob = await response.blob();
496
  const newItem = {
 
501
  const store = transaction.objectStore("songs");
502
  store.add(newItem);
503
 
504
+ // شرط نگهداری فقط ۱۰ آیتم آخر
505
  const countReq = store.count();
506
  countReq.onsuccess = () => {
507
  if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
 
536
  <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>
537
  </div>
538
  `;
539
+ // کلیک روی خود کارت برای پخش
540
  div.onclick = (e) => {
541
+ // اگر روی دکمه حذف کلیک نشده باشد
542
  if (!e.target.closest('.h-delete')) {
543
  openHistoryItem(item);
544
  }
 
550
 
551
  // --- توابع حذف ---
552
  function askToDelete(event, id) {
553
+ event.stopPropagation(); // جلوگیری از باز شدن پلیر
554
  songToDeleteId = id;
555
  deleteModal.classList.add('open');
556
  }
 
572
  }
573
  }
574
 
575
+ // بستن مودال با کلیک بیرون
576
  deleteModal.addEventListener('click', (e) => {
577
  if (e.target === deleteModal) closeDeleteModal();
578
  });
 
589
  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> آهنگ آرشیو شده`;
590
  headerText.style.color = 'var(--accent-primary)';
591
 
592
+ // تنظیم دکمه دانلود
593
  mainDownloadBtn.style.display = 'inline-flex';
594
  mainDownloadBtn.onclick = (e) => handleSecureDownload(audioURL, e);
595
 
 
602
 
603
  initDB();
604
 
605
+ // --- توابع تبدیل فرمت صوتی (Client-Side WAV Converter) ---
606
+ function writeString(view, offset, string) {
607
+ for (let i = 0; i < string.length; i++) {
608
+ view.setUint8(offset + i, string.charCodeAt(i));
609
+ }
610
+ }
611
+
612
+ function floatTo16BitPCM(output, offset, input) {
613
+ for (let i = 0; i < input.length; i++, offset += 2) {
614
+ let s = Math.max(-1, Math.min(1, input[i]));
615
+ output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
616
+ }
617
+ }
618
+
619
+ function interleave(inputL, inputR) {
620
+ const length = inputL.length + inputR.length;
621
+ const result = new Float32Array(length);
622
+ let index = 0;
623
+ let inputIndex = 0;
624
+ while (index < length) {
625
+ result[index++] = inputL[inputIndex];
626
+ result[index++] = inputR[inputIndex];
627
+ inputIndex++;
628
+ }
629
+ return result;
630
+ }
631
+
632
+ function bufferToWave(abuffer, len) {
633
+ let numOfChan = abuffer.numberOfChannels;
634
+ let length = len * numOfChan * 2 + 44;
635
+ let buffer = new ArrayBuffer(length);
636
+ let view = new DataView(buffer);
637
+ let channels = [], i, sample;
638
+ let offset = 0;
639
+ let pos = 0;
640
+
641
+ // write WAVE header
642
+ writeString(view, 0, 'RIFF');
643
+ view.setUint32(4, 36 + len * numOfChan * 2, true);
644
+ writeString(view, 8, 'WAVE');
645
+ writeString(view, 12, 'fmt ');
646
+ view.setUint32(16, 16, true);
647
+ view.setUint16(20, 1, true);
648
+ view.setUint16(22, numOfChan, true);
649
+ view.setUint32(24, abuffer.sampleRate, true);
650
+ view.setUint32(28, abuffer.sampleRate * 2 * numOfChan, true);
651
+ view.setUint16(32, numOfChan * 2, true);
652
+ view.setUint16(34, 16, true);
653
+ writeString(view, 36, 'data');
654
+ view.setUint32(40, len * numOfChan * 2, true);
655
+
656
+ // write interleaved data
657
+ for(i = 0; i < abuffer.numberOfChannels; i++)
658
+ channels.push(abuffer.getChannelData(i));
659
+
660
+ offset = 44;
661
+ // Interleave logic handled simply here for 1 or 2 channels
662
+ if (numOfChan === 2) {
663
+ while(pos < len) {
664
+ for(i = 0; i < numOfChan; i++) {
665
+ sample = Math.max(-1, Math.min(1, channels[i][pos]));
666
+ sample = (sample < 0 ? sample * 0x8000 : sample * 0x7FFF) | 0;
667
+ view.setInt16(offset, sample, true);
668
+ offset += 2;
669
+ }
670
+ pos++;
671
+ }
672
+ } else {
673
+ while(pos < len) {
674
+ sample = Math.max(-1, Math.min(1, channels[0][pos]));
675
+ sample = (sample < 0 ? sample * 0x8000 : sample * 0x7FFF) | 0;
676
+ view.setInt16(offset, sample, true);
677
+ offset += 2;
678
+ pos++;
679
+ }
680
+ }
681
+ return new Blob([buffer], { type: "audio/wav" });
682
+ }
683
+
684
  async function convertAudioToWav(file) {
685
  return new Promise((resolve, reject) => {
686
  const reader = new FileReader();
687
+ reader.onload = function(e) {
688
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
689
+ audioContext.decodeAudioData(e.target.result, function(buffer) {
690
+ const wavBlob = bufferToWave(buffer, buffer.length);
691
+ // ساخت فایل جدید با فرمت wav
692
+ const wavFile = new File([wavBlob], file.name.replace(/\.[^/.]+$/, "") + ".wav", { type: "audio/wav" });
693
+ resolve(wavFile);
694
+ }, function(e){
695
+ console.error("Audio decode failed", e);
696
+ reject("فرمت فایل صوتی پشتیبانی نمی‌شود.");
697
+ });
698
  };
699
+ reader.onerror = reject;
700
  reader.readAsArrayBuffer(file);
701
  });
702
  }
703
 
704
+ // --- تابع آپلود فایل صوتی به Gradio ---
705
  async function uploadAudioFile(file) {
706
  const formData = new FormData();
707
  formData.append("files", file);
708
  try {
709
+ const response = await fetch(`${ACE_SPACE_URL}gradio_api/upload`, {
710
+ method: "POST",
711
+ body: formData
712
+ });
713
  const data = await response.json();
714
  if (data && data.length > 0) {
715
  return {
716
  "path": data[0],
717
  "url": `${ACE_SPACE_URL}gradio_api/file=${data[0]}`,
718
+ "orig_name": file.name,
719
+ "size": file.size,
720
+ "mime_type": file.type,
721
  "meta": {"_type": "gradio.FileData"}
722
  };
723
  }
 
738
  loader.style.display = 'block';
739
 
740
  try {
741
+ // 1. آپلود و تبدیل فایل نمونه
742
  const audioInput = document.getElementById('audio_reference');
743
  let uploadedAudioObj = null;
 
744
 
745
  if (audioInput.files.length > 0) {
746
  let fileToUpload = audioInput.files[0];
747
+
748
+ // بررسی و تبدیل به WAV
749
  if (fileToUpload.type !== 'audio/wav' && !fileToUpload.name.toLowerCase().endsWith('.wav')) {
750
  loaderText.innerText = "در حال تبدیل فرمت فایل صوتی به WAV...";
751
+ try {
752
+ fileToUpload = await convertAudioToWav(fileToUpload);
753
+ console.log("Converted to WAV:", fileToUpload.name, fileToUpload.size);
754
+ } catch (err) {
755
+ console.error(err);
756
+ alert("خطا در تبدیل فایل صوتی. لطفاً فایل WAV معتبر آپلود کنید.");
757
+ throw new Error("Audio conversion failed");
758
+ }
759
  }
760
 
761
  loaderText.innerText = "در حال آپلود فایل نمونه...";
762
  uploadedAudioObj = await uploadAudioFile(fileToUpload);
 
 
 
 
763
  }
764
 
765
+ // 2. درخواست متن و پرامپت
766
  loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
767
  const isInstrumental = getChk('instrumental_chk');
768
 
769
+ // ارسال درخواست به سرور با فینگرپرینت و وضعیت اشتراک
770
  const response = await fetch('/api/refine', {
771
  method: 'POST',
772
  headers: {'Content-Type': 'application/json'},
773
  body: JSON.stringify({
774
+ idea: ideaInput.value,
775
+ fingerprint: getFingerprint(),
776
  is_premium: userSubscriptionStatus === 'paid',
777
  is_instrumental: isInstrumental
778
  })
779
  });
780
 
781
+ // بررسی خطای محدودیت روزانه (429)
782
  if (response.status === 429) {
783
  loader.style.display = 'none';
784
  step1.style.display = 'block';
785
  historySection.style.display = 'block';
786
  processBtn.disabled = false;
787
+
788
  upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
789
  upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت آهنگ‌های بیشتر و نامحدود، لطفا حساب خود را ارتقا دهید.";
790
  upgradeModal.classList.add('open');
 
799
 
800
  const finalLyrics = isInstrumental ? "" : lyrics;
801
 
802
+ // ایندکس 3: مدت زمان آهنگ
803
  const payload = [
804
  getVal('model_select'),
805
+ "custom",
806
  uploadedAudioObj,
807
  getVal('duration_select'),
808
  musicPrompt,
 
839
  }
840
  });
841
 
842
+ function handleAudioOutput(data, lyrics, idea) {
843
  const processedUrls = new Set();
844
  let hasResult = false;
845
 
 
858
  playerWrapper.innerHTML += `<div class="audio-item"><audio controls autoplay src="${fullUrl}"></audio></div>`;
859
  }
860
 
 
861
  function traverse(obj) {
862
+ if (typeof obj === 'string' && obj.includes('/file=') && obj.endsWith('.mp3')) addAudio(obj);
863
+ else if (obj && typeof obj === 'object') {
864
+ if (obj.url && obj.url.endsWith('.mp3')) addAudio(obj.url);
865
+ Object.values(obj).forEach(traverse);
 
 
 
 
 
 
866
  }
867
  }
868
+ traverse(data);
 
 
 
869
 
870
  if (hasResult) {
871
  const headerText = document.getElementById('resultHeaderText');
 
875
  finalLyricsBox.innerHTML = formatLyrics(lyrics);
876
  window.scrollTo({ top: 0, behavior: 'smooth' });
877
  } else {
878
+ alert("فایل صوتی یافت نشد!");
 
879
  step1.style.display = 'block';
880
  historySection.style.display = 'block';
881
  }
882
  }
883
 
884
+ function formatLyrics(text) { return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>'); }
 
 
 
885
 
886
  const canvas = document.getElementById('music-canvas');
887
  const ctx = canvas.getContext('2d');
 
903
  requestAnimationFrame(anim);
904
  }
905
  anim();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
906
  </script>
907
  </body>
908
  </html>