Opera8 commited on
Commit
19708db
·
verified ·
1 Parent(s): 7baa7eb

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +266 -306
index.html CHANGED
@@ -1,55 +1,55 @@
1
- <!DOCTYPE html>
2
- <html lang="fa" dir="rtl">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
- <title>استودیو موزیک آلفا (Alpha)</title>
7
- <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap" rel="stylesheet">
8
- <style>
9
- :root {
10
- --app-font: 'Vazirmatn', sans-serif;
11
- --app-bg: #F8F9FC;
12
- --panel-bg: #FFFFFF;
13
- --panel-border: #EAEFF7;
14
- --input-bg: #F6F8FB;
15
- --input-border: #E1E7EF;
16
- --text-primary: #1A202C;
17
- --text-secondary: #626F86;
18
- --accent-primary: #4A6CFA;
19
  --accent-secondary: #9F7AEA;
20
- --accent-glow: rgba(74, 108, 250, 0.2);
21
  --success-color: #38A169;
22
- --danger-color: #e53e3e;
23
  --premium-gold: #FFC107;
24
  --premium-gradient: linear-gradient(135deg, #FFC107 0%, #FF9800 100%);
25
- --radius-card: 20px;
26
- --radius-btn: 14px;
27
- }
28
 
29
  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
30
 
31
- body {
32
- font-family: var(--app-font);
33
- background-color: var(--app-bg);
34
- color: var(--text-primary);
35
- margin: 0;
36
- padding: 20px 15px;
37
  min-height: 100vh;
38
  display: flex;
39
  flex-direction: column;
40
  align-items: center;
41
- }
42
 
43
- .container { max-width: 650px; width: 100%; z-index: 2; position: relative; }
44
 
45
- #music-canvas {
46
- position: fixed; top: 0; left: 0; width: 100%; height: 400px;
47
  z-index: 0; opacity: 0.5; pointer-events: none;
48
  mask-image: linear-gradient(to bottom, black, transparent);
49
  -webkit-mask-image: linear-gradient(to bottom, black, transparent);
50
  }
51
 
52
- header { text-align: center; margin-bottom: 2rem; position: relative; }
53
 
54
  .logo-box {
55
  width: 80px; height: 80px; margin: 0 auto 15px;
@@ -59,13 +59,14 @@
59
  color: var(--accent-primary);
60
  }
61
 
62
- h1 {
63
- font-size: 1.8rem; font-weight: 800; margin: 0;
64
- background: linear-gradient(90deg, #2d3748, #4A6CFA);
65
- -webkit-background-clip: text; -webkit-text-fill-color: transparent;
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;
@@ -74,46 +75,45 @@
74
  }
75
  .status-badge.visible { opacity: 1; transform: translateY(0); }
76
  .badge-free { background: #E2E8F0; color: #64748b; }
77
- .badge-premium {
78
- background: var(--premium-gradient); color: #fff;
79
  box-shadow: 0 4px 15px rgba(255, 193, 7, 0.4);
80
  text-shadow: 0 1px 2px rgba(0,0,0,0.1);
81
  }
82
 
83
- .card {
84
- background: var(--panel-bg);
85
- border-radius: var(--radius-card);
86
- box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.05);
87
- border: 1px solid var(--panel-border);
88
  padding: 25px; margin-bottom: 20px;
89
  }
90
 
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;
98
  font-family: inherit; font-size: 1rem; color: #2d3748;
99
  outline: none; transition: border-color 0.3s;
100
  }
101
  textarea { min-height: 120px; resize: vertical; }
102
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
103
- input[type="file"] { padding: 12px; }
104
- input[type="file"]::file-selector-button {
105
- background: var(--accent-primary); color: white; border: none; padding: 8px 12px;
106
- border-radius: 8px; font-weight: 600; cursor: pointer; transition: 0.2s;
107
- }
108
- input[type="file"]::file-selector-button:hover { background: var(--accent-secondary); }
109
 
110
- @keyframes move-gradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
111
- .btn-main {
112
- width: 100%; padding: 16px;
 
 
 
 
 
113
  background: linear-gradient(110deg, var(--accent-secondary), var(--accent-primary), var(--accent-secondary));
114
  background-size: 200% 100%;
115
- color: #fff; border: none; border-radius: var(--radius-btn);
116
- font-size: 1.1rem; font-weight: 700; cursor: pointer;
117
  display: flex; justify-content: center; align-items: center; gap: 10px;
118
  transition: all 0.3s ease-out; position: relative; z-index: 1;
119
  }
@@ -128,9 +128,13 @@
128
  .btn-main:hover::before { opacity: 1; animation: move-gradient 4s linear infinite; }
129
  .btn-main:disabled { opacity: 0.7; cursor: not-allowed; filter: grayscale(1); }
130
 
131
- .btn-outline { background: transparent; border: 2px solid var(--input-border); color: var(--text-secondary); margin-top: 10px; transition: all 0.2s; }
 
 
 
132
  .btn-outline:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
