Hamed744 commited on
Commit
06f7f41
·
verified ·
1 Parent(s): 1827ca5

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +216 -202
index.html CHANGED
@@ -3,27 +3,29 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Alpha TTS Premium - تبدیل متن به صدا</title>
7
  <style>
8
- @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800;900&display=swap');
9
 
10
  :root {
11
  --app-font: 'Vazirmatn', sans-serif;
12
- --app-bg: #0D1117; /* Deep dark blue/black */
13
- --panel-bg: #161B22; /* Slightly lighter dark */
14
- --panel-border: #30363D;
15
- --text-primary: #C9D1D9; /* Light gray for text */
16
- --text-secondary: #8B949E; /* Medium gray */
17
- --accent-primary: #58A6FF; /* Bright blue accent */
18
- --accent-primary-hover: #79C0FF;
19
- --accent-secondary: #A371F7; /* Purple accent */
20
- --accent-secondary-hover: #B38FF9;
 
 
21
 
22
- --radius-card: 16px;
23
- --radius-input: 10px;
24
- --shadow-strong: 0 10px 30px -10px rgba(0,0,0,0.3);
25
- --shadow-glow-accent: 0 0 25px -5px var(--accent-primary);
26
- --shadow-glow-secondary: 0 0 25px -5px var(--accent-secondary);
27
  }
28
 
29
  body {
@@ -32,41 +34,36 @@
32
  background-color: var(--app-bg);
33
  color: var(--text-primary);
34
  font-size: 16px;
35
- line-height: 1.75;
36
  margin: 0;
37
- padding: 0;
38
  min-height: 100vh;
39
  -webkit-font-smoothing: antialiased;
40
  -moz-osx-font-smoothing: grayscale;
41
  display: flex;
42
  justify-content: center;
43
- align-items: flex-start; /* Align to top for long content */
44
- padding-top: 3rem;
45
- padding-bottom: 3rem;
46
  }
47
 
48
  .container {
49
- max-width: 700px;
50
  width: 90%;
51
  margin: 0 auto;
52
  }
53
 
54
  .app-header {
55
- padding: 1rem 0 2rem 0;
56
  text-align: center;
57
- margin-bottom: 2rem;
58
  }
59
  .app-header h1 {
60
- font-size: 3.2em;
61
  font-weight: 800;
62
  margin:0 0 0.5rem 0;
63
- background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
64
- -webkit-background-clip: text;
65
- -webkit-text-fill-color: transparent;
66
- text-shadow: 0 2px 10px rgba(0,0,0,0.2);
67
  }
68
  .app-header p {
69
- font-size: 1.1em;
70
  color: var(--text-secondary);
71
  margin-top:0;
72
  opacity: 0.9;
@@ -76,26 +73,26 @@
76
  padding: 2.5rem;
77
  background-color: var(--panel-bg);
78
  border-radius: var(--radius-card);
79
- box-shadow: 0 8px 25px rgba(0,0,0,0.2), 0 0 0 1px var(--panel-border);
80
  border: 1px solid var(--panel-border);
81
  }
82
 
83
- .form-group { margin-bottom: 2.5rem; }
84
  label {
85
  display: block;
86
  font-weight: 600;
87
  color: var(--text-primary);
88
  font-size: 1.05em;
89
- margin-bottom: 0.8rem;
90
  }
91
  textarea, input[type="text"] {
92
  width: 100%;
93
- padding: 0.9rem 1rem;
94
  border-radius: var(--radius-input);
95
  border: 1px solid var(--panel-border);
96
- background-color: #0D1117; /* Darker than panel for contrast */
97
  color: var(--text-primary);
98
- box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);
99
  font-family: var(--app-font);
100
  font-size: 1rem;
101
  box-sizing: border-box;
@@ -103,114 +100,115 @@
103
  }
104
  textarea:focus, input[type="text"]:focus {
105
  outline: none;
106
- border-color: var(--accent-primary);
107
- box-shadow: inset 0 1px 3px rgba(0,0,0,0.2), 0 0 0 3px rgba(88, 166, 255, 0.3);
108
- background-color: #10151D;
109
  }
110
- textarea { min-height: 100px; }
111
 
112
  /* --- نمایش گوینده منتخب --- */
113
  #selected-speaker-display { text-align: center; margin-top: 1rem; }
114
  #selected-speaker-card {
115
  display: inline-flex;
116
  align-items: center;
117
- background: linear-gradient(145deg, rgba(255,255,255,0.05), rgba(255,255,255,0.02));
118
  border-radius: 99px;
119
- padding: 12px 15px 12px 25px;
120
- box-shadow: 0 5px 15px rgba(0,0,0,0.2);
121
  border: 1px solid var(--panel-border);
122
  transition: all 0.3s ease;
 
123
  }
124
  #selected-speaker-card:hover {
125
- transform: translateY(-3px);
126
- box-shadow: 0 8px 20px rgba(0,0,0,0.25), var(--shadow-glow-secondary);
127
  }
128
  #selected-speaker-card img {
129
- width: 70px; height: 70px;
130
  border-radius: 50%;
131
  object-fit: cover;
132
- margin-left: 20px;
133
  border: 3px solid var(--accent-secondary);
134
- box-shadow: 0 0 15px -2px var(--accent-secondary);
135
- background-color: #333;
136
  }
137
- #selected-speaker-info h3 { margin: 0; font-size: 1.3em; font-weight: 700; color: var(--text-primary); }
138
- #selected-speaker-info p { margin: 4px 0 0; color: var(--text-secondary); font-size: 0.85em; }
 
139
  #change-speaker-btn {
140
- display: block; margin: 1rem auto 0;
141
- padding: 10px 22px;
142
  border-radius: var(--radius-input);
143
  background-color: transparent;
144
- border: 1px solid var(--accent-primary);
145
  color: var(--accent-primary);
146
  cursor: pointer;
147
- font-family: var(--app-font); font-weight: 500;
148
  transition: all 0.2s ease;
149
  }
150
  #change-speaker-btn:hover {
151
- background-color: rgba(88, 166, 255, 0.1);
152
- box-shadow: 0 0 10px -2px var(--accent-primary);
153
- transform: translateY(-1px);
 
154
  }
155
 
156
  /* --- مودال گالری گویندگان --- */
157
  #speaker-modal {
