Hamed744 commited on
Commit
18b1fa7
·
verified ·
1 Parent(s): aee7835

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +159 -238
index.html CHANGED
@@ -3,316 +3,235 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>آلفا TTS - استودیوی صدا</title>
7
- <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800;900&display=swap" rel="stylesheet">
8
  <style>
 
 
9
  :root {
10
  --app-font: 'Vazirmatn', sans-serif;
11
- --primary-color: #4A90E2;
12
- --primary-dark: #357ABD;
13
- --secondary-color: #50E3C2;
14
- --background-start: #F4F7FC;
15
- --background-end: #E9EDF4;
16
- --panel-bg: #FFFFFF;
17
- --text-primary: #2c3e50;
18
- --text-secondary: #7f8c8d;
19
- --border-color: #dfe6e9;
20
- --radius-xl: 24px;
21
- --radius-lg: 16px;
22
- --radius-md: 12px;
23
- --shadow-soft: 0 4px 12px rgba(0, 0, 0, 0.05);
24
- --shadow-medium: 0 8px 30px rgba(0, 0, 0, 0.1);
25
- }
26
-
27
- *, *::before, *::after {
28
- box-sizing: border-box;
29
  }
30
 
31
  body {
32
  font-family: var(--app-font);
33
  direction: rtl;
34
- background-image: linear-gradient(170deg, var(--background-start) 0%, var(--background-end) 100%);
35
- color: var(--text-primary);
36
  font-size: 16px;
37
  line-height: 1.7;
38
  margin: 0;
39
- padding: 0;
40
  min-height: 100vh;
 
 
 
 
41
  }
42
 
43
  .container {
44
- max-width: 960px;
45
- width: 95%;
46
- margin: 0 auto;
47
- padding: 40px 0;
48
  }
49
 
50
- .app-header {
 
 
 
 
 
 
 
 
 
51
  text-align: center;
52
- margin-bottom: 4rem;
53
  }
54
- .app-header h1 {
55
- font-size: 3.5em;
56
- font-weight: 900;
57
- margin: 0 0 0.5rem 0;
58
- background: -webkit-linear-gradient(45deg, var(--primary-color), var(--secondary-color));
59
  -webkit-background-clip: text;
60
  -webkit-text-fill-color: transparent;
61
  }
62
- .app-header p {
63
- font-size: 1.3em;
64
- color: var(--text-secondary);
65
- margin-top: 0;
66
- }
67
-
68
- .main-panel {
69
- padding: 2.5rem;
70
- background-color: var(--panel-bg);
71
- border-radius: var(--radius-xl);
72
- box-shadow: var(--shadow-medium);
73
  }
74
 
75
  .form-group {
76
  margin-bottom: 2.5rem;
77
  }
78
-
79
- .form-group:last-of-type {
80
- margin-bottom: 0;
81
- }
82
 
83
- label.section-title {
84
  display: flex;
85
  align-items: center;
 
86
  font-weight: 700;
87
- color: var(--text-primary);
88
- font-size: 1.25em;
89
- margin-bottom: 1.25rem;
90
- }
91
-
92
- label.section-title svg {
93
- margin-left: 12px;
94
- color: var(--primary-color);
95
  }
96
 
97
  textarea, input[type="text"] {
98
  width: 100%;
99
  padding: 1rem;
100
- border-radius: var(--radius-md);
101
- border: 1px solid var(--border-color);
102
- background-color: var(--background-start);
 
103
  font-family: var(--app-font);
104
  font-size: 1rem;
 
105
  transition: all 0.2s ease-in-out;
106
  }
107
  textarea:focus, input[type="text"]:focus {
108
  outline: none;
109
- border-color: var(--primary-color);
110
- box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.2);
111
  }
112
 
113
- /* Speaker Selection Grid - Redesigned */
114
  #speaker-grid {
115
  display: grid;
116
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
117
  gap: 1.5rem;
118
  }