133
 
 
134
  .accordion {
135
  background-color: var(--input-bg); color: var(--text-primary); cursor: pointer; padding: 15px; width: 100%; border: none;
136
  text-align: right; outline: none; font-size: 0.95rem; font-weight: 600; border-radius: 12px;
@@ -149,8 +153,10 @@
149
  .checkbox-wrapper { display: flex; align-items: center; gap: 10px; margin-top: 5px; padding-bottom: 15px; border-top: 1px solid #e2e8f0; padding-top: 15px; }
150
  .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
151
 
 
152
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
153
 
 
154
  .download-btn-style {
155
  background-color: rgba(74, 108, 250, 0.1); color: var(--accent-primary); text-decoration: none; font-size: 0.9rem;
156
  font-weight: 700; padding: 8px 16px; border-radius: 10px; transition: 0.2s; display: inline-flex; align-items: center; gap: 5px; cursor: pointer; border: none;
@@ -163,17 +169,11 @@
163
  audio { width: 100%; height: 45px; border-radius: 20px; margin-top: 5px; }
164
  .lyrics-container {
165
  background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto;
166
- white-space: pre-wrap; line-height: 2.2; font-size: 1.1rem; color: #69768a; text-align: center; border: 1px solid var(--input-border); margin-top: 15px;
167
- scroll-behavior: smooth;
168
  }
169
  .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; }
170
- .lyrics-line { display: block; transition: all 0.3s ease; transform-origin: center; }
171
- .lyrics-line.active {
172
- color: var(--text-primary);
173
- font-weight: 800;
174
- transform: scale(1.1);
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,6 +182,7 @@
182
  .bar:nth-child(4) { animation-delay: 0.3s; height: 50%; }
183
  @keyframes jump { 0%, 100% { height: 20%; } 50% { height: 100%; } }
184
 
 
185
  #historySection { margin-top: 30px; width: 100%; }
186
  .history-title { font-size: 1.2rem; font-weight: 800; color: var(--text-primary); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
187
  .history-list { display: grid; gap: 12px; }
@@ -196,7 +197,7 @@
196
  .h-details span { font-size: 0.8rem; color: var(--text-secondary); }
197
 
198
  .h-delete {
199
- position: absolute; left: 15px;
200
  background: #fee2e2; color: var(--danger-color);
201
  width: 38px; height: 38px; border-radius: 50%;
202
  display: flex; align-items: center; justify-content: center;
@@ -206,6 +207,7 @@
206
  .history-card:hover .h-delete { opacity: 1; transform: scale(1); }
207
  .h-delete:hover { background: var(--danger-color); color: white; transform: scale(1.1); }
208
 
 
209
  .modal-overlay {
210
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
211
  background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(8px);
@@ -231,33 +233,40 @@
231
  .btn-confirm { background: var(--danger-color); color: white; box-shadow: 0 5px 15px rgba(229, 62, 62, 0.3); }
232
  .btn-confirm:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(229, 62, 62, 0.4); }
233
 
 
234
  @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); } }
235
- .upgrade-icon {
236
- width: 70px; height: 70px;
237
  background: var(--premium-gradient);
238
- border-radius: 50%; color: white; margin: 0 auto 20px;
239
- display: flex; align-items: center; justify-content: center;
240
- font-size: 2rem; animation: pulse-gold 2s infinite;
 
 
 
 
 
 
 
 
241
  }
242
- .btn-upgrade { background: var(--premium-gradient); color: #333; box-shadow: 0 5px 15px rgba(255, 193, 7, 0.3); }
243
- .btn-upgrade:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(255, 193, 7, 0.5); }
244
 
245
  .modal-overlay.open { display: flex; opacity: 1; }
246
  .modal-overlay.open .modal-box { transform: scale(1); }
247
  </style>
248
- </head>
249
- <body>
250
  <canvas id="music-canvas"></canvas>
251
 
252
  <div class="container">
253
- <header>
254
  <div class="logo-box">
255
  <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>
256
  </div>
257
- <h1>استودیو موزیک آلفا</h1>
258
  <p class="subtitle">ساخت آهنگ حرفه‌ای با هوش مصنوعی</p>
259
  <div id="statusBadge" class="status-badge">نسخه رایگان</div>
260
- </header>
261
 
262
  <div id="step1" class="card">
263
  <div class="form-label">
@@ -273,14 +282,9 @@
273
  <div class="settings-group"><label>تعداد خروجی (آهنگ):</label><input type="number" id="batch_size" value="1" min="1" max="4"></div>
274
  <div class="settings-group"><label>تعداد گام (Steps):</label><input type="number" id="steps_input" value="20" min="4" max="50"></div>
275
  <div class="settings-group"><label>سید (Seed) تصادفی=-1:</label><input type="number" id="seed_input" value="-1"></div>
276
- <div class="settings-group" style="grid-column: 1 / -1;"><label>نمونه صوتی (اختیاری):</label><input type="file" id="audioConditionInput" accept="audio/*"></div>
277
- </div>
278
- <div class="checkbox-wrapper">
279
- <input type="checkbox" id="instrumentalCheckbox"><label for="instrumentalCheckbox" style="font-size: 0.9rem; cursor: pointer;"><b>فقط آهنگ بی‌کلام (Instrumental)</b></label>
280
- </div>
281
- <div class="checkbox-wrapper" style="border-top: none; padding-top: 0;">
282
- <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>
283
  </div>
 
284
  </div>
285
  <button id="processBtn" class="btn-main" style="margin-top: 15px;"><div class="btn-main-content">ساخت آهنگ <span>🎵</span></div></button>
286
  </div>
@@ -296,10 +300,8 @@
296
  <button id="mainDownloadBtn" class="download-btn-style">دانلود آهنگ 📥</button>
297
  </div>
298
  <div id="playerWrapper"></div>
299
- <div id="lyricsWrapper" style="display: none;">
300
- <div class="form-label" style="margin-top: 20px; justify-content: center; color: #718096;">متن آهنگ</div>
301
- <div class="lyrics-container" id="finalLyricsBox"></div>
302
- </div>
303
  <button onclick="location.reload()" class="btn-main btn-outline">برگشت و ساخت آهنگ جدید</button>
304
  </div>
305
 
@@ -312,7 +314,7 @@
312
  </div>
313
  </div>
314
 
315
- <!-- Modals -->
316
  <div class="modal-overlay" id="deleteConfirmModal">
317
  <div class="modal-box">
318
  <div class="modal-icon-warn">
@@ -327,6 +329,7 @@
327
  </div>
328
  </div>
329
 
 
330
  <div class="modal-overlay" id="upgradeModal">
331
  <div class="modal-box">
332
  <div class="upgrade-icon">👑</div>
@@ -341,16 +344,15 @@
341
 
342
  <script>
343
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
 
344
  const PREMIUM_PAGE_ID = '1149636';
345
  const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTU0MjI0ZWVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
346
 
347
  let db;
348
  let songToDeleteId = null;
349
- let userSubscriptionStatus = 'free';
350
- let timedLyrics = [];
351
- let currentAudioElement = null;
352
 
353
- // Elements
354
  const ideaInput = document.getElementById('ideaInput');
355
  const processBtn = document.getElementById('processBtn');
356
  const step1 = document.getElementById('step1');
@@ -359,7 +361,6 @@
359
  const finalResult = document.getElementById('finalResult');
360
  const playerWrapper = document.getElementById('playerWrapper');
361
  const finalLyricsBox = document.getElementById('finalLyricsBox');
362
- const lyricsWrapper = document.getElementById('lyricsWrapper');
363
  const mainDownloadBtn = document.getElementById('mainDownloadBtn');
364
  const historyList = document.getElementById('historyList');
365
  const historySection = document.getElementById('historySection');
@@ -368,29 +369,32 @@
368
  const statusBadge = document.getElementById('statusBadge');
369
  const upgradeModalTitle = document.getElementById('upgradeModalTitle');
370
  const upgradeModalText = document.getElementById('upgradeModalText');
371
- const audioConditionInput = document.getElementById('audioConditionInput');
372
 
373
  const getVal = (id) => document.getElementById(id).value;
374
  const getNum = (id) => Number(document.getElementById(id).value);
375
  const getChk = (id) => document.getElementById(id).checked;
376
 
377
- const acc = document.getElementsByClassName("accordion")[0];
378
- acc.addEventListener("click", function() {
379
- this.classList.toggle("active");
380
- const panel = this.nextElementSibling;
381
- panel.style.maxHeight = panel.style.maxHeight ? null : panel.scrollHeight + "px";
382
- });
 
 
383
 
384
- // --- Fingerprinting & Subscription ---
385
  function getFingerprint() {
386
  let fp = localStorage.getItem('user_fingerprint');
387
  if (!fp) {
 
388
  fp = 'user_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
389
  localStorage.setItem('user_fingerprint', fp);
390
  }
391
  return fp;
392
  }
393
 
 
394
  function isUserPaid(userObject) {
395
  return userObject && userObject.isLogin && userObject.accessible_pages &&
396
  (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) ||
@@ -398,10 +402,17 @@
398
  }
399
 
400
  function updateSubscriptionUI(status) {
401
- statusBadge.className = 'status-badge visible ' + (status === 'paid' ? 'badge-premium' : 'badge-free');
402
- statusBadge.textContent = status === 'paid' ? 'نسخه نامحدود' : 'نسخه رایگان';
 
 
 
 
 
 
403
  }
404
 
 
405
  window.addEventListener('message', (event) => {
406
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
407
  try {
@@ -413,59 +424,74 @@
413
  updateSubscriptionUI(userSubscriptionStatus);
414
  }
415
  });
 
 
416
  parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
417
 
418
- // --- Modals and Download Logic ---
419
  function handleSecureDownload(url, e) {
420
  if (e) e.preventDefault();
 
421
  if (userSubscriptionStatus === 'free') {
 
422
  upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
423
- upgradeModalText.innerText = "برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی، لطفا حساب کاربری خود را ارتقا دهید.";
424
  upgradeModal.classList.add('open');
425
  } else {
426
- parent.postMessage({ type: 'INITIATE_DOWNLOAD_FROM_URL', payload: { audioUrl: url } }, '*');
 
 
 
 
 
 
427
  const btn = e ? e.target.closest('button') : null;
428
- if (btn) {
429
  const originalText = btn.innerHTML;
430
  btn.innerHTML = 'در حال ارسال... ⏳';
431
  setTimeout(() => { btn.innerHTML = originalText; }, 2000);
432
  }
433
  }
434
  }
 
 
435
  document.getElementById('btnGoPremium').addEventListener('click', () => {
436
- parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*');
 
 
 
437
  });
438
- function closeUpgradeModal() { upgradeModal.classList.remove('open'); }
439
- function closeDeleteModal() { deleteModal.classList.remove('open'); songToDeleteId = null; }
440
 
441
- [deleteModal, upgradeModal].forEach(modal => {
442
- modal.addEventListener('click', (e) => {
443
- if (e.target === modal) modal.classList.remove('open');
444
- });
445
- });
446
 
447
- // --- IndexedDB History ---
448
  function initDB() {
449
  const request = indexedDB.open("alphaMusicDB", 1);
450
- request.onerror = (e) => console.error("IndexedDB error:", e);
451
- request.onsuccess = (e) => { db = e.target.result; loadHistory(); };
452
- request.onupgradeneeded = (e) => { e.target.result.createObjectStore("songs", { keyPath: "id" }); };
453
  }
454
 
455
  async function saveToHistory(idea, lyrics, audioUrl) {
456
  try {
 
457
  const response = await fetch(audioUrl);
458
  const audioBlob = await response.blob();
459
  const newItem = {
460
  id: Date.now(), idea, lyrics, audioBlob,
461
  date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' })
462
  };
463
- const tx = db.transaction(["songs"], "readwrite");
464
- const store = tx.objectStore("songs");
465
  store.add(newItem);
 
466
  const countReq = store.count();
467
- countReq.onsuccess = () => { if (countReq.result > 10) store.openCursor().onsuccess = (e) => e.target.result?.delete(); };
468
- tx.oncomplete = () => loadHistory();
 
 
469
  } catch (error) { console.error("Error saving:", error); }
470
  }
471
 
@@ -474,41 +500,85 @@
474
  const store = db.transaction(["songs"], "readonly").objectStore("songs");
475
  store.getAll().onsuccess = (e) => {
476
  const history = e.target.result.sort((a, b) => b.id - a.id);
477
- historyList.innerHTML = history.length === 0 ? '<div style="text-align:center; color:#999; padding:20px;">هنوز آهنگی نساخته‌اید</div>' : '';
 
 
 
 
478
  history.forEach((item) => {
479
  const div = document.createElement('div');
480
  div.className = 'history-card';
481
- div.innerHTML = `<div class="h-info"><div class="h-icon">🎵</div><div class="h-details"><h4>${item.idea.substring(0, 30)}${item.idea.length > 30 ? '...' : ''}</h4><span>${item.date}</span></div></div><div class="h-delete" onclick="askToDelete(event, ${item.id})"><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></div>`;
482
- div.onclick = (e) => { if (!e.target.closest('.h-delete')) openHistoryItem(item); };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  historyList.appendChild(div);
484
  });
485
  };
486
  }
487
 
 
488
  function askToDelete(event, id) {
489
- event.stopPropagation();
490
  songToDeleteId = id;
491
  deleteModal.classList.add('open');
492
  }
493
 
 
 
 
 
 
494
  function confirmDelete() {
495
  if (songToDeleteId && db) {
496
- const tx = db.transaction(["songs"], "readwrite");
497
- tx.objectStore("songs").delete(songToDeleteId);
498
- tx.oncomplete = () => { loadHistory(); closeDeleteModal(); };
 
 
 
 
499
  }
500
  }
501
 
 
 
 
 
 
 
 
 
502
  function openHistoryItem(item) {
503
  step1.style.display = 'none';
504
  historySection.style.display = 'none';
505
 
506
  const audioURL = URL.createObjectURL(item.audioBlob);
507
- document.getElementById('resultHeaderText').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> آهنگ آرشیو شده`;
 
 
 
 
 
508
  mainDownloadBtn.onclick = (e) => handleSecureDownload(audioURL, e);
 
509
  playerWrapper.innerHTML = `<audio controls autoplay src="${audioURL}"></audio>`;
510
-
511
- setupLyrics(item.lyrics);
512
 
513
  finalResult.style.display = 'block';
514
  window.scrollTo({ top: 0, behavior: 'smooth' });
@@ -516,27 +586,7 @@
516
 
517
  initDB();
518
 
519
- // --- Main Process ---
520
- async function uploadFile(file) {
521
- const uploadId = "uid" + Math.random().toString(36).substring(2);
522
- const formData = new FormData();
523
- formData.append("files", file);
524
-
525
- try {
526
- const response = await fetch(`${ACE_SPACE_URL}upload?upload_id=${uploadId}`, {
527
- method: 'POST',
528
- body: formData
529
- });
530
- if (!response.ok) throw new Error('File upload failed');
531
- const result = await response.json();
532
- return result[0]; // Returns the server path of the file
533
- } catch (error) {
534
- console.error("Upload error:", error);
535
- alert("خطا در آپلود فایل نمونه. لطفا دوباره تلاش کنید.");
536
- return null;
537
- }
538
- }
539
-
540
  processBtn.addEventListener('click', async () => {
541
  if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
542
 
@@ -546,45 +596,41 @@
546
  loader.style.display = 'block';
547
 
548
  try {
549
- let conditioningAudioPath = null;
550
- const audioFile = audioConditionInput.files[0];
551
- if (audioFile) {
552
- loaderText.innerText = "در حال آپلود فایل نمونه صوتی...";
553
- conditioningAudioPath = await uploadFile(audioFile);
554
- if (!conditioningAudioPath) throw new Error("File upload was cancelled or failed.");
555
- }
556
-
557
- const isInstrumental = getChk('instrumentalCheckbox');
558
-
559
- loaderText.innerText = isInstrumental ? "در حال آماده‌سازی ملودی..." : "در حال نوشتن شعر و ملودی...";
560
 
561
- const response = await fetch('/api/refine', {
562
- method: 'POST',
563
- headers: {'Content-Type': 'application/json'},
564
- body: JSON.stringify({
 
565
  idea: ideaInput.value,
566
  fingerprint: getFingerprint(),
567
  is_premium: userSubscriptionStatus === 'paid'
568
- })
569
  });
570
 
 
571
  if (response.status === 429) {
 
 
 
 
 
 
572
  upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
573
- upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت آهنگ‌های بیشتر، لطفا حساب خود را ارتقا دهید.";
574
  upgradeModal.classList.add('open');
575
- throw new Error("Daily limit reached");
576
  }
577
 
578
  const data = await response.json();
579
  if (data.error) throw new Error(data.error);
580
 
581
- const lyrics = isInstrumental ? "" : data.lyrics;
582
- const { musicPrompt } = data;
583
-
584
  loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
585
 
586
  const payload = [
587
- getVal('model_select'), "custom", conditioningAudioPath, "unknown", musicPrompt, lyrics, 0, "", "", "unknown",
588
  getNum('steps_input'), getNum('cfg_input'), true, getNum('seed_input'), null, -1,
589
  getNum('batch_size'), null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
590
  1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
@@ -592,10 +638,10 @@
592
  ];
593
 
594
  const session_hash = Math.random().toString(36).substring(2);
595
- const joinResp = await fetch(`${ACE_SPACE_URL}queue/join`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: payload, fn_index: 77, session_hash }) });
596
  if (!joinResp.ok) throw new Error('خطا در اتصال به سرور موزیک');
597
 
598
- const eventSource = new EventSource(`${ACE_SPACE_URL}queue/data?session_hash=${session_hash}`);
599
  eventSource.onmessage = (event) => {
600
  const msg = JSON.parse(event.data);
601
  if (msg.msg === 'process_starts') loaderText.innerText = "هوش مصنوعی در حال خواندن...";
@@ -616,137 +662,51 @@
616
  }
617
  });
618
 
619
- // --- Result Handling & Karaoke ---
620
  function handleAudioOutput(data, lyrics, idea) {
621
- let audioUrl = null;
622
- let vttUrl = null;
623
 
624
- function findUrls(obj) {
625
- if (typeof obj === 'string' && obj.includes('/file=')) {
626
- if (obj.endsWith('.mp3')) audioUrl = obj;
627
- if (obj.endsWith('.vtt')) vttUrl = obj;
628
- } else if (obj && typeof obj === 'object') {
629
- if (obj.url) {
630
- if (obj.url.endsWith('.mp3')) audioUrl = obj.url;
631
- if (obj.url.endsWith('.vtt')) vttUrl = obj.url;
632
- }
633
- Object.values(obj).forEach(findUrls);
 
 
634
  }
 
635
  }
636
- findUrls(data);
637
-
638
- if (audioUrl) {
639
- const fullAudioUrl = audioUrl.startsWith('http') ? audioUrl : ACE_SPACE_URL.replace(/\/$/, '') + audioUrl;
640
-
641
- document.getElementById('resultHeaderText').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>آهنگ جدید آماده شد`;
642
-
643
- mainDownloadBtn.style.display = 'inline-flex';
644
- mainDownloadBtn.onclick = (e) => handleSecureDownload(fullAudioUrl, e);
645
-
646
- playerWrapper.innerHTML = `<audio controls autoplay src="${fullAudioUrl}"></audio>`;
647
-
648
- if (lyrics) saveToHistory(idea, lyrics, fullAudioUrl);
649
-
650
- const fullVttUrl = vttUrl ? (vttUrl.startsWith('http') ? vttUrl : ACE_SPACE_URL.replace(/\/$/, '') + vttUrl) : null;
651
- setupLyrics(lyrics, fullVttUrl);
652
-
653
- finalResult.style.display = 'block';
654
- window.scrollTo({ top: 0, behavior: 'smooth' });
655
- } else {
656
- alert("فایل صوتی یافت نشد!");
657
- location.reload();
658
- }
659
- }
660
 
