Opera8 commited on
Commit
9a53dab
·
verified ·
1 Parent(s): 573d3ae

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +320 -264
index.html CHANGED
@@ -2,9 +2,9 @@
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">
6
- <title>استودیو هوشمند ACE-Step</title>
7
- <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700;800&display=swap" rel="stylesheet">
8
  <style>
9
  :root {
10
  --app-font: 'Vazirmatn', sans-serif;
@@ -15,335 +15,391 @@
15
  --input-border: #E1E7EF;
16
  --text-primary: #1A202C;
17
  --text-secondary: #626F86;
18
- --text-tertiary: #8A94A6;
19
  --accent-primary: #4A6CFA;
20
- --accent-secondary: #0FD4A8;
21
  --success-color: #38A169;
22
- --danger-color: #e53e3e;
23
- --radius-card: 24px;
24
  --radius-btn: 14px;
25
- --radius-input: 12px;
26
- --transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
27
- --accent-primary-glow: rgba(74, 108, 250, 0.25);
28
  }
29
 
30
- @keyframes fadeIn { from { opacity: 0; transform: translateY(15px); } to { opacity: 1; transform: translateY(0); } }
31
- @keyframes pulse-loader { 0% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } 50% { box-shadow: 0 0 60px rgba(56, 189, 248, 0.7); } 100% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } }
32
 
33
- body { font-family: var(--app-font); background-color: var(--app-bg); color: var(--text-primary); margin: 0; padding: 2.5rem 1rem; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; }
34
- .container { max-width: 820px; width: 100%; }
35
-
36
- header { position: relative; text-align: center; margin-bottom: 2.5rem; padding: 2rem 0; animation: fadeIn 0.8s 0.1s ease-out backwards; overflow: hidden; }
37
- #music-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; opacity: 0.6; }
38
- .header-content { position: relative; z-index: 2; }
39
-
40
- .ai-synapse-container { width: 100px; height: 100px; margin: 0 auto 1.5rem; position: relative; display: flex; align-items: center; justify-content: center; background: radial-gradient(circle, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)); border-radius: 50%; box-shadow: 0 0 30px var(--accent-primary-glow); }
41
- .music-icon { width: 60px; height: 60px; color: var(--accent-primary); filter: drop-shadow(0 0 5px var(--accent-primary)); }
42
-
43
- h1 { font-size: 2.8rem; font-weight: 800; margin: 0; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -1px; }
44
- .subtitle { font-size: 1.1rem; color: var(--text-secondary); margin-top: 0.5rem; }
45
-
46
- main { padding: 3rem; background-color: var(--panel-bg); border-radius: var(--radius-card); box-shadow: 0 20px 25px -5px rgba(26, 32, 44, 0.07); border: 1px solid var(--panel-border); animation: fadeIn 0.8s 0.3s ease-out backwards; }
47
 
48
- .form-group { margin-bottom: 2.5rem; }
49
- .form-label { display: flex; align-items: center; gap: 0.75rem; font-weight: 700; color: var(--text-primary); font-size: 1.2em; margin-bottom: 1.2rem; }
50
- .form-label svg { width: 24px; height: 24px; color: var(--accent-primary); }
 
 
 
 
 
 
51
 