119
  .speaker-card {
120
  cursor: pointer;
121
  position: relative;
122
- }
123
- .speaker-card .speaker-visual {
124
- border: 3px solid transparent;
125
- border-radius: var(--radius-lg);
126
  overflow: hidden;
127
- text-align: center;
128
- transition: all 0.3s ease;
129
- box-shadow: var(--shadow-soft);
130
  }
131
- .speaker-card:hover .speaker-visual {
132
- transform: translateY(-6px);
133
- box-shadow: var(--shadow-medium);
134
  }
135
  .speaker-card input[type="radio"] {
136
  display: none;
137
  }
 
 
 
 
 
138
  .speaker-card img {
139
  width: 100%;
140
- height: 180px;
141
  object-fit: cover;
142
  display: block;
143
- background-color: #e0e0e0;
144
- filter: grayscale(0.5);
145
- transition: filter 0.3s ease;
146
  }
147
- .speaker-card .speaker-name {
148
- padding: 0.8rem 0.5rem;
149
- font-weight: 700;
150
- background-color: rgba(255, 255, 255, 0.9);
151
- backdrop-filter: blur(8px);
152
- font-size: 0.95em;
153
- transition: all 0.3s ease;
154
- }
155
- .speaker-card input[type="radio"]:checked + .speaker-visual {
156
- border-color: var(--primary-color);
157
- transform: translateY(-6px) scale(1.03);
158
- box-shadow: 0 12px 35px rgba(74, 144, 226, 0.2);
159
- }
160
- .speaker-card input[type="radio"]:checked + .speaker-visual .speaker-name {
161
- background-color: var(--primary-color);
162
  color: white;
163
- }
164
- .speaker-card input[type="radio"]:checked + .speaker-visual img {
165
- filter: grayscale(0);
166
- }
167
-
168
- .slider-container {
169
- display: flex;
170
- align-items: center;
171
- gap: 1.5rem;
172
- }
173
- input[type="range"] {
174
- flex-grow: 1;
175
- cursor: pointer;
176
- -webkit-appearance: none;
177
- appearance: none;
178
- height: 8px;
179
- background: var(--border-color);
180
- border-radius: 5px;
181
- outline: none;
182
- }
183
- input[type="range"]::-webkit-slider-thumb {
184
- -webkit-appearance: none;
185
- appearance: none;
186
- width: 24px;
187
- height: 24px;
188
- background: var(--primary-color);
189
- border-radius: 50%;
190
- border: 4px solid white;
191
- box-shadow: 0 2px 5px rgba(0,0,0,0.2);
192
- }
193
- #temperature-value {
194
- font-weight: bold;
195
  font-size: 1.1em;
196
- color: var(--primary-dark);
197
- min-width: 45px;
198
- text-align: center;
199
- }
200
-
201
- #generate-btn {
202
- width: 100%;
203
- padding: 1.1rem 1.5rem;
204
- font-size: 1.25em;
205
- font-weight: 800;
206
- font-family: var(--app-font);
207
- background-image: linear-gradient(45deg, var(--primary-color) 0%, var(--secondary-color) 100%);
 
 
 
 
208
  color: white;
209
- border: none;
210
- border-radius: var(--radius-md);
211
- cursor: pointer;
212
- transition: all 0.3s ease;
213
- box-shadow: 0 6px 20px -5px rgba(74, 144, 226, 0.5);
214
- margin-top: 1rem;
215
- }
216
- #generate-btn:hover:not(:disabled) {
217
- transform: translateY(-3px);
218
- box-shadow: 0 8px 25px -5px rgba(74, 144, 226, 0.6);
219
- }
220
- #generate-btn:disabled {
221
- background-image: none;
222
- background-color: #bdc3c7;
223
- cursor: not-allowed;
224
- box-shadow: none;
225
- }
226
-
227
- #output-section {
228
- margin-top: 2.5rem;
229
- padding: 2rem;
230
- background-color: #e8eaf6;
231
- border: 2px dashed var(--border-color);
232
- border-radius: var(--radius-xl);
233
- min-height: 120px;
234
  display: flex;
235
  align-items: center;
236
  justify-content: center;
237
- flex-direction: column;
238
- gap: 1rem;
239
- transition: all 0.3s ease;
240
  }