661
- async function setupLyrics(lyricsText, vttUrl) {
662
- if (!lyricsText || lyricsText.trim() === '') {
663
- lyricsWrapper.style.display = 'none';
664
- return;
665
- }
666
-
667
- lyricsWrapper.style.display = 'block';
668
- timedLyrics = []; // Reset
669
-
670
- if (vttUrl) {
671
- try {
672
- const response = await fetch(vttUrl);
673
- const vttContent = await response.text();
674
- timedLyrics = parseVTT(vttContent);
675
- } catch (e) {
676
- console.error("Could not fetch or parse VTT file.", e);
677
- timedLyrics = []; // Fallback to non-timed
678
  }
679
  }
 
680
 
681
- if (timedLyrics.length > 0) {
682
- finalLyricsBox.innerHTML = timedLyrics.map((line, index) => `<span class="lyrics-line" data-index="${index}">${line.text}</span>`).join('');
683
- currentAudioElement = playerWrapper.querySelector('audio');
684
- if(currentAudioElement) {
685
- currentAudioElement.addEventListener('timeupdate', updateLyricsHighlight);
686
- }
 
687
  } else {
688
- // Fallback for non-timed lyrics
689
- finalLyricsBox.innerHTML = lyricsText.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>');
690
- }
691
- }
692
-
693
- function parseVTT(vttContent) {
694
- const lines = vttContent.split('\n');
695
- const cues = [];
696
- let currentCue = null;
697
-
698
- function timeToSeconds(timeStr) {
699
- const parts = timeStr.split(/[:.]/);
700
- return parseInt(parts[0], 10) * 3600 + parseInt(parts[1], 10) * 60 + parseInt(parts[2], 10) + parseInt(parts[3], 10) / 1000;
701
- }
702
-
703
- for (const line of lines) {
704
- const timeMatch = line.match(/(\d{2}:\d{2}:\d{2}\.\d{3}) --> (\d{2}:\d{2}:\d{2}\.\d{3})/);
705
- if (timeMatch) {
706
- currentCue = {
707
- startTime: timeToSeconds(timeMatch[1]),
708
- endTime: timeToSeconds(timeMatch[2]),
709
- text: ''
710
- };
711
- } else if (currentCue && line.trim() !== '' && !/^\d+$/.test(line.trim())) {
712
- currentCue.text += (currentCue.text ? '\n' : '') + line;
713
- } else if (currentCue && line.trim() === '' && currentCue.text) {
714
- cues.push(currentCue);
715
- currentCue = null;
716
- }
717
  }
718
- if(currentCue && currentCue.text) cues.push(currentCue);
719
- return cues;
720
  }
