Hamed744 commited on
Commit
3980071
·
verified ·
1 Parent(s): b51dc15

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +302 -53
index.html CHANGED
@@ -3,44 +3,265 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Alpha TTS - رابط کاربری نهایی</title>
7
  <style>
8
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
9
- :root{--app-font:'Vazirmatn',sans-serif;--app-header-grad-start:#2980b9;--app-header-grad-end:#2ecc71;--app-panel-bg:#FFFFFF;--app-input-bg:#F7F7F7;--app-button-bg:#2979FF;--app-main-bg:linear-gradient(170deg, #E0F2FE 0%, #F3E8FF 100%);--app-text-primary:#333;--app-text-secondary:#555;--app-border-color:#E0E0E0;--radius-card:20px;--radius-input:12px;--shadow-card:0 10px 30px -5px rgba(0,0,0,0.1);--shadow-button:0 4px 10px -2px rgba(41,121,255,0.5);--speaker-selected-border:3px solid var(--app-button-bg);}
10
- body{font-family:var(--app-font);direction:rtl;background:var(--app-main-bg);color:var(--app-text-primary);font-size:16px;line-height:1.65;margin:0;padding:0;min-height:100vh;display:flex;flex-direction:column;}
11
- .container{max-width:900px;width:95%;margin:0 auto;padding-bottom:40px;}
12
- .app-header{padding:3rem 1.5rem 5rem 1.5rem;text-align:center;background-image:linear-gradient(135deg, var(--app-header-grad-start) 0%, var(--app-header-grad-end) 100%);color:white;border-bottom-left-radius:var(--radius-card);border-bottom-right-radius:var(--radius-card);box-shadow:0 6px 20px -5px rgba(0,0,0,0.2);}
13
- .app-header h1{font-size:2.8em;font-weight:800;margin:0 0 .5rem 0;text-shadow:0 2px 4px rgba(0,0,0,0.15);}
14
- .app-header p{font-size:1.2em;color:rgba(255,255,255,0.9);margin-top:0;opacity:.9;}
15
- .main-content{padding:2.5rem;margin:-3.5rem auto 2rem auto;background-color:var(--app-panel-bg);border-radius:var(--radius-card);box-shadow:var(--shadow-card);}
16
- .form-group{margin-bottom:2rem;}
17
- label{display:block;font-weight:700;color:var(--app-text-primary);font-size:1.1em;margin-bottom:.8rem;}
18
- textarea,input[type=text]{width:100%;padding:1rem;border-radius:var(--radius-input);border:1px solid var(--app-border-color);background-color:var(--app-input-bg);box-shadow:inset 0 1px 2px rgba(0,0,0,0.05);font-family:var(--app-font);font-size:1rem;box-sizing:border-box;transition:all .2s ease-in-out;}
19
- textarea:focus,input[type=text]:focus{outline:none;border-color:var(--app-button-bg);box-shadow:0 0 0 3px rgba(41,121,255,0.2);}
20
- #speaker-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(120px, 1fr));gap:1.5rem;}
21
- .speaker-card{cursor:pointer;border:3px solid transparent;border-radius:var(--radius-card);overflow:hidden;text-align:center;transition:all .3s ease;box-shadow:0 4px 15px rgba(0,0,0,0.08);position:relative;}
22
- .speaker-card:hover{transform:translateY(-5px);box-shadow:0 8px 25px rgba(0,0,0,0.12);}
23
- .speaker-card input[type=radio]{display:none;}
24
- .speaker-card img{width:100%;height:120px;object-fit:cover;display:block;background-color:#eee;}
25
- .speaker-card .speaker-name{padding:.8rem .5rem;font-weight:500;background-color:rgba(255,255,255,0.8);backdrop-filter:blur(5px);transition:background-color .3s ease;}
26
- .speaker-card input[type=radio]:checked + .speaker-visual{border-color:var(--app-button-bg);box-shadow:0 8px 25px rgba(41,121,255,0.3);transform:translateY(-5px);}
27
- .speaker-card input[type=radio]:checked + .speaker-visual .speaker-name{background-color:var(--app-button-bg);color:white;font-weight:700;}
28
- .slider-container{display:flex;align-items:center;gap:1rem;}
29
- input[type=range]{flex-grow:1;cursor:pointer;}
30
- #temperature-value{font-weight:bold;background-color:var(--app-input-bg);padding:.2rem .8rem;border-radius:8px;border:1px solid var(--app-border-color);min-width:40px;text-align:center;}
31
- #generate-btn{width:100%;padding:1rem 1.5rem;font-size:1.2em;font-weight:700;font-family:var(--app-font);background:var(--app-button-bg);color:white;border:none;border-radius:var(--radius-input);cursor:pointer;transition:all .3s ease;box-shadow:var(--shadow-button);}
32
- #generate-btn:hover:not(:disabled){filter:brightness(1.1);transform:translateY(-2px);box-shadow:0 6px 12px -3px rgba(41,121,255,0.6);}
33
- #generate-btn:disabled{background-color:#999;cursor:not-allowed;box-shadow:none;}
34
- #output-section{margin-top:2.5rem;padding:1.5rem;background-color:var(--app-input-bg);border-radius:var(--radius-card);min-height:100px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:1rem;}
35
- #status-message{font-weight:500;color:var(--app-text-secondary);}
36
- #audio-player{width:100%;margin-top:1rem;display:none;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  </style>
38
  </head>
39
  <body>
40
  <div class="container">
41
  <header class="app-header">
42
- <h1>Alpha TTS</h1>
43
- <p>جادوی تبدیل متن به صدا با رابط کاربری سفارشی شما</p>
44
  </header>
45
 
46
  <main class="main-content">
@@ -54,11 +275,11 @@
54
  <input type="text" id="prompt-input" value="با صدایی طبیعی و روان." placeholder="مثال: با لحنی شاد و پرانرژی">
55
  </div>
56
  <div class="form-group">
57
- <label>🎤 گوینده و لهجه را انتخاب کنید</label>
58
  <div id="speaker-grid"></div>
59
  </div>
60
  <div class="form-group">
61
- <label for="temperature-slider">🌡️ میزان خلاقیت صدا (0.1 تا 1.5)</label>
62
  <div class="slider-container">
63
  <input type="range" id="temperature-slider" min="0.1" max="1.5" step="0.05" value="0.9">
64
  <span id="temperature-value">0.9</span>
@@ -71,20 +292,46 @@
71
  <audio id="audio-player" controls></audio>
72
  </div>
73
  </main>
 
 
 
 
74
  </div>
75
 
76
  <script>
77
  document.addEventListener('DOMContentLoaded', () => {
78
  const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
79
- // *** مسیرهای صحیح با پیشوند /gradio_api/ بر اساس لاگ‌ها ***
80
  const JOIN_QUEUE_URL = `${HF_SPACE_URL}/gradio_api/queue/join`;
81
  const GET_DATA_URL_BASE = `${HF_SPACE_URL}/gradio_api/queue/data`;
82
  const FILE_URL_BASE = `${HF_SPACE_URL}/gradio_api/file=`;
83
-
84
- // fn_index ها معمولا از 0 شروع میشن، اما با توجه به لاگ های شما، از 1 استفاده می کنیم
85
- const FN_INDEX = 1;
86
 
87
- const speakers = ["Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager", "Sulafat", "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux", "Pulcherrima", "Umbriel", "Algieba", "Despina", "Erinome", "Algenib", "Rasalthgeti", "Orus", "Aoede", "Callirrhoe", "Autonoe", "Enceladus", "Iapetus", "Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Leda"];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
  const form = document.getElementById('tts-form');
90
  const textInput = document.getElementById('text-input');
@@ -97,13 +344,22 @@
97
  const audioPlayer = document.getElementById('audio-player');
98
 
99
  function createSpeakerCards() {
100
- speakers.forEach((speakerName) => {
101
  const card = document.createElement('label');
102
  card.className = 'speaker-card';
103
- card.setAttribute('for', `speaker-${speakerName}`);
104
- const imageUrl = `https://picsum.photos/seed/${speakerName}/200/200`;
105
- const isChecked = speakerName === 'Charon' ? 'checked' : '';
106
- card.innerHTML = `<input type="radio" name="speaker" value="${speakerName}" id="speaker-${speakerName}" ${isChecked}><div class="speaker-visual"><img src="${imageUrl}" alt="عکس گوینده ${speakerName}"><div class="speaker-name">${speakerName}</div></div>`;
 
 
 
 
 
 
 
 
 
107
  speakerGrid.appendChild(card);
108
  });
109
  }
@@ -139,7 +395,6 @@
139
  };
140
 
141
  try {
142
- // 1. اتصال به صف
143
  const joinQueueResponse = await fetch(JOIN_QUEUE_URL, {
144
  method: "POST",
145
  headers: { "Content-Type": "application/json" },
@@ -151,10 +406,8 @@
151
  throw new Error(`خطا در اتصال به صف (${joinQueueResponse.status}): ${errorBody}`);
152
  }
153
 
154
- console.log("با موفقیت به صف متصل شد. در انتظار داده...");
155
  statusMessage.textContent = 'در انتظار نتیجه از سرور...';
156
 
157
- // 2. گوش دادن برای دریافت نتیجه
158
  const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
159
  const reader = dataResponse.body.getReader();
160
  const decoder = new TextDecoder();
@@ -174,7 +427,6 @@
174
 
175
  try {
176
  const data = JSON.parse(line.substring(5));
177
- console.log('پیام دریافتی از سرور:', data);
178
 
179
  if (data.msg === 'process_generating') {
180
  statusMessage.textContent = 'سرور در حال تولید فایل صوتی است...';
@@ -187,22 +439,19 @@
187
  }
188
  break;
189
  }
190
- } catch (e) {
191
- console.warn("خطا در پارس کردن خط:", line, e);
192
- }
193
  }
194
  if (finalFilePath) break;
195
  }
196
 
197
  if (finalFilePath) {
198
- statusMessage.textContent = 'فایل صوتی با موفقیت دریافت شد!';
199
  const audioUrl = `${FILE_URL_BASE}${finalFilePath}`;
200
- console.log("آدرس نهایی فایل صوتی:", audioUrl);
201
  audioPlayer.src = audioUrl;
202
  audioPlayer.style.display = 'block';
203
  audioPlayer.play();
204
  } else {
205
- throw new Error('فایل صوتی از سرور دریافت نشد. کنسول را برای اطلاعات بیشتر بررسی کنید.');
206
  }
207
 
208
  } catch (error) {
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>آوای آلفا - تبدیل متن به گفتار</title>
7
  <style>
8
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
9
+
10
+ :root {
11
+ --app-font: 'Vazirmatn', sans-serif;
12
+ --primary-color: #5A58E1; /* A modern, friendly purple */
13
+ --primary-light: #F0F0FF;
14
+ --header-grad-start: #4340d8;
15
+ --header-grad-end: #9439f1;
16
+ --panel-bg: #FFFFFF;
17
+ --input-bg: #F8F9FA;
18
+ --main-bg: #F4F7FC;
19
+ --text-primary: #1F2937;
20
+ --text-secondary: #6B7280;
21
+ --border-color: #E5E7EB;
22
+ --radius-card: 24px;
23
+ --radius-input: 12px;
24
+ --shadow-card: 0 12px 35px -8px rgba(90, 88, 225, 0.15);
25
+ --shadow-button: 0 4px 15px -2px rgba(90, 88, 225, 0.4);
26
+ --speaker-selected-glow: 0 0 0 3px rgba(90, 88, 225, 0.4);
27
+ }
28
+
29
+ body {
30
+ font-family: var(--app-font);
31
+ direction: rtl;
32
+ background-color: var(--main-bg);
33
+ color: var(--text-primary);
34
+ font-size: 16px;
35
+ line-height: 1.7;
36
+ margin: 0;
37
+ padding: 0;
38
+ min-height: 100vh;
39
+ }
40
+
41
+ .container {
42
+ max-width: 960px;
43
+ width: 90%;
44
+ margin: 0 auto;
45
+ padding: 40px 0;
46
+ }
47
+
48
+ .app-header {
49
+ text-align: center;
50
+ margin-bottom: 40px;
51
+ }
52
+ .app-header h1 {
53
+ font-size: 3em;
54
+ font-weight: 800;
55
+ color: var(--primary-color);
56
+ background: linear-gradient(45deg, var(--header-grad-start), var(--header-grad-end));
57
+ -webkit-background-clip: text;
58
+ -webkit-text-fill-color: transparent;
59
+ margin: 0 0 0.5rem 0;
60
+ }
61
+ .app-header p {
62
+ font-size: 1.2em;
63
+ color: var(--text-secondary);
64
+ margin-top:0;
65
+ }
66
+
67
+ .main-content {
68
+ padding: 2.5rem;
69
+ background-color: var(--panel-bg);
70
+ border-radius: var(--radius-card);
71
+ box-shadow: var(--shadow-card);
72
+ }
73
+
74
+ .form-group {
75
+ margin-bottom: 2.5rem;
76
+ }
77
+
78
+ label {
79
+ display: block;
80
+ font-weight: 700;
81
+ color: var(--text-primary);
82
+ font-size: 1.1em;
83
+ margin-bottom: 0.8rem;
84
+ }
85
+
86
+ textarea, input[type="text"] {
87
+ width: 100%;
88
+ padding: 1rem;
89
+ border-radius: var(--radius-input);
90
+ border: 2px solid var(--border-color);
91
+ background-color: var(--input-bg);
92
+ font-family: var(--app-font);
93
+ font-size: 1rem;
94
+ box-sizing: border-box;
95
+ transition: all 0.2s ease-in-out;
96
+ }
97
+
98
+ textarea:focus, input[type="text"]:focus {
99
+ outline: none;
100
+ border-color: var(--primary-color);
101
+ background-color: white;
102
+ box-shadow: var(--speaker-selected-glow);
103
+ }
104
+
105
+ #speaker-grid {
106
+ display: grid;
107
+ grid-template-columns: repeat(4, 1fr);
108
+ gap: 1.2rem;
109
+ }
110
+
111
+ @media (max-width: 768px) {
112
+ #speaker-grid {
113
+ grid-template-columns: repeat(3, 1fr);
114
+ }
115
+ }
116
+ @media (max-width: 480px) {
117
+ #speaker-grid {
118
+ grid-template-columns: repeat(2, 1fr);
119
+ }
120
+ }
121
+
122
+ .speaker-card {
123
+ cursor: pointer;
124
+ position: relative;
125
+ border-radius: var(--radius-input);
126
+ overflow: hidden;
127
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
128
+ }
129
+ .speaker-card:hover {
130
+ transform: translateY(-5px);
131
+ }
132
+ .speaker-card input[type="radio"] {
133
+ display: none;
134
+ }
135
+ .speaker-card img {
136
+ width: 100%;
137
+ height: 100%;
138
+ object-fit: cover;
139
+ display: block;
140
+ aspect-ratio: 1 / 1;
141
+ transition: transform 0.3s ease;
142
+ }
143
+ .speaker-card:hover img {
144
+ transform: scale(1.05);
145
+ }
146
+ .speaker-card .speaker-name-overlay {
147
+ position: absolute;
148
+ bottom: 0;
149
+ left: 0;
150
+ right: 0;
151
+ padding: 1.5rem 0.75rem 0.75rem;
152
+ background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 100%);
153
+ color: white;
154
+ font-weight: bold;
155
+ font-size: 0.9em;
156
+ text-align: center;
157
+ transition: opacity 0.3s ease;
158
+ }
159
+ .speaker-card input[type="radio"]:checked + .speaker-visual {
160
+ box-shadow: var(--speaker-selected-glow);
161
+ transform: scale(1.03);
162
+ }
163
+
164
+ .slider-container {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 1rem;
168
+ }
169
+ input[type="range"] {
170
+ flex-grow: 1;
171
+ -webkit-appearance: none;
172
+ appearance: none;
173
+ height: 8px;
174
+ background: var(--input-bg);
175
+ border-radius: 5px;
176
+ outline: none;
177
+ cursor: pointer;
178
+ }
179
+ input[type="range"]::-webkit-slider-thumb {
180
+ -webkit-appearance: none;
181
+ appearance: none;
182
+ width: 20px;
183
+ height: 20px;
184
+ background: var(--primary-color);
185
+ border-radius: 50%;
186
+ cursor: pointer;
187
+ }
188
+ input[type="range"]::-moz-range-thumb {
189
+ width: 20px;
190
+ height: 20px;
191
+ background: var(--primary-color);
192
+ border-radius: 50%;
193
+ cursor: pointer;
194
+ }
195
+ #temperature-value {
196
+ font-weight: bold;
197
+ background-color: var(--primary-light);
198
+ color: var(--primary-color);
199
+ padding: 0.2rem 0.8rem;
200
+ border-radius: 8px;
201
+ min-width: 40px;
202
+ text-align: center;
203
+ }
204
+
205
+ #generate-btn {
206
+ width: 100%;
207
+ padding: 1rem 1.5rem;
208
+ font-size: 1.2em;
209
+ font-weight: 700;
210
+ font-family: var(--app-font);
211
+ background: linear-gradient(45deg, var(--header-grad-start), var(--header-grad-end));
212
+ color: white;
213
+ border: none;
214
+ border-radius: var(--radius-input);
215
+ cursor: pointer;
216
+ transition: all 0.3s ease;
217
+ box-shadow: var(--shadow-button);
218
+ margin-top: 1rem;
219
+ }
220
+ #generate-btn:hover:not(:disabled) {
221
+ transform: translateY(-3px);
222
+ box-shadow: 0 7px 20px -5px rgba(90, 88, 225, 0.5);
223
+ }
224
+ #generate-btn:disabled {
225
+ background: #B0B0B0;
226
+ cursor: not-allowed;
227
+ box-shadow: none;
228
+ }
229
+
230
+ #output-section {
231
+ margin-top: 2.5rem;
232
+ padding: 2rem;
233
+ background-color: var(--panel-bg);
234
+ border-radius: var(--radius-card);
235
+ min-height: 100px;
236
+ display: flex;
237
+ align-items: center;
238
+ justify-content: center;
239
+ flex-direction: column;
240
+ gap: 1rem;
241
+ }
242
+ #status-message {
243
+ font-size: 1.1em;
244
+ font-weight: 500;
245
+ color: var(--text-secondary);
246
+ }
247
+ #audio-player {
248
+ width: 100%;
249
+ margin-top: 1rem;
250
+ display: none;
251
+ }
252
+ .app-footer {
253
+ text-align: center;
254
+ margin-top: 40px;
255
+ font-size: 0.9em;
256
+ color: var(--text-secondary);
257
+ }
258
  </style>