158
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
159
- background-color: rgba(13, 17, 23, 0.8);
160
- backdrop-filter: blur(10px) saturate(180%);
161
  display: none; align-items: center; justify-content: center;
162
  z-index: 1000; opacity: 0;
163
- transition: opacity 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
164
  }
165
  #speaker-modal.visible { display: flex; opacity: 1; }
166
  .modal-content {
167
  background: var(--panel-bg);
168
  padding: 2rem;
169
  border-radius: var(--radius-card);
170
- width: 90%; max-width: 650px;
171
  max-height: 85vh; overflow-y: auto;
172
- transform: scale(0.9);
173
- transition: transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1);
174
  border: 1px solid var(--panel-border);
175
- box-shadow: 0 15px 40px rgba(0,0,0,0.4);
 
176
  }
177
- #speaker-modal.visible .modal-content { transform: scale(1); }
178
  .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid var(--panel-border); }
179
- .modal-header h2 { margin: 0; font-size: 1.5em; }
180
- .close-modal-btn { background: none; border: none; font-size: 2.5rem; cursor: pointer; color: var(--text-secondary); transition: all 0.2s ease; line-height: 1; }
181
- .close-modal-btn:hover { color: var(--text-primary); transform: rotate(90deg) scale(1.1); }
182
 
183
- #speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 1.2rem; }
184
  @media (min-width: 576px) { #speaker-grid { grid-template-columns: repeat(4, 1fr); } }
185
- .speaker-card { cursor: pointer; transition: all 0.3s ease; text-align: center; position: relative;}
186
  .speaker-card .speaker-visual {
187
  border: 3px solid transparent;
188
  border-radius: var(--radius-card);
189
  overflow: hidden;
190
- box-shadow: 0 4px 10px rgba(0,0,0,0.2);
191
  position: relative;
192
- background-color: #0D1117;
193
- transition: all 0.3s ease;
194
  }
195
  .speaker-card:hover .speaker-visual {
196
  transform: translateY(-5px) scale(1.03);
197
- box-shadow: 0 8px 20px rgba(0,0,0,0.3);
198
  }
199
  .speaker-card input[type="radio"] { display: none; }
200
  .speaker-card img {
201
- width: 100%; height: 110px;
202
  object-fit: cover; display: block;
203
- background-color: #333;
204
- filter: grayscale(30%);
205
- transition: filter 0.3s ease;
206
- }
207
- .speaker-card:hover img, .speaker-card input[type="radio"]:checked + .speaker-visual img {
208
- filter: grayscale(0%);
209
  }
210
- .speaker-card .speaker-name { padding: 0.7rem 0.5rem; font-weight: 500; font-size: 0.9em; color: var(--text-secondary); }
 
211
  .speaker-card input[type="radio"]:checked + .speaker-visual {
212
  border-color: var(--accent-secondary);
213
- box-shadow: 0 0 20px -4px var(--accent-secondary);
214
  }
215
  .speaker-card input[type="radio"]:checked + .speaker-visual .speaker-name {
216
  color: var(--accent-secondary);
@@ -221,82 +219,91 @@
221
  .slider-container { display: flex; align-items: center; gap: 1.2rem; }
222
  input[type="range"] {
223
  flex-grow: 1; -webkit-appearance: none; appearance: none;
224
- width: 100%; height: 6px;
225
- background: linear-gradient(90deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
226
- border-radius: 3px; outline: none; opacity: 0.7; transition: opacity .2s;
227
  cursor: pointer;
 
 
 
 
 
 
228
  }
229
- input[type="range"]:hover { opacity: 1; }
230
  input[type="range"]::-webkit-slider-thumb {
231
  -webkit-appearance: none; appearance: none;
232
  width: 22px; height: 22px;
233
  background: #fff;
234
  border-radius: 50%; cursor: pointer;
235
  border: 3px solid var(--accent-primary);
236
- box-shadow: 0 0 8px rgba(88, 166, 255, 0.5);
237
- transition: transform 0.2s ease;
 
238
  }
239
  input[type="range"]::-webkit-slider-thumb:hover {
240
  transform: scale(1.1);
 
241
  }
242
  input[type="range"]::-moz-range-thumb {
243
  width: 22px; height: 22px;
244
  background: #fff;
245
  border-radius: 50%; cursor: pointer;
246
  border: 3px solid var(--accent-primary);
247
- box-shadow: 0 0 8px rgba(88, 166, 255, 0.5);
 
 
 
 
 
 
 
248
  }
249
  #temperature-value {
250
- font-weight: bold; background-color: rgba(255,255,255,0.05);
251
- padding: 0.3rem 0.9rem;
252
  border-radius: 8px;
253
  border: 1px solid var(--panel-border);
254
- min-width: 40px; text-align: center;
255
  color: var(--text-primary);
256
  }
257
 
258
  #generate-btn {
259
- width: 100%; padding: 1.1rem 1.5rem;
260
- font-size: 1.2em; font-weight: 700;
261
  font-family: var(--app-font);
262
- background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
263
- color: #0D1117; /* Dark text on bright button */
264
  border: none;
265
  border-radius: var(--radius-input);
266
  cursor: pointer;
267
  transition: all 0.3s ease;
268
- box-shadow: 0 4px 15px -5px var(--accent-primary), 0 4px 15px -5px var(--accent-secondary);
269
  position: relative;
270
  overflow: hidden;
271
  }
272
- #generate-btn::before {
273
- content: '';
274
- position: absolute;
275
- top: 0; left: -100%;
276
- width: 100%; height: 100%;
277
- background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
278
- transition: left 0.5s ease;
279
- }
280
- #generate-btn:hover::before {
281
- left: 100%;
282
  }
 
283
  #generate-btn:hover:not(:disabled) {
284
  transform: translateY(-3px);
285
- box-shadow: 0 6px 20px -5px var(--accent-primary), 0 6px 20px -5px var(--accent-secondary);
286
  }
287
  #generate-btn:disabled {
288
- background: var(--text-secondary);
289
- cursor: not-allowed; box-shadow: none; color: #0D1117; opacity: 0.7;
290
  transform: none;
291
  }
292
- #generate-btn:disabled::before {
293
- display: none;
294
- }
295
 
296
  #output-section {
297
  margin-top: 3rem;
298
  padding: 2.5rem;
299
- background-color: rgba(255,255,255,0.02);
300
  border-radius: var(--radius-card);
301
  min-height: 200px;
302
  display: flex; align-items: center; justify-content: center;