721
 
722
- function updateLyricsHighlight() {
723
- if (!currentAudioElement || timedLyrics.length === 0) return;
724
-
725
- const currentTime = currentAudioElement.currentTime;
726
- let activeIndex = -1;
727
-
728
- for (let i = 0; i < timedLyrics.length; i++) {
729
- if (currentTime >= timedLyrics[i].startTime && currentTime <= timedLyrics[i].endTime) {
730
- activeIndex = i;
731
- break;
732
- }
733
- }
734
-
735
- const lyricLines = finalLyricsBox.querySelectorAll('.lyrics-line');
736
- lyricLines.forEach((line, index) => {
737
- if (index === activeIndex) {
738
- if (!line.classList.contains('active')) {
739
- line.classList.add('active');
740
- // Scroll the active line into view
741
- line.scrollIntoView({ behavior: 'smooth', block: 'center' });
742
- }
743
- } else {
744
- line.classList.remove('active');
745
- }
746
- });
747
- }
748
 
749
- // --- Background Animation ---
750
  const canvas = document.getElementById('music-canvas');
751
  const ctx = canvas.getContext('2d');
752
  let t = 0;
@@ -768,5 +728,5 @@
768
  }
769
  anim();
770
  </script>
771
- </body>
772
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <title>استودیو موزیک آلفا (Alpha)</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --app-font: 'Vazirmatn', sans-serif;
11
+ --app-bg: #F8F9FC;
12
+ --panel-bg: #FFFFFF;
13
+ --panel-border: #EAEFF7;
14
+ --input-bg: #F6F8FB;
15
+ --input-border: #E1E7EF;
16
+ --text-primary: #1A202C;
17
+ --text-secondary: #626F86;
18
+ --accent-primary: #4A6CFA;
19
  --accent-secondary: #9F7AEA;
