Opera8 commited on
Commit
981a1e4
·
verified ·
1 Parent(s): 18bd4a5

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +325 -201
index.html CHANGED
@@ -66,6 +66,7 @@
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,23 +102,7 @@
101
  textarea { min-height: 120px; resize: vertical; }
102
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
103
 
104
- /* فایل آپلود */
105
- .file-upload-box {
106
- position: relative;
107
- border: 2px dashed var(--input-border);
108
- border-radius: 12px;
109
- padding: 20px;
110
- text-align: center;
111
- background: var(--input-bg);
112
- cursor: pointer;
113
- transition: 0.3s;
114
- }
115
- .file-upload-box:hover { border-color: var(--accent-primary); background: #fff; }
116
- .file-upload-box input {
117
- position: absolute; width: 100%; height: 100%; top: 0; left: 0; opacity: 0; cursor: pointer;
118
- }
119
- .file-info-text { font-size: 0.85rem; color: var(--text-secondary); pointer-events: none; }
120
-
121
  @keyframes move-gradient {
122
  0% { background-position: 0% 50%; }
123
  50% { background-position: 100% 50%; }
@@ -149,6 +134,7 @@
149
  }
150
  .btn-outline:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
151
 
 
152
  .accordion {
153
  background-color: var(--input-bg); color: var(--text-primary); cursor: pointer; padding: 15px; width: 100%; border: none;
154
  text-align: right; outline: none; font-size: 0.95rem; font-weight: 600; border-radius: 12px;
@@ -164,10 +150,13 @@
164
  margin-bottom: 0; border: 1px solid var(--input-border); border-top: none;
165
  }
166
  .settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; padding: 15px 0; }
167
- .checkbox-wrapper { display: flex; align-items: center; gap: 10px; margin-top: 5px; padding: 10px 0; border-top: 1px solid #e2e8f0; }
168
  .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
169
 
 
170
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
 
 
171
  .download-btn-style {
172
  background-color: rgba(74, 108, 250, 0.1); color: var(--accent-primary); text-decoration: none; font-size: 0.9rem;
173
  font-weight: 700; padding: 8px 16px; border-radius: 10px; transition: 0.2s; display: inline-flex; align-items: center; gap: 5px; cursor: pointer; border: none;
@@ -184,6 +173,7 @@
184
  }
185
  .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; }
186
 
 
187
  #loader { display: none; text-align: center; padding: 20px; }
188
  .wave-bars { display: flex; justify-content: center; gap: 4px; height: 30px; align-items: flex-end; }
189
  .bar { width: 5px; background: var(--accent-primary); animation: jump 1s infinite; border-radius: 2px; }
@@ -192,6 +182,7 @@
192
  .bar:nth-child(4) { animation-delay: 0.3s; height: 50%; }
193
  @keyframes jump { 0%, 100% { height: 20%; } 50% { height: 100%; } }
194
 
 
195
  #historySection { margin-top: 30px; width: 100%; }