52
- .text-input-container { background-color: var(--input-bg); border: 2px solid var(--input-border); border-radius: var(--radius-input); padding: 1rem; transition: var(--transition-smooth); }
53
- .text-input-container:focus-within { border-color: var(--accent-primary); background-color: #fff; box-shadow: 0 0 15px var(--accent-primary-glow); }
 
 
 
 
 
54
 
55
- textarea, input, select { width: 100%; border: none; background: transparent; font-family: var(--app-font); font-size: 1rem; color: var(--text-primary); outline: none; resize: vertical; }
56
- textarea { min-height: 100px; line-height: 1.6; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- .btn-magic { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 12px 24px; border-radius: var(--radius-btn); cursor: pointer; font-family: var(--app-font); font-weight: 600; display: flex; align-items: center; gap: 8px; width: 100%; justify-content: center; margin-top: 10px; transition: transform 0.2s; }
59
- .btn-magic:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(118, 75, 162, 0.3); }
60
- .btn-magic:disabled { opacity: 0.7; cursor: wait; }
 
 
61
 
62
- #generateButton { display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; padding: 1.1rem; font-size: 1.2rem; font-weight: 700; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 12px -3px var(--accent-primary-glow); margin-top: 2.5rem; }
63
- #generateButton:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); }
64
- #generateButton:disabled { background: var(--text-tertiary); cursor: not-allowed; }
 
 
 
 
 
 
65
 
66
- /* Result Area */
67
- #result-container { margin-top: 2rem; display: none; flex-direction: column; align-items: center; }
68
- #result-container.active { display: flex; animation: fadeIn 0.5s; }
69
-
70
- .status-message { width: 100%; padding: 0.8rem 1rem; margin-bottom: 0.5rem; border-radius: var(--radius-input); font-size: 0.95rem; background: var(--input-bg); border: 1px solid var(--input-border); color: var(--text-secondary); text-align: center; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- /* Loader */
73
- #aiLoader { width: 100%; display: none; justify-content: center; margin-bottom: 2rem; }
74
- .generator-container { position: relative; width: 100%; max-width: 400px; height: 120px; border: 2px solid #38bdf8; border-radius: 20px; overflow: hidden; box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); animation: pulse-loader 2s infinite ease-in-out; background-color: #161b22; color: #f0f6fc; display: flex; align-items: center; justify-content: center; }
75
- .waveform-visualizer { display: flex; align-items: center; gap: 4px; height: 60px; }
76
- .bar { width: 6px; background: var(--accent-secondary); border-radius: 3px; animation: waveform-bounce 1s infinite ease-in-out; }
77
- .bar:nth-child(odd) { background: var(--accent-primary); animation-duration: 0.8s; }
78
- .bar:nth-child(2) { animation-delay: 0.1s; } .bar:nth-child(3) { animation-delay: 0.2s; } .bar:nth-child(4) { animation-delay: 0.3s; }
79
- @keyframes waveform-bounce { 0%, 100% { height: 10px; } 50% { height: 50px; } }
80
-
81
- .audio-card { width: 100%; background: #fff; border: 1px solid var(--input-border); border-radius: var(--radius-input); padding: 1.5rem; box-shadow: 0 4px 6px rgba(0,0,0,0.05); margin-top: 1rem; border-right: 5px solid var(--success-color); }
82
- audio { width: 100%; margin-top: 10px; border-radius: 30px; }
83
 
84
- /* Grid for settings */
85
- .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
86
- details { background: var(--input-bg); border-radius: var(--radius-input); padding: 0.5rem; margin-bottom: 2rem; }
87
- summary { cursor: pointer; padding: 0.5rem; font-weight: 600; color: var(--text-secondary); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  </style>
89
  </head>
90
  <body>
91
- <div class="container">
 
 
92
  <header>
93
- <canvas id="music-canvas"></canvas>
94
- <div class="header-content">
95
- <div class="ai-synapse-container">
96
- <svg class="music-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
97
- <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/>
98
- </svg>
99
- </div>
100
- <h1>استودیو هوشمند ACE-Step</h1>
101
- <p class="subtitle">تبدیل ایده به موسیقی با هوش مصنوعی جمینای</p>
102
- </div>
103
  </header>
104
- <main>
105
- <!-- Step 1: Idea Input -->
106
- <div class="form-group">
107
  <div class="form-label">
108
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
109
- ۱. ایده اولیه آهنگ
110
  </div>
111
- <div class="text-input-container">
112
- <textarea id="ideaInput" placeholder="مثال: یک آهنگ شاد تولد برای پسرم آرش به سبک پاپ ایرانی..."></textarea>
 
 
 
 
 
 
 
 
 
113
  </div>
114
- <button id="refineBtn" class="btn-magic">
115
- <svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.384-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" /></svg>
116
- پردازش متن توسط هوش مصنوعی
 
 
 
 
 
 
 
 
 
 
117
  </button>
118
  </div>
119
 
120
- <!-- Step 2: Generated Prompt & Lyrics -->
121
- <div class="form-group">
122
- <div class="form-label">
123
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3" /></svg>
124
- ۲. پرامپت و شعر (تولید شده)
125
- </div>
126
- <div class="grid-2">
127
- <div class="text-input-container">
128
- <label style="font-size:0.8rem; color:var(--text-tertiary);">توصیف انگلیسی آهنگ</label>
129
- <textarea id="captions" rows="4"></textarea>
130
- </div>
131
- <div class="text-input-container">
132
- <label style="font-size:0.8rem; color:var(--text-tertiary);">متن شعر (با اعراب)</label>
133
- <textarea id="lyrics" rows="4" dir="rtl"></textarea>
134
- </div>
135
  </div>
136
- </div>
137
-
138
- <!-- Hidden Configs -->
139
- <details>
140
- <summary>تنظیمات پیشرفته (مدل و زمان)</summary>
141
- <div class="grid-2" style="margin-top:10px;">
142
- <select id="config_path" class="text-input-container">
143
- <option value="acestep-v15-turbo" selected>Turbo Model</option>
144
- <option value="acestep-v15-turbo-shift3">Shift3 Model</option>
145
- </select>
146
- <input type="number" id="audio_duration" value="30" class="text-input-container" placeholder="زمان (ثانیه)">
147
- <input type="number" id="inference_steps" value="8" class="text-input-container" placeholder="Steps">
148
- <input type="text" id="seed" value="-1" class="text-input-container" placeholder="Seed">
149
  </div>
150
- </details>
151
-
152
- <!-- Step 3: Generate Audio -->
153
- <button id="generateButton">
154
- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
155
- <span>ساخت نهایی آهنگ</span>
156
- </button>
157
-
158
- <div id="result-container">
159
- <div id="statusMessages"></div>
160
- <div id="aiLoader">
161
- <div class="generator-container">
162
- <div class="waveform-visualizer">
163
- <div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
164
- </div>
165
- <div style="margin-right:15px; font-weight:bold;">در حال آهنگسازی...</div>
166
- </div>
167
- </div>
168
- <div id="outputArea" style="width: 100%;"></div>
169
- </div>
170
- </main>
171
- </div>
172
-
173
- <script>
174
- (function() {
175
- // --- 1. Logic for Gemini (Refine Text) ---
176
- const refineBtn = document.getElementById('refineBtn');
177
- const ideaInput = document.getElementById('ideaInput');
178
- const captionsBox = document.getElementById('captions');
179
- const lyricsBox = document.getElementById('lyrics');
180
 
181
- refineBtn.addEventListener('click', async () => {
182
- const idea = ideaInput.value;
183
- if(!idea) { alert('لطفا ایده خود را بنویسید'); return; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
- refineBtn.disabled = true;
186
- refineBtn.innerHTML = 'در حال تفکر...';
 
187
 
 
 
 
 
 
 
188
  try {
189
- const res = await fetch('/api/refine', {
190
  method: 'POST',
191
  headers: {'Content-Type': 'application/json'},
192
- body: JSON.stringify({ idea: idea })
193
  });
194
- const data = await res.json();
195
 
196
- if(data.error) throw new Error(data.error);
 
197
 
198
- captionsBox.value = data.music_prompt;
199
- lyricsBox.value = data.lyrics;
200
-
201
- // Animation effect
202
- captionsBox.style.backgroundColor = '#e6fffa';
203
- setTimeout(() => captionsBox.style.backgroundColor = 'transparent', 1000);
204
-
205
- } catch(e) {
206
- alert('خطا در ارتباط با هوش مصنوعی: ' + e.message);
207
- } finally {
208
- refineBtn.disabled = false;
209
- refineBtn.innerHTML = `
210
- <svg width="20" height="20" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.384-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" /></svg>
211
- پردازش متن توسط هوش مصنوعی`;
212
- }
213
- });
214
-
215
- // --- 2. Logic for Music Generation (ACE-Step) ---
216
- const SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
217
- const GENERATE_FN_INDEX = 77;
218
- let processedUrls = new Set();
219
-
220
- const generateBtn = document.getElementById('generateButton');
221
- const statusDiv = document.getElementById('statusMessages');
222
- const loader = document.getElementById('aiLoader');
223
- const outputArea = document.getElementById('outputArea');
224
- const resultContainer = document.getElementById('result-container');
225
-
226
- const val = (id) => document.getElementById(id).value;
227
- const num = (id) => Number(document.getElementById(id).value);
228
-
229
- function setStatus(msg) {
230
- resultContainer.classList.add('active');
231
- statusDiv.innerHTML = `<div class="status-message">${msg}</div>`;
232
- }
233
 
234
- function addAudio(url) {
235
- if (processedUrls.has(url)) return;
236
- processedUrls.add(url);
237
- const fullUrl = url.startsWith('http') ? url : SPACE_URL.replace(/\/$/, '') + url;
238
-
239
- const card = document.createElement('div');
240
- card.className = 'audio-card';
241
- card.innerHTML = `
242
- <div style="display:flex; justify-content:space-between; align-items:center;">
243
- <strong>🎵 آهنگ نهایی شما</strong>
244
- <a href="${fullUrl}" target="_blank" style="color:#4A6CFA;text-decoration:none;">دانلود</a>
245
- </div>
246
- <audio controls src="${fullUrl}"></audio>
247
- `;
248
- outputArea.prepend(card);
249
- }
250
-
251
- function scanForAudios(data) {
252
- function traverse(obj) {
253
- if (typeof obj === 'string' && obj.includes('/file=') && obj.endsWith('.mp3')) {
254
- addAudio(obj);
255
- } else if (obj && typeof obj === 'object') {
256
- if (obj.url && obj.url.endsWith('.mp3')) addAudio(obj.url);
257
- Object.values(obj).forEach(traverse);
258
- }
259
- }
260
- traverse(data);
261
- }
262
 
263
- generateBtn.addEventListener('click', async () => {
264
- if(!val('captions') || !val('lyrics')) {
265
- alert('لطفا ابتدا متن و پرامپت را بسازید یا وارد کنید.');
266
- return;
 
267
  }
 
268
 
269
- generateBtn.disabled = true;
270
- generateBtn.innerHTML = 'در حال ارسال به سرور...';
271
- loader.style.display = 'flex';
272
- statusDiv.innerHTML = '';
273
- outputArea.innerHTML = '';
274
- processedUrls.clear();
275
 
276
  try {
277
- // Payload structure for ACE-Step
 
 
278
  const payload = [
279
- val('config_path'), "custom", null, "unknown",
280
- val('captions'), val('lyrics'),
281
- 0, "", "", "unknown", num('inference_steps'), 7, true,
282
- val('seed'), null, -1, 1, null, null, 0, -1,
 
 
283
  "Fill the audio semantic mask based on the given conditions:",
284
  1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
285
  true, 2, 0, 0.9, "NO USER INPUT", true, true, true, null, false, true, false, false, 0.5, 8, null, [], false, null, null, null, null
286
  ];
287
 
288
  const session_hash = Math.random().toString(36).substring(2);
289
- setStatus('در حال اتصال به صف پردازش...');
290
 
291
- const joinResp = await fetch(`${SPACE_URL}gradio_api/queue/join`, {
292
  method: 'POST',
293
  headers: { 'Content-Type': 'application/json' },
294
- body: JSON.stringify({ data: payload, fn_index: GENERATE_FN_INDEX, session_hash })
295
  });
296
 
297
- if (!joinResp.ok) throw new Error('خطا در اتصال به صف');
 
 
298
 
299
- const eventSource = new EventSource(`${SPACE_URL}gradio_api/queue/data?session_hash=${session_hash}`);
300
-
301
  eventSource.onmessage = (event) => {
302
  const msg = JSON.parse(event.data);
303
- if (msg.msg === 'estimation') setStatus(`در صف انتظار: نفر ${msg.rank + 1}`);
304
- else if (msg.msg === 'process_starts') setStatus('موتور هوش مصنوعی شروع به کار کرد...');
305
- else if (msg.msg === 'process_generating' || msg.msg === 'process_completed') {
306
- scanForAudios(msg.output);
307
- if (msg.msg === 'process_completed') {
308
- eventSource.close();
309
- loader.style.display = 'none';
310
- generateBtn.disabled = false;
311
- generateBtn.innerHTML = 'ساخت مجدد';
312
- setStatus('پایان پردازش.');
313
- }
314
  }
315
  };
 
 
 
 
316
 
317
  } catch (e) {
318
- setStatus('خطا: ' + e.message);
319
  loader.style.display = 'none';
320
- generateBtn.disabled = false;
321
- generateBtn.innerHTML = 'تلاش مجدد';
322
  }
323
  });
324
 
325
- // Canvas Animation
326
- const canvas = document.getElementById('music-canvas');
327
- if(canvas) {
328
- const ctx = canvas.getContext('2d');
329
- let time = 0;
330
- function resize() { canvas.width = canvas.parentElement.clientWidth; canvas.height = canvas.parentElement.clientHeight; }
331
- window.addEventListener('resize', resize); resize();
332
- function animate() {
333
- ctx.clearRect(0, 0, canvas.width, canvas.height);
334
- ctx.beginPath();
335
- for (let x = 0; x < canvas.width; x++) {
336
- const y = canvas.height / 2 + Math.sin(x * 0.01 + time) * 30;
337
- ctx.lineTo(x, y);
338
  }
339
- ctx.strokeStyle = 'rgba(74, 108, 250, 0.2)';
340
- ctx.stroke();
341
- time += 0.05;
342
- requestAnimationFrame(animate);
343
  }
344
- animate();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  }
346
- })();
347
  </script>
348
  </body>
349
  </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>استودیو موزیک ACE-Step</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;
 
15
  --input-border: #E1E7EF;
16
  --text-primary: #1A202C;
17
  --text-secondary: #626F86;
 
18
  --accent-primary: #4A6CFA;
19
+ --accent-glow: rgba(74, 108, 250, 0.2);
20
  --success-color: #38A169;
21
+ --radius-card: 20px;
 
22
  --radius-btn: 14px;
 
 
 
23
  }
24
 
25
+ * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
 
26
 
27
+ body {
28
+ font-family: var(--app-font);
29
+ background-color: var(--app-bg);
30
+ color: var(--text-primary);
31
+ margin: 0;
32
+ padding: 20px 15px;
33
+ min-height: 100vh;
34
+ display: flex;
35
+ flex-direction: column;
36
+ align-items: center;
37
+ }
38
+
39
+ .container { max-width: 650px; width: 100%; z-index: 2; position: relative; }
 
40
 
41
+ /* انیمیشن پس‌زمینه هدر */
42
+ #music-canvas {
43
+ position: fixed; top: 0; left: 0; width: 100%; height: 400px;
44
+ z-index: 0; opacity: 0.5; pointer-events: none;
45
+ mask-image: linear-gradient(to bottom, black, transparent);
46
+ -webkit-mask-image: linear-gradient(to bottom, black, transparent);
47
+ }
48
+
49
+ header { text-align: center; margin-bottom: 2rem; position: relative; }
50
 
51
+ .logo-box {
52
+ width: 80px; height: 80px; margin: 0 auto 15px;
53
+ background: #fff; border-radius: 50%;
54
+ display: flex; align-items: center; justify-content: center;
55
+ box-shadow: 0 10px 25px var(--accent-glow);
56
+ color: var(--accent-primary);
57
+ }
58
 
59
+ h1 {
60
+ font-size: 1.8rem; font-weight: 800; margin: 0;
61
+ background: linear-gradient(90deg, #2d3748, #4A6CFA);
62
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
63
+ }
64
+ .subtitle { font-size: 0.9rem; color: var(--text-secondary); margin-top: 5px; }
65
+
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
+ transition: transform 0.3s ease;
74
+ }
75
 
76
+ .form-label {
77
+ display: flex; align-items: center; gap: 8px;
78
+ font-weight: 700; margin-bottom: 12px; color: #2d3748;
79
+ }
80
+ .form-label svg { color: var(--accent-primary); width: 20px; }
81
 
82
+ textarea {
83
+ width: 100%; background: var(--input-bg);
84
+ border: 2px solid var(--input-border);
85
+ border-radius: 12px; padding: 15px;
86
+ font-family: inherit; font-size: 1rem; color: #2d3748;
87
+ min-height: 120px; resize: vertical; outline: none;
88
+ transition: border-color 0.3s;
89
+ }
90
+ textarea:focus { border-color: var(--accent-primary); background: #fff; }
91
 
92
+ /* دکمه‌ها */
93
+ .btn-main {
94
+ width: 100%; padding: 16px;
95
+ background: linear-gradient(135deg, var(--accent-primary), #3b5bdb);
96
+ color: #fff; border: none; border-radius: var(--radius-btn);
97
+ font-size: 1.1rem; font-weight: 700; cursor: pointer;
98
+ display: flex; justify-content: center; align-items: center; gap: 10px;
99
+ box-shadow: 0 5px 15px var(--accent-glow);
100
+ transition: transform 0.2s;
101
+ }
102
+ .btn-main:active { transform: scale(0.98); }
103
+ .btn-main:disabled { opacity: 0.7; cursor: not-allowed; filter: grayscale(1); }
104
+
105
+ .btn-outline {
106
+ background: transparent; border: 2px solid var(--input-border);
107
+ color: var(--text-secondary); margin-top: 10px;
108
+ }
109
+ .btn-outline:hover { border-color: var(--accent-primary); color: var(--accent-primary); }
110
+
111
+ /* بخش نتیجه نهایی */
112
+ #finalResult { display: none; animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1); }
113
 
114
+ .player-header {
115
+ display: flex; align-items: center; justify-content: space-between;
116
+ margin-bottom: 15px; padding-bottom: 15px;
117
+ border-bottom: 1px solid var(--panel-border);
118
+ }
 
 
 
 
 
 
119
 
120
+ audio { width: 100%; height: 40px; border-radius: 20px; }
121
+
122
+ .lyrics-container {
123
+ background: var(--input-bg);
124
+ border-radius: 16px;
125
+ padding: 20px;
126
+ max-height: 350px;
127
+ overflow-y: auto;
128
+ white-space: pre-wrap;
129
+ line-height: 2;
130
+ font-size: 1rem;
131
+ color: #4a5568;
132
+ text-align: center;
133
+ border: 1px solid var(--input-border);
134
+ margin-top: 15px;
135
+ }
136
+
137
+ /* استایل تگ‌های شعر */
138
+ .lyrics-tag {
139
+ color: var(--accent-primary);
140
+ font-weight: 800;
141
+ display: block;
142
+ margin-top: 20px;
143
+ margin-bottom: 5px;
144
+ font-size: 0.9em;
145
+ letter-spacing: 1px;
146
+ text-transform: uppercase;
147
+ }
148
+
149
+ /* لودر */
150
+ #loader { display: none; text-align: center; padding: 20px; }
151
+ .wave-bars { display: flex; justify-content: center; gap: 4px; height: 30px; align-items: flex-end; }
152
+ .bar { width: 5px; background: var(--accent-primary); animation: jump 1s infinite; border-radius: 2px; }
153
+ .bar:nth-child(2) { animation-delay: 0.1s; height: 60%; }
154
+ .bar:nth-child(3) { animation-delay: 0.2s; height: 80%; }
155
+ .bar:nth-child(4) { animation-delay: 0.3s; height: 50%; }
156
+ @keyframes jump { 0%, 100% { height: 20%; } 50% { height: 100%; } }
157
+
158
+ @keyframes slideUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
159
+
160
+ /* مخفی کردن المان‌های تکنیکال */
161
+ .hidden { display: none; }
162
  </style>
163
  </head>
164
  <body>
165
+ <canvas id="music-canvas"></canvas>
166
+
167
+ <div class="container">
168
  <header>
169
+ <div class="logo-box">
170
+ <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>
171
+ </div>
172
+ <h1>استودیو موزیک ACE</h1>
173
+ <p class="subtitle">ساخت آهنگ کامل با هوش مصنوعی</p>
 
 
 
 
 
174
  </header>
175
+
176
+ <!-- مرحله ۱: ایده -->
177
+ <div id="step1" class="card">
178
  <div class="form-label">
179
+ <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>
180
+ موضوع آهنگ چیه؟
181
  </div>
182
+ <textarea id="ideaInput" placeholder="مثال: آهنگ تولد برای سمیرا، شاد و پرانرژی..."></textarea>
183
+ <button id="processBtn" class="btn-main" style="margin-top: 15px;">
184
+ <span>نوشتن متن و آهنگسازی</span>
185
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"></polyline></svg>
186
+ </button>
187
+ </div>
188
+
189
+ <!-- لودر -->
190
+ <div id="loader">
191
+ <div class="wave-bars">
192
+ <div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div><div class="bar"></div>
193
  </div>
194
+ <p style="color: #718096; font-size: 0.9rem; margin-top: 10px;" id="loaderText">در حال پردازش...</p>
195
+ </div>
196
+
197
+ <!-- مرحله ۲: نمایش متن و ساخت آهنگ (مخفی) -->
198
+ <!-- ما این مرحله را اتوماتیک رد می‌کنیم یا به کاربر اجازه ویرایش می‌دهیم -->
199
+ <div id="step2" class="card hidden">
200
+ <div class="form-label">متن آماده شده (قابل ویرایش)</div>
201
+ <textarea id="generatedLyrics" style="height: 200px;"></textarea>
202
+ <textarea id="generatedPrompt" class="hidden"></textarea> <!-- پرامپت انگلیسی مخفی -->
203
+
204
+ <button id="generateAudioBtn" class="btn-main" style="margin-top: 15px;">
205
+ <span>ساخت نهایی آهنگ</span>
206
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
207
  </button>
208
  </div>
209
 
210
+ <!-- مرحله ۳: نتیجه نهایی -->
211
+ <div id="finalResult" class="card">
212
+ <div class="player-header">
213
+ <div style="font-weight: 700; color: var(--success-color); display: flex; align-items: center; gap: 5px;">
214
+ <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>
215
+ آهنگ آماده شد
216
+ </div>
217
+ <a id="downloadLink" href="#" style="color: var(--accent-primary); text-decoration: none; font-size: 0.9rem; font-weight: 600;">دانلود</a>
 
 
 
 
 
 
 
218
  </div>
219
+
220
+ <div id="playerWrapper"></div>
221
+
222
+ <div class="form-label" style="margin-top: 20px; justify-content: center; color: #718096;">متن آهنگ</div>
223
+ <div class="lyrics-container" id="finalLyricsBox">
224
+ <!-- متن شعر اینجا قرار میگیره -->
 
 
 
 
 
 
 
225
  </div>
226
+
227
+ <button onclick="location.reload()" class="btn-main btn-outline">ساخت آهنگ جدید</button>
228
+ </div>
229
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
+ <script>
232
+ const ACE_SPACE_URL = "https://ace-step-ace-step-v1-5.hf.space/";
233
+
234
+ // المان‌ها
235
+ const ideaInput = document.getElementById('ideaInput');
236
+ const processBtn = document.getElementById('processBtn');
237
+ const step1 = document.getElementById('step1');
238
+ const step2 = document.getElementById('step2');
239
+ const loader = document.getElementById('loader');
240
+ const loaderText = document.getElementById('loaderText');
241
+ const generatedLyrics = document.getElementById('generatedLyrics');
242
+ const generatedPrompt = document.getElementById('generatedPrompt');
243
+ const generateAudioBtn = document.getElementById('generateAudioBtn');
244
+ const finalResult = document.getElementById('finalResult');
245
+ const playerWrapper = document.getElementById('playerWrapper');
246
+ const finalLyricsBox = document.getElementById('finalLyricsBox');
247
+ const downloadLink = document.getElementById('downloadLink');
248
 
249
+ // مرحله ۱: تولید متن با جمینای
250
+ processBtn.addEventListener('click', async () => {
251
+ if (!ideaInput.value.trim()) return alert("لطفا موضوع آهنگ را بنویسید");
252
 
253
+ // انیمیشن UI
254
+ processBtn.disabled = true;
255
+ loader.style.display = 'block';
256
+ loaderText.innerText = "هوش مصنوعی در حال سرودن شعر و تنظیم آهنگ...";
257
+ step1.style.display = 'none';
258
+
259
  try {
260
+ const response = await fetch('/api/refine', {
261
  method: 'POST',
262
  headers: {'Content-Type': 'application/json'},
263
+ body: JSON.stringify({ idea: ideaInput.value })
264
  });
 
265
 
266
+ const data = await response.json();
267
+ if (data.error) throw new Error(data.error);
268
 
269
+ generatedLyrics.value = data.lyrics;
270
+ generatedPrompt.value = data.music_prompt;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
+ // نمایش مرحله ۲ (ویرایش متن)
273
+ loader.style.display = 'none';
274
+ step2.style.display = 'block';
275
+ step2.classList.remove('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
+ } catch (e) {
278
+ alert("خطا: " + e.message);
279
+ step1.style.display = 'block';
280
+ loader.style.display = 'none';
281
+ processBtn.disabled = false;
282
  }
283
+ });
284
 
285
+ // مرحله ۲: ساخت آهنگ با ACE-Step
286
+ generateAudioBtn.addEventListener('click', async () => {
287
+ generateAudioBtn.disabled = true;
288
+ step2.style.display = 'none';
289
+ loader.style.display = 'block';
290
+ loaderText.innerText = "در حال ضبط استودیویی (ممکن است ۱ تا ۲ دقیقه طول بکشد)...";
291
 
292
  try {
293
+ // فرمت کردن متن شعر برای نمایش زیباتر (هایلایت کردن تگ ها)
294
+ formatLyricsForDisplay(generatedLyrics.value);
295
+
296
  const payload = [
297
+ "acestep-v15-turbo", "custom", null, "unknown",
298
+ generatedPrompt.value, // Prompt English
299
+ generatedLyrics.value, // Lyrics Persian
300
+ 0, "", "", "unknown",
301
+ 8, // Steps
302
+ 7, true, -1, null, -1, 1, null, null, 0, -1,
303
  "Fill the audio semantic mask based on the given conditions:",
304
  1, "text2music", false, 0, 1, 3, "ode", "", "mp3", 0.85,
305
  true, 2, 0, 0.9, "NO USER INPUT", true, true, true, null, false, true, false, false, 0.5, 8, null, [], false, null, null, null, null
306
  ];
307
 
308
  const session_hash = Math.random().toString(36).substring(2);
 
309
 
310
+ const joinResp = await fetch(`${ACE_SPACE_URL}gradio_api/queue/join`, {
311
  method: 'POST',
312
  headers: { 'Content-Type': 'application/json' },
313
+ body: JSON.stringify({ data: payload, fn_index: 77, session_hash })
314
  });
315
 
316
+ if (!joinResp.ok) throw new Error('خطا در اتصال به سرور موزیک');
317
+
318
+ const eventSource = new EventSource(`${ACE_SPACE_URL}gradio_api/queue/data?session_hash=${session_hash}`);
319
 
 
 
320
  eventSource.onmessage = (event) => {
321
  const msg = JSON.parse(event.data);
322
+ if (msg.msg === 'process_starts') {
323
+ loaderText.innerText = واننده شروع به خواندن کرد...";
324
+ } else if (msg.msg === 'process_completed') {
325
+ eventSource.close();
326
+ loader.style.display = 'none';
327
+ handleAudioOutput(msg.output);
 
 
 
 
 
328
  }
329
  };
330
+
331
+ eventSource.onerror = () => {
332
+ throw new Error('قطع ارتباط با سرور.');
333
+ };
334
 
335
  } catch (e) {
336
+ alert(e.message);
337
  loader.style.display = 'none';
338
+ step2.style.display = 'block';
339
+ generateAudioBtn.disabled = false;
340
  }
341
  });
342
 
343
+ function handleAudioOutput(data) {
344
+ let audioUrl = null;
345
+ function traverse(obj) {
346
+ if (typeof obj === 'string' && obj.includes('/file=') && obj.endsWith('.mp3')) {
347
+ audioUrl = obj;
348
+ } else if (obj && typeof obj === 'object') {
349
+ if (obj.url && obj.url.endsWith('.mp3')) audioUrl = obj.url;
350
+ Object.values(obj).forEach(traverse);
 
 
 
 
 
351
  }
 
 
 
 
352
  }
353
+ traverse(data);
354
+
355
+ if (audioUrl) {
356
+ const fullUrl = audioUrl.startsWith('http') ? audioUrl : ACE_SPACE_URL.replace(/\/$/, '') + audioUrl;
357
+
358
+ playerWrapper.innerHTML = `<audio controls autoplay src="${fullUrl}"></audio>`;
359
+ downloadLink.href = fullUrl;
360
+
361
+ finalResult.style.display = 'block';
362
+ // اسکرول نرم به بالا
363
+ window.scrollTo({ top: 0, behavior: 'smooth' });
364
+ } else {
365
+ alert("فایل صوتی یافت نشد!");
366
+ step2.style.display = 'block';
367
+ }
368
+ }
369
+
370
+ function formatLyricsForDisplay(text) {
371
+ // این تابع تگ‌های [Intro] و غیره را پیدا کرده و استایل می‌دهد
372
+ let formatted = text.replace(/\[(.*?)\]/g, '<span class="lyrics-tag">[$1]</span>');
373
+ finalLyricsBox.innerHTML = formatted;
374
+ }
375
+
376
+ // انیمیشن ساده پس زمینه
377
+ const canvas = document.getElementById('music-canvas');
378
+ const ctx = canvas.getContext('2d');
379
+ let t = 0;
380
+
381
+ function resize() {
382
+ canvas.width = window.innerWidth;
383
+ canvas.height = 400;
384
+ }
385
+ window.addEventListener('resize', resize);
386
+ resize();
387
+
388
+ function anim() {
389
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
390
+ ctx.beginPath();
391
+ ctx.strokeStyle = "rgba(74, 108, 250, 0.1)";
392
+ ctx.lineWidth = 2;
393
+
394
+ for(let i=0; i<canvas.width; i+=20) {
395
+ ctx.moveTo(i, 0);
396
+ ctx.lineTo(i, Math.sin(i * 0.01 + t) * 50 + 100);
397
+ }
398
+ ctx.stroke();
399
+ t += 0.02;
400
+ requestAnimationFrame(anim);
401
  }
402
+ anim();
403
  </script>
404
  </body>
405
  </html>