20
+ --accent-glow: rgba(74, 108, 250, 0.2);
21
  --success-color: #38A169;
22
+ --danger-color: #e53e3e;
23
  --premium-gold: #FFC107;
24
  --premium-gradient: linear-gradient(135deg, #FFC107 0%, #FF9800 100%);
25
+ --radius-card: 20px;
26
+ --radius-btn: 14px;
27
+ }
28
 
29
  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
30
 
31
+ body {
32
+ font-family: var(--app-font);
33
+ background-color: var(--app-bg);
34
+ color: var(--text-primary);
35
+ margin: 0;
36
+ padding: 20px 15px;
37
  min-height: 100vh;
38
  display: flex;
39
  flex-direction: column;
40
  align-items: center;
41
+ }
42
 
43
+ .container { max-width: 650px; width: 100%; z-index: 2; position: relative; }
44
 
45
+ #music-canvas {
46
+ position: fixed; top: 0; left: 0; width: 100%; height: 400px;
47
  z-index: 0; opacity: 0.5; pointer-events: none;
48
  mask-image: linear-gradient(to bottom, black, transparent);
49
  -webkit-mask-image: linear-gradient(to bottom, black, transparent);
50
  }
51
 
52
+ header { text-align: center; margin-bottom: 2rem; position: relative; }
53
 
54
  .logo-box {
55
  width: 80px; height: 80px; margin: 0 auto 15px;
 
59
  color: var(--accent-primary);
60
  }
61
 
