Opera8 commited on
Commit
d0b4987
·
verified ·
1 Parent(s): 387288f

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +286 -224
index.html CHANGED
@@ -18,8 +18,8 @@
18
  --accent-primary: #4A6CFA;
19
  --accent-secondary: #9F7AEA;
20
  --accent-glow: rgba(74, 108, 250, 0.2);
21
- --success-color: #38A169;
22
- --error-color: #e53e3e;
23
  --radius-card: 20px;
24
  --radius-btn: 14px;
25
  }
@@ -27,9 +27,15 @@
27
  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
28
 
29
  body {
30
- font-family: var(--app-font); background-color: var(--app-bg); color: var(--text-primary);
31
- margin: 0; padding: 20px 15px; min-height: 100vh;
32
- display: flex; flex-direction: column; align-items: center;
 
 
 
 
 
 
33
  }
34
 
35
  .container { max-width: 650px; width: 100%; z-index: 2; position: relative; }
@@ -38,37 +44,60 @@
38
  position: fixed; top: 0; left: 0; width: 100%; height: 400px;
39
  z-index: 0; opacity: 0.5; pointer-events: none;
40
  mask-image: linear-gradient(to bottom, black, transparent);
 
41
  }
42
 
43
  header { text-align: center; margin-bottom: 2rem; position: relative; }
44
 
45
  .logo-box {
46
  width: 80px; height: 80px; margin: 0 auto 15px;
47
- background: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center;
48
- box-shadow: 0 10px 25px var(--accent-glow); color: var(--accent-primary);
 
 
49
  }
50
 
51
- h1 { font-size: 1.8rem; font-weight: 800; margin: 0; background: linear-gradient(90deg, #2d3748, #4A6CFA); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
 
 
 
 
52
  .subtitle { font-size: 0.9rem; color: var(--text-secondary); margin-top: 5px; }
53
 
54
- .card { background: var(--panel-bg); border-radius: var(--radius-card); box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.05); border: 1px solid var(--panel-border); padding: 25px; margin-bottom: 20px; }
 
 
 
 
 
 
55
 
56
  .form-label { display: flex; align-items: center; gap: 8px; font-weight: 700; margin-bottom: 12px; color: #2d3748; }
57
  .form-label svg { color: var(--accent-primary); width: 20px; }
58
 
59
  textarea, input, select {
60
- width: 100%; background: var(--input-bg); border: 2px solid var(--input-border); border-radius: 12px; padding: 15px;
61
- font-family: inherit; font-size: 1rem; color: #2d3748; outline: none; transition: border-color 0.3s;
 
 
 
62
  }
63
  textarea { min-height: 120px; resize: vertical; }
64
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
65
 
66
  /* دکمه اصلی */
67
- @keyframes move-gradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
 
 
 
 
68
  .btn-main {
69
- width: 100%; padding: 16px; background: linear-gradient(110deg, var(--accent-secondary), var(--accent-primary), var(--accent-secondary));
70
- background-size: 200% 100%; color: #fff; border: none; border-radius: var(--radius-btn);
71
- font-size: 1.1rem; font-weight: 700; cursor: pointer; display: flex; justify-content: center; align-items: center; gap: 10px;
 
 
 
72
  transition: all 0.3s ease-out; position: relative; z-index: 1;
73
  }
74
  .btn-main-content { display: flex; align-items: center; justify-content: center; gap: 10px; transition: transform 0.3s ease; }
@@ -80,10 +109,12 @@
80
  background-size: 400%; border-radius: 16px; z-index: -1; opacity: 0; transition: opacity 0.4s ease-out;
81
  }
82
  .btn-main:hover::before { opacity: 1; animation: move-gradient 4s linear infinite; }
83
- .btn-main:active { transform: scale(0.98); }
84
  .btn-main:disabled { opacity: 0.7; cursor: not-allowed; filter: grayscale(1); }
85
 
86
- .btn-outline { background: transparent; border: 2px solid var(--input-border); color: var(--text-secondary); margin-top: 10px; transition: all 0.2s; }
 
 
 
87
  .btn-outline:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
88
 
89
  /* تنظیمات پیشرفته */
@@ -96,65 +127,94 @@
96
  .accordion:after { content: '⚙️'; font-size: 1rem; transition: transform 0.3s; }
97
  .accordion.active { border-radius: 12px 12px 0 0; border-bottom: none; }
98
  .accordion.active:after { content: '🔼'; }
99
-
100
- .panel { padding: 0 15px; background-color: var(--input-bg); max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out; border-radius: 0 0 12px 12px; margin-bottom: 0; border: 1px solid var(--input-border); border-top: none; }
 
 
 
101
  .settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; padding: 15px 0; }
