Opera8 commited on
Commit
18bd4a5
·
verified ·
1 Parent(s): 70573b7

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +201 -435
index.html CHANGED
@@ -1,4 +1,3 @@
1
- <!-- START OF FILE index.html -->
2
  <!DOCTYPE html>
3
  <html lang="fa" dir="rtl">
4
  <head>
@@ -67,7 +66,6 @@
67
  }
68
  .subtitle { font-size: 0.9rem; color: var(--text-secondary); margin-top: 5px; margin-bottom: 10px; }
69
 
70
- /* استایل بج وضعیت اشتراک */
71
  .status-badge {
72
  display: inline-block; padding: 6px 16px; border-radius: 20px;
73
  font-size: 0.85rem; font-weight: 700; margin-top: 5px;
@@ -93,7 +91,7 @@
93
  .form-label { display: flex; align-items: center; gap: 8px; font-weight: 700; margin-bottom: 12px; color: #2d3748; }
94
  .form-label svg { color: var(--accent-primary); width: 20px; }
95
 
96
- textarea, input[type="text"], input[type="number"], select {
97
  width: 100%; background: var(--input-bg);
98
  border: 2px solid var(--input-border);
99
  border-radius: 12px; padding: 15px;
@@ -104,23 +102,22 @@
104
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
105
 
106
  /* فایل آپلود */
107
- .file-upload-wrapper {
108
  position: relative;
109
- background: var(--input-bg);
110
  border: 2px dashed var(--input-border);
111
  border-radius: 12px;
112
  padding: 20px;
113
  text-align: center;
114
- transition: 0.3s;
115
  cursor: pointer;
 
116
  }
117
- .file-upload-wrapper:hover { border-color: var(--accent-primary); background: #fff; }
118
- .file-upload-wrapper input[type="file"] {
119
  position: absolute; width: 100%; height: 100%; top: 0; left: 0; opacity: 0; cursor: pointer;
120
  }
121
- .file-info { font-size: 0.9rem; color: var(--text-secondary); pointer-events: none; }
122
 
123
- /* دکمه اصلی */
124
  @keyframes move-gradient {
125
  0% { background-position: 0% 50%; }
126
  50% { background-position: 100% 50%; }
@@ -152,7 +149,6 @@
152
  }
153
  .btn-outline:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
154
 
155
- /* تنظیمات پیشرفته */
156
  .accordion {
157
  background-color: var(--input-bg); color: var(--text-primary); cursor: pointer; padding: 15px; width: 100%; border: none;
158
  text-align: right; outline: none; font-size: 0.95rem; font-weight: 600; border-radius: 12px;
@@ -168,14 +164,10 @@
168
  margin-bottom: 0; border: 1px solid var(--input-border); border-top: none;
169
  }
170
  .settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; padding: 15px 0; }
171
- .checkbox-wrapper { display: flex; align-items: center; gap: 10px; margin-top: 5px; padding-bottom: 15px; border-top: 1px solid #e2e8f0; padding-top: 15px; }
172
  .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
173
- .full-width { grid-column: span 2; }
174
 
175
- /* پلیر و متن */
176
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
177
-
178
- /* استایل دکمه دانلود */
179
  .download-btn-style {
180
  background-color: rgba(74, 108, 250, 0.1); color: var(--accent-primary); text-decoration: none; font-size: 0.9rem;
181
  font-weight: 700; padding: 8px 16px; border-radius: 10px; transition: 0.2s; display: inline-flex; align-items: center; gap: 5px; cursor: pointer; border: none;
@@ -186,27 +178,12 @@
186
 
187
  .audio-item { margin-bottom: 10px; }
188
  audio { width: 100%; height: 45px; border-radius: 20px; margin-top: 5px; }
189
-
190
- /* استایل متن آهنگ (لیریکس) */
191
  .lyrics-container {
192
  background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto;
193
- white-space: pre-wrap; line-height: 2.2; font-size: 1rem; color: #718096; text-align: center; border: 1px solid var(--input-border); margin-top: 15px;
194
- position: relative;
195
- scroll-behavior: smooth;
196
- }
197
- .lyrics-line {
198
- display: block; margin-bottom: 12px; transition: all 0.3s ease; border-radius: 8px; padding: 2px 10px;
199
- }
200
- .lyrics-line.active {
201
- color: var(--accent-primary);
202
- font-weight: 800;
203
- transform: scale(1.05);
204
- background-color: rgba(74, 108, 250, 0.05);
205
- text-shadow: 0 0 1px rgba(74, 108, 250, 0.3);
206
  }
207
- .lyrics-tag { color: var(--accent-secondary); font-size: 0.85em; margin-bottom: 5px; opacity: 0.8; }
208
 
209
- /* لودر */
210
  #loader { display: none; text-align: center; padding: 20px; }
211
  .wave-bars { display: flex; justify-content: center; gap: 4px; height: 30px; align-items: flex-end; }
212
  .bar { width: 5px; background: var(--accent-primary); animation: jump 1s infinite; border-radius: 2px; }
@@ -215,7 +192,6 @@
215
  .bar:nth-child(4) { animation-delay: 0.3s; height: 50%; }
216
  @keyframes jump { 0%, 100% { height: 20%; } 50% { height: 100%; } }
217
 
218
- /* --- استایل‌های تاریخچه و حذف --- */
219
  #historySection { margin-top: 30px; width: 100%; }
220
  .history-title { font-size: 1.2rem; font-weight: 800; color: var(--text-primary); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
221
  .history-list { display: grid; gap: 12px; }
@@ -228,19 +204,16 @@
228
  .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; }
229
  .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; }
230
  .h-details span { font-size: 0.8rem; color: var(--text-secondary); }
231
-
232
  .h-delete {
233
  position: absolute; left: 15px;
234
  background: #fee2e2; color: var(--danger-color);
235
  width: 38px; height: 38px; border-radius: 50%;
236
  display: flex; align-items: center; justify-content: center;
237
- opacity: 0; transform: scale(0.8); transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
238
- z-index: 10; box-shadow: 0 4px 10px rgba(229, 62, 62, 0.2);
239
  }
240
  .history-card:hover .h-delete { opacity: 1; transform: scale(1); }
241
  .h-delete:hover { background: var(--danger-color); color: white; transform: scale(1.1); }
242
 
243
- /* مودال‌ها */
244
  .modal-overlay {
245
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
246
  background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(8px);
@@ -250,39 +223,18 @@
250
  .modal-box {
251
  background: white; width: 90%; max-width: 400px;
252
  border-radius: 24px; padding: 30px; text-align: center;
253
- transform: scale(0.9); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
254
  box-shadow: 0 20px 60px rgba(0,0,0,0.2);
255
  }
256
- .modal-icon-warn {
257
- width: 60px; height: 60px; background: #fee2e2; color: var(--danger-color);
258
- border-radius: 50%; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center;
259
- }
260
  .modal-box h3 { margin: 0 0 10px; color: var(--text-primary); }
261
  .modal-box p { color: var(--text-secondary); margin: 0 0 25px; font-size: 0.95rem; }
262
  .modal-actions { display: flex; gap: 10px; justify-content: center; }
263
  .btn-modal { padding: 12px 24px; border-radius: 12px; font-weight: 700; border: none; cursor: pointer; flex: 1; font-size: 1rem; transition: 0.2s; }
264
  .btn-cancel { background: var(--input-bg); color: var(--text-secondary); }
265
- .btn-cancel:hover { background: #e2e8f0; }
266
- .btn-confirm { background: var(--danger-color); color: white; box-shadow: 0 5px 15px rgba(229, 62, 62, 0.3); }
267
- .btn-confirm:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(229, 62, 62, 0.4); }
268
-
269
- /* مودال ارتقا */
270
- @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); } }
271
- .upgrade-icon {
272
- width: 70px; height: 70px;
273
- background: var(--premium-gradient);
274
- border-radius: 50%; color: white; margin: 0 auto 20px;
275
- display: flex; align-items: center; justify-content: center;
276
- font-size: 2rem; animation: pulse-gold 2s infinite;
277
- }
278
- .btn-upgrade {
279
- background: var(--premium-gradient); color: #333;
280
- box-shadow: 0 5px 15px rgba(255, 193, 7, 0.3);
281
- }
282
- .btn-upgrade:hover {
283
- transform: translateY(-2px);
284
- box-shadow: 0 8px 25px rgba(255, 193, 7, 0.5);
285
- }
286
 
287
  .modal-overlay.open { display: flex; opacity: 1; }
288
  .modal-overlay.open .modal-box { transform: scale(1); }
@@ -308,34 +260,31 @@
308
  </div>
309
  <textarea id="ideaInput" placeholder="مثال: آهنگ تولد برای سمیرا، سبک پاپ شاد..."></textarea>
310
 
311
- <button class="accordion">تنظیمات پیشرفته</button>
312
  <div class="panel">
313
  <div class="settings-grid">
314
- <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>
315
- <div class="settings-group"><label>تعداد خروجی (آهنگ):</label><input type="number" id="batch_size" value="1" min="1" max="4"></div>
316
- <div class="settings-group"><label>تعداد گام (Steps):</label><input type="number" id="steps_input" value="20" min="4" max="50"></div>
317
- <div class="settings-group"><label>سید (Seed) تصادفی=-1:</label><input type="number" id="seed_input" value="-1"></div>
318
-
319
- <!-- بخش جدید آپلود Audio Conditioning -->
320
- <div class="settings-group full-width">
321
- <label>Audio Conditioning (آپلود آهنگ نمونه - اختیاری):</label>
322
- <div class="file-upload-wrapper" id="dropZone">
323
- <input type="file" id="audio_ref_input" accept="audio/*">
324
- <span id="file_info_text" class="file-info">برای آپلود کلیک کنید یا فایل را اینجا رها کنید (MP3/WAV)</span>
325
- </div>
326
- </div>
327
-
328
- <div class="settings-group"><label>مقیاس هدایت (CFG):</label><input type="number" id="cfg_input" value="7.0" step="0.5"></div>
329
  </div>
330
 
 
 
 
 
 
 
 
 
 
 
331
  <div class="checkbox-wrapper">
332
- <input type="checkbox" id="think_checkbox" checked>
333
- <label for="think_checkbox" style="font-size: 0.9rem; cursor: pointer;"><b>فعال‌سازی تفکر مدل</b> <span style="font-size: 0.8rem; color: #666;">(افزایش کیفیت)</span></label>
334
  </div>
335
- <!-- بخش جدید Instrumental -->
336
- <div class="checkbox-wrapper" style="border-top: none; padding-top: 0;">
337
- <input type="checkbox" id="instrumental_mode">
338
- <label for="instrumental_mode" style="font-size: 0.9rem; cursor: pointer;"><b>حالت بی‌کلام (Instrumental)</b> <span style="font-size: 0.8rem; color: #666;">(بدون خواننده)</span></label>
339
  </div>
340
  </div>
341
  <button id="processBtn" class="btn-main" style="margin-top: 15px;"><div class="btn-main-content">ساخت آهنگ <span>🎵</span></div></button>
@@ -352,8 +301,10 @@
352
  <button id="mainDownloadBtn" class="download-btn-style">دانلود آهنگ 📥</button>
353
  </div>
354
  <div id="playerWrapper"></div>
355
- <div class="form-label" style="margin-top: 20px; justify-content: center; color: #718096;">متن آهنگ</div>
356
- <div class="lyrics-container" id="finalLyricsBox"></div>
 
 
357
  <button onclick="location.reload()" class="btn-main btn-outline">برگشت و ساخت آهنگ جدید</button>
358
  </div>
359
 
@@ -366,14 +317,12 @@
366
  </div>
367
  </div>
368
 
369
- <!-- مودال تایید حذف -->
370
  <div class="modal-overlay" id="deleteConfirmModal">
371
  <div class="modal-box">
372
- <div class="modal-icon-warn">
373
- <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>
374
- </div>
375
  <h3>آیا مطمئن هستید؟</h3>
376
- <p>این آهنگ برای همیشه از سوابق شما حذف خواهد شد و قابل بازگشت نیست.</p>
377
  <div class="modal-actions">
378
  <button class="btn-modal btn-cancel" onclick="closeDeleteModal()">انصراف</button>
379
  <button class="btn-modal btn-confirm" onclick="confirmDelete()">بله، حذف شود</button>
@@ -381,12 +330,11 @@
381
  </div>
382
  </div>
383
 
384
- <!-- مودال ارتقا به نسخه کامل -->
385
  <div class="modal-overlay" id="upgradeModal">
386
  <div class="modal-box">
387
  <div class="upgrade-icon">👑</div>
388
- <h3 style="font-weight: 800; color: #333;" id="upgradeModalTitle">دانلود مخصوص اعضای ویژه</h3>
389
- <p style="margin-bottom: 25px; line-height: 1.6;" id="upgradeModalText">برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.</p>
390
  <div class="modal-actions" style="flex-direction: column;">
391
  <button class="btn-modal btn-upgrade" id="btnGoPremium">⭐️ ارتقا به نسخه کامل</button>
392
  <button class="btn-modal btn-cancel" onclick="closeUpgradeModal()" style="width: 100%;">فعلاً نه</button>
@@ -396,17 +344,13 @@
396
 
397
  <script>
398
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
399
- // مشخصات نسخه پولی
400
  const PREMIUM_PAGE_ID = '1149636';
401
- const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTU0MjI0ZWVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
402
 
403
  let db;
404
  let songToDeleteId = null;
405
- let userSubscriptionStatus = 'free'; // پیش‌فرض رایگان
406
- let uploadedAudioPath = null; // مسیر فایل آپلود شده
407
- let lyricsSyncData = []; // داده‌های زمانی متن آهنگ
408
 
409
- // المان‌ها
410
  const ideaInput = document.getElementById('ideaInput');
411
  const processBtn = document.getElementById('processBtn');
412
  const step1 = document.getElementById('step1');
@@ -415,23 +359,16 @@
415
  const finalResult = document.getElementById('finalResult');
416
  const playerWrapper = document.getElementById('playerWrapper');
417
  const finalLyricsBox = document.getElementById('finalLyricsBox');
418
- const mainDownloadBtn = document.getElementById('mainDownloadBtn');
419
  const historyList = document.getElementById('historyList');
420
  const historySection = document.getElementById('historySection');
421
- const deleteModal = document.getElementById('deleteConfirmModal');
422
- const upgradeModal = document.getElementById('upgradeModal');
423
  const statusBadge = document.getElementById('statusBadge');
424
- const upgradeModalTitle = document.getElementById('upgradeModalTitle');
425
- const upgradeModalText = document.getElementById('upgradeModalText');
426
- const audioRefInput = document.getElementById('audio_ref_input');
427
- const fileInfoText = document.getElementById('file_info_text');
428
- const instrumentalMode = document.getElementById('instrumental_mode');
429
 
430
  const getVal = (id) => document.getElementById(id).value;
431
  const getNum = (id) => Number(document.getElementById(id).value);
432
  const getChk = (id) => document.getElementById(id).checked;
433
 
434
- // --- مدیریت رابط کاربری (UI) ---
435
  const acc = document.getElementsByClassName("accordion");
436
  for (let i = 0; i < acc.length; i++) {
437
  acc[i].addEventListener("click", function() {
@@ -441,16 +378,54 @@
441
  });
442
  }
443
 
444
- // تغییر متن فایل انتخاب شده
445
- audioRefInput.addEventListener('change', function() {
446
- if (this.files && this.files[0]) {
447
- fileInfoText.innerText = "فایل انتخاب شد: " + this.files[0].name;
448
- fileInfoText.style.color = "var(--success-color)";
449
- fileInfoText.style.fontWeight = "bold";
 
 
 
450
  }
451
- });
452
 
453
- // --- مدیریت اثر انگشت (Fingerprint) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  function getFingerprint() {
455
  let fp = localStorage.getItem('user_fingerprint');
456
  if (!fp) {
@@ -460,242 +435,97 @@
460
  return fp;
461
  }
462
 
463
- // --- مدیریت اشتراک ---
464
- function isUserPaid(userObject) {
465
- return userObject && userObject.isLogin && userObject.accessible_pages &&
466
- (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) ||
467
- userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)));
468
- }
469
-
470
- function updateSubscriptionUI(status) {
471
- if (status === 'paid') {
472
- statusBadge.textContent = 'نسخه نامحدود';
473
- statusBadge.className = 'status-badge visible badge-premium';
474
- mainDownloadBtn.classList.remove('locked');
475
- } else {
476
- statusBadge.textContent = 'نسخه رایگان';
477
- statusBadge.className = 'status-badge visible badge-free';
478
- }
479
- }
480
-
481
  window.addEventListener('message', (event) => {
482
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
483
  try {
484
  const userObject = JSON.parse(event.data.payload);
485
- userSubscriptionStatus = isUserPaid(userObject) ? 'paid' : 'free';
486
- } catch (e) {
487
- userSubscriptionStatus = 'free';
488
- }
489
  updateSubscriptionUI(userSubscriptionStatus);
490
  }
491
  });