@@ -304,71 +311,76 @@
304
  border: 1px dashed var(--panel-border);
305
  transition: all 0.3s ease;
306
  position: relative;
307
- overflow: hidden;
308
  }
309
  #status-message { font-weight: 500; color: var(--text-secondary); text-align: center; font-size: 1.1em; }
310
- #audio-player { width: 100%; margin-top: 1rem; display: none; filter: invert(90%) hue-rotate(180deg); } /* Custom style for dark theme */
311
- #audio-player::-webkit-media-controls-panel { background-color: var(--panel-bg); }
312
  #audio-player::-webkit-media-controls-play-button { color: var(--accent-primary); }
 
 
313
 
314
 
315
- /* --- انیمیشن پردازش فوق العاده زیبا --- */
316
  #loading-animation-wrapper {
317
  display: none; /* Hidden by default */
318
  flex-direction: column;
319
  align-items: center;
320
  justify-content: center;
321
- gap: 1.5rem;
322
  width: 100%;
323
- min-height: 150px; /* Ensure it takes space */
 
324
  }
325
 
326
- .aurora-loader {
327
- width: 120px;
328
- height: 120px;
329
  position: relative;
330
- filter: drop-shadow(0 0 10px var(--accent-primary)) drop-shadow(0 0 20px var(--accent-secondary));
331
  }
332
-
333
- .aurora-loader::before,
334
- .aurora-loader::after {
335
- content: "";
336
  position: absolute;
337
- top: 50%;
338
- left: 50%;
339
- width: 100%;
340
- height: 100%;
341
  border-radius: 50%;
342
- background: radial-gradient(circle, transparent 30%, var(--accent-primary) 50%, var(--accent-secondary) 70%, transparent 90%);
343
- opacity: 0.7;
344
- transform-origin: center center;
345
- animation: aurora-spin 4s linear infinite, aurora-pulse 3s ease-in-out infinite alternate;
 
346
  }
347
 
348
- .aurora-loader::after {
349
- width: 80%;
350
- height: 80%;
351
- background: radial-gradient(circle, transparent 20%, var(--accent-secondary) 40%, var(--accent-primary) 60%, transparent 80%);
352
- animation-delay: -2s; /* Offset animation */
353
- opacity: 0.5;
 
 
 
 
 
 
 
354
  }
355
 
356
- @keyframes aurora-spin {
357
- 0% { transform: translate(-50%, -50%) rotate(0deg) scale(0.8); }
358
- 50% { transform: translate(-50%, -50%) rotate(180deg) scale(1); }
359
- 100% { transform: translate(-50%, -50%) rotate(360deg) scale(0.8); }
360
  }
361
- @keyframes aurora-pulse {
362
- 0% { filter: brightness(0.8) blur(2px); }
363
- 100% { filter: brightness(1.2) blur(0px); }
364
  }
365
 
366
  #loading-text {
367
- font-size: 1.15em;
368
- font-weight: 500;
369
  color: var(--text-primary);
370
  text-align: center;
371
- text-shadow: 0 0 5px rgba(255,255,255,0.2);
372
  }
373
 
374
  </style>
@@ -376,51 +388,56 @@
376
  <body>
377
  <div class="container">
378
  <header class="app-header">
379
- <h1>آلفا TTS</h1>
380
- <p>جادوی تبدیل متن به صدا، با کیفیتی بی‌نظیر برای شما</p>
381
  </header>
382
 
383
  <main class="main-content">
384
  <form id="tts-form">
385
  <div class="form-group">
386
- <label for="text-input">📝 متن برای تبدیل</label>
387
- <textarea id="text-input" rows="4" placeholder="متن خود را با دقت اینجا وارد نمایید...">این یک آزمایش پیشرفته برای بررسی عمق و پویایی صدای تولید شده توسط نسل جدید هوش مصنوعی آلفا است، با تمرکز بر وضوح و طبیعی بودن گفتار.</textarea>
388
  </div>
389
  <div class="form-group">
390
- <label for="prompt-input">🗣️ سبک و لحن گفتار (اختیاری، برای نتایج حرفه‌ای)</label>
391
- <input type="text" id="prompt-input" value="با صدایی گیرا، آرام و متقاعدکننده." placeholder="مثال: با لحنی حماسی و پرشور، یا دوستانه و صمیمی">
392
  </div>
393
 
394
  <div class="form-group">
395
- <label>🎤 گوینده منتخب شما</label>
396
  <div id="selected-speaker-display">
397
- <div id="selected-speaker-card">
398
  <img id="selected-speaker-img" src="" alt="عکس گوینده">
399
  <div id="selected-speaker-info">
400
  <h3 id="selected-speaker-name"></h3>
401
- <p>برای انتخاب گوینده، کلیک کنید</p>
402
  </div>
403
  </div>
404
- <button type="button" id="change-speaker-btn">تغییر گوینده</button>
405
  </div>
406
  </div>
407
 
408
  <div class="form-group">
409
- <label for="temperature-slider">🌡️ میزان خلاقیت و پویایی صدا (0.1 تا 1.5)</label>
410
  <div class="slider-container">
411
  <input type="range" id="temperature-slider" min="0.1" max="1.5" step="0.05" value="0.9">
412
  <span id="temperature-value">0.9</span>
413
  </div>
414
  </div>
415
 
416
- <button type="submit" id="generate-btn">✨ خلق صدا با هوش مصنوعی آلفا</button>
417
  </form>
418
 
419
  <div id="output-section">
420
- <div id="status-message">خروجی صدای شما در اینجا ظاهر خواهد شد.</div>
421
  <div id="loading-animation-wrapper">
422
- <div class="aurora-loader"></div>
423
- <p id="loading-text">در حال خلق اثر صوتی شما با هوش مصنوعی آلفا...</p>
 
 
 
 
 
424
  </div>
425
  <audio id="audio-player" controls></audio>
426
  </div>
@@ -430,7 +447,7 @@
430
  <div id="speaker-modal">
431
  <div class="modal-content">
432
  <div class="modal-header">
433
- <h2>انتخاب استاد صدا</h2>
434
  <button type="button" class="close-modal-btn">×</button>
435
  </div>
436
  <div id="speaker-grid"></div>
@@ -469,19 +486,20 @@
469
  const speakerGridInModal = document.getElementById('speaker-grid');
470
  const selectedSpeakerImgDisplay = document.getElementById('selected-speaker-img');