196
  .history-title { font-size: 1.2rem; font-weight: 800; color: var(--text-primary); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
197
  .history-list { display: grid; gap: 12px; }
@@ -204,16 +195,19 @@
204
  .h-icon { width: 45px; height: 45px; background: var(--input-bg); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: var(--accent-primary); font-size: 1.2rem; }
205
  .h-details h4 { margin: 0; font-size: 1rem; color: var(--text-primary); font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; }
206
  .h-details span { font-size: 0.8rem; color: var(--text-secondary); }
 
207
  .h-delete {
208
  position: absolute; left: 15px;
209
  background: #fee2e2; color: var(--danger-color);
210
  width: 38px; height: 38px; border-radius: 50%;
211
  display: flex; align-items: center; justify-content: center;
212
- opacity: 0; transform: scale(0.8); transition: all 0.3s; z-index: 10;
 
213
  }
214
  .history-card:hover .h-delete { opacity: 1; transform: scale(1); }
215
  .h-delete:hover { background: var(--danger-color); color: white; transform: scale(1.1); }
216
 
 
217
  .modal-overlay {
218
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
219
  background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(8px);
@@ -223,18 +217,39 @@
223
  .modal-box {
224
  background: white; width: 90%; max-width: 400px;
225
  border-radius: 24px; padding: 30px; text-align: center;
226
- transform: scale(0.9); transition: transform 0.3s;
227
  box-shadow: 0 20px 60px rgba(0,0,0,0.2);
228
  }
229
- .modal-icon-warn { width: 60px; height: 60px; background: #fee2e2; color: var(--danger-color); border-radius: 50%; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center; }
 
 
 
230
  .modal-box h3 { margin: 0 0 10px; color: var(--text-primary); }
231
  .modal-box p { color: var(--text-secondary); margin: 0 0 25px; font-size: 0.95rem; }
232
  .modal-actions { display: flex; gap: 10px; justify-content: center; }
233
  .btn-modal { padding: 12px 24px; border-radius: 12px; font-weight: 700; border: none; cursor: pointer; flex: 1; font-size: 1rem; transition: 0.2s; }
234
  .btn-cancel { background: var(--input-bg); color: var(--text-secondary); }
235
- .btn-confirm { background: var(--danger-color); color: white; }
236
- .upgrade-icon { width: 70px; height: 70px; background: var(--premium-gradient); border-radius: 50%; color: white; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center; font-size: 2rem; }
237
- .btn-upgrade { background: var(--premium-gradient); color: #333; box-shadow: 0 5px 15px rgba(255, 193, 7, 0.3); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
 
239
  .modal-overlay.open { display: flex; opacity: 1; }
240
  .modal-overlay.open .modal-box { transform: scale(1); }
@@ -260,32 +275,29 @@
260
  </div>
261
  <textarea id="ideaInput" placeholder="مثال: آهنگ تولد برای سمیرا، سبک پاپ شاد..."></textarea>
262
 
263
- <button class="accordion">تنظیمات پیشرفته (Audio & Mode)</button>
264
  <div class="panel">
265
  <div class="settings-grid">
266
- <div class="settings-group"><label>مدل هوش مصنوعی:</label><select id="model_select"><option value="acestep-v15-turbo-shift3" selected>Turbo-Shift3 (دقیق)</option><option value="acestep-v15-turbo">Turbo (سریع)</option></select></div>
267
- <div class="settings-group"><label>مدت زمان خمینی):</label><select id="duration_select"><option value="short">کوتاه (۱-۲ دقیقه)</option><option value="standard" selected>استاندارد (۳ دقیقه)</option></select></div>
 
 
 
268
  </div>
269
-
270
- <!-- آپلود فایل -->
271
- <div class="settings-group" style="margin-top: 10px; margin-bottom: 15px;">
272
  <label>آپلود آهنگ نمونه (Audio Conditioning):</label>
273
- <div class="file-upload-box" onclick="document.getElementById('audio_file').click()">
274
- <input type="file" id="audio_file" accept="audio/*" onchange="handleFileSelect(this)">
275
- <div id="file_label" class="file-info-text">برای آپلود کلیک کنید یا فایل را اینجا رها کنید</div>
276
- </div>
277
  </div>
278
 
279
- <!-- چک‌باکس‌ها -->
280
  <div class="checkbox-wrapper">
281
  <input type="checkbox" id="instrumental_chk">
282
- <label for="instrumental_chk" style="cursor: pointer;"><b>حالت بی‌کلام (Instrumental)</b><br><span style="font-size: 0.8rem; color: #666;">بدون خواننده، فقط موسیقی</span></label>
283
  </div>
284
 
285
- <div class="settings-grid">
286
- <div class="settings-group"><label>تعداد خروجی:</label><input type="number" id="batch_size" value="1" min="1" max="2"></div>
287
- <div class="settings-group"><label>قدرت (CFG):</label><input type="number" id="cfg_input" value="7.0" step="0.5"></div>
288
- </div>
289
  </div>
290
  <button id="processBtn" class="btn-main" style="margin-top: 15px;"><div class="btn-main-content">ساخت آهنگ <span>🎵</span></div></button>
291
  </div>
@@ -301,10 +313,8 @@
301
  <button id="mainDownloadBtn" class="download-btn-style">دانلود آهنگ 📥</button>
302
  </div>
303
  <div id="playerWrapper"></div>
304
- <div id="lyricsSection">
305
- <div class="form-label" style="margin-top: 20px; justify-content: center; color: #718096;">متن آهنگ</div>
306
- <div class="lyrics-container" id="finalLyricsBox"></div>
307
- </div>
308
  <button onclick="location.reload()" class="btn-main btn-outline">برگشت و ساخت آهنگ جدید</button>
309
  </div>
310
 
@@ -317,12 +327,14 @@
317
  </div>
318
  </div>
319
 
320
- <!-- مودال‌ها -->
321
  <div class="modal-overlay" id="deleteConfirmModal">
322
  <div class="modal-box">
323
- <div class="modal-icon-warn">🗑️</div>
 
 
324
  <h3>آیا مطمئن هستید؟</h3>
325
- <p>این آهنگ برای همیشه حذف خواهد شد.</p>
326
  <div class="modal-actions">
327
  <button class="btn-modal btn-cancel" onclick="closeDeleteModal()">انصراف</button>
328
  <button class="btn-modal btn-confirm" onclick="confirmDelete()">بله، حذف شود</button>
@@ -330,11 +342,12 @@
330
  </div>
331
  </div>
332
 
 
333
  <div class="modal-overlay" id="upgradeModal">
334
  <div class="modal-box">
335
  <div class="upgrade-icon">👑</div>
336
- <h3 id="upgradeModalTitle">دانلود مخصوص اعضای ویژه</h3>
337
- <p id="upgradeModalText">برای دانلود با کیفیت اصلی، حساب خود را ارتقا دهید.</p>
338
  <div class="modal-actions" style="flex-direction: column;">
339
  <button class="btn-modal btn-upgrade" id="btnGoPremium">⭐️ ارتقا به نسخه کامل</button>
340
  <button class="btn-modal btn-cancel" onclick="closeUpgradeModal()" style="width: 100%;">فعلاً نه</button>
@@ -344,13 +357,15 @@
344
 
345
  <script>
346
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
 
347
  const PREMIUM_PAGE_ID = '1149636';
348
- const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/...';
349
 
350
  let db;
351
  let songToDeleteId = null;
352
- let userSubscriptionStatus = 'free';
353
 
 
354
  const ideaInput = document.getElementById('ideaInput');
355
  const processBtn = document.getElementById('processBtn');
356
  const step1 = document.getElementById('step1');
@@ -359,16 +374,19 @@
359
  const finalResult = document.getElementById('finalResult');
360
  const playerWrapper = document.getElementById('playerWrapper');
361
  const finalLyricsBox = document.getElementById('finalLyricsBox');
 
362
  const historyList = document.getElementById('historyList');
363
  const historySection = document.getElementById('historySection');
364
- const statusBadge = document.getElementById('statusBadge');
365
  const upgradeModal = document.getElementById('upgradeModal');
 
 
 
366
 
367
  const getVal = (id) => document.getElementById(id).value;
368
  const getNum = (id) => Number(document.getElementById(id).value);
369
  const getChk = (id) => document.getElementById(id).checked;
370
 
371
- // تنظیمات آکاردئونی
372
  const acc = document.getElementsByClassName("accordion");
373
  for (let i = 0; i < acc.length; i++) {
374
  acc[i].addEventListener("click", function() {
@@ -378,154 +396,239 @@
378
  });
379
  }
380
 
381
- // نمایش نام فایل انتخاب شده
382
- function handleFileSelect(input) {
383
- const label = document.getElementById('file_label');
384
- if (input.files && input.files[0]) {
385
- label.innerText = "✅ " + input.files[0].name;
386
- label.style.color = "var(--success-color)";
387
- } else {
388
- label.innerText = "برای آپلود کلیک کنید یا فایل را اینجا رها کنید";
389
- label.style.color = "var(--text-secondary)";
390
- }
391
- }
392
-
393
- // آپلود فایل به سرور Gradio
394
- async function uploadToGradio(file) {
395
- const formData = new FormData();
396
- formData.append('files', file);
397
-
398
- try {
399
- const response = await fetch(`${ACE_SPACE_URL}gradio_api/upload`, {
400
- method: 'POST',
401
- body: formData
402
- });
403
- const data = await response.json();
404
- // گرادیو معمولاً لیستی از فایل‌ها برمی‌گرداند. ما اولی را می‌خواهیم.
405
- if (data && data[0]) {
406
- // برای Gradio Client باید آبجکت کامل فایل را بفرستیم
407
- // لاگ‌ها نشان می‌دهند که سرور `gradio_api/upload` فقط مسیر فایل را برمی‌گرداند (در نسخه قدیمی)
408
- // اما در نسخه جدیدتر ممکن است آبجکت کامل بدهد.
409
- // بر اساس لاگ شما: response -> ["/tmp/gradio/..."]
410
- // ما باید آبجکت FileData را دستی بسازیم
411
- return {
412
- "path": data[0],
413
- "url": `${ACE_SPACE_URL}gradio_api/file=${data[0]}`,
414
- "orig_name": file.name,
415
- "size": file.size,
416
- "mime_type": file.type,
417
- "meta": {"_type": "gradio.FileData"}
418
- };
419
- }
420
- return null;
421
- } catch (e) {
422
- console.error("Upload Error:", e);
423
- alert("خطا در آپلود فایل صوتی");
424
- return null;
425
- }
426
- }
427
-
428
- // مدیریت اشتراک (مانند قبل)
429
  function getFingerprint() {
430
  let fp = localStorage.getItem('user_fingerprint');
431
  if (!fp) {
 
432
  fp = 'user_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
433
  localStorage.setItem('user_fingerprint', fp);
434
  }
435
  return fp;
436
  }
437
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
  window.addEventListener('message', (event) => {
439
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
440
  try {
441
  const userObject = JSON.parse(event.data.payload);
442
- userSubscriptionStatus = (userObject && userObject.isLogin && userObject.accessible_pages &&
443
- (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) ||
444
- userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)))) ? 'paid' : 'free';
445
- } catch (e) { userSubscriptionStatus = 'free'; }
446
  updateSubscriptionUI(userSubscriptionStatus);
447
  }
448
  });
 
 