492
-
493
  parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
494
 
495
- // --- توابع کمکی ---
496
- function handleSecureDownload(url, e) {
497
- if (e) e.preventDefault();
498
- if (userSubscriptionStatus === 'free') {
499
- upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
500
- upgradeModalText.innerText = "برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.";
501
- upgradeModal.classList.add('open');
502
  } else {
503
- parent.postMessage({
504
- type: 'INITIATE_DOWNLOAD_FROM_URL',
505
- payload: { audioUrl: url }
506
- }, '*');
507
- const btn = e ? e.target.closest('button') : null;
508
- if(btn) {
509
- const originalText = btn.innerHTML;
510
- btn.innerHTML = 'در حال ارسال... ⏳';
511
- setTimeout(() => { btn.innerHTML = originalText; }, 2000);
512
- }
513
  }
514
  }
515
 
516
- document.getElementById('btnGoPremium').addEventListener('click', () => {
517
- parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
518
- });
519
-
520
- function closeUpgradeModal() { upgradeModal.classList.remove('open'); }
521
-
522
- // --- دیتابیس و تاریخچه ---
523
- function initDB() {
524
  const request = indexedDB.open("alphaMusicDB", 1);
525
- request.onerror = (event) => console.error("IndexedDB error:", event);
526
- request.onsuccess = (event) => { db = event.target.result; loadHistory(); };
527
- request.onupgradeneeded = (event) => { event.target.result.createObjectStore("songs", { keyPath: "id" }); };
528
  }