102
- .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
103
  .checkbox-wrapper { display: flex; align-items: center; gap: 10px; margin-top: 5px; padding-bottom: 15px; border-top: 1px solid #e2e8f0; padding-top: 15px; }
104
- .checkbox-wrapper input { width: 20px; height: 20px; cursor: pointer; }
105
 
106
  /* پلیر و متن */
107
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
108
- #mainDownloadLink { background-color: rgba(74, 108, 250, 0.1); color: var(--accent-primary); text-decoration: none; font-size: 0.9rem; font-weight: 700; padding: 8px 16px; border-radius: 10px; transition: 0.2s; display: none; }
 
 
 
109
  #mainDownloadLink:hover { background-color: rgba(74, 108, 250, 0.2); }
110
  .audio-item { margin-bottom: 10px; }
111
  audio { width: 100%; height: 45px; border-radius: 20px; margin-top: 5px; }
112
- .lyrics-container { background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto; white-space: pre-wrap; line-height: 2; font-size: 1rem; color: #4a5568; text-align: center; border: 1px solid var(--input-border); margin-top: 15px; }
 
 
 
113
  .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; }
114
 
115
  /* لودر */
116
  #loader { display: none; text-align: center; padding: 20px; }
117
  .wave-bars { display: flex; justify-content: center; gap: 4px; height: 30px; align-items: flex-end; }
118
  .bar { width: 5px; background: var(--accent-primary); animation: jump 1s infinite; border-radius: 2px; }
 
 
 
119
  @keyframes jump { 0%, 100% { height: 20%; } 50% { height: 100%; } }
120
 
121
- /* تاریخچه */
122
  #historySection { margin-top: 30px; width: 100%; }
123
  .history-title { font-size: 1.2rem; font-weight: 800; color: var(--text-primary); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
124
  .history-list { display: grid; gap: 12px; }
125
- .history-card { background: white; border-radius: 16px; padding: 15px; display: flex; align-items: center; justify-content: space-between; border: 1px solid var(--panel-border); transition: all 0.3s; cursor: pointer; position: relative; }
 
 
 
126
  .history-card:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(74, 108, 250, 0.15); border-color: var(--accent-primary); }
127
  .h-info { display: flex; align-items: center; gap: 15px; }
128
  .h-icon { width: 45px; height: 45px; background: var(--input-bg); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: var(--accent-primary); font-size: 1.2rem; }
129
- .h-details h4 { margin: 0; font-size: 1rem; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; }
130
  .h-details span { font-size: 0.8rem; color: var(--text-secondary); }
131
- .h-delete-icon {
132
- position: absolute; left: 15px; top: 50%; transform: translateY(-50%) scale(0.8);
133
- color: var(--error-color); opacity: 0; transition: all 0.3s; font-size: 1.3rem;
 
 
 
 
 
 
134
  }
135
- .history-card:hover .h-delete-icon { opacity: 1; transform: translateY(-50%) scale(1); }
 
136
 
137
  /* مودال حذف */
138
  .modal-overlay {
139
- position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); backdrop-filter: blur(5px);
140
- z-index: 1000; display: none; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s;
 
 
141
  }
142
- .modal-content {
143
- width: 90%; max-width: 400px; background: white; border-radius: 20px; padding: 25px;
144
- transform: scale(0.9); transition: transform 0.3s; text-align: center;
 
 
145
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  .modal-overlay.open { display: flex; opacity: 1; }
147
- .modal-overlay.open .modal-content { transform: scale(1); }
148
- .modal-title { font-size: 1.2rem; font-weight: 800; margin-bottom: 10px; }
149
- .modal-text { font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 20px; }
150
- .modal-buttons { display: flex; gap: 10px; }
151
- .modal-btn { flex: 1; padding: 12px; border-radius: 10px; border: none; cursor: pointer; font-weight: 700; transition: 0.2s; }
152
- .btn-confirm { background-color: var(--error-color); color: white; }
153
- .btn-confirm:hover { background-color: #c53030; }
154
- .btn-cancel { background-color: var(--input-border); color: var(--text-secondary); }
155
- .btn-cancel:hover { background-color: #cbd5e0; }
156
-
157
- @keyframes slideUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
158
  </style>
159
  </head>
160
  <body>
@@ -162,20 +222,25 @@
162
 
163
  <div class="container">
164
  <header>
165
- <div class="logo-box"><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></div>
 
 
166
  <h1>استودیو موزیک آلفا</h1>
167
  <p class="subtitle">ساخت آهنگ حرفه‌ای با هوش مصنوعی</p>
168
  </header>
169
 
170
  <div id="step1" class="card">
171
- <div class="form-label"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path></svg>موضوع آهنگ چیه؟</div>
 
 
 
172
  <textarea id="ideaInput" placeholder="مثال: آهنگ تولد برای سمیرا، سبک پاپ شاد..."></textarea>
173
 
174
  <button class="accordion">تنظیمات پیشرفته</button>
175
  <div class="panel">
176
  <div class="settings-grid">
177
  <div class="settings-group"><label>مدل هوش مصنوعی:</label><select id="model_select"><option value="acestep-v15-turbo-shift3" selected>Turbo-Shift3 (دقیق‌ترین)</option><option value="acestep-v15-turbo">Turbo (سریع‌ترین)</option></select></div>
178
- <div class="settings-group"><label>تعداد خروجی:</label><input type="number" id="batch_size" value="1" min="1" max="4"></div>
179
  <div class="settings-group"><label>تعداد گام (Steps):</label><input type="number" id="steps_input" value="8" min="4" max="50"></div>
180
  <div class="settings-group"><label>سید (Seed) تصادفی=-1:</label><input type="number" id="seed_input" value="-1"></div>
181
  <div class="settings-group"><label>مقیاس هدایت (CFG):</label><input type="number" id="cfg_input" value="7.0" step="0.5"></div>
@@ -202,29 +267,35 @@
202
  </div>
203
 
204
  <div id="historySection">
205
- <div class="history-title"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>آخرین آهنگ‌های ساخته شده</div>
 
 
 
206
  <div class="history-list" id="historyList"></div>
207
  </div>
208
  </div>
209
 
210
- <!-- مودال حذف -->
211
- <div class="modal-overlay" id="deleteModal">
212
- <div class="modal-content">
213
- <div class="modal-title">تایید حذف آهنگ</div>
214
- <p class="modal-text">آیا از حذف این آهنگ از سابقه خود مطمئن هستید؟ این عمل غیرقابل بازگشت است.</p>
215
- <div class="modal-buttons">
216
- <button id="cancelDeleteBtn" class="modal-btn btn-cancel">انصراف</button>
217
- <button id="confirmDeleteBtn" class="modal-btn btn-confirm">تایید حذف</button>
 
 
 
218
  </div>
219
  </div>
220
  </div>
221
 
222
  <script>
223
- const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf-space/";
224
  let db;
225
- let pressTimer;
226
- let songToDeleteId = null;
227
 
 
228
  const ideaInput = document.getElementById('ideaInput');
229
  const processBtn = document.getElementById('processBtn');
230
  const step1 = document.getElementById('step1');
@@ -236,9 +307,7 @@
236
  const mainDownloadLink = document.getElementById('mainDownloadLink');
237
  const historyList = document.getElementById('historyList');
238
  const historySection = document.getElementById('historySection');
239
- const deleteModal = document.getElementById('deleteModal');
240
- const confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
241
- const cancelDeleteBtn = document.getElementById('cancelDeleteBtn');
242
 
243
  const getVal = (id) => document.getElementById(id).value;
244
  const getNum = (id) => Number(document.getElementById(id).value);
@@ -253,8 +322,33 @@
253
  });
254
  }