449
  parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
450
 
451
- function updateSubscriptionUI(status) {
452
- if (status === 'paid') {
453
- statusBadge.textContent = 'نسخه نامحدود';
454
- statusBadge.className = 'status-badge visible badge-premium';
 
 
 
 
 
455
  } else {
456
- statusBadge.textContent = 'نسخه رایگان';
457
- statusBadge.className = 'status-badge visible badge-free';
 
 
 
 
 
 
 
 
 
 
 
458
  }
459
  }
460
 
461
- // دیتابیس و تاریخچه (خلاصه شده برای اختصار - همان کد قبلی)
462
- function initDB() {
 
 
 
 
 
 
 
 
 
 
 
 
463
  const request = indexedDB.open("alphaMusicDB", 1);
464
- request.onupgradeneeded = (e) => e.target.result.createObjectStore("songs", { keyPath: "id" });
465
- request.onsuccess = (e) => { db = e.target.result; loadHistory(); };
 
466
  }
 
467
  async function saveToHistory(idea, lyrics, audioUrl) {
468
- const response = await fetch(audioUrl);
469
- const audioBlob = await response.blob();
470
- const transaction = db.transaction(["songs"], "readwrite");
471
- transaction.objectStore("songs").add({
472
- id: Date.now(), idea, lyrics, audioBlob,
473
- date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' })
474
- });
475
- transaction.oncomplete = () => loadHistory();
 
 
 
 
 
 
 
 
 
 
476
  }
 
477
  function loadHistory() {
 
478
  const store = db.transaction(["songs"], "readonly").objectStore("songs");
479
  store.getAll().onsuccess = (e) => {
480
  const history = e.target.result.sort((a, b) => b.id - a.id);
481
  historyList.innerHTML = '';
 
 
 
 
482
  history.forEach((item) => {
483
  const div = document.createElement('div');
484
  div.className = 'history-card';
485
- div.innerHTML = `<div class="h-info"><div class="h-icon">🎵</div><div class="h-details"><h4>${item.idea.substring(0,30)}...</h4><span>${item.date}</span></div></div>`;
486
- div.onclick = () => {
487
- const audioURL = URL.createObjectURL(item.audioBlob);
488
- playResult(audioURL, item.lyrics, true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
489
  };
490
  historyList.appendChild(div);
491
  });
492
  };
493
  }
494
- initDB();
495
 
496
- function playResult(audioUrl, lyrics, isArchive) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
  step1.style.display = 'none';
498
  historySection.style.display = 'none';
499
- finalResult.style.display = 'block';
500
- playerWrapper.innerHTML = `<audio controls autoplay src="${audioUrl}" style="width:100%"></audio>`;
501
 
502
- if (lyrics && lyrics.trim() !== "") {
503
- finalLyricsBox.innerHTML = lyrics.replace(/\n/g, '<br>');
504
- document.getElementById('lyricsSection').style.display = 'block';
505
- } else {
506
- document.getElementById('lyricsSection').style.display = 'none';
507
- }
508
-
509
  const headerText = document.getElementById('resultHeaderText');
510
- headerText.innerHTML = isArchive ? "آرشیو آهنگ‌ها" : "آهنگ جدید آماده شد";
 
 
 
 
 
 
 
 
511
 
512
- const btn = document.getElementById('mainDownloadBtn');
513
- btn.onclick = () => {
514
- if(userSubscriptionStatus === 'free') {
515
- document.getElementById('upgradeModal').classList.add('open');
516
- } else {
517
- parent.postMessage({ type: 'INITIATE_DOWNLOAD_FROM_URL', payload: { audioUrl } }, '*');
518
- }
519
- };
520
- window.scrollTo(0,0);
521
  }
 
 
522
 