471
  const selectedSpeakerNameDisplay = document.getElementById('selected-speaker-name');
472
-
 
 
473
  function getSpeakerById(id) {
474
  return speakers.find(s => s.id === id);
475
  }
476
 
477
- function getImageUrl(speaker, index, size = 'medium') { // 'thumb', 'medium', 'large'
478
  const gender = speaker.name.includes('(مرد)') ? 'men' : (speaker.name.includes('(زن)') ? 'women' : 'lego');
479
- const imageIndex = (index * 11 + 23) % 100; // Slightly different formula for variety
480
- // randomuser.me API provides different sizes. For "thumb" in modal, 'large' for selected.
481
- let portraitSize = 'thumb'; // default for modal
482
- if (size === 'large') portraitSize = ''; // API uses no path for largest, just /men/idx.jpg
483
 
484
- return `https://randomuser.me/api/portraits/${portraitSize ? portraitSize+'/' : ''}${gender}/${imageIndex}.jpg`;
485
  }
486
 
487
  function updateSelectedSpeakerDisplay(speakerId) {
@@ -489,7 +507,9 @@
489
  if (speaker) {
490
  const speakerIndex = speakers.findIndex(s => s.id === speakerId);
491
  selectedSpeakerImgDisplay.src = getImageUrl(speaker, speakerIndex, 'large');
 
492
  selectedSpeakerNameDisplay.textContent = speaker.name;
 
493
  selectedSpeakerIdStorage.value = speaker.id;
494
  }
495
  }
@@ -512,26 +532,24 @@
512
 
513
  card.addEventListener('click', () => {
514
  updateSelectedSpeakerDisplay(speaker.id);
515
- setTimeout(() => speakerModal.classList.remove('visible'), 250); // Slightly longer for smoother feel
516
  });
517
 
518
  speakerGridInModal.appendChild(card);
519
  });
520
  }
521
 
522
- changeSpeakerBtn.addEventListener('click', () => {
523
  createSpeakerCardsInModal();
524
  speakerModal.classList.add('visible');
525
- });
526
- // Also allow clicking the selected speaker card to open modal
527
- document.getElementById('selected-speaker-card').addEventListener('click', () => {
528
- createSpeakerCardsInModal();
529
- speakerModal.classList.add('visible');
530
- });
531
 
532
  closeModalBtn.addEventListener('click', () => speakerModal.classList.remove('visible'));
533
  speakerModal.addEventListener('click', (e) => {
534
- if (e.target === speakerModal) { // Click on backdrop
535
  speakerModal.classList.remove('visible');
536
  }
537
  });
@@ -544,7 +562,7 @@
544
  audioPlayer.src = '';
545
  loadingAnimationWrapper.style.display = 'flex';
546
  generateBtn.disabled = true;
547
- generateBtn.textContent = 'در حال خلق...';
548
  }
549
 
550
  function showResultState(isSuccess, message = '') {
@@ -552,14 +570,14 @@
552
  if (isSuccess) {
553
  statusMessage.style.display = 'none';
554
  audioPlayer.style.display = 'block';
555
- // audioPlayer.play(); // Autoplay can be annoying, let user click
556
  } else {
557
- statusMessage.textContent = message || 'یک خطای غیرمنتظره رخ داد. لطفاً مجدداً تلاش کنید.';
558
  statusMessage.style.display = 'block';
559
  audioPlayer.style.display = 'none';
560
  }
561
  generateBtn.disabled = false;
562
- generateBtn.textContent = ' خلق صدا با هوش مصنوعی آلفا';
563
  }
564
 
565
  async function generateAudio(event) {
@@ -572,14 +590,14 @@
572
  return;
573
  }
574
 
575
- const prompt = promptInput.value;
576
- const temperature = parseFloat(tempSlider.value);
577
- const selectedSpeaker = selectedSpeakerIdStorage.value;
578
  const sessionHash = Math.random().toString(36).substring(2);
579
 
580
  const payload = {
581
  fn_index: FN_INDEX,
582
- data: [false, null, text, prompt, selectedSpeaker, temperature],
583
  event_data: null,
584
  session_hash: sessionHash
585
  };
@@ -593,7 +611,7 @@
593
 
594
  if (!joinQueueResponse.ok) {
595
  const errorBody = await joinQueueResponse.text();
596
- throw new Error(`خطا در برقراری ارتباط با سرویس (${joinQueueResponse.status}).`);
597
  }
598
 
599
  const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
@@ -612,16 +630,13 @@
612
  if (!line.startsWith('data:')) continue;
613
  try {
614
  const data = JSON.parse(line.substring(5));
615
- if (data.msg === 'process_generating') {
616
- // You could potentially update a progress bar here if the API provided steps
617
- }
618
  if (data.msg === 'process_completed') {
619
  if (data.success && data.output.data && data.output.data[0] && (data.output.data[0].name || data.output.data[0].path)) {
620
  finalFilePath = data.output.data[0].name || data.output.data[0].path;
621
  } else { console.error("ساختار داده پاسخ سرور معتبر نیست:", data); }
622
  break;
623
  }
624
- } catch (e) { /* Ignore JSON parse errors on partial stream data */ }
625
  }
626
  if (finalFilePath) break;
627
  }
@@ -631,7 +646,7 @@
631
  audioPlayer.src = audioUrl;
632
  showResultState(true);
633
  } else {
634
- throw new Error('فایل صوتی از سرور دریافت نشد. ممکن است پردازش با مشکل مواجه شده باشد.');
635
  }
636
 
637
  } catch (error) {
@@ -640,10 +655,9 @@
640
  }
641
  }
642
 
643
- updateSelectedSpeakerDisplay(selectedSpeakerIdStorage.value); // Load default speaker
644
  form.addEventListener('submit', generateAudio);
645
 
646
- // Initial state for output section
647
  statusMessage.style.display = 'block';
648
  loadingAnimationWrapper.style.display = 'none';
649
  audioPlayer.style.display = 'none';
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Alpha TTS Nova - نسل جدید تبدیل متن به صدا</title>
7
  <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
9
 