255
 
256
- function initDB() { /* ... دون تغییر) ... */ }
257
- async function saveToHistory(idea, lyrics, audioUrl) { /* ... (بدون تغییر) ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
  function loadHistory() {
260
  if (!db) return;
@@ -277,32 +371,26 @@
277
  <span>${item.date}</span>
278
  </div>
279
  </div>
280
- <div class="h-delete-icon">🗑️</div>
 
 
281
  `;
282
-
283
- div.addEventListener('mousedown', () => {
284
- pressTimer = setTimeout(() => showDeleteConfirmation(item.id), 500);
285
- });
286
- div.addEventListener('mouseup', () => {
287
- clearTimeout(pressTimer);
288
- });
289
- div.addEventListener('click', (event) => {
290
- // اطمینان از اینکه کلیک کوتاه بوده
291
- if (!deleteModal.classList.contains('open')) {
292
- openHistoryItem(item);
293
  }
294
- });
295
-
296
  historyList.appendChild(div);
297
  });
298
  };
299
  }
300
 
301
- function openHistoryItem(item) { /* ... (بدون تغییر) ... */ }
302
-
303
- // --- مدیریت مودال حذف ---
304
- function showDeleteConfirmation(itemId) {
305
- songToDeleteId = itemId;
306
  deleteModal.classList.add('open');
307
  }
308
 
@@ -312,7 +400,7 @@
312
  }
313
 
314
  function confirmDelete() {
315
- if (songToDeleteId) {
316
  const transaction = db.transaction(["songs"], "readwrite");
317
  const store = transaction.objectStore("songs");
318
  store.delete(songToDeleteId);
@@ -322,17 +410,124 @@
322
  };
323
  }
324
  }
325
-
326
- confirmDeleteBtn.addEventListener('click', confirmDelete);
327
- cancelDeleteBtn.addEventListener('click', closeDeleteModal);
328
  deleteModal.addEventListener('click', (e) => {
329
  if (e.target === deleteModal) closeDeleteModal();
330
  });
331
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  initDB();
333
 