523
- function closeUpgradeModal() { document.getElementById('upgradeModal').classList.remove('open'); }
524
- document.getElementById('btnGoPremium').addEventListener('click', () => {
525
- parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
526
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
 
528
- // --- پردازش اصلی ---
529
  processBtn.addEventListener('click', async () => {
530
  if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
531
 
@@ -535,18 +638,19 @@
535
  loader.style.display = 'block';
536
 
537
  try {
538
- // 1. آپلود فایل صوتی (اگر وجود داشت)
539
- let audioInputObj = null;
540
- const fileInput = document.getElementById('audio_file');
541
- if (fileInput.files.length > 0) {
542
- loaderText.innerText = "در حال آپلود فایل صوتی...";
543
- audioInputObj = await uploadToGradio(fileInput.files[0]);
544
  }
545
 
546
- // 2. دریافت متن/پرامپت از بک‌اند
547
- loaderText.innerText = "آلفا در حال تفکر...";
548
  const isInstrumental = getChk('instrumental_chk');
549
 
 
550
  const response = await fetch('/api/refine', {
551
  method: 'POST',
552
  headers: {'Content-Type': 'application/json'},
@@ -554,57 +658,54 @@
554
  idea: ideaInput.value,
555
  fingerprint: getFingerprint(),
556
  is_premium: userSubscriptionStatus === 'paid',
557
- is_instrumental: isInstrumental
558
  })
559
  });
560
 
561
- if (response.status === 429) { throw new Error("محدودیت استفاده روزانه"); }
 
 
 
 
 
 
 
 
 
 
 
 
 
562
  const data = await response.json();
563
  if (data.error) throw new Error(data.error);
564
 
565
  const { lyrics, musicPrompt } = data;
566
- loaderText.innerText = "در حال ساخت آهنگ در استودیو...";
567
-
568
- // 3. ارسال به Gradio
569
- // ساختار دقیق Payload بر اساس لاگ‌های کاربر و ساختار ACE-Step
570
- // Index 2 = Audio Input (File Object or null)
571
- // Index 5 = Lyrics (Empty string if instrumental)
572
 
 
573
  const finalLyrics = isInstrumental ? "" : lyrics;
574
 
 
575
  const payload = [
576
- getVal('model_select'), // 0: Model
577
- "custom", // 1: Mode
578
- audioInputObj, // 2: Audio Input (Conditioning) - NEW
579
- "unknown", // 3: Sound Duration (custom handling)
580
- musicPrompt, // 4: Prompt
581
- finalLyrics, // 5: Lyrics
582
- 0, "", "", "unknown", // 6-9
583
- 20, getNum('cfg_input'), true, -1, null, -1, // 10-15 (Steps, CFG, Seed)
584
- getNum('batch_size'), null, null, 0, -1, // 16-20 (Batch size)
585
- "Fill the audio semantic mask based on the given conditions:",
586
  1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
587
- false, 2, 0, 0.9, "NO USER INPUT", true, true, true, null, false, true, false, false, 0.5, 8, null, [], false, null, null, null, null
588
  ];
589
 
590
  const session_hash = Math.random().toString(36).substring(2);
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: payload, fn_index: 77, session_hash })
595
- });
596
-
597
- if (!joinResp.ok) throw new Error('خطا در اتصال به موتور هوش مصنوعی');
598
 
599
  const eventSource = new EventSource(`${ACE_SPACE_URL}gradio_api/queue/data?session_hash=${session_hash}`);
600
-
601
  eventSource.onmessage = (event) => {
602
  const msg = JSON.parse(event.data);
603
- if (msg.msg === 'process_starts') loaderText.innerText = isInstrumental ? "در حال تنظیم ملودی..." : "در حال خواندن...";
604
  else if (msg.msg === 'process_completed') {
605
  eventSource.close();
606
  loader.style.display = 'none';
607
- handleAudioOutput(msg.output, finalLyrics);
608
  }
609
  };
610
  eventSource.onerror = () => { throw new Error('ارتباط با سرور قطع شد.'); };
@@ -613,33 +714,56 @@
613
  alert("خطا: " + e.message);
614
  loader.style.display = 'none';
615
  step1.style.display = 'block';
 
616
  processBtn.disabled = false;
617
  }
618
  });
619
 
620
- function handleAudioOutput(data, lyrics) {
621
- // پیدا کردن URL فایل MP3 از خروجی Gradio
622
- let audioUrl = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  function traverse(obj) {
624
- if (typeof obj === 'string' && obj.includes('/file=') && obj.endsWith('.mp3')) audioUrl = obj;
625
  else if (obj && typeof obj === 'object') {
626
- if (obj.url && obj.url.endsWith('.mp3')) audioUrl = obj.url;
627
  Object.values(obj).forEach(traverse);
628
  }
629
  }
630
  traverse(data);
631
 
632
- if (audioUrl) {
633
- const fullUrl = audioUrl.startsWith('http') ? audioUrl : ACE_SPACE_URL.replace(/\/$/, '') + audioUrl;
634
- saveToHistory(ideaInput.value, lyrics, fullUrl);
635
- playResult(fullUrl, lyrics, false);
 
 
 
636
  } else {
637
  alert("فایل صوتی یافت نشد!");
638
- location.reload();
 
639
  }
640
  }
641
 
642
- // انیمیشن پس‌زمینه
 
643
  const canvas = document.getElementById('music-canvas');
644
  const ctx = canvas.getContext('2d');
645
  let t = 0;
 
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
  textarea { min-height: 120px; resize: vertical; }
103
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
104
 
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
+ /* تنظیمات پیشرفته */
138
  .accordion {
139
  background-color: var(--input-bg); color: var(--text-primary); cursor: pointer; padding: 15px; width: 100%; border: none;
140
  text-align: right; outline: none; font-size: 0.95rem; font-weight: 600; border-radius: 12px;
 
150
  margin-bottom: 0; border: 1px solid var(--input-border); border-top: none;
151
  }
152
  .settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; padding: 15px 0; }
153
+ .checkbox-wrapper { display: flex; align-items: center; gap: 10px; margin-top: 5px; padding-bottom: 15px; border-top: 1px solid #e2e8f0; padding-top: 15px; }
154
  .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
155
 
156
+ /* پلیر و متن */
157
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
158
+
159
+ /* استایل دکمه دانلود */
160
  .download-btn-style {
161
  background-color: rgba(74, 108, 250, 0.1); color: var(--accent-primary); text-decoration: none; font-size: 0.9rem;
162
  font-weight: 700; padding: 8px 16px; border-radius: 10px; transition: 0.2s; display: inline-flex; align-items: center; gap: 5px; cursor: pointer; border: none;
 
173
  }
174
  .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; }
175
 
176
+ /* لودر */
177
  #loader { display: none; text-align: center; padding: 20px; }
178
  .wave-bars { display: flex; justify-content: center; gap: 4px; height: 30px; align-items: flex-end; }
179
  .bar { width: 5px; background: var(--accent-primary); animation: jump 1s infinite; border-radius: 2px; }
 
182
  .bar:nth-child(4) { animation-delay: 0.3s; height: 50%; }