62
+ h1 {
63
+ font-size: 1.8rem; font-weight: 800; margin: 0;
64
+ background: linear-gradient(90deg, #2d3748, #4A6CFA);
65
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
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;
 
75
  }
76
  .status-badge.visible { opacity: 1; transform: translateY(0); }
77
  .badge-free { background: #E2E8F0; color: #64748b; }
78
+ .badge-premium {
79
+ background: var(--premium-gradient); color: #fff;
80
  box-shadow: 0 4px 15px rgba(255, 193, 7, 0.4);
81
  text-shadow: 0 1px 2px rgba(0,0,0,0.1);
82
  }
83
 
84
+ .card {
85
+ background: var(--panel-bg);
86
+ border-radius: var(--radius-card);
87
+ box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.05);
88
+ border: 1px solid var(--panel-border);
89
  padding: 25px; margin-bottom: 20px;
90
  }
91
 
92
  .form-label { display: flex; align-items: center; gap: 8px; font-weight: 700; margin-bottom: 12px; color: #2d3748; }
93
  .form-label svg { color: var(--accent-primary); width: 20px; }
94
 
95
+ textarea, input, select {
96
+ width: 100%; background: var(--input-bg);
97
+ border: 2px solid var(--input-border);
98
+ border-radius: 12px; padding: 15px;
99
  font-family: inherit; font-size: 1rem; color: #2d3748;
100
  outline: none; transition: border-color 0.3s;
101
  }
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%; }
109
+ 100% { background-position: 0% 50%; }
110
+ }
111
+ .btn-main {
112
+ width: 100%; padding: 16px;
113
  background: linear-gradient(110deg, var(--accent-secondary), var(--accent-primary), var(--accent-secondary));
114
  background-size: 200% 100%;
115
+ color: #fff; border: none; border-radius: var(--radius-btn);
116
+ font-size: 1.1rem; font-weight: 700; cursor: pointer;
117
  display: flex; justify-content: center; align-items: center; gap: 10px;
118
  transition: all 0.3s ease-out; position: relative; z-index: 1;
119
  }
 
128
  .btn-main:hover::before { opacity: 1; animation: move-gradient 4s linear infinite; }
129
  .btn-main:disabled { opacity: 0.7; cursor: not-allowed; filter: grayscale(1); }
130
 
131
+ .btn-outline {
132
+ background: transparent; border: 2px solid var(--input-border);
133
+ color: var(--text-secondary); margin-top: 10px; transition: all 0.2s;
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;
 
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;
 
169
  audio { width: 100%; height: 45px; border-radius: 20px; margin-top: 5px; }
170
  .lyrics-container {
171
  background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto;
172
+ white-space: pre-wrap; line-height: 2; font-size: 1rem; color: #4a5568; text-align: center; border: 1px solid var(--input-border); margin-top: 15px;
 
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; }
 
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;
 
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);
 
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); }
256
  </style>
257
+ </head>
258
+ <body>
259
  <canvas id="music-canvas"></canvas>
260
 
261
  <div class="container">
262
+ <header>
263
  <div class="logo-box">
264
  <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>
265
  </div>
266
+ <h1>استودیو موزیک آلفا</h1>
267
  <p class="subtitle">ساخت آهنگ حرفه‌ای با هوش مصنوعی</p>
268
  <div id="statusBadge" class="status-badge">نسخه رایگان</div>
269
+ </header>
270
 
271
  <div id="step1" class="card">
272
  <div class="form-label">
 
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
+ <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>
288
  </div>
289
  <button id="processBtn" class="btn-main" style="margin-top: 15px;"><div class="btn-main-content">ساخت آهنگ <span>🎵</span></div></button>
290
  </div>
 
300
  <button id="mainDownloadBtn" class="download-btn-style">دانلود آهنگ 📥</button>
301
  </div>
302
  <div id="playerWrapper"></div>
303
+ <div class="form-label" style="margin-top: 20px; justify-content: center; color: #718096;">متن آهنگ</div>
304
+ <div class="lyrics-container" id="finalLyricsBox"></div>
 
 
305
  <button onclick="location.reload()" class="btn-main btn-outline">برگشت و ساخت آهنگ جدید</button>
306
  </div>
307
 
 
314
  </div>
315
  </div>
316
 
317
+ <!-- مودال تایید حذف -->
318
  <div class="modal-overlay" id="deleteConfirmModal">
319
  <div class="modal-box">
320
  <div class="modal-icon-warn">
 
329
  </div>
330
  </div>
331
 
332
+ <!-- مودال ارتقا به نسخه کامل -->
333
  <div class="modal-overlay" id="upgradeModal">
334
  <div class="modal-box">
335
  <div class="upgrade-icon">👑</div>
 
344
 
345
  <script>
346
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
347
+ // مشخصات نسخه پولی
348
  const PREMIUM_PAGE_ID = '1149636';
349
  const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/eyJpdiI6InZSVUdlLzBlR0FzOHZJdXFZeWhER0E9PSIsInZhbHVlIjoiWFhqRXBLc29vSFpHdk9nYmRjZGVuWHRHRHVSZHRlTG1BUENLaE5mNXBNVVRGWFg3ZWN0djJ5K1dIY1RqTHJGaCIsIm1hYyI6IjIzYzFlZTMwYmVmMTdkYjQ0YTQ4YWMxNmFhN2RmNWQ2OTU0MjI0ZWVlZGI3ZjJjMjhkNmQxNjM4MDFlZTIxNmUiLCJ0YWciOiIifQ==/20934991';
350
 
351
  let db;
352
  let songToDeleteId = null;
353
+ let userSubscriptionStatus = 'free'; // پیش‌فرض رایگان
 
 
354
 
355
+ // المان‌ها
356
  const ideaInput = document.getElementById('ideaInput');
357
  const processBtn = document.getElementById('processBtn');
358
  const step1 = document.getElementById('step1');
 
361
  const finalResult = document.getElementById('finalResult');
362
  const playerWrapper = document.getElementById('playerWrapper');
363
  const finalLyricsBox = document.getElementById('finalLyricsBox');
 
364
  const mainDownloadBtn = document.getElementById('mainDownloadBtn');
365
  const historyList = document.getElementById('historyList');
366
  const historySection = document.getElementById('historySection');
 
369
  const statusBadge = document.getElementById('statusBadge');
370
  const upgradeModalTitle = document.getElementById('upgradeModalTitle');
371
  const upgradeModalText = document.getElementById('upgradeModalText');
 
372
 
373
  const getVal = (id) => document.getElementById(id).value;
374
  const getNum = (id) => Number(document.getElementById(id).value);
375
  const getChk = (id) => document.getElementById(id).checked;
376
 
377
+ const acc = document.getElementsByClassName("accordion");
378
+ for (let i = 0; i < acc.length; i++) {
379
+ acc[i].addEventListener("click", function() {
380
+ this.classList.toggle("active");
381
+ const panel = this.nextElementSibling;
382
+ panel.style.maxHeight = panel.style.maxHeight ? null : panel.scrollHeight + "px";
383
+ });
384
+ }
385
 