241
- #status-message {
242
- font-weight: 500;
243
- font-size: 1.1em;
244
- color: var(--text-secondary);
245
  }
246
- #audio-player {
247
- width: 100%;
248
- margin-top: 1rem;
249
- display: none;
250
  }
251
- .app-footer {
252
- text-align: center;
253
- margin-top: 4rem;
254
- color: var(--text-secondary);
255
- font-size: 0.9em;
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  }
257
  </style>
258
  </head>
259
  <body>
260
  <div class="container">
261
- <header class="app-header">
262
- <h1>استودیوی صدای آلفا</h1>
263
- <p>جادوی تبدیل متن به صدا، در دستان شما</p>
264
- </header>
265
-
266
- <main class="main-panel">
267
  <form id="tts-form">
268
  <div class="form-group">
269
- <label class="section-title" for="text-input">
270
- <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"><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path></svg>
271
- متن اصلی
272
- </label>
273
- <textarea id="text-input" rows="5" placeholder="اینجا متن خود را به فارسی وارد کنید...">این یک آزمایش برای بررسی کیفی�� صدای تولید شده توسط هوش مصنوعی آلفا است.</textarea>
274
  </div>
275
-
276
  <div class="form-group">
277
- <label class="section-title" for="prompt-input">
278
- <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"><path d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z"></path><path d="M8 14s1.5 2 4 2 4-2 4-2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line></svg>
279
- سبک و لحن گفتار
280
- </label>
281
  <input type="text" id="prompt-input" value="با صدایی طبیعی و روان." placeholder="مثال: با لحنی شاد و پرانرژی">
282
  </div>
283
-
284
  <div class="form-group">
285
- <label class="section-title">
286
- <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"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>
287
- انتخاب گوینده
288
- </label>
289
  <div id="speaker-grid"></div>
290
  </div>
291
-
292
  <div class="form-group">
293
- <label class="section-title" for="temperature-slider">
294
- <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"><path d="M14 14.76V3.5a2.5 2.5 0 0 0-5 0v11.26a4.5 4.5 0 1 0 5 0z"></path></svg>
295
- میزان خلاقیت
296
- </label>
297
  <div class="slider-container">
298
  <input type="range" id="temperature-slider" min="0.1" max="1.5" step="0.05" value="0.9">
299
  <span id="temperature-value">0.9</span>
300
  </div>
301
  </div>
302
-
303
- <button type="submit" id="generate-btn">🚀 تولید صدا</button>
304
  </form>
305
-
 
 
 
306
  </main>
307
-
308
- <div id="output-section">
309
- <div id="status-message">خروجی صدا در اینجا نمایش داده می‌شود</div>
310
- <audio id="audio-player" controls></audio>
311
- </div>
312
-
313
- <footer class="app-footer">
314
- <p>ساخته شده با ❤️ برای Alpha Language Learning</p>
315
- </footer>
316
  </div>
317
 
318
  <script>
@@ -325,13 +244,14 @@
325
  const FN_INDEX = 1;
326
 
327
  const speakers = {
328
- "Achird": "آوا", "Zubenelgenubi": "آرتین", "Vindemiatrix": "باران", "Sadachbia": "پرهام",
329
- "Sadaltager": "ترانه", "Sulafat": "شهاب", "Laomedeia": "دریا", "Achernar": "رامین",
330
- "Alnilam": "روشنک", "Schedar": "سامان", "Gacrux": "سپیده", "Pulcherrima": "فربد",
331
- "Umbriel": "کیان", "Algieba": "گلسا", "Despina": "ماهان", "Erinome": "نوا",
332
- "Algenib": "هرمز", "Rasalthgeti": "یاسمن", "Orus": "ارشیا", "Aoede": "چکاوک",
333
- "Callirrhoe": "خورشید", "Autonoe": "رادین", "Enceladus": "سارینا", "Iapetus": "شایان",
334
- "Zephyr": "نسیم", "Puck": "پویا", "Charon": "کارون", "Kore": "کیمیا", "Fenrir": "فردین", "Leda": "لیدا"
 
335
  };
336
 