183
  @keyframes jump { 0%, 100% { height: 20%; } 50% { height: 100%; } }
184
 
185
+ /* --- استایل‌های تاریخچه و حذف --- */
186
  #historySection { margin-top: 30px; width: 100%; }
187
  .history-title { font-size: 1.2rem; font-weight: 800; color: var(--text-primary); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
188
  .history-list { display: grid; gap: 12px; }
 
195
  .h-icon { width: 45px; height: 45px; background: var(--input-bg); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: var(--accent-primary); font-size: 1.2rem; }
196
  .h-details h4 { margin: 0; font-size: 1rem; color: var(--text-primary); font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; }
197
  .h-details span { font-size: 0.8rem; color: var(--text-secondary); }
198
+
199
  .h-delete {
200
  position: absolute; left: 15px;
201
  background: #fee2e2; color: var(--danger-color);
202
  width: 38px; height: 38px; border-radius: 50%;
203
  display: flex; align-items: center; justify-content: center;
204
+ opacity: 0; transform: scale(0.8); transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
205
+ z-index: 10; box-shadow: 0 4px 10px rgba(229, 62, 62, 0.2);
206
  }
207
  .history-card:hover .h-delete { opacity: 1; transform: scale(1); }
208
  .h-delete:hover { background: var(--danger-color); color: white; transform: scale(1.1); }
209
 
210
+ /* مودال‌ها */
211
  .modal-overlay {
212
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
213
  background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(8px);
 
217
  .modal-box {
218
  background: white; width: 90%; max-width: 400px;
219
  border-radius: 24px; padding: 30px; text-align: center;
220
+ transform: scale(0.9); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
221
  box-shadow: 0 20px 60px rgba(0,0,0,0.2);
222
  }
223
+ .modal-icon-warn {
224
+ width: 60px; height: 60px; background: #fee2e2; color: var(--danger-color);
225
+ border-radius: 50%; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center;
226
+ }
227
  .modal-box h3 { margin: 0 0 10px; color: var(--text-primary); }
228
  .modal-box p { color: var(--text-secondary); margin: 0 0 25px; font-size: 0.95rem; }
229
  .modal-actions { display: flex; gap: 10px; justify-content: center; }
230
  .btn-modal { padding: 12px 24px; border-radius: 12px; font-weight: 700; border: none; cursor: pointer; flex: 1; font-size: 1rem; transition: 0.2s; }
231
  .btn-cancel { background: var(--input-bg); color: var(--text-secondary); }
232
+ .btn-cancel:hover { background: #e2e8f0; }
233
+ .btn-confirm { background: var(--danger-color); color: white; box-shadow: 0 5px 15px rgba(229, 62, 62, 0.3); }
234
+ .btn-confirm:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(229, 62, 62, 0.4); }
235
+
236
+ /* مودال ارتقا */
237
+ @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); } }
238
+ .upgrade-icon {
239
+ width: 70px; height: 70px;
240
+ background: var(--premium-gradient);
241
+ border-radius: 50%; color: white; margin: 0 auto 20px;
242
+ display: flex; align-items: center; justify-content: center;
243
+ font-size: 2rem; animation: pulse-gold 2s infinite;
244
+ }
245
+ .btn-upgrade {
246
+ background: var(--premium-gradient); color: #333;
247
+ box-shadow: 0 5px 15px rgba(255, 193, 7, 0.3);
248
+ }
249
+ .btn-upgrade:hover {
250
+ transform: translateY(-2px);
251
+ box-shadow: 0 8px 25px rgba(255, 193, 7, 0.5);
252
+ }
253
 
254
  .modal-overlay.open { display: flex; opacity: 1; }
255
  .modal-overlay.open .modal-box { transform: scale(1); }
 
275
  </div>
276
  <textarea id="ideaInput" placeholder="مثال: آهنگ تولد برای سمیرا، سبک پاپ شاد..."></textarea>
277
 
278
+ <button class="accordion">تنظیمات پیشرفته</button>
279
  <div class="panel">
280
  <div class="settings-grid">
281
+ <div class="settings-group"><label>مدل هوش مصنوعی:</label><select id="model_select"><option value="acestep-v15-turbo-shift3" selected>Turbo-Shift3 (دقیق‌ترین)</option><option value="acestep-v15-turbo">Turbo (سریع‌ترین)</option></select></div>
282
+ <div class="settings-group"><label>تعداد خروجی (آهنگ):</label><input type="number" id="batch_size" value="1" min="1" max="4"></div>
283
+ <div class="settings-group"><label>تعداد گام (Steps):</label><input type="number" id="steps_input" value="20" min="4" max="50"></div>
284
+ <div class="settings-group"><label>سید (Seed) تصادفی=-1:</label><input type="number" id="seed_input" value="-1"></div>
285
+ <div class="settings-group"><label>مقیاس هدایت (CFG):</label><input type="number" id="cfg_input" value="7.0" step="0.5"></div>
286
  </div>
287
+
288
+ <!-- بخش اضافه شده: آپلود فایل نمونه -->
289
+ <div class="settings-group" style="border-top: 1px solid #e2e8f0; padding-top: 15px; margin-bottom: 10px;">
290
  <label>آپلود آهنگ نمونه (Audio Conditioning):</label>
291
+ <input type="file" id="audio_reference" accept="audio/*" style="padding: 10px;">
 
 
 
292
  </div>
293
 
294
+ <!-- بخش اضافه شده: چک‌باکس بیکلام -->
295
  <div class="checkbox-wrapper">
296
  <input type="checkbox" id="instrumental_chk">
297
+ <label for="instrumental_chk" style="font-size: 0.9rem; cursor: pointer;"><b>حالت بی‌کلام (Instrumental)</b><br><span style="font-size: 0.8rem; color: #666;">بدون خواننده، فقط موسیقی</span></label>
298
  </div>
299
 
300
+ <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>
 
 
 
301
  </div>
302
  <button id="processBtn" class="btn-main" style="margin-top: 15px;"><div class="btn-main-content">ساخت آهنگ <span>🎵</span></div></button>
303
  </div>
 
313
  <button id="mainDownloadBtn" class="download-btn-style">دانلود آهنگ 📥</button>
314
  </div>
315
  <div id="playerWrapper"></div>