529
-
530
  async function saveToHistory(idea, lyrics, audioUrl) {
531
- try {
532
- const response = await fetch(audioUrl);
533
- const audioBlob = await response.blob();
534
- const newItem = {
535
- id: Date.now(), idea, lyrics, audioBlob,
536
- date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' })
537
- };
538
- const transaction = db.transaction(["songs"], "readwrite");
539
- const store = transaction.objectStore("songs");
540
- store.add(newItem);
541
- const countReq = store.count();
542
- countReq.onsuccess = () => {
543
- if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
544
- };
545
- transaction.oncomplete = () => loadHistory();
546
- } catch (error) { console.error("Error saving:", error); }
547
  }
548
-
549
  function loadHistory() {
550
- if (!db) return;
551
  const store = db.transaction(["songs"], "readonly").objectStore("songs");
552
  store.getAll().onsuccess = (e) => {
553
  const history = e.target.result.sort((a, b) => b.id - a.id);
554
  historyList.innerHTML = '';
555
- if (history.length === 0) {
556
- historyList.innerHTML = '<div style="text-align:center; color:#999; padding:20px;">هنوز آهنگی نساخته‌اید</div>';
557
- return;
558
- }
559
  history.forEach((item) => {
560
  const div = document.createElement('div');
561
  div.className = 'history-card';
562
- div.innerHTML = `
563
- <div class="h-info">
564
- <div class="h-icon">🎵</div>
565
- <div class="h-details">
566
- <h4>${item.idea.substring(0, 30)}${item.idea.length > 30 ? '...' : ''}</h4>
567
- <span>${item.date}</span>
568
- </div>
569
- </div>
570
- <div class="h-delete" onclick="askToDelete(event, ${item.id})">
571
- <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>
572
- </div>
573
- `;
574
- div.onclick = (e) => { if (!e.target.closest('.h-delete')) openHistoryItem(item); };
575
  historyList.appendChild(div);
576
  });
577
  };
578
  }