10
  :root {
11
  --app-font: 'Vazirmatn', sans-serif;
12
+ --app-bg: #F0F7FA; /* Very light cool gray/blue */
13
+ --panel-bg: #FFFFFF;
14
+ --panel-border: #E0E8EF;
15
+ --text-primary: #1A202C; /* Dark Gray */
16
+ --text-secondary: #4A5568; /* Medium Gray */
17
+ --accent-primary: #2563EB; /* Vibrant Blue */
18
+ --accent-primary-hover: #1D4ED8;
19
+ --accent-secondary: #0D9488; /* Teal */
20
+ --accent-secondary-hover: #0F766E;
21
+ --input-bg: #F7FAFC;
22
+ --input-border-focus: var(--accent-primary);
23
 
24
+ --radius-card: 20px;
25
+ --radius-input: 12px;
26
+ --shadow-subtle: 0 4px 12px -2px rgba(26, 32, 44, 0.06);
27
+ --shadow-medium: 0 8px 20px -4px rgba(26, 32, 44, 0.1);
28
+ --shadow-strong: 0 12px 30px -6px rgba(26, 32, 44, 0.15);
29
  }
30
 
31
  body {
 
34
  background-color: var(--app-bg);
35
  color: var(--text-primary);
36
  font-size: 16px;
37
+ line-height: 1.7;
38
  margin: 0;
39
+ padding: 2rem 0;
40
  min-height: 100vh;
41
  -webkit-font-smoothing: antialiased;
42
  -moz-osx-font-smoothing: grayscale;
43
  display: flex;
44
  justify-content: center;
45
+ align-items: flex-start;
 
 
46
  }
47
 
48
  .container {
49
+ max-width: 720px;
50
  width: 90%;
51
  margin: 0 auto;
52
  }
53
 
54
  .app-header {
55
+ padding: 0.5rem 0 2.5rem 0;
56
  text-align: center;
57
+ margin-bottom: 1.5rem;
58
  }
59
  .app-header h1 {
60
+ font-size: 2.8em;
61
  font-weight: 800;
62
  margin:0 0 0.5rem 0;
63
+ color: var(--accent-primary);
 
 
 
64
  }
65
  .app-header p {
66
+ font-size: 1.15em;
67
  color: var(--text-secondary);
68
  margin-top:0;
69
  opacity: 0.9;
 
73
  padding: 2.5rem;
74
  background-color: var(--panel-bg);
75
  border-radius: var(--radius-card);
76
+ box-shadow: var(--shadow-medium);
77
  border: 1px solid var(--panel-border);
78
  }
79
 
80
+ .form-group { margin-bottom: 2.2rem; }
81
  label {
82
  display: block;
83
  font-weight: 600;
84
  color: var(--text-primary);
85
  font-size: 1.05em;
86
+ margin-bottom: 0.75rem;
87
  }
88
  textarea, input[type="text"] {
89
  width: 100%;
90
+ padding: 0.9rem 1.1rem;
91
  border-radius: var(--radius-input);
92
  border: 1px solid var(--panel-border);
93
+ background-color: var(--input-bg);
94
  color: var(--text-primary);
95
+ box-shadow: var(--shadow-subtle);
96
  font-family: var(--app-font);
97
  font-size: 1rem;
98
  box-sizing: border-box;
 
100
  }
101
  textarea:focus, input[type="text"]:focus {
102
  outline: none;
103
+ border-color: var(--input-border-focus);
104
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2), var(--shadow-subtle);
105
+ background-color: #fff;
106
  }
107
+ textarea { min-height: 110px; }
108
 
109
  /* --- نمایش گوینده منتخب --- */
110
  #selected-speaker-display { text-align: center; margin-top: 1rem; }
111
  #selected-speaker-card {
112
  display: inline-flex;
113
  align-items: center;
114
+ background: linear-gradient(145deg, var(--input-bg), #fff);
115
  border-radius: 99px;
116
+ padding: 10px 12px 10px 20px; /* R L T B */
117
+ box-shadow: var(--shadow-medium);
118
  border: 1px solid var(--panel-border);
119
  transition: all 0.3s ease;
120
+ cursor: pointer;
121
  }
122
  #selected-speaker-card:hover {
123
+ transform: translateY(-3px) scale(1.02);
124
+ box-shadow: var(--shadow-strong);
125
  }
126
  #selected-speaker-card img {
127
+ width: 75px; height: 75px;
128
  border-radius: 50%;
129
  object-fit: cover;
130
+ margin-left: 18px;
131
  border: 3px solid var(--accent-secondary);
132
+ box-shadow: 0 0 12px -2px rgba(13, 148, 136, 0.5);
133
+ background-color: #e0e0e0;
134
  }
135
+ #selected-speaker-info h3 { margin: 0; font-size: 1.35em; font-weight: 700; color: var(--text-primary); }
136
+ #selected-speaker-info p { margin: 4px 0 0; color: var(--text-secondary); font-size: 0.88em; }
137
+
138
  #change-speaker-btn {
139
+ display: block; margin: 1.2rem auto 0;
140
+ padding: 10px 24px;
141
  border-radius: var(--radius-input);
142
  background-color: transparent;
143
+ border: 2px solid var(--accent-primary);
144
  color: var(--accent-primary);
145
  cursor: pointer;
146
+ font-family: var(--app-font); font-weight: 600;
147
  transition: all 0.2s ease;
148
  }
149
  #change-speaker-btn:hover {
150
+ background-color: var(--accent-primary);
151
+ color: #fff;
152
+ transform: translateY(-2px);
153
+ box-shadow: 0 4px 10px -2px rgba(37, 99, 235, 0.3);
154
  }
155
 
156
  /* --- مودال گالری گویندگان --- */
157
  #speaker-modal {
158
  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
159
+ background-color: rgba(26, 32, 44, 0.5);
160
+ backdrop-filter: blur(8px) saturate(150%);
161
  display: none; align-items: center; justify-content: center;
162
  z-index: 1000; opacity: 0;
163
+ transition: opacity 0.35s cubic-bezier(0.4, 0, 0.2, 1);
164
  }
165
  #speaker-modal.visible { display: flex; opacity: 1; }
166
  .modal-content {
167
  background: var(--panel-bg);
168
  padding: 2rem;
169
  border-radius: var(--radius-card);
170
+ width: 90%; max-width: 680px;
171
  max-height: 85vh; overflow-y: auto;
172
+ transform: scale(0.92) translateY(20px);
173
+ transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.35s;
174
  border: 1px solid var(--panel-border);
175
+ box-shadow: var(--shadow-strong);
176
+ opacity: 0;
177
  }