316
+ <div class="form-label" style="margin-top: 20px; justify-content: center; color: #718096;">متن آهنگ</div>
317
+ <div class="lyrics-container" id="finalLyricsBox"></div>
 
 
318
  <button onclick="location.reload()" class="btn-main btn-outline">برگشت و ساخت آهنگ جدید</button>
319
  </div>
320
 
 
327
  </div>
328
  </div>
329
 
330
+ <!-- مودال تایید حذف -->
331
  <div class="modal-overlay" id="deleteConfirmModal">
332
  <div class="modal-box">
333
+ <div class="modal-icon-warn">
334
+ <svg width="30" height="30" 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>
335
+ </div>
336
  <h3>آیا مطمئن هستید؟</h3>
337
+ <p>این آهنگ برای همیشه از سوابق شما حذف خواهد شد و قابل بازگشت نیست.</p>
338
  <div class="modal-actions">
339
  <button class="btn-modal btn-cancel" onclick="closeDeleteModal()">انصراف</button>
340
  <button class="btn-modal btn-confirm" onclick="confirmDelete()">بله، حذف شود</button>
 
342
  </div>
343
  </div>
344
 
345
+ <!-- مودال ارتقا به نسخه کامل -->
346
  <div class="modal-overlay" id="upgradeModal">
347
  <div class="modal-box">
348
  <div class="upgrade-icon">👑</div>
349
+ <h3 style="font-weight: 800; color: #333;" id="upgradeModalTitle">دانلود مخصوص اعضای ویژه</h3>
350
+ <p style="margin-bottom: 25px; line-height: 1.6;" id="upgradeModalText">برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.</p>
351
  <div class="modal-actions" style="flex-direction: column;">
352
  <button class="btn-modal btn-upgrade" id="btnGoPremium">⭐️ ارتقا به نسخه کامل</button>
353
  <button class="btn-modal btn-cancel" onclick="closeUpgradeModal()" style="width: 100%;">فعلاً نه</button>
 
357
 
358
  <script>
359
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
360
+ // مشخصات نسخه پولی
361
  const PREMIUM_PAGE_ID = '1149636';
362
+ const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTU0MjI0ZWVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
363
 
364
  let db;
365
  let songToDeleteId = null;
366
+ let userSubscriptionStatus = 'free'; // پیش‌فرض رایگان
367
 
368
+ // المان‌ها
369
  const ideaInput = document.getElementById('ideaInput');
370
  const processBtn = document.getElementById('processBtn');
371
  const step1 = document.getElementById('step1');
 
374
  const finalResult = document.getElementById('finalResult');
375
  const playerWrapper = document.getElementById('playerWrapper');
376
  const finalLyricsBox = document.getElementById('finalLyricsBox');
377
+ const mainDownloadBtn = document.getElementById('mainDownloadBtn');
378
  const historyList = document.getElementById('historyList');
379
  const historySection = document.getElementById('historySection');
380
+ const deleteModal = document.getElementById('deleteConfirmModal');
381
  const upgradeModal = document.getElementById('upgradeModal');
382
+ const statusBadge = document.getElementById('statusBadge');
383
+ const upgradeModalTitle = document.getElementById('upgradeModalTitle');
384
+ const upgradeModalText = document.getElementById('upgradeModalText');
385
 
386
  const getVal = (id) => document.getElementById(id).value;
387
  const getNum = (id) => Number(document.getElementById(id).value);
388
  const getChk = (id) => document.getElementById(id).checked;
389
 
 
390
  const acc = document.getElementsByClassName("accordion");
391
  for (let i = 0; i < acc.length; i++) {
392
  acc[i].addEventListener("click", function() {
 
396
  });
397
  }
398
 
399
+ // --- مدیریت اثر انگشت (Fingerprint) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  function getFingerprint() {
401
  let fp = localStorage.getItem('user_fingerprint');
402
  if (!fp) {
403
+ // تولید یک شناسه تصادفی و ذخیره آن
404
  fp = 'user_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
405
  localStorage.setItem('user_fingerprint', fp);
406
  }
407
  return fp;
408
  }
409
 
410
+ // --- مدیریت اشتراک و ارتباط با آیفریم مادر ---
411
+ function isUserPaid(userObject) {
412
+ return userObject && userObject.isLogin && userObject.accessible_pages &&
413
+ (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) ||
414
+ userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)));
415
+ }
416
+
417
+ function updateSubscriptionUI(status) {
418
+ if (status === 'paid') {
419
+ statusBadge.textContent = 'نسخه نامحدود';
420
+ statusBadge.className = 'status-badge visible badge-premium';
421
+ mainDownloadBtn.classList.remove('locked');
422
+ } else {
423
+ statusBadge.textContent = 'نسخه رایگان';
424
+ statusBadge.className = 'status-badge visible badge-free';
425
+ }
426
+ }
427
+
428
+ // شنونده پیام از آیفریم مادر
429
  window.addEventListener('message', (event) => {
430
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
431
  try {
432
  const userObject = JSON.parse(event.data.payload);
433
+ userSubscriptionStatus = isUserPaid(userObject) ? 'paid' : 'free';
434
+ } catch (e) {
435
+ userSubscriptionStatus = 'free';
436
+ }
437
  updateSubscriptionUI(userSubscriptionStatus);
438
  }
439
  });
440
+
441
+ // درخواست وضعیت کاربر در شروع برنامه
442
  parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
443
 
444
+ // مدیریت کلیک دکمه دانلود (تابع امنیتی)
445
+ function handleSecureDownload(url, e) {
446
+ if (e) e.preventDefault();
447
+
448
+ if (userSubscriptionStatus === 'free') {
449
+ // تنظیم متن مودال برای دانلود
450
+ upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
451
+ upgradeModalText.innerText = "برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.";
452
+ upgradeModal.classList.add('open');
453
  } else {
454
+ // ارسال درخواست دانلود به آیفریم مادر
455
+ parent.postMessage({
456
+ type: 'INITIATE_DOWNLOAD_FROM_URL',
457
+ payload: { audioUrl: url }
458
+ }, '*');
459
+
460
+ // تغییر موقت متن دکمه برای فیدبک
461
+ const btn = e ? e.target.closest('button') : null;
462
+ if(btn) {
463
+ const originalText = btn.innerHTML;
464
+ btn.innerHTML = 'در حال ارسال... ⏳';
465
+ setTimeout(() => { btn.innerHTML = originalText; }, 2000);
466
+ }
467
  }
468
  }
