Opera8 commited on
Commit
b989671
·
verified ·
1 Parent(s): f1bf7b2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +70 -143
index.html CHANGED
@@ -401,7 +401,6 @@
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,7 +425,6 @@
426
  }
427
  }
428
 
429
- // شنونده پیام از آیفریم مادر
430
  window.addEventListener('message', (event) => {
431
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
432
  try {
@@ -439,26 +437,16 @@
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,12 +456,8 @@
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,7 +474,6 @@
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,7 +484,6 @@
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,9 +518,7 @@
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,7 +530,7 @@
550
 
551
  // --- توابع حذف ---
552
  function askToDelete(event, id) {
553
- event.stopPropagation(); // جلوگیری از باز شدن پلیر
554
  songToDeleteId = id;
555
  deleteModal.classList.add('open');
556
  }
@@ -572,7 +552,6 @@
572
  }
573
  }
574
 
575
- // بستن مودال با کلیک بیرون
576
  deleteModal.addEventListener('click', (e) => {
577
  if (e.target === deleteModal) closeDeleteModal();
578
  });
@@ -589,7 +568,6 @@
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,122 +580,36 @@
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,53 +630,44 @@
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,10 +682,9 @@
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,
@@ -903,6 +785,51 @@
903
  requestAnimationFrame(anim);
904
  }
905
  anim();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
906
  </script>
907
  </body>
908
  </html>
 
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
  }
426
  }
427
 
 
428
  window.addEventListener('message', (event) => {
429
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
430
  try {
 
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
  }
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
 
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
  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
  <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
 
531
  // --- توابع حذف ---
532
  function askToDelete(event, id) {
533
+ event.stopPropagation();
534
  songToDeleteId = id;
535
  deleteModal.classList.add('open');
536
  }
 
552
  }
553
  }
554
 
 
555
  deleteModal.addEventListener('click', (e) => {
556
  if (e.target === deleteModal) closeDeleteModal();
557
  });
 
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
 
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
  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
+ // --- **نکته کلیدی: تغییر حالت** ---
648
+ if (uploadedAudioObj) {
649
+ mode = "Continuation";
650
+ }
651
  }
652
 
 
653
  loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
654
  const isInstrumental = getChk('instrumental_chk');
655
 
 
656
  const response = await fetch('/api/refine', {
657
  method: 'POST',
658
  headers: {'Content-Type': 'application/json'},
659
  body: JSON.stringify({
660
+ idea: ideaInput.value, fingerprint: getFingerprint(),
 
661
  is_premium: userSubscriptionStatus === 'paid',
662
  is_instrumental: isInstrumental
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
  upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
672
  upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت آهنگ‌های بیشتر و نامحدود، لطفا حساب خود را ارتقا دهید.";
673
  upgradeModal.classList.add('open');
 
682
 
683
  const finalLyrics = isInstrumental ? "" : lyrics;
684
 
 
685
  const payload = [
686
  getVal('model_select'),
687
+ mode, // **اصلاح کلیدی:** استفاده از حالت داینامیک
688
  uploadedAudioObj,
689
  getVal('duration_select'),
690
  musicPrompt,
 
785
  requestAnimationFrame(anim);
786
  }
787
  anim();
788
+
789
+ // --- توابع کمکی برای تبدیل به WAV ---
790
+ function bufferToWave(aBuffer, len) {
791
+ let numOfChan = aBuffer.numberOfChannels,
792
+ length = len * numOfChan * 2 + 44,
793
+ buffer = new ArrayBuffer(length),
794
+ view = new DataView(buffer),
795
+ channels = [], i, sample,
796
+ offset = 0,
797
+ pos = 0;
798
+
799
+ writeString(view, 0, 'RIFF');
800
+ view.setUint32(4, 36 + len * numOfChan * 2, true);
801
+ writeString(view, 8, 'WAVE');
802
+ writeString(view, 12, 'fmt ');
803
+ view.setUint32(16, 16, true);
804
+ view.setUint16(20, 1, true);
805
+ view.setUint16(22, numOfChan, true);
806
+ view.setUint32(24, aBuffer.sampleRate, true);
807
+ view.setUint32(28, aBuffer.sampleRate * 4, true);
808
+ view.setUint16(32, numOfChan * 2, true);
809
+ view.setUint16(34, 16, true);
810
+ writeString(view, 36, 'data');
811
+ view.setUint32(40, len * numOfChan * 2, true);
812
+
813
+ for (i = 0; i < aBuffer.numberOfChannels; i++)
814
+ channels.push(aBuffer.getChannelData(i));
815
+
816
+ while (pos < len) {
817
+ for (i = 0; i < numOfChan; i++) {
818
+ sample = Math.max(-1, Math.min(1, channels[i][pos]));
819
+ sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0;
820
+ view.setInt16(offset, sample, true);
821
+ offset += 2;
822
+ }
823
+ pos++;
824
+ }
825
+ return new Blob([view], { type: 'audio/wav' });
826
+ }
827
+
828
+ function writeString(view, offset, string) {
829
+ for (var i = 0; i < string.length; i++) {
830
+ view.setUint8(offset + i, string.charCodeAt(i));
831
+ }
832
+ }
833
  </script>
834
  </body>
835
  </html>