178
+ #speaker-modal.visible .modal-content { transform: scale(1) translateY(0); opacity: 1;}
179
  .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid var(--panel-border); }
180
+ .modal-header h2 { margin: 0; font-size: 1.6em; color: var(--accent-primary);}
181
+ .close-modal-btn { background: none; border: none; font-size: 2.4rem; cursor: pointer; color: var(--text-secondary); transition: all 0.2s ease; line-height: 1; }
182
+ .close-modal-btn:hover { color: var(--accent-primary); transform: rotate(90deg) scale(1.05); }
183
 
184
+ #speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(115px, 1fr)); gap: 1.3rem; }
185
  @media (min-width: 576px) { #speaker-grid { grid-template-columns: repeat(4, 1fr); } }
186
+ .speaker-card { cursor: pointer; transition: all 0.25s ease-out; text-align: center; position: relative;}
187
  .speaker-card .speaker-visual {
188
  border: 3px solid transparent;
189
  border-radius: var(--radius-card);
190
  overflow: hidden;
191
+ box-shadow: var(--shadow-subtle);
192
  position: relative;
193
+ background-color: #fff;
194
+ transition: all 0.25s ease-out;
195
  }
196
  .speaker-card:hover .speaker-visual {
197
  transform: translateY(-5px) scale(1.03);
198
+ box-shadow: var(--shadow-medium);
199
  }
200
  .speaker-card input[type="radio"] { display: none; }
201
  .speaker-card img {
202
+ width: 100%; height: 115px;
203
  object-fit: cover; display: block;
204
+ background-color: #e9e9e9;
205
+ transition: transform 0.3s ease;
 
 
 
 
206
  }
207
+ .speaker-card:hover img { transform: scale(1.05); }
208
+ .speaker-card .speaker-name { padding: 0.8rem 0.5rem; font-weight: 500; font-size: 0.92em; color: var(--text-secondary); }
209
  .speaker-card input[type="radio"]:checked + .speaker-visual {
210
  border-color: var(--accent-secondary);
211
+ box-shadow: 0 0 15px -3px rgba(13, 148, 136, 0.6);
212
  }
213
  .speaker-card input[type="radio"]:checked + .speaker-visual .speaker-name {
214
  color: var(--accent-secondary);
 
219
  .slider-container { display: flex; align-items: center; gap: 1.2rem; }
220
  input[type="range"] {
221
  flex-grow: 1; -webkit-appearance: none; appearance: none;
222
+ width: 100%; height: 8px;
223
+ background: #E2E8F0;
224
+ border-radius: 4px; outline: none;
225
  cursor: pointer;
226
+ transition: background 0.2s;
227
+ }
228
+ input[type="range"]::-webkit-slider-runnable-track {
229
+ background: linear-gradient(to right, var(--accent-secondary) 0%, var(--accent-primary) 100%);
230
+ height: 8px;
231
+ border-radius: 4px;
232
  }
 
233
  input[type="range"]::-webkit-slider-thumb {
234
  -webkit-appearance: none; appearance: none;
235
  width: 22px; height: 22px;
236
  background: #fff;
237
  border-radius: 50%; cursor: pointer;
238
  border: 3px solid var(--accent-primary);
239
+ box-shadow: 0 2px 6px rgba(0,0,0,0.15);
240
+ margin-top: -7px; /* Center thumb on track */
241
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
242
  }
243
  input[type="range"]::-webkit-slider-thumb:hover {
244
  transform: scale(1.1);
245
+ box-shadow: 0 3px 8px rgba(37, 99, 235, 0.3);
246
  }
247
  input[type="range"]::-moz-range-thumb {
248
  width: 22px; height: 22px;
249
  background: #fff;
250
  border-radius: 50%; cursor: pointer;
251
  border: 3px solid var(--accent-primary);
252
+ box-shadow: 0 2px 6px rgba(0,0,0,0.15);
253
+ }
254
+ /* For Firefox, style the track directly */
255
+ input[type="range"]::-moz-range-track {
256
+ background: linear-gradient(to right, var(--accent-secondary) 0%, var(--accent-primary) 100%);
257
+ height: 8px;
258
+ border-radius: 4px;
259
+ border: none;
260
  }
261
  #temperature-value {
262
+ font-weight: 600; background-color: var(--input-bg);
263
+ padding: 0.4rem 1rem;
264
  border-radius: 8px;
265
  border: 1px solid var(--panel-border);
266
+ min-width: 45px; text-align: center;
267
  color: var(--text-primary);
268
  }
269
 
270
  #generate-btn {
271
+ width: 100%; padding: 1.05rem 1.5rem;
272
+ font-size: 1.25em; font-weight: 700;
273
  font-family: var(--app-font);
274
+ background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
275
+ color: #fff;
276
  border: none;
277
  border-radius: var(--radius-input);
278
  cursor: pointer;
279
  transition: all 0.3s ease;
280
+ box-shadow: 0 5px 15px -4px rgba(37, 99, 235, 0.4), 0 5px 15px -4px rgba(13, 148, 136, 0.3);
281
  position: relative;
282
  overflow: hidden;
283
  }
284
+ #generate-btn::before { /* Shine effect */
285
+ content: ''; position: absolute;
286
+ top: 0; left: -150%; width: 70%; height: 100%;
287
+ background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.4) 50%, rgba(255,255,255,0) 100%);
288
+ transform: skewX(-25deg);
289
+ transition: left 0.6s cubic-bezier(0.23, 1, 0.32, 1);
 
 
 
 
290
  }
291
+ #generate-btn:hover::before { left: 150%; }
292
  #generate-btn:hover:not(:disabled) {
293
  transform: translateY(-3px);
294
+ box-shadow: 0 8px 20px -4px rgba(37, 99, 235, 0.5), 0 8px 20px -4px rgba(13, 148, 136, 0.4);
295
  }
296
  #generate-btn:disabled {
297
+ background: #A0AEC0; /* Lighter gray for disabled */
298
+ cursor: not-allowed; box-shadow: none; color: #E2E8F0;
299
  transform: none;
300
  }
301
+ #generate-btn:disabled::before { display: none; }
 
 
302
 
303
  #output-section {
304
  margin-top: 3rem;
305
  padding: 2.5rem;
306
+ background-color: #fff;
307
  border-radius: var(--radius-card);
308
  min-height: 200px;
309
  display: flex; align-items: center; justify-content: center;
 
311
  border: 1px dashed var(--panel-border);