337
  const form = document.getElementById('tts-form');
@@ -349,16 +269,20 @@
349
  const card = document.createElement('label');
350
  card.className = 'speaker-card';
351
  card.setAttribute('for', `speaker-${englishName}`);
352
-
353
- // استفاده از سرویس Unsplash برای عکس‌های با کیفیت
354
- const imageUrl = `https://source.unsplash.com/200x280/?portrait,${englishName}`;
355
  const isChecked = englishName === 'Charon' ? 'checked' : '';
356
 
357
  card.innerHTML = `
358
  <input type="radio" name="speaker" value="${englishName}" id="speaker-${englishName}" ${isChecked}>
359
  <div class="speaker-visual">
360
- <img src="${imageUrl}" alt="عکس گوینده ${persianName}" loading="lazy">
361
- <div class="speaker-name">${persianName}</div>
 
 
 
 
 
362
  </div>
363
  `;
364
  speakerGrid.appendChild(card);
@@ -384,7 +308,7 @@
384
  if (!text.trim()) {
385
  statusMessage.textContent = 'خطا: متن ورودی نمی‌تواند خالی باشد.';
386
  generateBtn.disabled = false;
387
- generateBtn.textContent = '🚀 تولید صدا';
388
  return;
389
  }
390
 
@@ -407,7 +331,7 @@
407
  throw new Error(`خطا در اتصال به صف (${joinQueueResponse.status}): ${errorBody}`);
408
  }
409
 
410
- statusMessage.textContent = 'در صف انتظار...';
411
 
412
  const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
413
  const reader = dataResponse.body.getReader();
@@ -428,7 +352,6 @@
428
 
429
  try {
430
  const data = JSON.parse(line.substring(5));
431
-
432
  if (data.msg === 'process_generating') {
433
  statusMessage.textContent = 'سرور در حال تولید فایل صوتی است...';
434
  }
@@ -440,21 +363,19 @@
440
  }
441
  break;
442
  }
443
- } catch (e) {
444
- console.warn("خطا در پارس کردن خط:", line, e);
445
- }
446
  }
447
  if (finalFilePath) break;
448
  }
449
 
450
  if (finalFilePath) {
451
- statusMessage.textContent = 'فایل صوتی شما آماده است!';
452
  const audioUrl = `${FILE_URL_BASE}${finalFilePath}`;
453
  audioPlayer.src = audioUrl;
454
  audioPlayer.style.display = 'block';
455
  audioPlayer.play();
456
  } else {
457
- throw new Error('فایل صوتی از سرور دریافت نشد. کنسول را برای اطلاعات بیشتر بررسی کنید.');
458
  }
459
 
460
  } catch (error) {
@@ -462,7 +383,7 @@
462
  statusMessage.textContent = `یک خطا رخ داد: ${error.message}`;
463
  } finally {
464
  generateBtn.disabled = false;
465
- generateBtn.textContent = '🚀 تولید صدا';
466
  }
467
  }
468
 
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>آلفا TTS - نسخه نهایی</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
+ --app-header-grad-start: #1a2980;
13
+ --app-header-grad-end: #26d0ce;
14
+ --app-panel-bg: rgba(255, 255, 255, 0.65);
15
+ --app-input-bg: rgba(255, 255, 255, 0.7);
16
+ --app-button-bg: #5f27cd;
17
+ --app-main-bg: linear-gradient(170deg, #e0c3fc 0%, #8ec5fc 100%);
18
+ --app-text-primary: #1e1e1e;
19
+ --app-text-secondary: #4a4a4a;
20
+ --app-border-color: rgba(255, 255, 255, 0.4);
21
+ --radius-card: 24px;
22
+ --radius-input: 14px;
23
+ --shadow-card: 0 10px 40px -10px rgba(0,0,0,0.15);
24
+ --shadow-button: 0 5px 15px -3px rgba(95, 39, 205, 0.5);
25
+ --speaker-selected-border: 4px solid var(--app-button-bg);
 
 
 
 
26
  }
27
 
