Opera8 commited on
Commit
7baa7eb
·
verified ·
1 Parent(s): 46da690

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +341 -326
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,12 +59,12 @@
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;
@@ -74,40 +74,46 @@
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
 
104
  @keyframes move-gradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
105
- .btn-main {
106
- width: 100%; padding: 16px;
107
  background: linear-gradient(110deg, var(--accent-secondary), var(--accent-primary), var(--accent-secondary));
108
  background-size: 200% 100%;
109
- color: #fff; border: none; border-radius: var(--radius-btn);
110
- font-size: 1.1rem; font-weight: 700; cursor: pointer;
111
  display: flex; justify-content: center; align-items: center; gap: 10px;
112
  transition: all 0.3s ease-out; position: relative; z-index: 1;
113
  }
@@ -143,13 +149,6 @@
143
  .checkbox-wrapper { display: flex; align-items: center; gap: 10px; margin-top: 5px; padding-bottom: 15px; border-top: 1px solid #e2e8f0; padding-top: 15px; }
144
  .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
145
 
146
- /* استایل آپلود فایل */
147
- .file-input-wrapper { display: flex; align-items: center; gap: 10px; width: 100%; }
148
- .btn-file-upload { background: #fff; border: 1px solid var(--input-border); border-radius: 10px; padding: 10px 15px; font-size: 0.9rem; cursor: pointer; transition: 0.2s; flex-shrink: 0; }
149
- .btn-file-upload:hover { background: #f0f4ff; border-color: var(--accent-primary); color: var(--accent-primary); }
150
- #file-name-display { flex-grow: 1; text-align: right; font-size: 0.85rem; color: #666; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
151
- #clear-file-btn { background: #fee2e2; color: var(--danger-color); border: none; width: 28px; height: 28px; border-radius: 50%; cursor: pointer; font-weight: bold; flex-shrink: 0; }
152
-
153
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
154
 
155
  .download-btn-style {
@@ -162,22 +161,18 @@
162
 
163
  .audio-item { margin-bottom: 10px; }
164
  audio { width: 100%; height: 45px; border-radius: 20px; margin-top: 5px; }
165
-
166
- /* استایل متن آهنگ کارائوکه */
167
  .lyrics-container {
168
  background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto;
169
- text-align: center; border: 1px solid var(--input-border); margin-top: 15px;
170
- }
171
- .lyrics-line {
172
- font-size: 1.1rem; line-height: 1.8; color: #626F86;
173
- transition: all 0.3s ease; padding: 8px 12px; border-radius: 10px;
174
- opacity: 0.6; cursor: default;
175
  }
 
 
176
  .lyrics-line.active {
177
- background-color: var(--accent-glow); color: var(--accent-primary);
178
- transform: scale(1.05); font-weight: 800; opacity: 1;
 
179
  }
180
- .lyrics-tag { color: var(--accent-secondary); font-weight: 700; display: block; margin-top: 15px; font-size: 0.8em; }
181
 
182
  #loader { display: none; text-align: center; padding: 20px; }
183
  .wave-bars { display: flex; justify-content: center; gap: 4px; height: 30px; align-items: flex-end; }
@@ -201,8 +196,10 @@
201
  .h-details span { font-size: 0.8rem; color: var(--text-secondary); }
202
 
203
  .h-delete {
204
- position: absolute; left: 15px; background: #fee2e2; color: var(--danger-color);
205
- width: 38px; height: 38px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
 
 
206
  opacity: 0; transform: scale(0.8); transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
207
  z-index: 10; box-shadow: 0 4px 10px rgba(229, 62, 62, 0.2);
208
  }
@@ -235,11 +232,12 @@
235
  .btn-confirm:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(229, 62, 62, 0.4); }
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; background: var(--premium-gradient);
240
- border-radius: 50%; color: white; margin: 0 auto 20px;
241
- display: flex; align-items: center; justify-content: center;
242
- font-size: 2rem; animation: pulse-gold 2s infinite;
 
243
  }
244
  .btn-upgrade { background: var(--premium-gradient); color: #333; box-shadow: 0 5px 15px rgba(255, 193, 7, 0.3); }
245
  .btn-upgrade:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(255, 193, 7, 0.5); }
@@ -247,19 +245,19 @@
247
  .modal-overlay.open { display: flex; opacity: 1; }
248
  .modal-overlay.open .modal-box { transform: scale(1); }
249
  </style>
250
- </head>
251
- <body>
252
  <canvas id="music-canvas"></canvas>
253
 
254
  <div class="container">
255
- <header>
256
  <div class="logo-box">
257
  <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>
258
  </div>
259
- <h1>استودیو موزیک آلفا</h1>
260
  <p class="subtitle">ساخت آهنگ حرفه‌ای با هوش مصنوعی</p>
261
  <div id="statusBadge" class="status-badge">نسخه رایگان</div>
262
- </header>
263
 
264
  <div id="step1" class="card">
265
  <div class="form-label">
@@ -275,24 +273,13 @@
275
  <div class="settings-group"><label>تعداد خروجی (آهنگ):</label><input type="number" id="batch_size" value="1" min="1" max="4"></div>
276
  <div class="settings-group"><label>تعداد گام (Steps):</label><input type="number" id="steps_input" value="20" min="4" max="50"></div>
277
  <div class="settings-group"><label>سید (Seed) تصادفی=-1:</label><input type="number" id="seed_input" value="-1"></div>
278
- <div class="settings-group"><label>مقیاس هدایت (CFG):</label><input type="number" id="cfg_input" value="7.0" step="0.5"></div>
279
- <!-- المان جدید آپلود فایل -->
280
- <div class="settings-group" style="grid-column: 1 / -1;">
281
- <label>آهنگ نمونه (برای الهام گرفتن مدل):</label>
282
- <div class="file-input-wrapper">
283
- <input type="file" id="audio_condition_input" accept="audio/*" style="display:none;">
284
- <button type="button" class="btn-file-upload" onclick="document.getElementById('audio_condition_input').click();">انتخاب فایل صوتی</button>
285
- <span id="file-name-display">فایلی انتخاب نشده</span>
286
- <button id="clear-file-btn" style="display:none;">&times;</button>
287
- </div>
288
- </div>
289
  </div>
290
  <div class="checkbox-wrapper">
291
- <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>
292
  </div>
293
- <!-- چک‌باکس جدید بی‌کلام -->
294
  <div class="checkbox-wrapper" style="border-top: none; padding-top: 0;">
295
- <input type="checkbox" id="instrumental_checkbox"><label for="instrumental_checkbox" style="font-size: 0.9rem; cursor: pointer;"><b>ساخت آهنگ بیکلام (Instrumental)</b><br><span style="font-size: 0.8rem; color: #666;">شعر ساخته نخواهد شد</span></label>
296
  </div>
297
  </div>
298
  <button id="processBtn" class="btn-main" style="margin-top: 15px;"><div class="btn-main-content">ساخت آهنگ <span>🎵</span></div></button>
@@ -309,7 +296,7 @@
309
  <button id="mainDownloadBtn" class="download-btn-style">دانلود آهنگ 📥</button>
310
  </div>
311
  <div id="playerWrapper"></div>
312
- <div id="lyricsSection" style="display: block;">
313
  <div class="form-label" style="margin-top: 20px; justify-content: center; color: #718096;">متن آهنگ</div>
314
  <div class="lyrics-container" id="finalLyricsBox"></div>
315
  </div>
@@ -325,14 +312,14 @@
325
  </div>
326
  </div>
327
 
328
- <!-- مودال‌ها -->
329
  <div class="modal-overlay" id="deleteConfirmModal">
330
  <div class="modal-box">
331
  <div class="modal-icon-warn">
332
  <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>
333
  </div>
334
  <h3>آیا مطمئن هستید؟</h3>
335
- <p>این آهنگ برای همیشه از سوابق شما حذف خواهد شد.</p>
336
  <div class="modal-actions">
337
  <button class="btn-modal btn-cancel" onclick="closeDeleteModal()">انصراف</button>
338
  <button class="btn-modal btn-confirm" onclick="confirmDelete()">بله، حذف شود</button>
@@ -343,8 +330,8 @@
343
  <div class="modal-overlay" id="upgradeModal">
344
  <div class="modal-box">
345
  <div class="upgrade-icon">👑</div>
346
- <h3 style="font-weight: 800; color: #333;" id="upgradeModalTitle">ارتقا به نسخه کامل</h3>
347
- <p style="margin-bottom: 25px; line-height: 1.6;" id="upgradeModalText">برای استفاده نامحدود و دانلود با کیفیت اصلی، حساب خود را ارتقا دهید.</p>
348
  <div class="modal-actions" style="flex-direction: column;">
349
  <button class="btn-modal btn-upgrade" id="btnGoPremium">⭐️ ارتقا به نسخه کامل</button>
350
  <button class="btn-modal btn-cancel" onclick="closeUpgradeModal()" style="width: 100%;">فعلاً نه</button>
@@ -355,12 +342,15 @@
355
  <script>
356
  const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
357
  const PREMIUM_PAGE_ID = '1149636';
358
- const PREMIUM_URL = '#/nav/online/news/getSingle/1149636/...'; //
 
359
  let db;
360
  let songToDeleteId = null;
361
  let userSubscriptionStatus = 'free';
 
 
362
 
363
- // المان‌ها
364
  const ideaInput = document.getElementById('ideaInput');
365
  const processBtn = document.getElementById('processBtn');
366
  const step1 = document.getElementById('step1');
@@ -369,6 +359,7 @@
369
  const finalResult = document.getElementById('finalResult');
370
  const playerWrapper = document.getElementById('playerWrapper');
371
  const finalLyricsBox = document.getElementById('finalLyricsBox');
 
372
  const mainDownloadBtn = document.getElementById('mainDownloadBtn');
373
  const historyList = document.getElementById('historyList');
374
  const historySection = document.getElementById('historySection');
@@ -377,25 +368,20 @@
377
  const statusBadge = document.getElementById('statusBadge');
378
  const upgradeModalTitle = document.getElementById('upgradeModalTitle');
379
  const upgradeModalText = document.getElementById('upgradeModalText');
380
- const audioConditionInput = document.getElementById('audio_condition_input');
381
- const fileNameDisplay = document.getElementById('file-name-display');
382
- const clearFileBtn = document.getElementById('clear-file-btn');
383
- const instrumentalCheckbox = document.getElementById('instrumental_checkbox');
384
- const lyricsSection = document.getElementById('lyricsSection');
385
 
386
  const getVal = (id) => document.getElementById(id).value;
387
  const getNum = (id) => Number(document.getElementById(id).value);
388
  const getChk = (id) => document.getElementById(id).checked;
389
 
390
- const acc = document.getElementsByClassName("accordion");
391
- for (let i = 0; i < acc.length; i++) {
392
- acc[i].addEventListener("click", function() {
393
- this.classList.toggle("active");
394
- const panel = this.nextElementSibling;
395
- panel.style.maxHeight = panel.style.maxHeight ? null : panel.scrollHeight + "px";
396
- });
397
- }
398
-
399
  function getFingerprint() {
400
  let fp = localStorage.getItem('user_fingerprint');
401
  if (!fp) {
@@ -405,129 +391,150 @@
405
  return fp;
406
  }
407
 
408
- // --- مدیریت اشتراک ---
409
- function isUserPaid(userObject) { return userObject && userObject.isLogin && userObject.accessible_pages && (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) || userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID))); }
 
 
 
 
410
  function updateSubscriptionUI(status) {
411
- if (status === 'paid') {
412
- statusBadge.textContent = 'نسخه نامحدود';
413
- statusBadge.className = 'status-badge visible badge-premium';
414
- } else {
415
- statusBadge.textContent = 'نسخه رایگان';
416
- statusBadge.className = 'status-badge visible badge-free';
417
- }
418
  }
 