469
 
470
+ // دکمه ارتقا در مودال
471
+ document.getElementById('btnGoPremium').addEventListener('click', () => {
472
+ parent.postMessage({
473
+ type: 'NAVIGATE_TO_PREMIUM',
474
+ payload: { url: PREMIUM_URL }
475
+ }, '*');
476
+ });
477
+
478
+ function closeUpgradeModal() {
479
+ upgradeModal.classList.remove('open');
480
+ }
481
+
482
+ // --- مدیریت دیتابیس ---
483
+ function initDB() {
484
  const request = indexedDB.open("alphaMusicDB", 1);
485
+ request.onerror = (event) => console.error("IndexedDB error:", event);
486
+ request.onsuccess = (event) => { db = event.target.result; loadHistory(); };
487
+ request.onupgradeneeded = (event) => { event.target.result.createObjectStore("songs", { keyPath: "id" }); };
488
  }
489
+
490
  async function saveToHistory(idea, lyrics, audioUrl) {
491
+ try {
492
+ // برای ذخیره در DB باید فایل را فچ کنیم
493
+ const response = await fetch(audioUrl);
494
+ const audioBlob = await response.blob();
495
+ const newItem = {
496
+ id: Date.now(), idea, lyrics, audioBlob,
497
+ date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' })
498
+ };
499
+ const transaction = db.transaction(["songs"], "readwrite");
500
+ const store = transaction.objectStore("songs");
501
+ store.add(newItem);
502
+
503
+ const countReq = store.count();
504
+ countReq.onsuccess = () => {
505
+ if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
506
+ };
507
+ transaction.oncomplete = () => loadHistory();
508
+ } catch (error) { console.error("Error saving:", error); }
509
  }
510
+
511
  function loadHistory() {
512
+ if (!db) return;
513
  const store = db.transaction(["songs"], "readonly").objectStore("songs");
514
  store.getAll().onsuccess = (e) => {
515
  const history = e.target.result.sort((a, b) => b.id - a.id);
516
  historyList.innerHTML = '';
517
+ if (history.length === 0) {
518
+ historyList.innerHTML = '<div style="text-align:center; color:#999; padding:20px;">هنوز آهنگی نساخته‌اید</div>';
519
+ return;
520
+ }
521
  history.forEach((item) => {
522
  const div = document.createElement('div');
523
  div.className = 'history-card';
524
+ div.innerHTML = `
525
+ <div class="h-info">
526
+ <div class="h-icon">🎵</div>
527
+ <div class="h-details">
528
+ <h4>${item.idea.substring(0, 30)}${item.idea.length > 30 ? '...' : ''}</h4>
529
+ <span>${item.date}</span>
530
+ </div>
531
+ </div>
532
+ <div class="h-delete" onclick="askToDelete(event, ${item.id})">
533
+ <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>
534
+ </div>
535
+ `;
536
+ // کلیک روی خود کارت برای پخش
537
+ div.onclick = (e) => {
538
+ // اگر روی دکمه حذف کلیک نشده باشد
539
+ if (!e.target.closest('.h-delete')) {
540
+ openHistoryItem(item);
541
+ }
542
  };
543
  historyList.appendChild(div);
544
  });
545
  };
546
  }
 
547
 