28
  body {
29
  font-family: var(--app-font);
30
  direction: rtl;
31
+ background: var(--app-main-bg);
32
+ color: var(--app-text-primary);
33
  font-size: 16px;
34
  line-height: 1.7;
35
  margin: 0;
36
+ padding: 2rem 1rem;
37
  min-height: 100vh;
38
+ box-sizing: border-box;
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: center;
42
  }
43
 
44
  .container {
45
+ max-width: 950px;
46
+ width: 100%;
 
 
47
  }
48
 
49
+ .main-content {
50
+ padding: 2.5rem;
51
+ background: var(--app-panel-bg);
52
+ border-radius: var(--radius-card);
53
+ box-shadow: var(--shadow-card);
54
+ backdrop-filter: blur(20px);
55
+ border: 1px solid var(--app-border-color);
56
+ }
57
+
58
+ .app-title {
59
  text-align: center;
60
+ margin-bottom: 2.5rem;
61
  }
62
+ .app-title h1 {
63
+ font-size: 2.8em;
64
+ font-weight: 800;
65
+ margin: 0;
66
+ background: linear-gradient(45deg, var(--app-header-grad-start), var(--app-header-grad-end));
67
  -webkit-background-clip: text;
68
  -webkit-text-fill-color: transparent;
69
  }
70
+ .app-title p {
71
+ font-size: 1.2em;
72
+ color: var(--app-text-secondary);
73
+ margin-top: 0.5rem;
 
 
 
 
 
 
 
74
  }
75
 
76
  .form-group {
77
  margin-bottom: 2.5rem;
78
  }
 
 
 
 
79
 
80
+ label {
81
  display: flex;
82
  align-items: center;
83
+ gap: 0.7rem;
84
  font-weight: 700;
85
+ color: var(--app-text-primary);
86
+ font-size: 1.1em;
87
+ margin-bottom: 1rem;
 
 
 
 
 
88
  }
89
 
90
  textarea, input[type="text"] {
91
  width: 100%;
92
  padding: 1rem;
93
+ border-radius: var(--radius-input);
94
+ border: 1px solid var(--app-border-color);
95
+ background-color: var(--app-input-bg);
96
+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.04);
97
  font-family: var(--app-font);
98
  font-size: 1rem;
99
+ box-sizing: border-box;
100
  transition: all 0.2s ease-in-out;
101
  }
102
  textarea:focus, input[type="text"]:focus {
103
  outline: none;
104
+ border-color: var(--app-button-bg);
105
+ box-shadow: 0 0 0 4px rgba(95, 39, 205, 0.2);
106
  }
107
 
108
+ /* Speaker Selection Grid - NEW DESIGN */
109
  #speaker-grid {
110
  display: grid;
111
+ grid-template-columns: repeat(4, 1fr); /* 4 columns */
112
  gap: 1.5rem;
113
  }
114
  .speaker-card {
115
  cursor: pointer;
116
  position: relative;
 
 
 
 
117
  overflow: hidden;
118
+ border-radius: var(--radius-input);
119
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
120
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
121
  }
122
+ .speaker-card:hover {
123
+ transform: scale(1.05);
124
+ box-shadow: 0 8px 25px rgba(0,0,0,0.15);
125
  }
126
  .speaker-card input[type="radio"] {
127
  display: none;
128
  }
129
+ .speaker-card .speaker-visual {
130
+ border-radius: var(--radius-input);
131
+ border: 4px solid transparent;
132
+ transition: border-color 0.3s ease;
133
+ }
134
  .speaker-card img {
135
  width: 100%;
136
+ height: 200px;
137
  object-fit: cover;
138
  display: block;
 
 
 
139
  }