579
-
580
- function askToDelete(event, id) {
581
- event.stopPropagation(); songToDeleteId = id; deleteModal.classList.add('open');
582
- }
583
- function closeDeleteModal() { deleteModal.classList.remove('open'); songToDeleteId = null; }
584
- function confirmDelete() {
585
- if (songToDeleteId && db) {
586
- const transaction = db.transaction(["songs"], "readwrite");
587
- const store = transaction.objectStore("songs");
588
- store.delete(songToDeleteId);
589
- transaction.oncomplete = () => { loadHistory(); closeDeleteModal(); };
590
- }
591
- }
592
- deleteModal.addEventListener('click', (e) => { if (e.target === deleteModal) closeDeleteModal(); });
593
- upgradeModal.addEventListener('click', (e) => { if (e.target === upgradeModal) closeUpgradeModal(); });
594
-
595
- function openHistoryItem(item) {
596
- step1.style.display = 'none'; historySection.style.display = 'none';
597
- const audioURL = URL.createObjectURL(item.audioBlob);
598
- const headerText = document.getElementById('resultHeaderText');
599
- headerText.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg> آهنگ آرشیو شده`;
600
- headerText.style.color = 'var(--accent-primary)';
601
- mainDownloadBtn.style.display = 'inline-flex';
602
- mainDownloadBtn.onclick = (e) => handleSecureDownload(audioURL, e);
603
- playerWrapper.innerHTML = `<audio controls autoplay src="${audioURL}"></audio>`;
604
- finalLyricsBox.innerHTML = formatLyrics(item.lyrics);
605
- finalResult.style.display = 'block';
606
- window.scrollTo({ top: 0, behavior: 'smooth' });
607
- }
608
-
609
  initDB();
610
 
611
- // --- توابع آپلود و پردازش صدا ---
612
- async function uploadAudioFile(file) {
613
- const formData = new FormData();
614
- formData.append('files', file);
615
- // تولید شناسه آپلود تصادفی
616
- const uploadId = Math.random().toString(36).substring(2);
617
-
618
- const response = await fetch(`${ACE_SPACE_URL}upload?upload_id=${uploadId}`, {
619
- method: 'POST',
620
- body: formData
621
- });
622
-
623
- if (!response.ok) throw new Error("آپلود فایل صوتی با خطا مواجه شد.");
624
 
625
- const data = await response.json();
626
- // خروجی به صورت ["/tmp/gradio/..."] است
627
- return data[0];
628
- }
629
-
630
- // --- پردازش VTT برای متن هماهنگ ---
631
- async function fetchAndParseVTT(vttUrl) {
632
- try {
633
- // اگر URL نسبی است، دامنه را اضافه کن
634
- const fullUrl = vttUrl.startsWith('http') ? vttUrl : ACE_SPACE_URL.replace(/\/$/, '') + vttUrl;
635
- const response = await fetch(fullUrl);
636
- const text = await response.text();
637
-
638
- lyricsSyncData = [];
639
- // رگکس ساده برای پارس کردن فرمت WEBVTT
640
- // 00:00:16.320 --> 00:00:20.640
641
- const regex = /(\d{2}:\d{2}:\d{2}\.\d{3})\s-->\s(\d{2}:\d{2}:\d{2}\.\d{3})\s*\n(.*?)(?=\n\n|\n\d|$)/gs;
642
- let match;
643
-
644
- while ((match = regex.exec(text)) !== null) {
645
- const start = parseTime(match[1]);
646
- const end = parseTime(match[2]);
647
- const content = match[3].trim();
648
- if (content) {
649
- lyricsSyncData.push({ start, end, text: content });
650
- }
651
- }
652
-
653
- // رندر کردن مجدد متن آهنگ با قابلیت سینک
654
- renderSyncedLyrics();
655
- } catch (e) {
656
- console.error("Error parsing subtitles:", e);
657
  }
658
- }
659
-
660
- function parseTime(timeStr) {
661
- const parts = timeStr.split(':');
662
- const seconds = parts[2].split('.');
663
- return (parseInt(parts[0]) * 3600) + (parseInt(parts[1]) * 60) + parseInt(seconds[0]) + (parseInt(seconds[1]) / 1000);
664
- }
665
-
666
- function renderSyncedLyrics() {
667
- finalLyricsBox.innerHTML = '';
668
- if (lyricsSyncData.length === 0) return;
669
-
670
- lyricsSyncData.forEach((line, index) => {
671
- const div = document.createElement('div');
672
- div.className = 'lyrics-line';
673
- div.id = `lyric-${index}`;
674
- div.innerHTML = line.text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>');
675
- finalLyricsBox.appendChild(div);
676
- });
677
- }
678
 
679
- function syncLyricsToAudio(currentTime) {
680
- if (!lyricsSyncData.length) return;
681
-
682
- // پیدا کردن خط فعلی
683
- const currentLineIndex = lyricsSyncData.findIndex(line => currentTime >= line.start && currentTime <= line.end);
684
-
685
- // حذف کلاس active از همه
686
- document.querySelectorAll('.lyrics-line').forEach(el => el.classList.remove('active'));
687
 
688
- if (currentLineIndex !== -1) {
689
- const activeEl = document.getElementById(`lyric-${currentLineIndex}`);
690
- if (activeEl) {
691
- activeEl.classList.add('active');
692
- // اسکرول نرم به خط فعال
693
- activeEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
694
- }
695
- }
 
696
  }
697
 
698
- // --- فرآیند اصلی ---
 
 
 
 
 
699
  processBtn.addEventListener('click', async () => {
700
  if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
701
 
@@ -705,22 +535,17 @@
705
  loader.style.display = 'block';
706
 
707
  try {
708
- // 1. آپلود فایل اگر وجود داشته باشد
709
- if (audioRefInput.files.length > 0) {
710
- loaderText.innerText = "در حال آپلود فایل صوتی شما...";
711
- try {
712
- uploadedAudioPath = await uploadAudioFile(audioRefInput.files[0]);
713
- } catch (e) {
714
- alert("خطا در آپلود فایل: " + e.message);
715
- throw e;
716
- }
717
- } else {
718
- uploadedAudioPath = null;
719
  }
720
 
721
- // 2. دریافت پرامپت و متن از Gemini
722
- loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
723
- const isInstrumental = getChk('instrumental_mode');
724
 
725
  const response = await fetch('/api/refine', {
726
  method: 'POST',
@@ -729,151 +554,92 @@
729
  idea: ideaInput.value,
730
  fingerprint: getFingerprint(),
731
  is_premium: userSubscriptionStatus === 'paid',
732
- is_instrumental: isInstrumental // ارسال وضعیت اینسترومنتال
733
  })
734
  });
735
 
736
- if (response.status === 429) {
737
- loader.style.display = 'none'; step1.style.display = 'block'; historySection.style.display = 'block'; processBtn.disabled = false;
738
- upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
739
- upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت آهنگ‌های بیشتر و نامحدود، لطفا حساب خود را ارتقا دهید.";
740
- upgradeModal.classList.add('open');
741
- return;
742
- }
743
-
744
  const data = await response.json();
745
  if (data.error) throw new Error(data.error);
746
 
747
- let { lyrics, musicPrompt } = data;
748
-
749
- // اگر کاربر Instrumental خواسته، متن را خالی رد می‌کنیم تا مدل نخواند
750
- if (isInstrumental) {
751
- lyrics = "";
752
- }
753
 
754
- loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
 
 
 
755
 
756
- // ساخت Payload برای نسخه ACE-Step-v1.5
757
- // جایگذاری uploadedAudioPath در ایندکس 14 (جایگزین null)
758
  const payload = [
759
  getVal('model_select'), // 0: Model
760
- "custom", // 1: Prompt type
761
- null, // 2
762
- "unknown", // 3
763
- musicPrompt, // 4: Music Prompt
764
- lyrics, // 5: Lyrics
765
  0, "", "", "unknown", // 6-9
766
- getNum('steps_input'), // 10: Steps
767
- getNum('cfg_input'), // 11: CFG
768
- true, // 12
769
- getNum('seed_input'), // 13: Seed
770
- uploadedAudioPath, // 14: Audio Conditioning (null if empty)
771
- -1, // 15
772
- getNum('batch_size'), // 16
773
- null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
774
  1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
775
- getChk('think_checkbox'), 2, 0, 0.9, "NO USER INPUT", true, true, true, null, false, true, false, false, 0.5, 8, null, [], false, null, null, null, null
776
  ];
777
 
778
  const session_hash = Math.random().toString(36).substring(2);
779
- 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 }) });
780
- if (!joinResp.ok) throw new Error('خطا در اتصال به سرور موزیک');
 
 
 
 
 
781
 
782
  const eventSource = new EventSource(`${ACE_SPACE_URL}gradio_api/queue/data?session_hash=${session_hash}`);
 
783
  eventSource.onmessage = (event) => {
784
  const msg = JSON.parse(event.data);
785
- if (msg.msg === 'process_starts') loaderText.innerText = "هوش مصنوعی در حال خواندن...";
786
  else if (msg.msg === 'process_completed') {
787
  eventSource.close();
788
  loader.style.display = 'none';
789
- handleAudioOutput(msg.output, lyrics, ideaInput.value, isInstrumental);
790
  }
791
  };
792
  eventSource.onerror = () => { throw new Error('ارتباط با سرور قطع شد.'); };
793
 
794
  } catch (e) {
795
  alert("خطا: " + e.message);
796
- loader.style.display = 'none'; step1.style.display = 'block'; historySection.style.display = 'block'; processBtn.disabled = false;
 
 
797
  }
798
  });
799
 
800
- function handleAudioOutput(data, lyrics, idea, isInstrumental) {
801
- const processedUrls = new Set();
802
- let hasResult = false;
803
- let vttUrl = null;
804
-
805
- // تابع بازگشتی برای پیدا کردن فایل‌ها
806
  function traverse(obj) {
807
- if (typeof obj === 'string') {
808
- if (obj.includes('/file=') && obj.endsWith('.mp3')) addAudio(obj);
809
- if ((obj.includes('/file=') || obj.endsWith('.vtt')) && obj.includes('subtitles')) vttUrl = obj;
810
- } else if (obj && typeof obj === 'object') {
811
- if (obj.url && obj.url.endsWith('.mp3')) addAudio(obj.url);
812
- if (obj.url && obj.url.endsWith('.vtt')) vttUrl = obj.url;
813
  Object.values(obj).forEach(traverse);
814
  }
815
  }
816
-
817
- function addAudio(url) {
818
- const fullUrl = url.startsWith('http') ? url : ACE_SPACE_URL.replace(/\/$/, '') + url;
819
- if (processedUrls.has(fullUrl)) return;
820
- processedUrls.add(fullUrl);
821
- hasResult = true;
822
-
823
- if (processedUrls.size === 1) {
824
- mainDownloadBtn.style.display = 'inline-flex';
825
- mainDownloadBtn.onclick = (e) => handleSecureDownload(fullUrl, e);
826
- saveToHistory(idea, lyrics, fullUrl);
827
- }
828
-
829
- // ایجاد پلیر و اتصال رویداد timeupdate برای سینک کردن متن
830
- const div = document.createElement('div');
831
- div.className = 'audio-item';
832
- const audio = document.createElement('audio');
833
- audio.controls = true;
834
- audio.autoplay = true;
835
- audio.src = fullUrl;
836
-
837
- // اگر فایل زیرنویس پیدا شده، رویداد سینک را وصل کن
838
- audio.ontimeupdate = () => syncLyricsToAudio(audio.currentTime);
839
-
840
- div.appendChild(audio);
841
- playerWrapper.appendChild(div);
842
- }
843
-
844
- // پاک کردن پلیر قبلی
845
- playerWrapper.innerHTML = '';
846
  traverse(data);
847
 
848
- if (hasResult) {
849
- const headerText = document.getElementById('resultHeaderText');
850
- headerText.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>آهنگ جدید آماده شد`;
851
- headerText.style.color = 'var(--success-color)';
852
- finalResult.style.display = 'block';
853
-
854
- if (isInstrumental) {
855
- finalLyricsBox.innerHTML = '<div style="margin-top:20px; font-weight:bold;">(موسیقی بی‌کلام / Instrumental)</div>';
856
- } else {
857
- // نمایش اولیه متن (اگر VTT پیدا نشد، فرمت ساده)
858
- finalLyricsBox.innerHTML = formatLyrics(lyrics);
859
- // اگر فایل VTT پیدا شد، آن را لود و جایگزین کن
860
- if (vttUrl) {
861
- fetchAndParseVTT(vttUrl);
862
- }
863
- }
864
-
865
- window.scrollTo({ top: 0, behavior: 'smooth' });
866
  } else {
867
  alert("فایل صوتی یافت نشد!");
868
- step1.style.display = 'block'; historySection.style.display = 'block';
869
  }