334
- processBtn.addEventListener('click', async () => { /* ... (بدون تغییر) ... */ });
335
- function handleAudioOutput(data, lyrics, idea) { /* ... (بدون تغییر) ... */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  function formatLyrics(text) { return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>'); }
337
 
338
  const canvas = document.getElementById('music-canvas');
@@ -355,139 +550,6 @@
355
  requestAnimationFrame(anim);
356
  }
357
  anim();
358
-
359
- // --- کد JS برای توابع بدون تغییر ---
360
- (function() {
361
- function initDB_impl() {
362
- const request = indexedDB.open("alphaMusicDB", 1);
363
- request.onerror = (event) => console.error("IndexedDB error:", event);
364
- request.onsuccess = (event) => { db = event.target.result; loadHistory(); };
365
- request.onupgradeneeded = (event) => {
366
- event.target.result.createObjectStore("songs", { keyPath: "id" });
367
- };
368
- }
369
- async function saveToHistory_impl(idea, lyrics, audioUrl) {
370
- try {
371
- const response = await fetch(audioUrl);
372
- const audioBlob = await response.blob();
373
- const newItem = {
374
- id: Date.now(), idea, lyrics, audioBlob,
375
- date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' })
376
- };
377
- const transaction = db.transaction(["songs"], "readwrite");
378
- const store = transaction.objectStore("songs");
379
- store.add(newItem);
380
-
381
- const countReq = store.count();
382
- countReq.onsuccess = () => {
383
- if (countReq.result > 10) {
384
- store.openCursor().onsuccess = (e) => {
385
- const cursor = e.target.result;
386
- if (cursor) cursor.delete();
387
- };
388
- }
389
- };
390
- transaction.oncomplete = () => loadHistory();
391
- } catch (error) { console.error("Error saving song:", error); }
392
- }
393
- function openHistoryItem_impl(item) {
394
- step1.style.display = 'none';
395
- historySection.style.display = 'none';
396
- const audioURL = URL.createObjectURL(item.audioBlob);
397
- const headerText = document.getElementById('resultHeaderText');
398
- 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> آهنگ آرشیو شده`;
399
- headerText.style.color = 'var(--accent-primary)';
400
- mainDownloadLink.href = audioURL;
401
- mainDownloadLink.style.display = 'inline-block';
402
- playerWrapper.innerHTML = `<audio controls autoplay src="${audioURL}"></audio>`;
403
- finalLyricsBox.innerHTML = formatLyrics(item.lyrics);
404
- finalResult.style.display = 'block';
405
- window.scrollTo({ top: 0, behavior: 'smooth' });
406
- }
407
- processBtn.onclick_original = async () => {
408
- if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
409
- processBtn.disabled = true;
410
- step1.style.display = 'none';
411
- historySection.style.display = 'none';
412
- loader.style.display = 'block';
413
- try {
414
- loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
415
- const response = await fetch('/api/refine', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ idea: ideaInput.value }) });
416
- const data = await response.json();
417
- if (data.error) throw new Error(data.error);
418
- const { lyrics, musicPrompt } = data;
419
- loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
420
- const payload = [
421
- getVal('model_select'), "custom", null, "unknown", musicPrompt, lyrics, 0, "", "", "unknown",
422
- getNum('steps_input'), getNum('cfg_input'), true, getNum('seed_input'), null, -1,
423
- getNum('batch_size'), null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
424
- 1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
425
- getChk('think_checkbox'), 2, 0, 0.9, "NO USER INPUT", true, true, true, null, false, true, false, false, 0.5, 8, null, [], false, null, null, null, null
426
- ];
427
- const session_hash = Math.random().toString(36).substring(2);
428
- 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 }) });
429
- if (!joinResp.ok) throw new Error('خطا در اتصال به سرور موزیک');
430
- const eventSource = new EventSource(`${ACE_SPACE_URL}gradio_api/queue/data?session_hash=${session_hash}`);
431
- eventSource.onmessage = (event) => {
432
- const msg = JSON.parse(event.data);
433
- if (msg.msg === 'process_starts') loaderText.innerText = "هوش مصنوعی در حال خواندن...";
434
- else if (msg.msg === 'process_completed') {
435
- eventSource.close();
436
- loader.style.display = 'none';
437
- handleAudioOutput(msg.output, lyrics, ideaInput.value);
438
- }
439
- };
440
- eventSource.onerror = () => { throw new Error('ارتباط با سرور قطع شد.'); };
441
- } catch (e) {
442
- alert("خطا: " + e.message);
443
- loader.style.display = 'none';
444
- step1.style.display = 'block';
445
- historySection.style.display = 'block';
446
- processBtn.disabled = false;
447
- }
448
- };
449
- function handleAudioOutput_impl(data, lyrics, idea) {
450
- const processedUrls = new Set();
451
- let hasResult = false;
452
- function addAudio(url) {
453
- const fullUrl = url.startsWith('http') ? url : ACE_SPACE_URL.replace(/\/$/, '') + url;
454
- if (processedUrls.has(fullUrl)) return;
455
- processedUrls.add(fullUrl);
456
- hasResult = true;
457
- if (processedUrls.size === 1) {
458
- mainDownloadLink.href = fullUrl;
459
- mainDownloadLink.style.display = 'inline-block';
460
- saveToHistory(idea, lyrics, fullUrl);
461
- }
462
- playerWrapper.innerHTML += `<div class="audio-item"><audio controls autoplay src="${fullUrl}"></audio></div>`;
463
- }
464
- function traverse(obj) {
465
- if (typeof obj === 'string' && obj.includes('/file=') && obj.endsWith('.mp3')) addAudio(obj);
466
- else if (obj && typeof obj === 'object') {
467
- if (obj.url && obj.url.endsWith('.mp3')) addAudio(obj.url);
468
- Object.values(obj).forEach(traverse);
469
- }
470
- }
471
- traverse(data);
472
- if (hasResult) {
473
- const headerText = document.getElementById('resultHeaderText');
474
- 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>آهنگ جدید آماده شد`;
475
- headerText.style.color = 'var(--success-color)';
476
- finalResult.style.display = 'block';
477
- finalLyricsBox.innerHTML = formatLyrics(lyrics);
478
- window.scrollTo({ top: 0, behavior: 'smooth' });
479
- } else {
480
- alert("فایل صوتی یافت نشد!");
481
- step1.style.display = 'block';
482
- historySection.style.display = 'block';
483
- }
484
- }
485
- if (typeof initDB !== 'function') var initDB = initDB_impl;
486
- if (typeof saveToHistory !== 'function') var saveToHistory = saveToHistory_impl;
487
- if (typeof openHistoryItem !== 'function') var openHistoryItem = openHistoryItem_impl;
488
- if (typeof processBtn.onclick !== 'function') processBtn.onclick = processBtn.onclick_original;
489
- if (typeof handleAudioOutput !== 'function') var handleAudioOutput = handleAudioOutput_impl;
490
- })();
491
  </script>
492
  </body>
493
  </html>
 
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
  --radius-card: 20px;
24
  --radius-btn: 14px;
25
  }
 
27
  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
28
 
29
  body {
30
+ font-family: var(--app-font);
31
+ background-color: var(--app-bg);
32
+ color: var(--text-primary);
33
+ margin: 0;
34
+ padding: 20px 15px;
35
+ min-height: 100vh;
36
+ display: flex;
37
+ flex-direction: column;
38
+ align-items: center;
39
  }
40
 
41
  .container { max-width: 650px; width: 100%; z-index: 2; position: relative; }
 