140
+ .speaker-card .speaker-name-overlay {
141
+ position: absolute;
142
+ bottom: 0;
143
+ left: 0;
144
+ right: 0;
145
+ background: linear-gradient(to top, rgba(0,0,0,0.8), transparent);
 
 
 
 
 
 
 
 
 
146
  color: white;
147
+ padding: 2rem 1rem 1rem 1rem;
148
+ font-weight: 700;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  font-size: 1.1em;
150
+ text-align: right;
151
+ opacity: 0;
152
+ transform: translateY(20px);
153
+ transition: opacity 0.3s ease, transform 0.3s ease;
154
+ }
155
+ .speaker-card:hover .speaker-name-overlay {
156
+ opacity: 1;
157
+ transform: translateY(0);
158
+ }
159
+ .speaker-card .selection-tick {
160
+ position: absolute;
161
+ top: 10px;
162
+ left: 10px;
163
+ width: 30px;
164
+ height: 30px;
165
+ background-color: var(--app-button-bg);
166
  color: white;
167
+ border-radius: 50%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  display: flex;
169
  align-items: center;
170
  justify-content: center;
171
+ transform: scale(0);
172
+ transition: transform 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28);
 
173
  }
174
+ .speaker-card input[type="radio"]:checked + .speaker-visual {
175
+ border-color: var(--app-button-bg);
 
 
176
  }
177
+ .speaker-card input[type="radio"]:checked + .speaker-visual .selection-tick {
178
+ transform: scale(1);
 
 
179
  }
180
+
181
+ /* Slider */
182
+ .slider-container { display: flex; align-items: center; gap: 1rem; }
183
+ input[type="range"] { flex-grow: 1; cursor: pointer; }
184
+ #temperature-value { font-weight: bold; background-color: var(--app-input-bg); padding: 0.2rem 0.8rem; border-radius: 8px; border: 1px solid var(--app-border-color); min-width: 40px; text-align: center; }
185
+
186
+ #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 0.3s ease; box-shadow: var(--shadow-button); }
187
+ #generate-btn:hover:not(:disabled) { filter: brightness(1.1); transform: translateY(-2px); box-shadow: 0 8px 20px -5px rgba(95, 39, 205, 0.6); }
188
+ #generate-btn:disabled { background-color: #999; cursor: not-allowed; box-shadow: none; }
189
+
190
+ #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; }
191
+ #status-message { font-weight: 500; color: var(--app-text-secondary); }
192
+ #audio-player { width: 100%; margin-top: 1rem; display: none; }
193
+
194
+ @media (max-width: 768px) {
195
+ #speaker-grid { grid-template-columns: repeat(2, 1fr); }
196
+ .main-content { padding: 1.5rem; }
197
+ body { padding: 1rem 0.5rem; }
198
  }
199
  </style>
200
  </head>
201
  <body>
202
  <div class="container">
203
+ <main class="main-content">
204
+ <div class="app-title">
205
+ <h1>آلفا TTS</h1>
206
+ <p>جادوی تبدیل متن به صدا</p>
207
+ </div>
 
208
  <form id="tts-form">
209
  <div class="form-group">
210
+ <label for="text-input">📝 متن برای تبدیل</label>
211
+ <textarea id="text-input" rows="4" placeholder="اینجا متن خود را به فارسی وارد کنید...">این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است.</textarea>
 
 
 
212
  </div>
 
213
  <div class="form-group">
214
+ <label for="prompt-input">🗣️ سبک و لحن گفتار (اختیاری)</label>
 
 
 
215
  <input type="text" id="prompt-input" value="با صدایی طبیعی و روان." placeholder="مثال: با لحنی شاد و پرانرژی">
216
  </div>
 
217
  <div class="form-group">
218
+ <label>🎤 گوینده را انتخاب کنید</label>
 
 
 
219
  <div id="speaker-grid"></div>
220
  </div>
 
221
  <div class="form-group">
222
+ <label for="temperature-slider">🌡️ میزان خلاقیت صدا</label>
 
 
 
223
  <div class="slider-container">
224
  <input type="range" id="temperature-slider" min="0.1" max="1.5" step="0.05" value="0.9">
225
  <span id="temperature-value">0.9</span>
226
  </div>
227
  </div>
228
+ <button type="submit" id="generate-btn">🚀 تولید و پخش صدا</button>
 
229
  </form>
230
+ <div id="output-section">
231
+ <div id="status-message">خروجی صدا در اینجا نمایش داده می‌شود</div>
232
+ <audio id="audio-player" controls></audio>
233
+ </div>
234
  </main>
 
 
 
 
 
 
 
 
 