548
+ // --- توابع حذف ---
549
+ function askToDelete(event, id) {
550
+ event.stopPropagation(); // جلوگیری از باز شدن پلیر
551
+ songToDeleteId = id;
552
+ deleteModal.classList.add('open');
553
+ }
554
+
555
+ function closeDeleteModal() {
556
+ deleteModal.classList.remove('open');
557
+ songToDeleteId = null;
558
+ }
559
+
560
+ function confirmDelete() {
561
+ if (songToDeleteId && db) {
562
+ const transaction = db.transaction(["songs"], "readwrite");
563
+ const store = transaction.objectStore("songs");
564
+ store.delete(songToDeleteId);
565
+ transaction.oncomplete = () => {
566
+ loadHistory();
567
+ closeDeleteModal();
568
+ };
569
+ }
570
+ }
571
+
572
+ // بستن مودال با کلیک بیرون
573
+ deleteModal.addEventListener('click', (e) => {
574
+ if (e.target === deleteModal) closeDeleteModal();
575
+ });
576
+ upgradeModal.addEventListener('click', (e) => {
577
+ if (e.target === upgradeModal) closeUpgradeModal();
578
+ });
579
+
580
+ function openHistoryItem(item) {
581
  step1.style.display = 'none';
582
  historySection.style.display = 'none';
 
 
583
 
584
+ const audioURL = URL.createObjectURL(item.audioBlob);
 
 
 
 
 
 
585
  const headerText = document.getElementById('resultHeaderText');
586
+ 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> آهنگ آرشیو شده`;
587
+ headerText.style.color = 'var(--accent-primary)';
588
+
589
+ // تنظیم دکمه دانلود
590
+ mainDownloadBtn.style.display = 'inline-flex';
591
+ mainDownloadBtn.onclick = (e) => handleSecureDownload(audioURL, e);
592
+
593
+ playerWrapper.innerHTML = `<audio controls autoplay src="${audioURL}"></audio>`;
594
+ finalLyricsBox.innerHTML = formatLyrics(item.lyrics);
595
 
596
+ finalResult.style.display = 'block';
597
+ window.scrollTo({ top: 0, behavior: 'smooth' });
 
 
 
 
 
 
 
598
  }
599
+
600
+ initDB();
601
 
602
+ // --- تابع آپلود فایل صوتی به Gradio ---
603
+ async function uploadAudioFile(file) {
604
+ const formData = new FormData();
605
+ formData.append("files", file);
606
+ try {
607
+ const response = await fetch(`${ACE_SPACE_URL}gradio_api/upload`, {
608
+ method: "POST",
609
+ body: formData
610
+ });
611
+ const data = await response.json();
612
+ if (data && data.length > 0) {
613
+ // ساختن آبجکت مورد نیاز Gradio Client
614
+ // طبق لاگ‌ها، response یک آرایه از مسیرهاست: ["/tmp/gradio/..."]
615
+ return {
616
+ "path": data[0],
617
+ "url": `${ACE_SPACE_URL}gradio_api/file=${data[0]}`,
618
+ "orig_name": file.name,
619
+ "size": file.size,
620
+ "mime_type": file.type,
621
+ "meta": {"_type": "gradio.FileData"}
622
+ };
623
+ }
624
+ return null;
625
+ } catch (error) {
626
+ console.error("Upload failed:", error);
627
+ return null;
628
+ }
629
+ }
630
 
631
+ // --- فرآیند اصلی ---
632
  processBtn.addEventListener('click', async () => {
633
  if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
634
 
 
638
  loader.style.display = 'block';
639
 
640
  try {
641
+ // 1. آپلود فایل صوتی اگر انتخاب شده باشد
642
+ const audioInput = document.getElementById('audio_reference');
643
+ let uploadedAudioObj = null;
644
+ if (audioInput.files.length > 0) {
645
+ loaderText.innerText = "در حال آپلود فایل نمونه...";
646
+ uploadedAudioObj = await uploadAudioFile(audioInput.files[0]);
647
  }
648
 
649
+ // 2. درخواست متن و پرامپت از بک‌اند
650
+ loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
651
  const isInstrumental = getChk('instrumental_chk');
652
 
653
+ // ارسال درخواست به سرور با فینگرپرینت و وضعیت اشتراک
654
  const response = await fetch('/api/refine', {
655
  method: 'POST',
656
  headers: {'Content-Type': 'application/json'},
 
658
  idea: ideaInput.value,
659
  fingerprint: getFingerprint(),
660
  is_premium: userSubscriptionStatus === 'paid',
661
+ is_instrumental: isInstrumental // ارسال وضعیت بی‌کلام
662
  })
663
  });
664
 
665
+ // بررسی خطای محدودیت روزانه (429)
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
+ // نمایش مودال ارتقا با متن مناسب
673
+ upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
674
+ upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت آهنگ‌های بیشتر و نامحدود، لطفا حساب خود را ارتقا دهید.";
675
+ upgradeModal.classList.add('open');
676
+ return;
677
+ }
678
+
679
  const data = await response.json();
680
  if (data.error) throw new Error(data.error);
681
 
682
  const { lyrics, musicPrompt } = data;
683
+ loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
 
 
 
 
 
684
 
685
+ // اگر بی‌کلام انتخاب شده، متن خالی ارسال می‌شود (ایندکس 5)
686
  const finalLyrics = isInstrumental ? "" : lyrics;
687
 
688
+ // ایندکس 2: فایل آپلود شده (null یا آبجکت فایل)
689
  const payload = [
690
+ getVal('model_select'), "custom", uploadedAudioObj, "unknown", musicPrompt, finalLyrics, 0, "", "", "unknown",
691
+ getNum('steps_input'), getNum('cfg_input'), true, getNum('seed_input'), null, -1,
692
+ getNum('batch_size'), null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
 
 
 
 
 
 
 
693
  1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
694
+ 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
695
  ];
696
 
697
  const session_hash = Math.random().toString(36).substring(2);
698
+ const joinResp = await fetch(`${ACE_SPACE_URL}gradio_api/queue/join`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: payload, fn_index: 77, session_hash }) });
699
+ if (!joinResp.ok) throw new Error('خطا در اتصال به سرور موزیک');
 
 
 
 
 
700
 
701
  const eventSource = new EventSource(`${ACE_SPACE_URL}gradio_api/queue/data?session_hash=${session_hash}`);
 
702
  eventSource.onmessage = (event) => {
703
  const msg = JSON.parse(event.data);
704
+ if (msg.msg === 'process_starts') loaderText.innerText = "هوش مصنوعی در حال خواندن...";
705
  else if (msg.msg === 'process_completed') {
706
  eventSource.close();
707
  loader.style.display = 'none';
708
+ handleAudioOutput(msg.output, lyrics, ideaInput.value);
709
  }
710
  };
711
  eventSource.onerror = () => { throw new Error('ارتباط با سرور قطع شد.'); };
 
714
  alert("خطا: " + e.message);
715
  loader.style.display = 'none';
716
  step1.style.display = 'block';
717
+ historySection.style.display = 'block';
718
  processBtn.disabled = false;
719
  }
720
  });
721
 
722
+ function handleAudioOutput(data, lyrics, idea) {
723
+ const processedUrls = new Set();
724
+ let hasResult = false;
725
+
726
+ function addAudio(url) {
727
+ const fullUrl = url.startsWith('http') ? url : ACE_SPACE_URL.replace(/\/$/, '') + url;
728
+ if (processedUrls.has(fullUrl)) return;
729
+ processedUrls.add(fullUrl);
730
+ hasResult = true;
731
+
732
+ if (processedUrls.size === 1) {
733
+ // تنظیم دکمه دانلود اصلی
734
+ mainDownloadBtn.style.display = 'inline-flex';
735
+ mainDownloadBtn.onclick = (e) => handleSecureDownload(fullUrl, e);
736
+
737
+ saveToHistory(idea, lyrics, fullUrl);
738
+ }
739
+ playerWrapper.innerHTML += `<div class="audio-item"><audio controls autoplay src="${fullUrl}"></audio></div>`;
740
+ }
741
+
742
  function traverse(obj) {
743
+ if (typeof obj === 'string' && obj.includes('/file=') && obj.endsWith('.mp3')) addAudio(obj);
744
  else if (obj && typeof obj === 'object') {
745
+ if (obj.url && obj.url.endsWith('.mp3')) addAudio(obj.url);
746
  Object.values(obj).forEach(traverse);
747
  }
748
  }
749
  traverse(data);
750
 
751
+ if (hasResult) {
752
+ const headerText = document.getElementById('resultHeaderText');
753
+ 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>آهنگ جدید آماده شد`;
754
+ headerText.style.color = 'var(--success-color)';
755
+ finalResult.style.display = 'block';
756
+ finalLyricsBox.innerHTML = formatLyrics(lyrics);
757
+ window.scrollTo({ top: 0, behavior: 'smooth' });
758
  } else {
759
  alert("فایل صوتی یافت نشد!");
760
+ step1.style.display = 'block';
761
+ historySection.style.display = 'block';
762
  }
763
  }
764
 
765
+ function formatLyrics(text) { return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>'); }
766
+
767
  const canvas = document.getElementById('music-canvas');
768
  const ctx = canvas.getContext('2d');
769
  let t = 0;