312
  transition: all 0.3s ease;
313
  position: relative;
314
+ box-shadow: var(--shadow-subtle);
315
  }
316
  #status-message { font-weight: 500; color: var(--text-secondary); text-align: center; font-size: 1.1em; }
317
+ #audio-player { width: 100%; margin-top: 1rem; display: none; }
318
+ #audio-player::-webkit-media-controls-panel { background-color: #f9fafb; border-radius: 8px; }
319
  #audio-player::-webkit-media-controls-play-button { color: var(--accent-primary); }
320
+ #audio-player::-webkit-media-controls-current-time-display,
321
+ #audio-player::-webkit-media-controls-time-remaining-display { color: var(--text-secondary); }
322
 
323
 
324
+ /* --- انیمیشن پردازش جدید: ذرات هوشمند --- */
325
  #loading-animation-wrapper {
326
  display: none; /* Hidden by default */
327
  flex-direction: column;
328
  align-items: center;
329
  justify-content: center;
330
+ gap: 2rem;
331
  width: 100%;
332
+ min-height: 150px;
333
+ perspective: 800px; /* For 3D effect on particles if desired */
334
  }
335
 
336
+ .smart-particles-loader {
337
+ width: 100px;
338
+ height: 100px;
339
  position: relative;
340
+ animation: rotate-loader 10s linear infinite;
341
  }
342
+
343
+ .particle {
 
 
344
  position: absolute;
345
+ width: 10px;
346
+ height: 10px;
 
 
347
  border-radius: 50%;
348
+ background-color: var(--accent-primary);
349
+ box-shadow: 0 0 8px var(--accent-primary), 0 0 12px var(--accent-secondary);
350
+ opacity: 0;
351
+ animation: particle-orbit 2.5s ease-in-out infinite,
352
+ particle-fade 2.5s ease-in-out infinite;
353
  }
354
 
355
+ .particle:nth-child(1) { top: 0; left: 50%; transform: translateX(-50%); animation-delay: 0s; }
356
+ .particle:nth-child(2) { top: 15%; left: 85%; transform: translate(-50%, -50%); animation-delay: -0.3s; background-color: var(--accent-secondary);}
357
+ .particle:nth-child(3) { top: 50%; left: 100%; transform: translateY(-50%); animation-delay: -0.6s; }
358
+ .particle:nth-child(4) { top: 85%; left: 85%; transform: translate(-50%, -50%); animation-delay: -0.9s; background-color: var(--accent-secondary);}
359
+ .particle:nth-child(5) { top: 100%; left: 50%; transform: translateX(-50%); animation-delay: -1.2s; }
360
+ .particle:nth-child(6) { top: 85%; left: 15%; transform: translate(-50%, -50%); animation-delay: -1.5s; background-color: var(--accent-secondary);}
361
+ .particle:nth-child(7) { top: 50%; left: 0; transform: translateY(-50%); animation-delay: -1.8s; }
362
+ .particle:nth-child(8) { top: 15%; left: 15%; transform: translate(-50%, -50%); animation-delay: -2.1s; background-color: var(--accent-secondary);}
363
+
364
+
365
+ @keyframes rotate-loader {
366
+ from { transform: rotate(0deg); }
367
+ to { transform: rotate(360deg); }
368
  }
369
 
370
+ @keyframes particle-orbit {
371
+ 0%, 100% { transform: scale(0.5); }
372
+ 50% { transform: scale(1.2); }
 
373
  }
374
+ @keyframes particle-fade {
375
+ 0%, 100% { opacity: 0; }
376
+ 20%, 80% { opacity: 1; }
377
  }
378
 
379
  #loading-text {
380
+ font-size: 1.2em;
381
+ font-weight: 600;
382
  color: var(--text-primary);
383
  text-align: center;
 
384
  }
385
 
386
  </style>
 
388
  <body>
389
  <div class="container">
390
  <header class="app-header">
391
+ <h1>آلفا TTS Nova</h1>
392
+ <p>صدایی نو، تجربه‌ای نوین در تبدیل متن به گفتار</p>
393
  </header>
394
 
395
  <main class="main-content">
396
  <form id="tts-form">
397
  <div class="form-group">
398
+ <label for="text-input">📝 متن مورد نظر شما</label>
399
+ <textarea id="text-input" rows="4" placeholder="متن خود را اینجا وارد کنید...">سلام دنیا! این یک نمونه متن برای آزمایش نسل جدید تبدیل متن به صدا آلفا نوا است. امیدوارم از کیفیت آن لذت ببرید.</textarea>
400
  </div>
401
  <div class="form-group">
402
+ <label for="prompt-input">🗣️ سبک و لحن گفتار (اختیاری)</label>
403
+ <input type="text" id="prompt-input" value="با صدایی شفاف، دوستانه و پرانرژی." placeholder="مثال: با لحنی رسمی و موقر، یا شاد و کودکانه">
404
  </div>
405
 
406
  <div class="form-group">
407
+ <label>🎤 انتخاب گوینده حرفه‌ای</label>
408
  <div id="selected-speaker-display">
409
+ <div id="selected-speaker-card" title="برای تغییر گوینده کلیک کنید">
410
  <img id="selected-speaker-img" src="" alt="عکس گوینده">
411
  <div id="selected-speaker-info">
412
  <h3 id="selected-speaker-name"></h3>
413
+ <p>گوینده پیش‌فرض</p>
414
  </div>
415
  </div>
416
+ <button type="button" id="change-speaker-btn">مشاهده همه گویندگان</button>
417
  </div>
418
  </div>
419
 
420
  <div class="form-group">
421
+ <label for="temperature-slider">🌡️ میزان خلاقیت و نوآوری صدا (0.1 تا 1.5)</label>
422
  <div class="slider-container">
423
  <input type="range" id="temperature-slider" min="0.1" max="1.5" step="0.05" value="0.9">
424
  <span id="temperature-value">0.9</span>
425
  </div>
426
  </div>
427
 
428
+ <button type="submit" id="generate-btn">🚀 تولید صدا با آلفا نوا</button>
429
  </form>
430
 
431
  <div id="output-section">
432
+ <div id="status-message">صدای تولید شده در اینجا نمایش داده خواهد شد.</div>
433
  <div id="loading-animation-wrapper">