44
  position: fixed; top: 0; left: 0; width: 100%; height: 400px;
45
  z-index: 0; opacity: 0.5; pointer-events: none;
46
  mask-image: linear-gradient(to bottom, black, transparent);
47
+ -webkit-mask-image: linear-gradient(to bottom, black, transparent);
48
  }
49
 
50
  header { text-align: center; margin-bottom: 2rem; position: relative; }
51
 
52
  .logo-box {
53
  width: 80px; height: 80px; margin: 0 auto 15px;
54
+ background: #fff; border-radius: 50%;
55
+ display: flex; align-items: center; justify-content: center;
56
+ box-shadow: 0 10px 25px var(--accent-glow);
57
+ color: var(--accent-primary);
58
  }
59
 
60
+ h1 {
61
+ font-size: 1.8rem; font-weight: 800; margin: 0;
62
+ background: linear-gradient(90deg, #2d3748, #4A6CFA);
63
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
64
+ }
65
  .subtitle { font-size: 0.9rem; color: var(--text-secondary); margin-top: 5px; }
66
 
67
+ .card {
68
+ background: var(--panel-bg);
69
+ border-radius: var(--radius-card);
70
+ box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.05);
71
+ border: 1px solid var(--panel-border);
72
+ padding: 25px; margin-bottom: 20px;
73
+ }
74
 
75
  .form-label { display: flex; align-items: center; gap: 8px; font-weight: 700; margin-bottom: 12px; color: #2d3748; }
76
  .form-label svg { color: var(--accent-primary); width: 20px; }
77
 
78
  textarea, input, select {
79
+ width: 100%; background: var(--input-bg);
80
+ border: 2px solid var(--input-border);
81
+ border-radius: 12px; padding: 15px;
82
+ font-family: inherit; font-size: 1rem; color: #2d3748;
83
+ outline: none; transition: border-color 0.3s;
84
  }
85
  textarea { min-height: 120px; resize: vertical; }
86
  textarea:focus, input:focus, select:focus { border-color: var(--accent-primary); background: #fff; }
87
 
88
  /* دکمه اصلی */
89
+ @keyframes move-gradient {
90
+ 0% { background-position: 0% 50%; }
91
+ 50% { background-position: 100% 50%; }
92
+ 100% { background-position: 0% 50%; }
93
+ }
94
  .btn-main {
95
+ width: 100%; padding: 16px;
96
+ background: linear-gradient(110deg, var(--accent-secondary), var(--accent-primary), var(--accent-secondary));
97
+ background-size: 200% 100%;
98
+ color: #fff; border: none; border-radius: var(--radius-btn);
99
+ font-size: 1.1rem; font-weight: 700; cursor: pointer;
100
+ display: flex; justify-content: center; align-items: center; gap: 10px;
101
  transition: all 0.3s ease-out; position: relative; z-index: 1;
102
  }
103
  .btn-main-content { display: flex; align-items: center; justify-content: center; gap: 10px; transition: transform 0.3s ease; }
 
109
  background-size: 400%; border-radius: 16px; z-index: -1; opacity: 0; transition: opacity 0.4s ease-out;
110
  }
111
  .btn-main:hover::before { opacity: 1; animation: move-gradient 4s linear infinite; }
 
112
  .btn-main:disabled { opacity: 0.7; cursor: not-allowed; filter: grayscale(1); }
113
 
114
+ .btn-outline {
115
+ background: transparent; border: 2px solid var(--input-border);
116
+ color: var(--text-secondary); margin-top: 10px; transition: all 0.2s;
117
+ }
118
  .btn-outline:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
119
 
120
  /* تنظیمات پیشرفته */
 
127
  .accordion:after { content: '⚙️'; font-size: 1rem; transition: transform 0.3s; }
128
  .accordion.active { border-radius: 12px 12px 0 0; border-bottom: none; }
129
  .accordion.active:after { content: '🔼'; }
130
+ .panel {
131
+ padding: 0 15px; background-color: var(--input-bg); max-height: 0; overflow: hidden;
132
+ transition: max-height 0.3s ease-out; border-radius: 0 0 12px 12px;
133
+ margin-bottom: 0; border: 1px solid var(--input-border); border-top: none;
134
+ }
135
  .settings-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; padding: 15px 0; }
 
136
  .checkbox-wrapper { display: flex; align-items: center; gap: 10px; margin-top: 5px; padding-bottom: 15px; border-top: 1px solid #e2e8f0; padding-top: 15px; }
137
+ .settings-group label { font-size: 0.8rem; color: var(--text-secondary); display: block; margin-bottom: 6px; font-weight: 500; }
138
 
139
  /* پلیر و متن */
140
  .player-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid var(--panel-border); }
141
+ #mainDownloadLink {
142
+ background-color: rgba(74, 108, 250, 0.1); color: var(--accent-primary); text-decoration: none; font-size: 0.9rem;
143
+ font-weight: 700; padding: 8px 16px; border-radius: 10px; transition: 0.2s; display: none;
144
+ }
145
  #mainDownloadLink:hover { background-color: rgba(74, 108, 250, 0.2); }
146
  .audio-item { margin-bottom: 10px; }
147
  audio { width: 100%; height: 45px; border-radius: 20px; margin-top: 5px; }
148
+ .lyrics-container {
149
+ background: var(--input-bg); border-radius: 16px; padding: 20px; max-height: 350px; overflow-y: auto;
150
+ white-space: pre-wrap; line-height: 2; font-size: 1rem; color: #4a5568; text-align: center; border: 1px solid var(--input-border); margin-top: 15px;
151
+ }
152
  .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; }