386
+ // --- مدیریت اثر انگشت (Fingerprint) ---
387
  function getFingerprint() {
388
  let fp = localStorage.getItem('user_fingerprint');
389
  if (!fp) {
390
+ // تولید یک شناسه تصادفی و ذخیره آن
391
  fp = 'user_' + Math.random().toString(36).substr(2, 9) + Date.now().toString(36);
392
  localStorage.setItem('user_fingerprint', fp);
393
  }
394
  return fp;
395
  }
396
 
397
+ // --- مدیریت اشتراک و ارتباط با آیفریم مادر ---
398
  function isUserPaid(userObject) {
399
  return userObject && userObject.isLogin && userObject.accessible_pages &&
400
  (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) ||
 
402
  }
403
 
404
  function updateSubscriptionUI(status) {
405
+ if (status === 'paid') {
406
+ statusBadge.textContent = 'نسخه نامحدود';
407
+ statusBadge.className = 'status-badge visible badge-premium';
408
+ mainDownloadBtn.classList.remove('locked');
409
+ } else {
410
+ statusBadge.textContent = 'نسخه رایگان';
411
+ statusBadge.className = 'status-badge visible badge-free';
412
+ }
413
  }
414
 
415
+ // شنونده پیام از آیفریم مادر
416
  window.addEventListener('message', (event) => {
417
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
418
  try {
 
424
  updateSubscriptionUI(userSubscriptionStatus);
425
  }
426
  });
427
+
428
+ // درخواست وضعیت کاربر در شروع برنامه
429
  parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
430
 
431
+ // مدیریت کلیک دکمه دانلود (تابع امنیتی)
432
  function handleSecureDownload(url, e) {
433
  if (e) e.preventDefault();
434
+
435
  if (userSubscriptionStatus === 'free') {
436
+ // تنظیم متن مودال برای دانلود
437
  upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
438
+ upgradeModalText.innerText = "برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.";
439
  upgradeModal.classList.add('open');
440
  } else {
441
+ // ارسال درخواست دانلود به آیفریم مادر
442
+ parent.postMessage({
443
+ type: 'INITIATE_DOWNLOAD_FROM_URL',
444
+ payload: { audioUrl: url }
445
+ }, '*');
446
+
447
+ // تغییر موقت متن دکمه برای فیدبک
448
  const btn = e ? e.target.closest('button') : null;
449
+ if(btn) {
450
  const originalText = btn.innerHTML;
451
  btn.innerHTML = 'در حال ارسال... ⏳';
452
  setTimeout(() => { btn.innerHTML = originalText; }, 2000);
453
  }
454
  }
455
  }
456
+
457
+ // دکمه ارتقا در مودال
458
  document.getElementById('btnGoPremium').addEventListener('click', () => {
459
+ parent.postMessage({
460
+ type: 'NAVIGATE_TO_PREMIUM',
461
+ payload: { url: PREMIUM_URL }
462
+ }, '*');
463
  });
 
 
464
 
465
+ function closeUpgradeModal() {
466
+ upgradeModal.classList.remove('open');
467
+ }
 
 
468
 
469
+ // --- مدیریت دیتابیس ---
470
  function initDB() {
471
  const request = indexedDB.open("alphaMusicDB", 1);
472
+ request.onerror = (event) => console.error("IndexedDB error:", event);
473
+ request.onsuccess = (event) => { db = event.target.result; loadHistory(); };
474
+ request.onupgradeneeded = (event) => { event.target.result.createObjectStore("songs", { keyPath: "id" }); };
475
  }
476
 
477
  async function saveToHistory(idea, lyrics, audioUrl) {
478
  try {
479
+ // برای ذخیره در DB باید فایل را فچ کنیم
480
  const response = await fetch(audioUrl);
481
  const audioBlob = await response.blob();
482
  const newItem = {
483
  id: Date.now(), idea, lyrics, audioBlob,
484
  date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' })
485
  };
486
+ const transaction = db.transaction(["songs"], "readwrite");
487
+ const store = transaction.objectStore("songs");
488
  store.add(newItem);
489
+
490
  const countReq = store.count();
491
+ countReq.onsuccess = () => {
492
+ if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
493
+ };
494
+ transaction.oncomplete = () => loadHistory();
495
  } catch (error) { console.error("Error saving:", error); }
496
  }
497
 
 
500
  const store = db.transaction(["songs"], "readonly").objectStore("songs");
501
  store.getAll().onsuccess = (e) => {
502
  const history = e.target.result.sort((a, b) => b.id - a.id);
503
+ historyList.innerHTML = '';
504
+ if (history.length === 0) {
505
+ historyList.innerHTML = '<div style="text-align:center; color:#999; padding:20px;">هنوز آهنگی نساخته‌اید</div>';
506
+ return;
507
+ }
508
  history.forEach((item) => {
509
  const div = document.createElement('div');
510
  div.className = 'history-card';
511
+ div.innerHTML = `
512
+ <div class="h-info">
513
+ <div class="h-icon">🎵</div>
514
+ <div class="h-details">
515
+ <h4>${item.idea.substring(0, 30)}${item.idea.length > 30 ? '...' : ''}</h4>
516
+ <span>${item.date}</span>
517
+ </div>
518
+ </div>
519
+ <div class="h-delete" onclick="askToDelete(event, ${item.id})">
520
+ <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>
521
+ </div>
522
+ `;
523
+ // کلیک روی خود کارت برای پخش
524
+ div.onclick = (e) => {
525
+ // اگر روی دکمه حذف کلیک نشده باشد
526
+ if (!e.target.closest('.h-delete')) {
527
+ openHistoryItem(item);
528
+ }
529
+ };
530
  historyList.appendChild(div);
531
  });
532
  };
533
  }
534
 
535
+ // --- توابع حذف ---
536
  function askToDelete(event, id) {
537
+ event.stopPropagation(); // جلوگیری از باز شدن پلیر
538
  songToDeleteId = id;
539
  deleteModal.classList.add('open');
540
  }
541
 
542
+ function closeDeleteModal() {
543
+ deleteModal.classList.remove('open');
544
+ songToDeleteId = null;
545
+ }
546
+
547
  function confirmDelete() {
548
  if (songToDeleteId && db) {
549
+ const transaction = db.transaction(["songs"], "readwrite");
550
+ const store = transaction.objectStore("songs");
551
+ store.delete(songToDeleteId);
552
+ transaction.oncomplete = () => {
553
+ loadHistory();
554
+ closeDeleteModal();
555
+ };
556
  }
557
  }