419
  window.addEventListener('message', (event) => {
420
  if (event.data && event.data.type === 'USER_STATUS_RESPONSE') {
421
- userSubscriptionStatus = isUserPaid(JSON.parse(event.data.payload)) ? 'paid' : 'free';
 
 
 
 
 
422
  updateSubscriptionUI(userSubscriptionStatus);
423
  }
424
  });
425
  parent.postMessage({ type: 'REQUEST_USER_STATUS' }, '*');
426
-
427
- // --- مدیریت مودال‌ها و دانلود ---
428
  function handleSecureDownload(url, e) {
429
  if (e) e.preventDefault();
430
  if (userSubscriptionStatus === 'free') {
431
  upgradeModalTitle.innerText = "دانلود مخصوص اعضای ویژه";
432
- upgradeModalText.innerText = "برای دانلود آهنگ‌ها با کیفیت اصلی، لطفا حساب خود را به نسخه نامحدود ارتقا دهید.";
433
  upgradeModal.classList.add('open');
434
  } else {
435
  parent.postMessage({ type: 'INITIATE_DOWNLOAD_FROM_URL', payload: { audioUrl: url } }, '*');
 
 
 
 
 
 
436
  }
437
  }
438
- document.getElementById('btnGoPremium').addEventListener('click', () => { parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM', payload: { url: PREMIUM_URL } }, '*'); });
 
 
439
  function closeUpgradeModal() { upgradeModal.classList.remove('open'); }
440
  function closeDeleteModal() { deleteModal.classList.remove('open'); songToDeleteId = null; }
441
- deleteModal.addEventListener('click', (e) => { if (e.target === deleteModal) closeDeleteModal(); });
442
- upgradeModal.addEventListener('click', (e) => { if (e.target === upgradeModal) closeUpgradeModal(); });
443
-
444
- // --- مدیریت دیتابیس IndexedDB ---
 
 
 
 
445
  function initDB() {
446
  const request = indexedDB.open("alphaMusicDB", 1);
447
- request.onerror = (e) => console.error("DB error:", e);
448
  request.onsuccess = (e) => { db = e.target.result; loadHistory(); };
449
  request.onupgradeneeded = (e) => { e.target.result.createObjectStore("songs", { keyPath: "id" }); };
450
  }
451
- async function saveToHistory(idea, lyrics, audioUrl, vttUrl) {
 
452
  try {
453
  const response = await fetch(audioUrl);
454
  const audioBlob = await response.blob();
455
- let vttContent = "";
456
- if (vttUrl) {
457
- const vttResponse = await fetch(vttUrl);
458
- vttContent = await vttResponse.text();
459
- }
460
- const newItem = { id: Date.now(), idea, lyrics, audioBlob, vttContent, date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' }) };
461
- const transaction = db.transaction(["songs"], "readwrite");
462
- const store = transaction.objectStore("songs");
463
  store.add(newItem);
464
- transaction.oncomplete = () => loadHistory();
 
 
465
  } catch (error) { console.error("Error saving:", error); }
466
  }
 
467
  function loadHistory() {
468
  if (!db) return;
469
- db.transaction(["songs"],"readonly").objectStore("songs").getAll().onsuccess = (e) => {
470
- const history = e.target.result.sort((a,b) => b.id - a.id);
 
471
  historyList.innerHTML = history.length === 0 ? '<div style="text-align:center; color:#999; padding:20px;">هنوز آهنگی نساخته‌اید</div>' : '';
472
- history.forEach(item => {
473
  const div = document.createElement('div');
474
  div.className = 'history-card';
475
  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>`;
476
- div.onclick = (ev) => { if (!ev.target.closest('.h-delete')) openHistoryItem(item); };
477
  historyList.appendChild(div);
478
  });
479
  };
480
  }
481
- function askToDelete(event, id) { event.stopPropagation(); songToDeleteId = id; deleteModal.classList.add('open'); }
 
 
 
 
 
 
482
  function confirmDelete() {
483
  if (songToDeleteId && db) {
484
- const transaction = db.transaction(["songs"], "readwrite");
485
- transaction.objectStore("songs").delete(songToDeleteId);
486
- transaction.oncomplete = () => { loadHistory(); closeDeleteModal(); };
487
  }
488
  }
 
489
  function openHistoryItem(item) {
490
- step1.style.display = 'none'; historySection.style.display = 'none';
 
 
491
  const audioURL = URL.createObjectURL(item.audioBlob);
492
  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> آهنگ آرشیو شده`;
493
  mainDownloadBtn.onclick = (e) => handleSecureDownload(audioURL, e);
494
- const audioEl = document.createElement('audio');
495
- audioEl.controls = true; audioEl.autoplay = true; audioEl.src = audioURL;
496
- playerWrapper.innerHTML = ''; playerWrapper.appendChild(audioEl);
497
- if (item.vttContent) {
498
- lyricsSection.style.display = 'block';
499
- displayKaraokeLyrics(item.vttContent, audioEl);
500
- } else {
501
- lyricsSection.style.display = 'none';
502
- }
503
- finalResult.style.display = 'block'; window.scrollTo({ top: 0, behavior: 'smooth' });
504
  }
 
505
  initDB();
506
 
507
- // --- مدیریت آپلود و حالت بی‌کلام ---
508
- audioConditionInput.addEventListener('change', () => {
509
- if (audioConditionInput.files.length > 0) {
510
- fileNameDisplay.textContent = audioConditionInput.files[0].name;
511
- clearFileBtn.style.display = 'inline-block';
512
- }
513
- });
514
- clearFileBtn.addEventListener('click', () => {
515
- audioConditionInput.value = '';
516
- fileNameDisplay.textContent = 'فایلی انتخاب نشده';
517
- clearFileBtn.style.display = 'none';
518
- });
519
- instrumentalCheckbox.addEventListener('change', (e) => {
520
- ideaInput.placeholder = e.target.checked ? 'توضیحات آهنگ بی‌کلام (مثال: آرامش‌بخش، پیانو و ویولن)' : 'مثال: آهنگ تولد برای سمیرا، سبک پاپ شاد...';
521
- });
522
-
523
- // --- فرآیند اصلی ---
524
  async function uploadFile(file) {
 
525
  const formData = new FormData();
526
  formData.append("files", file);
527
- const response = await fetch(`${ACE_SPACE_URL}gradio_api/upload`, { method: 'POST', body: formData });
528
- if (!response.ok) throw new Error('خطا در آپلود فایل نمونه');
529
- const result = await response.json();
530
- return result[0]; // [ "/tmp/gradio/.../file.wav" ]
 
 
 
 
 
 
 
 
 
 
531
  }
532
 
533
  processBtn.addEventListener('click', async () => {
@@ -538,55 +545,46 @@
538
  historySection.style.display = 'none';
539
  loader.style.display = 'block';
540
 
541
- let audioConditionObj = null;
542
- let lyrics = "";
543
- let musicPrompt = "";
544
-
545
  try {
546
- // مرحله ۱: آپلود فایل نمونه (در صورت وجود)
547
- if (audioConditionInput.files.length > 0) {
548
- loaderText.innerText = "در حال آپلود آهنگ نمونه...";
549
- const file = audioConditionInput.files[0];
550
- const serverPath = await uploadFile(file);
551
- audioConditionObj = {
552
- path: serverPath,
553
- url: ACE_SPACE_URL + 'gradio_api/file=' + serverPath,
554
- orig_name: file.name,
555
- size: file.size,
556
- mime_type: file.type,
557
- meta: { _type: "gradio.FileData" }
558
- };
559
  }
560
 
561
- // مرحله ۲: آماده‌سازی متن و شعر
562
- const isInstrumental = instrumentalCheckbox.checked;
563
- if (isInstrumental) {
564
- lyrics = ""; // برای بی‌کلام، شعر خالی است
565
- musicPrompt = ideaInput.value + " ,instrumental"; // اضافه کردن کلمه بی‌کلام به پرامپت
566
- } else {
567
- loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
568
- const refineResponse = await fetch('/api/refine', {
569
- method: 'POST',
570
- headers: {'Content-Type': 'application/json'},
571
- body: JSON.stringify({ idea: ideaInput.value, fingerprint: getFingerprint(), is_premium: userSubscriptionStatus === 'paid' })
572
- });
573
-
574
- if (refineResponse.status === 429) { // مدیریت محدودیت
575
- loader.style.display = 'none'; step1.style.display = 'block'; historySection.style.display = 'block'; processBtn.disabled = false;
576
- upgradeModalTitle.innerText = "محدودیت ساخت روزانه"; upgradeModalText.innerText = "شما به سقف ۵ آهنگ رایگان در روز رسیده‌اید. برای ساخت نامحدود، حساب خود را ارتقا دهید.";
577
- upgradeModal.classList.add('open');
578
- return;
579
- }
580
- const data = await refineResponse.json();
581
- if (data.error) throw new Error(data.error);
582
- lyrics = data.lyrics;
583
- musicPrompt = data.music_prompt;
584
  }
 
 
 
 
 
 
585
 
586
- // مرحله ۳: ارسال درخواست اصلی ساخت آهنگ
587
  loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
 
588
  const payload = [
589
- getVal('model_select'), "custom", audioConditionObj, "unknown", musicPrompt, lyrics, 0, "", "", "unknown",
590
  getNum('steps_input'), getNum('cfg_input'), true, getNum('seed_input'), null, -1,
591
  getNum('batch_size'), null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
592
  1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
@@ -594,10 +592,10 @@
594
  ];
595
 
596
  const session_hash = Math.random().toString(36).substring(2);
597
- 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 }) });
598
  if (!joinResp.ok) throw new Error('خطا در اتصال به سرور موزیک');
599
 
600
- const eventSource = new EventSource(`${ACE_SPACE_URL}gradio_api/queue/data?session_hash=${session_hash}`);
601
  eventSource.onmessage = (event) => {
602
  const msg = JSON.parse(event.data);
603
  if (msg.msg === 'process_starts') loaderText.innerText = "هوش مصنوعی در حال خواندن...";
@@ -611,127 +609,144 @@
611
 
612
  } catch (e) {
613
  alert("خطا: " + e.message);
614
- loader.style.display = 'none'; step1.style.display = 'block'; historySection.style.display = 'block'; processBtn.disabled = false;
 
 
 
615
  }
616
  });
617
-
618
- async function handleAudioOutput(data, lyrics, idea) {
619
- let hasResult = false;
620
  let audioUrl = null;
621
  let vttUrl = null;
622
-
623
- function traverse(obj) {
624
- if (typeof obj === 'string') {
625
- if (obj.includes('/file=') && obj.endsWith('.mp3')) {
626
- hasResult = true;
627
- audioUrl = obj.startsWith('http') ? obj : ACE_SPACE_URL.replace(/\/$/, '') + obj;
628
- }
629
- if (obj.includes('/file=') && obj.endsWith('.vtt')) {
630
- vttUrl = obj.startsWith('http') ? obj : ACE_SPACE_URL.replace(/\/$/, '') + obj;
631
- }
632
  } else if (obj && typeof obj === 'object') {
633
- if (obj.url && obj.url.endsWith('.mp3')) {
634
- hasResult = true; audioUrl = obj.url;
 
635
  }
636
- if (obj.url && obj.url.endsWith('.vtt')) { vttUrl = obj.url; }
637
- Object.values(obj).forEach(traverse);
638
  }
639
  }
640
- traverse(data);
641
-
642
- if (hasResult) {
 
 
643
  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>آهنگ جدید آماده شد`;
644
- mainDownloadBtn.onclick = (e) => handleSecureDownload(audioUrl, e);
645
- saveToHistory(idea, lyrics, audioUrl, vttUrl);
646
 
647
- const audioEl = document.createElement('audio');
648
- audioEl.controls = true; audioEl.autoplay = true; audioEl.src = audioUrl;
649
- playerWrapper.innerHTML = ''; playerWrapper.appendChild(audioEl);
650
-
651
- if (vttUrl) {
652
- lyricsSection.style.display = 'block';
653
- const vttResponse = await fetch(vttUrl);
654
- const vttContent = await vttResponse.text();
655
- displayKaraokeLyrics(vttContent, audioEl);
656
- } else {
657
- lyricsSection.style.display = 'none';
658
- }
659
 
660
  finalResult.style.display = 'block';
661
  window.scrollTo({ top: 0, behavior: 'smooth' });
662
  } else {
663
  alert("فایل صوتی یافت نشد!");
664
- step1.style.display = 'block'; historySection.style.display = 'block';
665
  }
666
  }
667
-
668
- // --- توابع کارائوکه ---
669
- function timeToSeconds(timeStr) {
670
- const parts = timeStr.split(':');
671
- const secondsAndMs = parts[2].split('.');
672
- return parseInt(parts[0], 10) * 3600 + parseInt(parts[1], 10) * 60 + parseInt(secondsAndMs[0], 10) + parseInt(secondsAndMs[1], 10) / 1000;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
673
  }
674
 
675
  function parseVTT(vttContent) {
676
  const lines = vttContent.split('\n');
677
- const lyricsData = [];
678
- for (let i = 0; i < lines.length; i++) {
679
- const match = lines[i].match(/(\d{2}:\d{2}:\d{2}\.\d{3}) --> (\d{2}:\d{2}:\d{2}\.\d{3})/);
680
- if (match) {
681
- let text = '';
682
- let j = i + 1;
683
- while (j < lines.length && lines[j].trim() !== '') {
684
- text += lines[j] + '\n';
685
- j++;
686
- }
687
- lyricsData.push({
688
- start: timeToSeconds(match[1]),
689
- end: timeToSeconds(match[2]),
690
- text: text.trim().replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>')
691
- });
692
- i = j;
 
 
 
 
 
693
  }
694
  }
695
- return lyricsData;
696
- }
697
-
698
- function displayKaraokeLyrics(vttContent, audioEl) {
699
- const lyricsData = parseVTT(vttContent);
700
- finalLyricsBox.innerHTML = '';
701
- lyricsData.forEach(line => {
702
- const p = document.createElement('p');
703
- p.className = 'lyrics-line';
704
- p.innerHTML = line.text;
705
- p.dataset.start = line.start;
706
- p.dataset.end = line.end;
707
- finalLyricsBox.appendChild(p);
708
- });
709
 
710
- let currentLine = null;
711
- audioEl.addEventListener('timeupdate', () => {
712
- const currentTime = audioEl.currentTime;
713
- const lines = finalLyricsBox.children;
714
- let found = false;
715
- for (let i = 0; i < lines.length; i++) {
716
- const line = lines[i];
717
- if (currentTime >= line.dataset.start && currentTime <= line.dataset.end) {
718
- if (line !== currentLine) {
719
- if (currentLine) currentLine.classList.remove('active');
720
- line.classList.add('active');
721
- currentLine = line;
722
- line.scrollIntoView({ behavior: 'smooth', block: 'center' });
723
- }
724
- found = true;
725
- break;
726
- }
727
  }
728
- if (!found && currentLine) {
729
- currentLine.classList.remove('active');
730
- currentLine = null;
 
 
 
 
 
 
 
 
 
731
  }
732
  });
733
  }
734
 
 
735
  const canvas = document.getElementById('music-canvas');
736
  const ctx = canvas.getContext('2d');
737
  let t = 0;
@@ -753,5 +768,5 @@
753
  }
754
  anim();
755
  </script>
756
- </body>
757
  </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
  .status-badge {
70
  display: inline-block; padding: 6px 16px; border-radius: 20px;
 
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
  }
 
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 {
 
161
 
162
  .audio-item { margin-bottom: 10px; }
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; }
 
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;
203
  opacity: 0; transform: scale(0.8); transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
204
  z-index: 10; box-shadow: 0 4px 10px rgba(229, 62, 62, 0.2);
205
  }
 
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); }
 
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
  <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>
 
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>
 
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">
319
  <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>
320
  </div>
321
  <h3>آیا مطمئن هستید؟</h3>
322
+ <p>این آهنگ برای همیشه از سوابق شما حذف خواهد شد و قابل بازگشت نیست.</p>
323
  <div class="modal-actions">
324
  <button class="btn-modal btn-cancel" onclick="closeDeleteModal()">انصراف</button>
325
  <button class="btn-modal btn-confirm" onclick="confirmDelete()">بله، حذف شود</button>
 
330
  <div class="modal-overlay" id="upgradeModal">
331
  <div class="modal-box">
332
  <div class="upgrade-icon">👑</div>
333
+ <h3 style="font-weight: 800; color: #333;" id="upgradeModalTitle">دانلود مخصوص اعضای ویژه</h3>
334
+ <p style="margin-bottom: 25px; line-height: 1.6;" id="upgradeModalText">برای دانلود آهنگ‌های ساخته شده با کیفیت اصلی و بدون محدودیت، لطفا حساب کاربری خود را به نسخه نامحدود ارتقا دهید.</p>
335
  <div class="modal-actions" style="flex-direction: column;">
336
  <button class="btn-modal btn-upgrade" id="btnGoPremium">⭐️ ارتقا به نسخه کامل</button>
337
  <button class="btn-modal btn-cancel" onclick="closeUpgradeModal()" style="width: 100%;">فعلاً نه</button>
 
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
  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
  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) {
 
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) ||
397
+ userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)));
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 {
408
+ const userObject = JSON.parse(event.data.payload);
409
+ userSubscriptionStatus = isUserPaid(userObject) ? 'paid' : 'free';
410
+ } catch (e) {
411
+ userSubscriptionStatus = 'free';
412
+ }
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
+
472
  function loadHistory() {
473
  if (!db) return;
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' });
 
 
 
 
515
  }
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 () => {
 
545
  historySection.style.display = 'none';
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
  ];
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 = "هوش مصنوعی در حال خواندن...";
 
609
 
610
  } catch (e) {
611
  alert("خطا: " + e.message);
612
+ loader.style.display = 'none';
613
+ step1.style.display = 'block';
614
+ historySection.style.display = 'block';
615
+ processBtn.disabled = false;
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
  }
769
  anim();
770
  </script>
771
+ </body>
772
  </html>