870
  }
871
 
872
- function formatLyrics(text) {
873
- // فرمت ساده برای وقتی که VTT هنوز لود نشده یا وجود ندارد
874
- return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>');
875
- }
876
-
877
  const canvas = document.getElementById('music-canvas');
878
  const ctx = canvas.getContext('2d');
879
  let t = 0;
 
 
1
  <!DOCTYPE html>
2
  <html lang="fa" dir="rtl">
3
  <head>
 
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;
 
91
  .form-label { display: flex; align-items: center; gap: 8px; font-weight: 700; margin-bottom: 12px; color: #2d3748; }
92
  .form-label svg { color: var(--accent-primary); width: 20px; }
93
 
94
+ textarea, input, select {
95
  width: 100%; background: var(--input-bg);
96
  border: 2px solid var(--input-border);
97
  border-radius: 12px; padding: 15px;
 
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
  }
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
  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;
 
178
 
179
  .audio-item { margin-bottom: 10px; }
180
  audio { width: 100%; height: 45px; border-radius: 20px; margin-top: 5px; }
 
 
181
  .lyrics-container {
182
  background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto;
183
+ white-space: pre-wrap; line-height: 2; font-size: 1rem; color: #4a5568; text-align: center; border: 1px solid var(--input-border); margin-top: 15px;
 
 
 
 
 
 
 
 
 
 
 
 
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
  .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
  .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
  .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
  </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>
 
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
  </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
  </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
 
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
  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
  });
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) {
 
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
  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',
 
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('ارتباط با سرور قطع شد.'); };
611
 
612
  } catch (e) {
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;