558
 
559
+ // بستن مودال با کلیک بیرون
560
+ deleteModal.addEventListener('click', (e) => {
561
+ if (e.target === deleteModal) closeDeleteModal();
562
+ });
563
+ upgradeModal.addEventListener('click', (e) => {
564
+ if (e.target === upgradeModal) closeUpgradeModal();
565
+ });
566
+
567
  function openHistoryItem(item) {
568
  step1.style.display = 'none';
569
  historySection.style.display = 'none';
570
 
571
  const audioURL = URL.createObjectURL(item.audioBlob);
572
+ const headerText = document.getElementById('resultHeaderText');
573
+ 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> آهنگ آرشیو شده`;
574
+ headerText.style.color = 'var(--accent-primary)';
575
+
576
+ // تنظیم دکمه دانلود
577
+ mainDownloadBtn.style.display = 'inline-flex';
578
  mainDownloadBtn.onclick = (e) => handleSecureDownload(audioURL, e);
579
+
580
  playerWrapper.innerHTML = `<audio controls autoplay src="${audioURL}"></audio>`;
581
+ finalLyricsBox.innerHTML = formatLyrics(item.lyrics);
 
582
 
583
  finalResult.style.display = 'block';
584
  window.scrollTo({ top: 0, behavior: 'smooth' });
 
586
 
587
  initDB();
588
 
589
+ // --- فرآیند اصلی ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  processBtn.addEventListener('click', async () => {
591
  if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
592
 
 
596
  loader.style.display = 'block';
597
 
598
  try {
599
+ loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
 
 
 
 
 
 
 
 
 
 
600
 
601
+ // ارسال درخواست به سرور با فینگرپرینت و وضعیت اشتراک
602
+ const response = await fetch('/api/refine', {
603
+ method: 'POST',
604
+ headers: {'Content-Type': 'application/json'},
605
+ body: JSON.stringify({
606
  idea: ideaInput.value,
607
  fingerprint: getFingerprint(),
608
  is_premium: userSubscriptionStatus === 'paid'
609
+ })
610
  });
611
 
612
+ // بررسی خطای محدودیت روزانه (429)
613
  if (response.status === 429) {
614
+ loader.style.display = 'none';
615
+ step1.style.display = 'block';
616
+ historySection.style.display = 'block';
617
+ processBtn.disabled = false;
618
+
619
+ // نمایش مودال ارتقا با متن مناسب
620
  upgradeModalTitle.innerText = "محدودیت ساخت روزانه";
621
+ upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت آهنگ‌های بیشتر و نامحدود، لطفا حساب خود را ارتقا دهید.";
622
  upgradeModal.classList.add('open');
623
+ return;
624
  }
625
 
626
  const data = await response.json();
627
  if (data.error) throw new Error(data.error);
628
 
629
+ const { lyrics, musicPrompt } = data;
 
 
630
  loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
631
 
632
  const payload = [
633
+ getVal('model_select'), "custom", null, "unknown", musicPrompt, lyrics, 0, "", "", "unknown",
634
  getNum('steps_input'), getNum('cfg_input'), true, getNum('seed_input'), null, -1,
635
  getNum('batch_size'), null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
636
  1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
 
638
  ];
639
 
640
  const session_hash = Math.random().toString(36).substring(2);
641
+ 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 }) });
642
  if (!joinResp.ok) throw new Error('خطا در اتصال به سرور موزیک');
643
 
644
+ const eventSource = new EventSource(`${ACE_SPACE_URL}gradio_api/queue/data?session_hash=${session_hash}`);
645
  eventSource.onmessage = (event) => {
646
  const msg = JSON.parse(event.data);
647
  if (msg.msg === 'process_starts') loaderText.innerText = "هوش مصنوعی در حال خواندن...";
 
662
  }
663
  });
664
 
 
665
  function handleAudioOutput(data, lyrics, idea) {
666
+ const processedUrls = new Set();
667
+ let hasResult = false;
668
 
669
+ function addAudio(url) {
670
+ const fullUrl = url.startsWith('http') ? url : ACE_SPACE_URL.replace(/\/$/, '') + url;
671
+ if (processedUrls.has(fullUrl)) return;
672
+ processedUrls.add(fullUrl);
673
+ hasResult = true;
674
+
675
+ if (processedUrls.size === 1) {
676
+ // تنظیم دکمه دانلود اصلی
677
+ mainDownloadBtn.style.display = 'inline-flex';
678
+ mainDownloadBtn.onclick = (e) => handleSecureDownload(fullUrl, e);
679
+
680
+ saveToHistory(idea, lyrics, fullUrl);
681
  }
682
+ playerWrapper.innerHTML += `<div class="audio-item"><audio controls autoplay src="${fullUrl}"></audio></div>`;
683
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
684
 
685
+ function traverse(obj) {
686
+ if (typeof obj === 'string' && obj.includes('/file=') && obj.endsWith('.mp3')) addAudio(obj);
687
+ else if (obj && typeof obj === 'object') {
688
+ if (obj.url && obj.url.endsWith('.mp3')) addAudio(obj.url);
689
+ Object.values(obj).forEach(traverse);
 
 
 
 
 
 
 
 
 
 
 
 
690
  }
691
  }
692
+ traverse(data);
693
 
694
+ if (hasResult) {
695
+ const headerText = document.getElementById('resultHeaderText');
696
+ 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>آهنگ جدید آماده شد`;
697
+ headerText.style.color = 'var(--success-color)';
698
+ finalResult.style.display = 'block';
699
+ finalLyricsBox.innerHTML = formatLyrics(lyrics);
700
+ window.scrollTo({ top: 0, behavior: 'smooth' });
701
  } else {
702
+ alert("فایل صوتی یافت نشد!");
703
+ step1.style.display = 'block';
704
+ historySection.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
  }
 
 
706
  }
707
 
708
+ function formatLyrics(text) { return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>'); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
 
 
710
  const canvas = document.getElementById('music-canvas');
711
  const ctx = canvas.getContext('2d');
712
  let t = 0;
 
728
  }
729
  anim();
730
  </script>
731
+ </body>
732
  </html>