153
 
154
  /* لودر */
155
  #loader { display: none; text-align: center; padding: 20px; }
156
  .wave-bars { display: flex; justify-content: center; gap: 4px; height: 30px; align-items: flex-end; }
157
  .bar { width: 5px; background: var(--accent-primary); animation: jump 1s infinite; border-radius: 2px; }
158
+ .bar:nth-child(2) { animation-delay: 0.1s; height: 60%; }
159
+ .bar:nth-child(3) { animation-delay: 0.2s; height: 80%; }
160
+ .bar:nth-child(4) { animation-delay: 0.3s; height: 50%; }
161
  @keyframes jump { 0%, 100% { height: 20%; } 50% { height: 100%; } }
162
 
163
+ /* --- استایل‌های جدید تاریخچه و حذف --- */
164
  #historySection { margin-top: 30px; width: 100%; }
165
  .history-title { font-size: 1.2rem; font-weight: 800; color: var(--text-primary); margin-bottom: 15px; display: flex; align-items: center; gap: 10px; }
166
  .history-list { display: grid; gap: 12px; }
167
+ .history-card {
168
+ background: white; border-radius: 16px; padding: 15px; display: flex; align-items: center; justify-content: space-between;
169
+ border: 1px solid var(--panel-border); transition: all 0.3s; cursor: pointer; position: relative; overflow: hidden;
170
+ }
171
  .history-card:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(74, 108, 250, 0.15); border-color: var(--accent-primary); }
172
  .h-info { display: flex; align-items: center; gap: 15px; }
173
  .h-icon { width: 45px; height: 45px; background: var(--input-bg); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: var(--accent-primary); font-size: 1.2rem; }
174
+ .h-details h4 { margin: 0; font-size: 1rem; color: var(--text-primary); font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; }
175
  .h-details span { font-size: 0.8rem; color: var(--text-secondary); }
176
+
177
+ /* دکمه حذف در سمت چپ */
178
+ .h-delete {
179
+ position: absolute; left: 15px; /* سمت چپ فیزیکی */
180
+ background: #fee2e2; color: var(--danger-color);
181
+ width: 38px; height: 38px; border-radius: 50%;
182
+ display: flex; align-items: center; justify-content: center;
183
+ opacity: 0; transform: scale(0.8); transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
184
+ z-index: 10; box-shadow: 0 4px 10px rgba(229, 62, 62, 0.2);
185
  }
186
+ .history-card:hover .h-delete { opacity: 1; transform: scale(1); }
187
+ .h-delete:hover { background: var(--danger-color); color: white; transform: scale(1.1); }
188
 
189
  /* مودال حذف */
190
  .modal-overlay {
191
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%;
192
+ background: rgba(0, 0, 0, 0.6); backdrop-filter: blur(8px);
193
+ z-index: 1000; display: none; align-items: center; justify-content: center;
194
+ opacity: 0; transition: opacity 0.3s;
195
  }
196
+ .modal-box {
197
+ background: white; width: 90%; max-width: 400px;
198
+ border-radius: 24px; padding: 30px; text-align: center;
199
+ transform: scale(0.9); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
200
+ box-shadow: 0 20px 60px rgba(0,0,0,0.2);
201
  }
202
+ .modal-icon-warn {
203
+ width: 60px; height: 60px; background: #fee2e2; color: var(--danger-color);
204
+ border-radius: 50%; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center;
205
+ }
206
+ .modal-box h3 { margin: 0 0 10px; color: var(--text-primary); }
207
+ .modal-box p { color: var(--text-secondary); margin: 0 0 25px; font-size: 0.95rem; }
208
+ .modal-actions { display: flex; gap: 10px; justify-content: center; }
209
+ .btn-modal { padding: 12px 24px; border-radius: 12px; font-weight: 700; border: none; cursor: pointer; flex: 1; font-size: 1rem; transition: 0.2s; }
210
+ .btn-cancel { background: var(--input-bg); color: var(--text-secondary); }
211
+ .btn-cancel:hover { background: #e2e8f0; }
212
+ .btn-confirm { background: var(--danger-color); color: white; box-shadow: 0 5px 15px rgba(229, 62, 62, 0.3); }
213
+ .btn-confirm:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(229, 62, 62, 0.4); }
214
+
215
+ /* نمایش مودال */
216
  .modal-overlay.open { display: flex; opacity: 1; }
217
+ .modal-overlay.open .modal-box { transform: scale(1); }
 
 
 
 
 
 
 
 
 
 
218
  </style>
219
  </head>
220
  <body>
 
222
 
223
  <div class="container">
224
  <header>
225
+ <div class="logo-box">
226
+ <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>
227
+ </div>
228
  <h1>استودیو موزیک آلفا</h1>
229
  <p class="subtitle">ساخت آهنگ حرفه‌ای با هوش مصنوعی</p>
230
  </header>
231
 
232
  <div id="step1" class="card">
233
+ <div class="form-label">
234
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path></svg>
235
+ موضوع آهنگ چیه؟
236
+ </div>
237
  <textarea id="ideaInput" placeholder="مثال: آهنگ تولد برای سمیرا، سبک پاپ شاد..."></textarea>
238
 
239
  <button class="accordion">تنظیمات پیشرفته</button>
240
  <div class="panel">
241
  <div class="settings-grid">
242
  <div class="settings-group"><label>مدل هوش مصنوعی:</label><select id="model_select"><option value="acestep-v15-turbo-shift3" selected>Turbo-Shift3 (دقیق‌ترین)</option><option value="acestep-v15-turbo">Turbo (سریع‌ترین)</option></select></div>