259
  </head>
260
  <body>
261
  <div class="container">
262
  <header class="app-header">
263
+ <h1>آوای آلفا</h1>
264
+ <p>جادوی تبدیل متن به گفتار، با صدای دلخواه شما</p>
265
  </header>
266
 
267
  <main class="main-content">
 
275
  <input type="text" id="prompt-input" value="با صدایی طبیعی و روان." placeholder="مثال: با لحنی شاد و پرانرژی">
276
  </div>
277
  <div class="form-group">
278
+ <label>🎤 گوینده را انتخاب کنید</label>
279
  <div id="speaker-grid"></div>
280
  </div>
281
  <div class="form-group">
282
+ <label for="temperature-slider">🌡️ میزان خلاقیت صدا</label>
283
  <div class="slider-container">
284
  <input type="range" id="temperature-slider" min="0.1" max="1.5" step="0.05" value="0.9">
285
  <span id="temperature-value">0.9</span>
 
292
  <audio id="audio-player" controls></audio>
293
  </div>
294
  </main>
295
+
296
+ <footer class="app-footer">
297
+ <p>© 2024 - Alpha Language Learning</p>
298
+ </footer>
299
  </div>
300
 
301
  <script>
302
  document.addEventListener('DOMContentLoaded', () => {
303
  const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
 
304
  const JOIN_QUEUE_URL = `${HF_SPACE_URL}/gradio_api/queue/join`;
305
  const GET_DATA_URL_BASE = `${HF_SPACE_URL}/gradio_api/queue/data`;
306
  const FILE_URL_BASE = `${HF_SPACE_URL}/gradio_api/file=`;
307
+ const FN_INDEX = 1;
 
 
308
 
309
+ const speakers = [
310
+ { id: "Charon", name: "آرش", gender: "male" },
311
+ { id: "Zephyr", name: "پریسا", gender: "female" },
312
+ { id: "Iapetus", name: "کیان", gender: "male" },
313
+ { id: "Leda", name: "رویا", gender: "female" },
314
+ { id: "Orus", name: "بهرام", gender: "male" },
315
+ { id: "Aoede", name: "آوا", gender: "female" },
316
+ { id: "Achernar", name: "سامان", gender: "male" },
317
+ { id: "Schedar", name: "سارا", gender: "female" },
318
+ { id: "Gacrux", name: "مانی", gender: "male" },
319
+ { id: "Laomedeia", name: "لادن", gender: "female" },
320
+ { id: "Alnilam", name: "بردیا", gender: "male" },
321
+ { id: "Sadachbia", name: "شبنم", gender: "female" },
322
+ { id: "Enceladus", name: "اردلان", gender: "male" },
323
+ { id: "Despina", name: "دریا", gender: "female" },
324
+ { id: "Rasalthgeti", name: "رامین", gender: "male" },
325
+ { id: "Erinome", name: "الهه", gender: "female" },
326
+ { id: "Puck", name: "پویا", gender: "male" },
327
+ { id: "Kore", name: "کتایون", gender: "female" },
328
+ { id: "Fenrir", name: "فریدون", gender: "male" },
329
+ { id: "Umbriel", name: "یگانه", gender: "female" },
330
+ { id: "Achird", name: "آرمان", gender: "male" },
331
+ { id: "Vindemiatrix", name: "ویدا", gender: "female" },
332
+ { id: "Sulafat", name: "سهراب", gender: "male" },
333
+ { id: "Callirrhoe", name: "گلاره", gender: "female" }
334
+ ];
335
 
336
  const form = document.getElementById('tts-form');
337
  const textInput = document.getElementById('text-input');
 
344
  const audioPlayer = document.getElementById('audio-player');
345
 
346
  function createSpeakerCards() {
347
+ speakers.forEach((speaker) => {
348
  const card = document.createElement('label');
349
  card.className = 'speaker-card';
350
+ card.setAttribute('for', `speaker-${speaker.id}`);
351
+
352
+ // از یک API برای تولید عکس‌های واقعی بر اساس جنسیت استفاده می‌کنیم
353
+ const imageUrl = `https://xsgames.co/randomusers/assets/avatars/${speaker.gender}/${Math.floor(Math.random() * 50)}.jpg`;
354
+ const isChecked = speaker.id === 'Charon' ? 'checked' : '';
355
+
356
+ card.innerHTML = `
357
+ <input type="radio" name="speaker" value="${speaker.id}" id="speaker-${speaker.id}" ${isChecked}>
358
+ <div class="speaker-visual">
359
+ <img src="${imageUrl}" alt="عکس گوینده ${speaker.name}">
360
+ <div class="speaker-name-overlay">${speaker.name}</div>
361
+ </div>
362
+ `;
363
  speakerGrid.appendChild(card);
364
  });
365
  }
 
395
  };
396
 
397
  try {
 
398
  const joinQueueResponse = await fetch(JOIN_QUEUE_URL, {
399
  method: "POST",
400
  headers: { "Content-Type": "application/json" },
 
406
  throw new Error(`خطا در اتصال به صف (${joinQueueResponse.status}): ${errorBody}`);
407
  }
408
 
 
409
  statusMessage.textContent = 'در انتظار نتیجه از سرور...';
410
 
 
411
  const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
412
  const reader = dataResponse.body.getReader();
413
  const decoder = new TextDecoder();
 
427
 
428
  try {
429
  const data = JSON.parse(line.substring(5));
 
430
 
431
  if (data.msg === 'process_generating') {
432
  statusMessage.textContent = 'سرور در حال تولید فایل صوتی است...';
 
439
  }
440
  break;
441
  }
442
+ } catch (e) { /* ignore parse errors */ }
 
 
443
  }
444
  if (finalFilePath) break;
445
  }
446
 
447
  if (finalFilePath) {
448
+ statusMessage.textContent = '';
449
  const audioUrl = `${FILE_URL_BASE}${finalFilePath}`;
 
450
  audioPlayer.src = audioUrl;
451
  audioPlayer.style.display = 'block';
452
  audioPlayer.play();
453
  } else {
454
+ throw new Error('فایل صوتی از سرور دریافت نشد. لطفا کنسول را برای اطلاعات بیشتر بررسی کنید.');
455
  }
456
 
457
  } catch (error) {