235
  </div>
236
 
237
  <script>
 
244
  const FN_INDEX = 1;
245
 
246
  const speakers = {
247
+ "Charon": "شهاب", "Zephyr": "نسیم", "Iapetus": "کیان", "Enceladus": "آوا",
248
+ "Leda": "لیدا", "Kore": "ترنم", "Puck": "پژمان", "Fenrir": "آرش",
249
+ "Aoede": "چکاوک", "Callirrhoe": "باران", "Autonoe": "��وشنک", "Orus": "داریوش",
250
+ "Rasalthgeti": "هما", "Algenib": "البرز", "Erinome": "ارمغان", "Despina": "دریا",
251
+ "Algieba": "آناهیتا", "Umbriel": "سهند", "Pulcherrima": "پریچهر", "Gacrux": "گرشا",
252
+ "Schedar": "ستاره", "Alnilam": "آرتین", "Achernar": "آناهید", "Laomedeia": "ماندانا",
253
+ "Sulafat": "سروش", "Sadaltager": "سپهر", "Sadachbia": "سحر", "Vindemiatrix": "پروین",
254
+ "Zubenelgenubi": "آذر", "Achird": "آرشام"
255
  };
256
 
257
  const form = document.getElementById('tts-form');
 
269
  const card = document.createElement('label');
270
  card.className = 'speaker-card';
271
  card.setAttribute('for', `speaker-${englishName}`);
272
+ // استفاده از Unsplash برای عکس‌های واقعی و با کیفیت
273
+ const imageUrl = `https://source.unsplash.com/200x200/?portrait,person,${englishName}`;
 
274
  const isChecked = englishName === 'Charon' ? 'checked' : '';
275
 
276
  card.innerHTML = `
277
  <input type="radio" name="speaker" value="${englishName}" id="speaker-${englishName}" ${isChecked}>
278
  <div class="speaker-visual">
279
+ <img src="${imageUrl}" alt="عکس گوینده ${persianName}">
280
+ <div class="speaker-name-overlay">${persianName}</div>
281
+ <div class="selection-tick">
282
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
283
+ <path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425z"/>
284
+ </svg>
285
+ </div>
286
  </div>
287
  `;
288
  speakerGrid.appendChild(card);
 
308
  if (!text.trim()) {
309
  statusMessage.textContent = 'خطا: متن ورودی نمی‌تواند خالی باشد.';
310
  generateBtn.disabled = false;
311
+ generateBtn.textContent = '🚀 تولید و پخش صدا';
312
  return;
313
  }
314
 
 
331
  throw new Error(`خطا در اتصال به صف (${joinQueueResponse.status}): ${errorBody}`);
332
  }
333
 
334
+ statusMessage.textContent = 'در انتظار نتیجه از سرور...';
335
 
336
  const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
337
  const reader = dataResponse.body.getReader();
 
352
 
353
  try {
354
  const data = JSON.parse(line.substring(5));
 
355
  if (data.msg === 'process_generating') {
356
  statusMessage.textContent = 'سرور در حال تولید فایل صوتی است...';
357
  }
 
363
  }
364
  break;
365
  }
366
+ } catch (e) { /* نادیده گرفتن خطاهای پارس کردن */ }
 
 
367
  }
368
  if (finalFilePath) break;
369
  }
370
 
371
  if (finalFilePath) {
372
+ statusMessage.textContent = 'فایل صوتی با موفقیت دریافت شد!';
373
  const audioUrl = `${FILE_URL_BASE}${finalFilePath}`;
374
  audioPlayer.src = audioUrl;
375
  audioPlayer.style.display = 'block';
376
  audioPlayer.play();
377
  } else {
378
+ throw new Error('فایل صوتی از سرور دریافت نشد.');
379
  }
380
 
381
  } catch (error) {
 
383
  statusMessage.textContent = `یک خطا رخ داد: ${error.message}`;
384
  } finally {
385
  generateBtn.disabled = false;
386
+ generateBtn.textContent = '🚀 تولید و پخش صدا';
387
  }
388
  }
389