243
+ <div class="settings-group"><label>تعداد خروجی (آهنگ):</label><input type="number" id="batch_size" value="1" min="1" max="4"></div>
244
  <div class="settings-group"><label>تعداد گام (Steps):</label><input type="number" id="steps_input" value="8" min="4" max="50"></div>
245
  <div class="settings-group"><label>سید (Seed) تصادفی=-1:</label><input type="number" id="seed_input" value="-1"></div>
246
  <div class="settings-group"><label>مقیاس هدایت (CFG):</label><input type="number" id="cfg_input" value="7.0" step="0.5"></div>
 
267
  </div>
268
 
269
  <div id="historySection">
270
+ <div class="history-title">
271
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
272
+ آخرین آهنگ‌های ساخته شده
273
+ </div>
274
  <div class="history-list" id="historyList"></div>
275
  </div>
276
  </div>
277
 
278
+ <!-- مودال تایید حذف -->
279
+ <div class="modal-overlay" id="deleteConfirmModal">
280
+ <div class="modal-box">
281
+ <div class="modal-icon-warn">
282
+ <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>
283
+ </div>
284
+ <h3>آیا مطمئن هستید؟</h3>
285
+ <p>این آهنگ برای همیشه از سوابق شما حذف خواهد شد و قابل بازگشت نیست.</p>
286
+ <div class="modal-actions">
287
+ <button class="btn-modal btn-cancel" onclick="closeDeleteModal()">انصراف</button>
288
+ <button class="btn-modal btn-confirm" onclick="confirmDelete()">بله، حذف شود</button>
289
  </div>
290
  </div>
291
  </div>
292
 
293
  <script>
294
+ const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
295
  let db;
296
+ let songToDeleteId = null; // برای نگهداری شناسه آهنگی که قرار است حذف شود
 
297
 
298
+ // المان‌ها
299
  const ideaInput = document.getElementById('ideaInput');
300
  const processBtn = document.getElementById('processBtn');
301
  const step1 = document.getElementById('step1');
 
307
  const mainDownloadLink = document.getElementById('mainDownloadLink');
308
  const historyList = document.getElementById('historyList');
309
  const historySection = document.getElementById('historySection');
310
+ const deleteModal = document.getElementById('deleteConfirmModal');
 
 
311
 
312
  const getVal = (id) => document.getElementById(id).value;
313
  const getNum = (id) => Number(document.getElementById(id).value);
 
322
  });
323
  }
324
 
325
+ // --- مدیریت دیتابیس ---
326
+ function initDB() {
327
+ const request = indexedDB.open("alphaMusicDB", 1);
328
+ request.onerror = (event) => console.error("IndexedDB error:", event);
329
+ request.onsuccess = (event) => { db = event.target.result; loadHistory(); };
330
+ request.onupgradeneeded = (event) => { event.target.result.createObjectStore("songs", { keyPath: "id" }); };
331
+ }
332
+
333
+ async function saveToHistory(idea, lyrics, audioUrl) {
334
+ try {
335
+ const response = await fetch(audioUrl);
336
+ const audioBlob = await response.blob();
337
+ const newItem = {
338
+ id: Date.now(), idea, lyrics, audioBlob,
339
+ date: new Date().toLocaleDateString('fa-IR', { hour: '2-digit', minute: '2-digit' })
340
+ };
341
+ const transaction = db.transaction(["songs"], "readwrite");
342
+ const store = transaction.objectStore("songs");
343
+ store.add(newItem);
344
+
345
+ const countReq = store.count();
346
+ countReq.onsuccess = () => {
347
+ if (countReq.result > 10) store.openCursor().onsuccess = (e) => { if(e.target.result) e.target.result.delete(); };
348
+ };
349
+ transaction.oncomplete = () => loadHistory();
350
+ } catch (error) { console.error("Error saving:", error); }
351
+ }
352
 
353
  function loadHistory() {
354
  if (!db) return;
 
371
  <span>${item.date}</span>
372
  </div>
373
  </div>
374
+ <div class="h-delete" onclick="askToDelete(event, ${item.id})">
375
+ <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>
376
+ </div>
377
  `;
378
+ // کلیک روی خود کارت برای پخش
379
+ div.onclick = (e) => {
380
+ // اگر روی دکمه حذف کلیک نشده باشد
381
+ if (!e.target.closest('.h-delete')) {
382
+ openHistoryItem(item);
 
 
 
 
 
 
383
  }
384
+ };
 
385
  historyList.appendChild(div);
386
  });
387
  };
388
  }
389
 
390
+ // --- توابع حذف ---
391
+ function askToDelete(event, id) {
392
+ event.stopPropagation(); // جلوگیری از باز شدن پلیر
393
+ songToDeleteId = id;
 
394
  deleteModal.classList.add('open');
395
  }
396
 
 
400
  }
401
 
402
  function confirmDelete() {
403
+ if (songToDeleteId && db) {
404
  const transaction = db.transaction(["songs"], "readwrite");
405
  const store = transaction.objectStore("songs");
406
  store.delete(songToDeleteId);
 
410
  };
411
  }
412
  }
413
+
414
+ // بستن مودال با کلیک بیرون
 
415
  deleteModal.addEventListener('click', (e) => {
416
  if (e.target === deleteModal) closeDeleteModal();
417
  });
418
 
419
+ function openHistoryItem(item) {
420
+ step1.style.display = 'none';
421
+ historySection.style.display = 'none';
422
+
423
+ const audioURL = URL.createObjectURL(item.audioBlob);
424
+ const headerText = document.getElementById('resultHeaderText');
425
+ 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> آهنگ آرشیو شده`;
426
+ headerText.style.color = 'var(--accent-primary)';
427
+
428
+ mainDownloadLink.href = audioURL;
429
+ mainDownloadLink.style.display = 'inline-block';
430
+ playerWrapper.innerHTML = `<audio controls autoplay src="${audioURL}"></audio>`;
431
+ finalLyricsBox.innerHTML = formatLyrics(item.lyrics);
432
+
433
+ finalResult.style.display = 'block';
434
+ window.scrollTo({ top: 0, behavior: 'smooth' });
435
+ }
436
+
437
  initDB();