434
+ <div class="smart-particles-loader">
435
+ <div class="particle"></div> <div class="particle"></div>
436
+ <div class="particle"></div> <div class="particle"></div>
437
+ <div class="particle"></div> <div class="particle"></div>
438
+ <div class="particle"></div> <div class="particle"></div>
439
+ </div>
440
+ <p id="loading-text">در حال پردازش هوشمند و تولید صدا...</p>
441
  </div>
442
  <audio id="audio-player" controls></audio>
443
  </div>
 
447
  <div id="speaker-modal">
448
  <div class="modal-content">
449
  <div class="modal-header">
450
+ <h2>گالری گویندگان آلفا نوا</h2>
451
  <button type="button" class="close-modal-btn">×</button>
452
  </div>
453
  <div id="speaker-grid"></div>
 
486
  const speakerGridInModal = document.getElementById('speaker-grid');
487
  const selectedSpeakerImgDisplay = document.getElementById('selected-speaker-img');
488
  const selectedSpeakerNameDisplay = document.getElementById('selected-speaker-name');
489
+ const selectedSpeakerCard = document.getElementById('selected-speaker-card');
490
+ const selectedSpeakerInfoP = selectedSpeakerCard.querySelector('#selected-speaker-info p');
491
+
492
  function getSpeakerById(id) {
493
  return speakers.find(s => s.id === id);
494
  }
495
 
496
+ function getImageUrl(speaker, index, size = 'medium') {
497
  const gender = speaker.name.includes('(مرد)') ? 'men' : (speaker.name.includes('(زن)') ? 'women' : 'lego');
498
+ const imageIndex = (index * 13 + 7) % 100;
499
+ let portraitSizePath = 'thumb/';
500
+ if (size === 'large') portraitSizePath = '';
 
501
 
502
+ return `https://randomuser.me/api/portraits/${portraitSizePath}${gender}/${imageIndex}.jpg`;
503
  }
504
 
505
  function updateSelectedSpeakerDisplay(speakerId) {
 
507
  if (speaker) {
508
  const speakerIndex = speakers.findIndex(s => s.id === speakerId);
509
  selectedSpeakerImgDisplay.src = getImageUrl(speaker, speakerIndex, 'large');
510
+ selectedSpeakerImgDisplay.alt = `عکس گوینده ${speaker.name}`;
511
  selectedSpeakerNameDisplay.textContent = speaker.name;
512
+ selectedSpeakerInfoP.textContent = "گوینده منتخب شما";
513
  selectedSpeakerIdStorage.value = speaker.id;
514
  }
515
  }
 
532
 
533
  card.addEventListener('click', () => {
534
  updateSelectedSpeakerDisplay(speaker.id);
535
+ setTimeout(() => speakerModal.classList.remove('visible'), 250);
536
  });
537
 
538
  speakerGridInModal.appendChild(card);
539
  });
540
  }
541
 
542
+ function openSpeakerModal() {
543
  createSpeakerCardsInModal();
544
  speakerModal.classList.add('visible');
545
+ }
546
+
547
+ changeSpeakerBtn.addEventListener('click', openSpeakerModal);
548
+ selectedSpeakerCard.addEventListener('click', openSpeakerModal); // Allow clicking on card too
 
 
549
 
550
  closeModalBtn.addEventListener('click', () => speakerModal.classList.remove('visible'));
551
  speakerModal.addEventListener('click', (e) => {
552
+ if (e.target === speakerModal) {
553
  speakerModal.classList.remove('visible');
554
  }
555
  });
 
562
  audioPlayer.src = '';
563
  loadingAnimationWrapper.style.display = 'flex';
564
  generateBtn.disabled = true;
565
+ generateBtn.textContent = 'در حال پردازش...';
566
  }
567
 
568
  function showResultState(isSuccess, message = '') {
 
570
  if (isSuccess) {
571
  statusMessage.style.display = 'none';
572
  audioPlayer.style.display = 'block';
573
+ // Consider if autoplay is desired: audioPlayer.play();
574
  } else {
575
+ statusMessage.textContent = message || 'خطایی رخ داد. لطفاً دوباره تلاش کنید.';
576
  statusMessage.style.display = 'block';
577
  audioPlayer.style.display = 'none';
578
  }
579
  generateBtn.disabled = false;
580
+ generateBtn.textContent = '🚀 تولید صدا با آلفا نوا';
581
  }
582
 
583
  async function generateAudio(event) {
 
590
  return;
591
  }
592
 
593
+ const promptVal = promptInput.value;
594
+ const temperatureVal = parseFloat(tempSlider.value);
595
+ const selectedSpeakerVal = selectedSpeakerIdStorage.value;
596
  const sessionHash = Math.random().toString(36).substring(2);
597
 
598
  const payload = {
599
  fn_index: FN_INDEX,
600
+ data: [false, null, text, promptVal, selectedSpeakerVal, temperatureVal],
601
  event_data: null,
602
  session_hash: sessionHash
603
  };
 
611
 
612
  if (!joinQueueResponse.ok) {
613
  const errorBody = await joinQueueResponse.text();
614
+ throw new Error(`خطا در ارتباط با سرویس آلفا (${joinQueueResponse.status}).`);
615
  }
616
 
617
  const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
 
630
  if (!line.startsWith('data:')) continue;
631
  try {
632
  const data = JSON.parse(line.substring(5));
 
 
 
633
  if (data.msg === 'process_completed') {
634
  if (data.success && data.output.data && data.output.data[0] && (data.output.data[0].name || data.output.data[0].path)) {
635
  finalFilePath = data.output.data[0].name || data.output.data[0].path;
636
  } else { console.error("ساختار داده پاسخ سرور معتبر نیست:", data); }
637
  break;
638
  }
639
+ } catch (e) { /* Ignore JSON parse errors */ }
640
  }
641
  if (finalFilePath) break;
642
  }
 
646
  audioPlayer.src = audioUrl;
647
  showResultState(true);
648
  } else {
649
+ throw new Error('فایل صوتی از سرور دریافت نشد. پردازش ممکن است ناموفق بوده باشد.');
650
  }
651
 
652
  } catch (error) {
 
655
  }
656
  }
657
 
658
+ updateSelectedSpeakerDisplay(selectedSpeakerIdStorage.value);
659
  form.addEventListener('submit', generateAudio);
660
 
 
661
  statusMessage.style.display = 'block';
662
  loadingAnimationWrapper.style.display = 'none';
663
  audioPlayer.style.display = 'none';