438
 
439
+ // --- فرآیند اصلی ---
440
+ processBtn.addEventListener('click', async () => {
441
+ if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
442
+
443
+ processBtn.disabled = true;
444
+ step1.style.display = 'none';
445
+ historySection.style.display = 'none';
446
+ loader.style.display = 'block';
447
+
448
+ try {
449
+ loaderText.innerText = "آلفا در حال نوشتن شعر و ملودی...";
450
+ const response = await fetch('/api/refine', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ idea: ideaInput.value }) });
451
+ const data = await response.json();
452
+ if (data.error) throw new Error(data.error);
453
+
454
+ const { lyrics, musicPrompt } = data;
455
+ loaderText.innerText = "در حال ضبط آهنگ در استودیو آلفا...";
456
+
457
+ const payload = [
458
+ getVal('model_select'), "custom", null, "unknown", musicPrompt, lyrics, 0, "", "", "unknown",
459
+ getNum('steps_input'), getNum('cfg_input'), true, getNum('seed_input'), null, -1,
460
+ getNum('batch_size'), null, null, 0, -1, "Fill the audio semantic mask based on the given conditions:",
461
+ 1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
462
+ getChk('think_checkbox'), 2, 0, 0.9, "NO USER INPUT", true, true, true, null, false, true, false, false, 0.5, 8, null, [], false, null, null, null, null
463
+ ];
464
+
465
+ const session_hash = Math.random().toString(36).substring(2);
466
+ 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 }) });
467
+ if (!joinResp.ok) throw new Error('خطا در اتصال به سرور موزیک');
468
+
469
+ const eventSource = new EventSource(`${ACE_SPACE_URL}gradio_api/queue/data?session_hash=${session_hash}`);
470
+ eventSource.onmessage = (event) => {
471
+ const msg = JSON.parse(event.data);
472
+ if (msg.msg === 'process_starts') loaderText.innerText = "هوش مصنوعی در حال خواندن...";
473
+ else if (msg.msg === 'process_completed') {
474
+ eventSource.close();
475
+ loader.style.display = 'none';
476
+ handleAudioOutput(msg.output, lyrics, ideaInput.value);
477
+ }
478
+ };
479
+ eventSource.onerror = () => { throw new Error('ارتباط با سرور قطع شد.'); };
480
+
481
+ } catch (e) {
482
+ alert("خطا: " + e.message);
483
+ loader.style.display = 'none';
484
+ step1.style.display = 'block';
485
+ historySection.style.display = 'block';
486
+ processBtn.disabled = false;
487
+ }
488
+ });
489
+
490
+ function handleAudioOutput(data, lyrics, idea) {
491
+ const processedUrls = new Set();
492
+ let hasResult = false;
493
+
494
+ function addAudio(url) {
495
+ const fullUrl = url.startsWith('http') ? url : ACE_SPACE_URL.replace(/\/$/, '') + url;
496
+ if (processedUrls.has(fullUrl)) return;
497
+ processedUrls.add(fullUrl);
498
+ hasResult = true;
499
+
500
+ if (processedUrls.size === 1) {
501
+ mainDownloadLink.href = fullUrl;
502
+ mainDownloadLink.style.display = 'inline-block';
503
+ saveToHistory(idea, lyrics, fullUrl);
504
+ }
505
+ playerWrapper.innerHTML += `<div class="audio-item"><audio controls autoplay src="${fullUrl}"></audio></div>`;
506
+ }
507
+
508
+ function traverse(obj) {
509
+ if (typeof obj === 'string' && obj.includes('/file=') && obj.endsWith('.mp3')) addAudio(obj);
510
+ else if (obj && typeof obj === 'object') {
511
+ if (obj.url && obj.url.endsWith('.mp3')) addAudio(obj.url);
512
+ Object.values(obj).forEach(traverse);
513
+ }
514
+ }
515
+ traverse(data);
516
+
517
+ if (hasResult) {
518
+ const headerText = document.getElementById('resultHeaderText');
519
+ 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>آهنگ جدید آماده شد`;
520
+ headerText.style.color = 'var(--success-color)';
521
+ finalResult.style.display = 'block';
522
+ finalLyricsBox.innerHTML = formatLyrics(lyrics);
523
+ window.scrollTo({ top: 0, behavior: 'smooth' });
524
+ } else {
525
+ alert("فایل صوتی یافت نشد!");
526
+ step1.style.display = 'block';
527
+ historySection.style.display = 'block';
528
+ }
529
+ }
530
+
531
  function formatLyrics(text) { return text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>'); }
532
 
533
  const canvas = document.getElementById('music-canvas');
 
550
  requestAnimationFrame(anim);
551
  }
552
  anim();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
  </script>
554
  </body>
555
  </html>