Hamed744 commited on
Commit
76efbef
·
verified ·
1 Parent(s): 5c0555e

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +356 -674
index.html CHANGED
@@ -3,742 +3,255 @@
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
 
10
  :root {
11
  --app-font: 'Vazirmatn', sans-serif;
12
- --app-header-grad-start: #667eea;
13
- --app-header-grad-end: #764ba2;
14
- --app-panel-bg: #FFFFFF;
15
- --app-input-bg: #F8F9FA;
16
- --app-button-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
- --app-button-hover-bg: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%);
18
- --app-main-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
19
- --app-text-primary: #2d3748;
20
- --app-text-secondary: #4a5568;
21
- --app-border-color: #e2e8f0;
22
- --radius-card: 20px;
23
  --radius-input: 12px;
24
- --shadow-card: 0 20px 40px -10px rgba(0,0,0,0.1);
25
- --shadow-button: 0 8px 25px -5px rgba(102, 126, 234, 0.4);
26
- --speaker-selected-glow: 0 0 20px rgba(102, 126, 234, 0.6);
27
- --accent-color: #667eea;
28
- }
29
-
30
- * {
31
- box-sizing: border-box;
32
  }
33
 
 
34
  body {
35
  font-family: var(--app-font);
36
  direction: rtl;
37
- background: var(--app-main-bg);
38
- color: var(--app-text-primary);
39
  font-size: 16px;
40
- line-height: 1.65;
41
  margin: 0;
42
- padding: 0;
43
  min-height: 100vh;
44
  -webkit-font-smoothing: antialiased;
45
  -moz-osx-font-smoothing: grayscale;
46
- overflow-x: hidden;
47
- }
48
-
49
- /* Animated Background */
50
- .animated-bg {
51
- position: fixed;
52
- top: 0;
53
- left: 0;
54
- width: 100%;
55
- height: 100%;
56
- z-index: -1;
57
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
58
- }
59
-
60
- .animated-bg::before {
61
- content: '';
62
- position: absolute;
63
- top: 0;
64
- left: 0;
65
- width: 100%;
66
- height: 100%;
67
- background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="20" cy="20" r="2" fill="rgba(255,255,255,0.1)"><animate attributeName="opacity" values="0.1;0.3;0.1" dur="3s" repeatCount="indefinite"/></circle><circle cx="80" cy="40" r="1.5" fill="rgba(255,255,255,0.1)"><animate attributeName="opacity" values="0.1;0.4;0.1" dur="2s" repeatCount="indefinite"/></circle><circle cx="40" cy="70" r="2.5" fill="rgba(255,255,255,0.1)"><animate attributeName="opacity" values="0.1;0.2;0.1" dur="4s" repeatCount="indefinite"/></circle><circle cx="90" cy="80" r="1" fill="rgba(255,255,255,0.1)"><animate attributeName="opacity" values="0.1;0.5;0.1" dur="2.5s" repeatCount="indefinite"/></circle></svg>');
68
- animation: float 20s infinite linear;
69
- }
70
-
71
- @keyframes float {
72
- 0% { transform: translateY(0px); }
73
- 50% { transform: translateY(-20px); }
74
- 100% { transform: translateY(0px); }
75
- }
76
-
77
- .container {
78
- max-width: 900px;
79
- width: 95%;
80
- margin: 0 auto;
81
- padding-bottom: 40px;
82
- position: relative;
83
- z-index: 1;
84
- }
85
-
86
- .app-header {
87
- padding: 4rem 2rem 6rem 2rem;
88
- text-align: center;
89
- background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
90
- backdrop-filter: blur(10px);
91
- border: 1px solid rgba(255,255,255,0.2);
92
- color: white;
93
- border-radius: var(--radius-card);
94
- box-shadow: 0 20px 40px -10px rgba(0,0,0,0.3);
95
- margin-bottom: 2rem;
96
- position: relative;
97
  overflow: hidden;
98
- }
99
-
100
- .app-header::before {
101
- content: '';
102
- position: absolute;
103
- top: -50%;
104
- left: -50%;
105
- width: 200%;
106
- height: 200%;
107
- background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.1) 50%, transparent 70%);
108
- animation: shine 3s infinite;
109
- }
110
-
111
- @keyframes shine {
112
- 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }
113
- 100% { transform: translateX(100%) translateY(100%) rotate(45deg); }
114
- }
115
-
116
- .app-header h1 {
117
- font-size: 3.5em;
118
- font-weight: 800;
119
- margin: 0 0 1rem 0;
120
- text-shadow: 0 4px 8px rgba(0,0,0,0.3);
121
- background: linear-gradient(45deg, #fff, #f0f0f0);
122
- background-clip: text;
123
- -webkit-background-clip: text;
124
- -webkit-text-fill-color: transparent;
125
- position: relative;
126
- z-index: 2;
127
- }
128
-
129
- .app-header p {
130
- font-size: 1.3em;
131
- color: rgba(255,255,255,0.9);
132
- margin-top: 0;
133
- opacity: 0.9;
134
  position: relative;
135
- z-index: 2;
136
  }
137
-
138
- .main-content {
139
- padding: 3rem;
140
- background: rgba(255,255,255,0.95);
141
- backdrop-filter: blur(10px);
142
- border: 1px solid rgba(255,255,255,0.2);
143
- border-radius: var(--radius-card);
144
- box-shadow: var(--shadow-card);
145
- position: relative;
146
- overflow: hidden;
147
- }
148
-
149
- .main-content::before {
150
  content: '';
151
  position: absolute;
152
  top: 0;
153
  left: 0;
154
  width: 100%;
155
  height: 100%;
156
- background: linear-gradient(135deg, rgba(102,126,234,0.02) 0%, rgba(118,75,162,0.02) 100%);
157
- pointer-events: none;
158
- }
159
-
160
- .form-group {
161
- margin-bottom: 2.5rem;
162
- position: relative;
163
- z-index: 1;
164
- }
165
-
166
- label {
167
- display: block;
168
- font-weight: 700;
169
- color: var(--app-text-primary);
170
- font-size: 1.1em;
171
- margin-bottom: 0.8rem;
172
- display: flex;
173
- align-items: center;
174
- gap: 0.5rem;
175
- }
176
-
177
- textarea, input[type="text"] {
178
- width: 100%;
179
- padding: 1.2rem;
180
- border-radius: var(--radius-input);
181
- border: 2px solid var(--app-border-color);
182
- background: rgba(255,255,255,0.8);
183
- backdrop-filter: blur(5px);
184
- box-shadow: 0 4px 15px rgba(0,0,0,0.05);
185
- font-family: var(--app-font);
186
- font-size: 1rem;
187
- transition: all 0.3s ease;
188
- }
189
-
190
- textarea:focus, input[type="text"]:focus {
191
- outline: none;
192
- border-color: var(--accent-color);
193
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2), 0 8px 25px rgba(0,0,0,0.1);
194
- background: rgba(255,255,255,0.95);
195
- transform: translateY(-2px);
196
- }
197
-
198
- /* Selected Speaker Display */
199
- #selected-speaker-display {
200
- text-align: center;
201
- }
202
-
203
- #selected-speaker-card {
204
- display: inline-flex;
205
- align-items: center;
206
- background: rgba(255,255,255,0.9);
207
- backdrop-filter: blur(10px);
208
- border-radius: 25px;
209
- padding: 12px;
210
- box-shadow: 0 10px 30px rgba(0,0,0,0.1);
211
- border: 2px solid rgba(102,126,234,0.2);
212
- transition: all 0.3s ease;
213
- }
214
-
215
- #selected-speaker-card:hover {
216
- transform: translateY(-3px);
217
- box-shadow: 0 15px 35px rgba(0,0,0,0.15);
218
- }
219
-
220
- #selected-speaker-card img {
221
- width: 90px;
222
- height: 90px;
223
- border-radius: 50%;
224
- object-fit: cover;
225
- margin-left: 15px;
226
- background-color: #eee;
227
- border: 3px solid rgba(102,126,234,0.3);
228
- }
229
-
230
- #selected-speaker-info h3 {
231
- margin: 0;
232
- font-size: 1.4em;
233
- color: var(--app-text-primary);
234
- }
235
-
236
- #selected-speaker-info p {
237
- margin: 5px 0 0;
238
- color: var(--app-text-secondary);
239
- font-size: 0.9em;
240
- }
241
-
242
- #change-speaker-btn {
243
- display: block;
244
- margin: 1.5rem auto 0;
245
- padding: 12px 30px;
246
- border-radius: 25px;
247
- background: linear-gradient(135deg, rgba(102,126,234,0.1) 0%, rgba(118,75,162,0.1) 100%);
248
- border: 2px solid rgba(102,126,234,0.2);
249
- cursor: pointer;
250
- font-family: var(--app-font);
251
- font-weight: 600;
252
- transition: all 0.3s ease;
253
- backdrop-filter: blur(5px);
254
- }
255
-
256
- #change-speaker-btn:hover {
257
- background: linear-gradient(135deg, rgba(102,126,234,0.2) 0%, rgba(118,75,162,0.2) 100%);
258
- transform: translateY(-2px);
259
- box-shadow: 0 8px 20px rgba(102,126,234,0.2);
260
- }
261
-
262
- /* Speaker Modal */
263
- #speaker-modal {
264
- position: fixed;
265
- top: 0;
266
- left: 0;
267
- width: 100%;
268
- height: 100%;
269
- background-color: rgba(0,0,0,0.6);
270
- backdrop-filter: blur(10px);
271
- display: none;
272
- align-items: center;
273
- justify-content: center;
274
- z-index: 1000;
275
- opacity: 0;
276
- transition: opacity 0.3s ease;
277
  }
278
 
279
- #speaker-modal.visible {
280
- display: flex;
281
- opacity: 1;
282
  }
283
 
284
- .modal-content {
285
- background: rgba(255,255,255,0.95);
 
 
 
 
 
286
  backdrop-filter: blur(20px);
287
- padding: 2.5rem;
288
- border-radius: var(--radius-card);
289
- width: 90%;
290
- max-width: 800px;
291
- max-height: 80vh;
292
- overflow-y: auto;
293
- transform: scale(0.9);
294
- transition: transform 0.3s ease;
295
- border: 1px solid rgba(255,255,255,0.3);
296
- box-shadow: 0 30px 60px rgba(0,0,0,0.3);
297
- }
298
-
299
- #speaker-modal.visible .modal-content {
300
- transform: scale(1);
301
- }
302
-
303
- .modal-header {
304
- display: flex;
305
- justify-content: space-between;
306
- align-items: center;
307
- margin-bottom: 2rem;
308
- }
309
-
310
- .modal-header h2 {
311
- margin: 0;
312
- color: var(--app-text-primary);
313
- font-size: 1.8em;
314
- }
315
-
316
- .close-modal-btn {
317
- background: none;
318
- border: none;
319
- font-size: 2rem;
320
- cursor: pointer;
321
- color: #aaa;
322
- transition: all 0.2s ease;
323
- width: 40px;
324
- height: 40px;
325
- border-radius: 50%;
326
- display: flex;
327
- align-items: center;
328
- justify-content: center;
329
  }
330
 
331
- .close-modal-btn:hover {
332
- color: #333;
333
- background: rgba(0,0,0,0.1);
334
- }
335
 
336
- #speaker-grid {
337
- display: grid;
338
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
339
- gap: 1.5rem;
340
- }
341
-
342
- @media (min-width: 576px) {
343
- #speaker-grid {
344
- grid-template-columns: repeat(4, 1fr);
345
- }
346
- }
347
-
348
- .speaker-card {
349
- cursor: pointer;
350
- transition: all 0.3s ease;
351
- }
352
-
353
- .speaker-card .speaker-visual {
354
- border: 3px solid transparent;
355
- border-radius: var(--radius-card);
356
- overflow: hidden;
357
- text-align: center;
358
- box-shadow: 0 8px 20px rgba(0,0,0,0.1);
359
- position: relative;
360
- background: rgba(255,255,255,0.9);
361
- backdrop-filter: blur(5px);
362
- }
363
-
364
- .speaker-card:hover .speaker-visual {
365
- transform: translateY(-5px);
366
- box-shadow: 0 15px 30px rgba(0,0,0,0.2);
367
- }
368
-
369
- .speaker-card input[type="radio"] {
370
- display: none;
371
- }
372
-
373
- .speaker-card img {
374
- width: 100%;
375
- height: 140px;
376
- object-fit: cover;
377
- display: block;
378
- background-color: #eee;
379
- }
380
-
381
- .speaker-card .speaker-name {
382
- padding: 1rem 0.5rem;
383
- font-weight: 600;
384
- font-size: 0.9em;
385
- color: var(--app-text-primary);
386
- }
387
-
388
- .speaker-card input[type="radio"]:checked + .speaker-visual {
389
- border-color: var(--accent-color);
390
- box-shadow: var(--speaker-selected-glow);
391
- }
392
-
393
- /* Slider */
394
- .slider-container {
395
- display: flex;
396
- align-items: center;
397
- gap: 1rem;
398
- }
399
-
400
- input[type="range"] {
401
- flex-grow: 1;
402
- cursor: pointer;
403
- height: 6px;
404
- border-radius: 3px;
405
- background: linear-gradient(135deg, var(--accent-color) 0%, #764ba2 100%);
406
- outline: none;
407
- -webkit-appearance: none;
408
- }
409
-
410
- input[type="range"]::-webkit-slider-thumb {
411
- -webkit-appearance: none;
412
- width: 20px;
413
- height: 20px;
414
- border-radius: 50%;
415
- background: linear-gradient(135deg, var(--accent-color) 0%, #764ba2 100%);
416
- cursor: pointer;
417
- box-shadow: 0 4px 10px rgba(0,0,0,0.2);
418
- }
419
-
420
- #temperature-value {
421
- font-weight: bold;
422
- background: linear-gradient(135deg, rgba(102,126,234,0.1) 0%, rgba(118,75,162,0.1) 100%);
423
- padding: 0.5rem 1rem;
424
- border-radius: 20px;
425
- border: 2px solid rgba(102,126,234,0.2);
426
- min-width: 50px;
427
- text-align: center;
428
- backdrop-filter: blur(5px);
429
- }
430
-
431
- #generate-btn {
432
- width: 100%;
433
- padding: 1.5rem 2rem;
434
- font-size: 1.3em;
435
- font-weight: 700;
436
- font-family: var(--app-font);
437
- background: var(--app-button-bg);
438
- color: white;
439
- border: none;
440
- border-radius: var(--radius-input);
441
- cursor: pointer;
442
- transition: all 0.3s ease;
443
- box-shadow: var(--shadow-button);
444
- position: relative;
445
- overflow: hidden;
446
- }
447
-
448
- #generate-btn:hover:not(:disabled) {
449
- background: var(--app-button-hover-bg);
450
- transform: translateY(-3px);
451
- box-shadow: 0 12px 35px -5px rgba(102, 126, 234, 0.5);
452
- }
453
-
454
- #generate-btn:disabled {
455
- background: #999;
456
- cursor: not-allowed;
457
- box-shadow: none;
458
- }
459
-
460
- #output-section {
461
- margin-top: 2.5rem;
462
- padding: 2rem;
463
- background: rgba(255,255,255,0.8);
464
- backdrop-filter: blur(10px);
465
- border-radius: var(--radius-card);
466
- min-height: 120px;
467
- display: flex;
468
- align-items: center;
469
- justify-content: center;
470
- flex-direction: column;
471
- gap: 1rem;
472
- border: 2px solid rgba(102,126,234,0.1);
473
- box-shadow: 0 10px 25px rgba(0,0,0,0.05);
474
- }
475
-
476
- #status-message {
477
- font-weight: 600;
478
- color: var(--app-text-secondary);
479
- font-size: 1.1em;
480
- }
481
-
482
- #audio-player {
483
- width: 100%;
484
- margin-top: 1rem;
485
- display: none;
486
- border-radius: 10px;
487
- }
488
-
489
- /* Loading Animation */
490
- .loading-overlay {
491
- position: fixed;
492
- top: 0;
493
- left: 0;
494
  width: 100%;
495
- height: 100%;
496
- background: rgba(0,0,0,0.8);
497
- backdrop-filter: blur(10px);
498
- display: none;
499
- align-items: center;
500
- justify-content: center;
501
- z-index: 9999;
502
- opacity: 0;
503
- transition: opacity 0.3s ease;
504
- }
505
-
506
- .loading-overlay.visible {
507
- display: flex;
508
- opacity: 1;
509
- }
510
-
511
- .loading-content {
512
- text-align: center;
513
- color: white;
514
- max-width: 500px;
515
- padding: 2rem;
516
- }
517
-
518
- .loading-title {
519
- font-size: 2em;
520
- font-weight: 700;
521
- margin-bottom: 1rem;
522
- background: linear-gradient(45deg, #667eea, #764ba2, #667eea, #764ba2);
523
- background-size: 300% 300%;
524
- background-clip: text;
525
- -webkit-background-clip: text;
526
- -webkit-text-fill-color: transparent;
527
- animation: gradientFlow 3s ease-in-out infinite;
528
- }
529
-
530
- @keyframes gradientFlow {
531
- 0%, 100% { background-position: 0% 50%; }
532
- 50% { background-position: 100% 50%; }
533
- }
534
-
535
- .loading-subtitle {
536
- font-size: 1.2em;
537
- margin-bottom: 2rem;
538
- opacity: 0.8;
539
- }
540
-
541
- .sound-wave {
542
- display: flex;
543
- justify-content: center;
544
- align-items: flex-end;
545
- height: 100px;
546
- gap: 4px;
547
- margin-bottom: 2rem;
548
- }
549
-
550
- .wave-bar {
551
- width: 6px;
552
- background: linear-gradient(45deg, #667eea, #764ba2);
553
- border-radius: 3px;
554
- animation: wave 1.5s ease-in-out infinite;
555
- }
556
-
557
- .wave-bar:nth-child(1) { animation-delay: 0s; }
558
- .wave-bar:nth-child(2) { animation-delay: 0.1s; }
559
- .wave-bar:nth-child(3) { animation-delay: 0.2s; }
560
- .wave-bar:nth-child(4) { animation-delay: 0.3s; }
561
- .wave-bar:nth-child(5) { animation-delay: 0.4s; }
562
- .wave-bar:nth-child(6) { animation-delay: 0.5s; }
563
- .wave-bar:nth-child(7) { animation-delay: 0.6s; }
564
- .wave-bar:nth-child(8) { animation-delay: 0.7s; }
565
-
566
- @keyframes wave {
567
- 0%, 100% { height: 20px; }
568
- 50% { height: 80px; }
569
  }
570
-
571
- .floating-particles {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
  position: absolute;
573
- width: 100%;
574
- height: 100%;
575
- overflow: hidden;
576
- pointer-events: none;
 
 
577
  }
578
-
579
- .particle {
580
  position: absolute;
581
- width: 4px;
582
- height: 4px;
583
- background: rgba(255,255,255,0.6);
584
  border-radius: 50%;
585
- animation: float-particle 4s infinite ease-in-out;
 
586
  }
 
 
 
 
 
 
 
 
587
 
588
- .particle:nth-child(1) { left: 10%; animation-delay: 0s; }
589
- .particle:nth-child(2) { left: 20%; animation-delay: 0.5s; }
590
- .particle:nth-child(3) { left: 30%; animation-delay: 1s; }
591
- .particle:nth-child(4) { left: 40%; animation-delay: 1.5s; }
592
- .particle:nth-child(5) { left: 50%; animation-delay: 2s; }
593
- .particle:nth-child(6) { left: 60%; animation-delay: 2.5s; }
594
- .particle:nth-child(7) { left: 70%; animation-delay: 3s; }
595
- .particle:nth-child(8) { left: 80%; animation-delay: 3.5s; }
596
- .particle:nth-child(9) { left: 90%; animation-delay: 4s; }
597
-
598
- @keyframes float-particle {
599
- 0% {
600
- transform: translateY(100px) rotate(0deg);
601
- opacity: 0;
602
- }
603
- 10% { opacity: 1; }
604
- 90% { opacity: 1; }
605
- 100% {
606
- transform: translateY(-100px) rotate(360deg);
607
- opacity: 0;
608
- }
609
  }
610
-
611
- .ai-icon {
612
- font-size: 4em;
613
- margin-bottom: 1rem;
614
- animation: pulse 2s ease-in-out infinite;
615
- }
616
-
617
- @keyframes pulse {
618
- 0%, 100% { transform: scale(1); }
619
- 50% { transform: scale(1.1); }
620
  }
621
 
622
- /* Responsive Design */
623
- @media (max-width: 768px) {
624
- .app-header {
625
- padding: 3rem 1.5rem 4rem 1.5rem;
626
- }
627
-
628
- .app-header h1 {
629
- font-size: 2.5em;
630
- }
631
-
632
- .main-content {
633
- padding: 2rem;
634
- }
635
-
636
- .container {
637
- width: 98%;
638
- }
639
-
640
- #speaker-grid {
641
- grid-template-columns: repeat(2, 1fr);
642
- }
643
- }
644
  </style>
645
  </head>
646
  <body>
647
- <div class="animated-bg"></div>
648
-
649
  <div class="container">
650
- <header class="app-header">
651
- <h1>🎤 آلفا TTS</h1>
652
- <p>✨ جادوی تبدیل متن به صدا، به سبک شما ✨</p>
653
- </header>
654
-
655
- <main class="main-content">
656
- <form id="tts-form">
657
- <div class="form-group">
658
- <label for="text-input">📝 متن برای تبدیل</label>
659
- <textarea id="text-input" rows="5" placeholder="اینجا متن خود را به فارسی وارد کنید...">این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است.</textarea>
660
- </div>
661
-
662
- <div class="form-group">
663
- <label for="prompt-input">🎭 سبک و لحن گفتار (اختیاری)</label>
664
- <input type="text" id="prompt-input" value="با صدایی طبیعی و روان." placeholder="مثال: با لحنی شاد و پرانرژی">
665
- </div>
666
-
667
- <div class="form-group">
668
- <label>🎤 گوینده منتخب</label>
669
- <div id="selected-speaker-display">
670
- <div id="selected-speaker-card">
671
- <img id="selected-speaker-img" src="" alt="عکس گوینده">
672
- <div id="selected-speaker-info">
673
- <h3 id="selected-speaker-name"></h3>
674
- <p>💡 برای تغییر، روی دکمه زیر کلیک کنید</p>
675
  </div>
 
676
  </div>
677
- <button type="button" id="change-speaker-btn">🔄 تغییر گوینده</button>
678
  </div>
679
- </div>
680
-
681
- <div class="form-group">
682
- <label for="temperature-slider">🌡️ میزان خلاقیت صدا (0.1 تا 1.5)</label>
683
- <div class="slider-container">
684
- <input type="range" id="temperature-slider" min="0.1" max="1.5" step="0.05" value="0.9">
685
- <span id="temperature-value">0.9</span>
686
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
687
  </div>
688
-
689
- <button type="submit" id="generate-btn">🚀 تولید و پخش صدا</button>
690
- </form>
691
-
692
- <div id="output-section">
693
- <div id="status-message">🎵 خروجی صدا در اینجا نمایش داده می‌شود</div>
694
- <audio id="audio-player" controls></audio>
695
- </div>
696
- </main>
697
  </div>
698
 
699
- <!-- Speaker Selection Modal -->
700
  <div id="speaker-modal">
701
  <div class="modal-content">
702
  <div class="modal-header">
703
- <h2>🎭 انتخاب گوینده</h2>
704
  <button type="button" class="close-modal-btn">×</button>
705
  </div>
706
  <div id="speaker-grid"></div>
707
  </div>
708
  </div>
709
-
710
- <!-- Loading Animation Overlay -->
711
- <div class="loading-overlay" id="loading-overlay">
712
- <div class="loading-content">
713
- <div class="ai-icon">🤖</div>
714
- <div class="loading-title">در حال تبدیل متن به صدا</div>
715
- <div class="loading-subtitle">با هوش مصنوعی آلفا</div>
716
-
717
- <div class="sound-wave">
718
- <div class="wave-bar"></div>
719
- <div class="wave-bar"></div>
720
- <div class="wave-bar"></div>
721
- <div class="wave-bar"></div>
722
- <div class="wave-bar"></div>
723
- <div class="wave-bar"></div>
724
- <div class="wave-bar"></div>
725
- <div class="wave-bar"></div>
726
- </div>
727
-
728
- <div class="floating-particles">
729
- <div class="particle"></div>
730
- <div class="particle"></div>
731
- <div class="particle"></div>
732
- <div class="particle"></div>
733
- <div class="particle"></div>
734
- <div class="particle"></div>
735
- <div class="particle"></div>
736
- <div class="particle"></div>
737
- <div class="particle"></div>
738
- </div>
739
- </div>
740
- </div>
741
-
742
  <input type="hidden" id="selected_speaker_id_storage" value="Charon">
743
 
744
  <script>
@@ -746,4 +259,173 @@
746
  const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
747
  const JOIN_QUEUE_URL = `${HF_SPACE_URL}/gradio_api/queue/join`;
748
  const GET_DATA_URL_BASE = `${HF_SPACE_URL}/gradio_api/queue/data`;
749
- const FILE_URL_BASE =
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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&display=swap');
9
 
10
  :root {
11
  --app-font: 'Vazirmatn', sans-serif;
12
+ --bg-color: #0d0f21;
13
+ --panel-bg: rgba(26, 30, 56, 0.5);
14
+ --panel-border: rgba(255, 255, 255, 0.15);
15
+ --text-primary: #f0f4f9;
16
+ --text-secondary: #a8b2d1;
17
+ --accent-glow: #a77dfd;
18
+ --accent-glow-rgb: 167, 125, 253;
19
+ --button-bg: linear-gradient(135deg, rgba(167, 125, 253, 0.8) 0%, rgba(89, 97, 240, 0.8) 100%);
20
+ --button-hover-bg: linear-gradient(135deg, rgba(167, 125, 253, 1) 0%, rgba(89, 97, 240, 1) 100%);
21
+ --radius-card: 24px;
 
22
  --radius-input: 12px;
 
 
 
 
 
 
 
 
23
  }
24
 
25
+ /* --- پس‌زمینه متحرک و زیبا --- */
26
  body {
27
  font-family: var(--app-font);
28
  direction: rtl;
29
+ background-color: var(--bg-color);
30
+ color: var(--text-primary);
31
  font-size: 16px;
32
+ line-height: 1.7;
33
  margin: 0;
34
+ padding: 40px 0;
35
  min-height: 100vh;
36
  -webkit-font-smoothing: antialiased;
37
  -moz-osx-font-smoothing: grayscale;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  position: relative;
 
40
  }
41
+
42
+ body::before {
 
 
 
 
 
 
 
 
 
 
 
43
  content: '';
44
  position: absolute;
45
  top: 0;
46
  left: 0;
47
  width: 100%;
48
  height: 100%;
49
+ background:
50
+ radial-gradient(circle at 15% 25%, rgba(var(--accent-glow-rgb), 0.2), transparent 40%),
51
+ radial-gradient(circle at 85% 75%, rgba(89, 97, 240, 0.2), transparent 40%);
52
+ animation: move-glow 20s infinite alternate;
53
+ z-index: -1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
55
 
56
+ @keyframes move-glow {
57
+ from { transform: translate(0, 0) scale(1); }
58
+ to { transform: translate(50px, -50px) scale(1.2); }
59
  }
60
 
61
+ .container { max-width: 800px; width: 95%; margin: 0 auto; z-index: 1; position: relative; }
62
+
63
+ /* --- پنل اصلی با افکت شیشه‌ای --- */
64
+ .main-panel {
65
+ background: var(--panel-bg);
66
+ border: 1px solid var(--panel-border);
67
+ border-radius: var(--radius-card);
68
  backdrop-filter: blur(20px);
69
+ -webkit-backdrop-filter: blur(20px);
70
+ padding: 2rem 2.5rem;
71
+ box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  }
73
 
74
+ .app-header { text-align: center; margin-bottom: 3rem; }
75
+ .app-header h1 { font-size: 3em; font-weight: 800; margin:0; text-shadow: 0 0 20px rgba(var(--accent-glow-rgb), 0.5); }
76
+ .app-header p { font-size: 1.25em; color: var(--text-secondary); margin-top:0.5rem; }
 
77
 
78
+ .form-group { margin-bottom: 2.5rem; }
79
+ label { display: block; font-weight: 700; color: var(--text-primary); font-size: 1.1em; margin-bottom: 1rem; }
80
+ textarea, input[type="text"] {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  width: 100%;
82
+ padding: 1rem;
83
+ border-radius: var(--radius-input);
84
+ border: 1px solid var(--panel-border);
85
+ background-color: rgba(0,0,0,0.2);
86
+ box-shadow: none;
87
+ font-family: var(--app-font);
88
+ font-size: 1rem;
89
+ box-sizing: border-box;
90
+ color: var(--text-primary);
91
+ transition: all 0.25s ease-in-out;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  }
93
+ textarea:focus, input[type="text"]:focus {
94
+ outline: none;
95
+ border-color: var(--accent-glow);
96
+ box-shadow: 0 0 15px rgba(var(--accent-glow-rgb), 0.3);
97
+ background-color: rgba(0,0,0,0.3);
98
+ }
99
+
100
+ /* --- نمایش گوینده منتخب --- */
101
+ #selected-speaker-display { text-align: center; }
102
+ #selected-speaker-card { display: inline-flex; align-items: center; background: rgba(0,0,0,0.2); border-radius: 99px; padding: 10px; border: 1px solid var(--panel-border); }
103
+ #selected-speaker-card img { width: 70px; height: 70px; border-radius: 50%; object-fit: cover; margin-left: 15px; border: 3px solid rgba(255,255,255,0.5); }
104
+ #selected-speaker-info h3 { margin: 0; font-size: 1.4em; font-weight: 700; }
105
+ #selected-speaker-info p { margin: 5px 0 0; color: var(--text-secondary); font-size: 0.9em; }
106
+ #change-speaker-btn { display: block; margin: 1.2rem auto 0; padding: 10px 24px; border-radius: var(--radius-input); background-color: rgba(255,255,255,0.1); border: 1px solid var(--panel-border); cursor: pointer; font-family: var(--app-font); font-weight: 500; color: var(--text-primary); transition: all 0.2s ease; }
107
+ #change-speaker-btn:hover { background-color: rgba(255,255,255,0.2); border-color: rgba(255,255,255,0.3); transform: translateY(-2px); }
108
+
109
+ /* --- مودال گالری گویندگان --- */
110
+ #speaker-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(13, 15, 33, 0.7); backdrop-filter: blur(10px); display: none; align-items: center; justify-content: center; z-index: 1000; opacity: 0; transition: opacity 0.3s ease; }
111
+ #speaker-modal.visible { display: flex; opacity: 1; }
112
+ .modal-content { background: var(--panel-bg); border: 1px solid var(--panel-border); padding: 2rem; border-radius: var(--radius-card); width: 90%; max-width: 700px; max-height: 85vh; overflow-y: auto; transform: scale(0.95); transition: transform 0.3s ease; }
113
+ #speaker-modal.visible .modal-content { transform: scale(1); }
114
+ .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); }
115
+ .modal-header h2 { margin: 0; }
116
+ .close-modal-btn { background: none; border: none; font-size: 2.2rem; cursor: pointer; color: var(--text-secondary); transition: color 0.2s ease, transform 0.2s ease; line-height: 1; }
117
+ .close-modal-btn:hover { color: var(--text-primary); transform: rotate(90deg); }
118
+ #speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 1.5rem; }
119
+ .speaker-card { cursor: pointer; text-align: center; }
120
+ .speaker-card .speaker-visual { border: 3px solid transparent; border-radius: var(--radius-input); overflow: hidden; position: relative; background-color: rgba(0,0,0,0.2); transition: all 0.3s ease; }
121
+ .speaker-card:hover .speaker-visual { transform: translateY(-5px); box-shadow: 0 8px 25px rgba(0,0,0,0.3); }
122
+ .speaker-card input[type="radio"] { display: none; }
123
+ .speaker-card img { width: 100%; height: 120px; object-fit: cover; display: block; filter: saturate(0.8); transition: filter 0.3s; }
124
+ .speaker-card:hover img { filter: saturate(1); }
125
+ .speaker-card .speaker-name { padding: 0.8rem 0.5rem; font-weight: 500; font-size: 0.95em; color: var(--text-secondary); transition: color 0.2s; }
126
+ .speaker-card input[type="radio"]:checked + .speaker-visual { border-color: var(--accent-glow); box-shadow: 0 0 20px rgba(var(--accent-glow-rgb), 0.4); }
127
+ .speaker-card input[type="radio"]:checked + .speaker-visual .speaker-name { color: var(--accent-glow); font-weight: 700; }
128
+
129
+ /* --- Slider & Button & Output --- */
130
+ .slider-container { display: flex; align-items: center; gap: 1.5rem; }
131
+ input[type="range"] { flex-grow: 1; -webkit-appearance: none; appearance: none; width: 100%; height: 6px; background: rgba(0,0,0,0.3); border-radius: 5px; outline: none; }
132
+ input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 22px; height: 22px; background: var(--accent-glow); border-radius: 50%; cursor: pointer; box-shadow: 0 0 10px rgba(var(--accent-glow-rgb), 0.7); }
133
+ input[type="range"]::-moz-range-thumb { width: 22px; height: 22px; background: var(--accent-glow); border-radius: 50%; cursor: pointer; box-shadow: 0 0 10px rgba(var(--accent-glow-rgb), 0.7); }
134
+ #temperature-value { font-weight: 700; background-color: rgba(0,0,0,0.3); padding: 0.4rem 1rem; border-radius: 8px; border: 1px solid var(--panel-border); min-width: 45px; text-align: center; }
135
+ #generate-btn { width: 100%; padding: 1rem 1.5rem; font-size: 1.25em; font-weight: 700; font-family: var(--app-font); background: var(--button-bg); color: white; border: none; border-radius: var(--radius-input); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 5px 20px rgba(var(--accent-glow-rgb), 0.2); }
136
+ #generate-btn:hover:not(:disabled) { background: var(--button-hover-bg); transform: translateY(-3px); box-shadow: 0 8px 25px rgba(var(--accent-glow-rgb), 0.4); }
137
+ #generate-btn:disabled { background: rgba(107, 114, 128, 0.5); cursor: not-allowed; box-shadow: none; transform: none; }
138
+ #output-section { margin-top: 2.5rem; padding: 2rem; min-height: 180px; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 1rem; border-radius: var(--radius-input); background: rgba(0,0,0,0.2); border: 1px solid var(--panel-border); }
139
+ #status-message { font-weight: 500; color: var(--text-secondary); text-align: center; }
140
+ #audio-player { width: 100%; margin-top: 1rem; display: none; }
141
+ audio::-webkit-media-controls-panel { background-color: rgba(40, 45, 80, 0.8); }
142
+ audio::-webkit-media-controls-play-button { color: var(--accent-glow); }
143
+ audio::-webkit-media-controls-current-time-display, audio::-webkit-media-controls-time-remaining-display { color: var(--text-primary); }
144
+
145
+ /* --- انیمیشن پردازش فوق العاده زیبا --- */
146
+ #premium-loader { display: none; flex-direction: column; align-items: center; justify-content: center; gap: 2rem; }
147
+ .loader-container { position: relative; width: 150px; height: 150px; }
148
+ .loader-orb {
149
  position: absolute;
150
+ top: 50%; left: 50%;
151
+ width: 80px; height: 80px;
152
+ background: radial-gradient(circle, var(--accent-glow) 0%, transparent 70%);
153
+ border-radius: 50%;
154
+ transform: translate(-50%, -50%);
155
+ animation: orb-pulse 2.5s infinite ease-in-out;
156
  }
157
+ .loader-particle {
 
158
  position: absolute;
159
+ top: 50%; left: 50%;
160
+ width: 5px; height: 5px;
161
+ background: var(--accent-glow);
162
  border-radius: 50%;
163
+ box-shadow: 0 0 10px var(--accent-glow), 0 0 20px var(--accent-glow);
164
+ animation: orbit 4s infinite linear;
165
  }
166
+ .loader-particle:nth-child(2) { animation-delay: -0.5s; width: 4px; height: 4px; }
167
+ .loader-particle:nth-child(3) { animation-delay: -1s; width: 6px; height: 6px; }
168
+ .loader-particle:nth-child(4) { animation-delay: -1.5s; }
169
+ .loader-particle:nth-child(5) { animation-delay: -2s; width: 3px; height: 3px; }
170
+ .loader-particle:nth-child(6) { animation-delay: -2.5s; }
171
+ .loader-particle:nth-child(7) { animation-delay: -3s; width: 4px; height: 4px; }
172
+ .loader-particle:nth-child(8) { animation-delay: -3.5s; }
173
+ #loading-text { font-size: 1.1em; font-weight: 500; color: var(--text-primary); text-shadow: 0 0 10px rgba(var(--accent-glow-rgb), 0.3); }
174
 
175
+ @keyframes orb-pulse {
176
+ 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.8; }
177
+ 50% { transform: translate(-50%, -50%) scale(1.2); opacity: 1; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  }
179
+ @keyframes orbit {
180
+ from { transform: rotate(0deg) translateX(70px) rotate(0deg); }
181
+ to { transform: rotate(360deg) translateX(70px) rotate(-360deg); }
 
 
 
 
 
 
 
182
  }
183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  </style>
185
  </head>
186
  <body>
 
 
187
  <div class="container">
188
+ <div class="main-panel">
189
+ <header class="app-header">
190
+ <h1>آلفا TTS</h1>
191
+ <p>صدای آینده، خلق شده توسط هوش مصنوعی</p>
192
+ </header>
193
+
194
+ <main>
195
+ <form id="tts-form">
196
+ <div class="form-group">
197
+ <label for="text-input">📝 متن برای تبدیل</label>
198
+ <textarea id="text-input" rows="5" placeholder="اینجا متن خود را به فارسی وارد کنید...">این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است.</textarea>
199
+ </div>
200
+ <div class="form-group">
201
+ <label for="prompt-input">🗣️ سبک و لحن گفتار (اختیاری)</label>
202
+ <input type="text" id="prompt-input" value="با صدایی طبیعی و روان." placeholder="مثال: با لحنی شاد و پرانرژی">
203
+ </div>
204
+ <div class="form-group">
205
+ <label>🎤 گوینده منتخب</label>
206
+ <div id="selected-speaker-display">
207
+ <div id="selected-speaker-card">
208
+ <img id="selected-speaker-img" src="" alt="عکس گوینده">
209
+ <div id="selected-speaker-info">
210
+ <h3 id="selected-speaker-name"></h3>
211
+ <p>برای تغییر کلیک کنید</p>
212
+ </div>
213
  </div>
214
+ <button type="button" id="change-speaker-btn">تغییر گوینده</button>
215
  </div>
 
216
  </div>
217
+ <div class="form-group">
218
+ <label for="temperature-slider">🌡️ میزان خلاقیت صدا (0.1 تا 1.5)</label>
219
+ <div class="slider-container">
220
+ <input type="range" id="temperature-slider" min="0.1" max="1.5" step="0.05" value="0.9">
221
+ <span id="temperature-value">0.9</span>
222
+ </div>
 
223
  </div>
224
+ <button type="submit" id="generate-btn">🚀 تولید و پخش صدا</button>
225
+ </form>
226
+
227
+ <div id="output-section">
228
+ <div id="status-message">خروجی صدا در اینجا نمایش داده می‌شود</div>
229
+ <!-- انیمیشن لودینگ پرمیوم -->
230
+ <div id="premium-loader">
231
+ <div class="loader-container">
232
+ <div class="loader-orb"></div>
233
+ <span class="loader-particle"></span><span class="loader-particle"></span><span class="loader-particle"></span>
234
+ <span class="loader-particle"></span><span class="loader-particle"></span><span class="loader-particle"></span>
235
+ <span class="loader-particle"></span><span class="loader-particle"></span>
236
+ </div>
237
+ <p id="loading-text">در حال تبدیل متن به صدا با هوش مصنوعی آلفا...</p>
238
+ </div>
239
+ <audio id="audio-player" controls></audio>
240
  </div>
241
+ </main>
242
+ </div>
 
 
 
 
 
 
 
243
  </div>
244
 
 
245
  <div id="speaker-modal">
246
  <div class="modal-content">
247
  <div class="modal-header">
248
+ <h2>انتخاب گوینده</h2>
249
  <button type="button" class="close-modal-btn">×</button>
250
  </div>
251
  <div id="speaker-grid"></div>
252
  </div>
253
  </div>
254
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  <input type="hidden" id="selected_speaker_id_storage" value="Charon">
256
 
257
  <script>
 
259
  const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
260
  const JOIN_QUEUE_URL = `${HF_SPACE_URL}/gradio_api/queue/join`;
261
  const GET_DATA_URL_BASE = `${HF_SPACE_URL}/gradio_api/queue/data`;
262
+ const FILE_URL_BASE = `${HF_SPACE_URL}/gradio_api/file=`;
263
+ const FN_INDEX = 1;
264
+
265
+ const speakers = [
266
+ { id: "Charon", name: "شهاب (مرد)" }, { id: "Zephyr", name: "آوا (زن)" }, { id: "Achird", name: "نوید (مرد)" }, { id: "Zubenelgenubi", name: "رویا (زن)" }, { id: "Vindemiatrix", name: "کیان (مرد)" }, { id: "Sadachbia", name: "پریسا (زن)" }, { id: "Sadaltager", name: "آرش (مرد)" }, { id: "Sulafat", name: "شبنم (زن)" }, { id: "Laomedeia", name: "سهیل (مرد)" }, { id: "Achernar", name: "مریم (زن)" }, { id: "Alnilam", name: "بهرام (مرد)" }, { id: "Schedar", name: "نگار (زن)" }, { id: "Gacrux", name: "فرید (م��د)" }, { id: "Pulcherrima", name: "سارا (زن)" }, { id: "Umbriel", name: "مانی (مرد)" }, { id: "Algieba", name: "آناهیتا (زن)" }, { id: "Despina", name: "دلنواز (زن)" }, { id: "Erinome", name: "رسا (مرد)" }, { id: "Algenib", name: "امید (مرد)" }, { id: "Rasalthgeti", name: "الهه (زن)" }, { id: "Orus", name: "بردیا (مرد)" }, { id: "Aoede", name: "ترانه (زن)" }, { id: "Callirrhoe", name: "نیما (مرد)" }, { id: "Autonoe", name: "هستی (زن)" }, { id: "Enceladus", name: "کامیار (مرد)" }, { id: "Iapetus", name: "ستاره (زن)" }, { id: "Puck", name: "پویا (مرد)" }, { id: "Kore", name: "مهتاب (زن)" }, { id: "Fenrir", name: "سام (مرد)" }, { id: "Leda", name: "لیدا (زن)" }
267
+ ];
268
+
269
+ const form = document.getElementById('tts-form');
270
+ const textInput = document.getElementById('text-input');
271
+ const promptInput = document.getElementById('prompt-input');
272
+ const tempSlider = document.getElementById('temperature-slider');
273
+ const tempValueSpan = document.getElementById('temperature-value');
274
+ const generateBtn = document.getElementById('generate-btn');
275
+
276
+ const statusMessage = document.getElementById('status-message');
277
+ const audioPlayer = document.getElementById('audio-player');
278
+ const premiumLoader = document.getElementById('premium-loader');
279
+
280
+ const selectedSpeakerIdStorage = document.getElementById('selected_speaker_id_storage');
281
+ const speakerModal = document.getElementById('speaker-modal');
282
+ const changeSpeakerBtn = document.getElementById('change-speaker-btn');
283
+ const closeModalBtn = document.querySelector('.close-modal-btn');
284
+ const speakerGridInModal = document.getElementById('speaker-grid');
285
+ const selectedSpeakerImgDisplay = document.getElementById('selected-speaker-img');
286
+ const selectedSpeakerNameDisplay = document.getElementById('selected-speaker-name');
287
+
288
+ function getSpeakerById(id) { return speakers.find(s => s.id === id); }
289
+
290
+ function getImageUrl(speaker, index) {
291
+ const gender = speaker.name.includes('(مرد)') ? 'men' : (speaker.name.includes('(زن)') ? 'women' : 'lego');
292
+ const imageIndex = (index * 11 + 7) % 100;
293
+ return `https://randomuser.me/api/portraits/${gender}/${imageIndex}.jpg`;
294
+ }
295
+
296
+ function updateSelectedSpeakerDisplay(speakerId) {
297
+ const speaker = getSpeakerById(speakerId);
298
+ if (speaker) {
299
+ const speakerIndex = speakers.findIndex(s => s.id === speakerId);
300
+ selectedSpeakerImgDisplay.src = getImageUrl(speaker, speakerIndex);
301
+ selectedSpeakerNameDisplay.textContent = speaker.name;
302
+ selectedSpeakerIdStorage.value = speaker.id;
303
+ }
304
+ }
305
+
306
+ function createSpeakerCardsInModal() {
307
+ speakerGridInModal.innerHTML = '';
308
+ speakers.forEach((speaker, index) => {
309
+ const card = document.createElement('label');
310
+ card.className = 'speaker-card';
311
+ card.setAttribute('for', `modal-speaker-${speaker.id}`);
312
+ const isChecked = speaker.id === selectedSpeakerIdStorage.value ? 'checked' : '';
313
+
314
+ card.innerHTML = `
315
+ <input type="radio" name="modal_speaker_selection" value="${speaker.id}" id="modal-speaker-${speaker.id}" ${isChecked}>
316
+ <div class="speaker-visual">
317
+ <img src="${getImageUrl(speaker, index)}" alt="عکس گوینده ${speaker.name}" loading="lazy">
318
+ <div class="speaker-name">${speaker.name}</div>
319
+ </div>
320
+ `;
321
+
322
+ card.addEventListener('click', () => {
323
+ updateSelectedSpeakerDisplay(speaker.id);
324
+ setTimeout(() => speakerModal.classList.remove('visible'), 200);
325
+ });
326
+
327
+ speakerGridInModal.appendChild(card);
328
+ });
329
+ }
330
+
331
+ changeSpeakerBtn.addEventListener('click', () => {
332
+ createSpeakerCardsInModal();
333
+ speakerModal.classList.add('visible');
334
+ });
335
+ closeModalBtn.addEventListener('click', () => speakerModal.classList.remove('visible'));
336
+ speakerModal.addEventListener('click', (e) => {
337
+ if (e.target === speakerModal) speakerModal.classList.remove('visible');
338
+ });
339
+
340
+ tempSlider.addEventListener('input', () => { tempValueSpan.textContent = tempSlider.value; });
341
+
342
+ function showLoadingState() {
343
+ statusMessage.style.display = 'none';
344
+ audioPlayer.style.display = 'none';
345
+ audioPlayer.src = '';
346
+ premiumLoader.style.display = 'flex';
347
+ generateBtn.disabled = true;
348
+ generateBtn.textContent = 'در حال خلق صدا...';
349
+ }
350
+
351
+ function showResultState(isSuccess, message = '') {
352
+ premiumLoader.style.display = 'none';
353
+ if (isSuccess) {
354
+ statusMessage.style.display = 'none';
355
+ audioPlayer.style.display = 'block';
356
+ audioPlayer.play();
357
+ } else {
358
+ statusMessage.textContent = message || 'یک خطای ناشناخته رخ داد.';
359
+ statusMessage.style.display = 'block';
360
+ audioPlayer.style.display = 'none';
361
+ }
362
+ generateBtn.disabled = false;
363
+ generateBtn.textContent = '🚀 تولید و پخش صدا';
364
+ }
365
+
366
+ async function generateAudio(event) {
367
+ event.preventDefault();
368
+ showLoadingState();
369
+
370
+ const text = textInput.value;
371
+ if (!text.trim()) {
372
+ showResultState(false, 'خطا: متن ورودی نمی‌تواند خالی باشد.');
373
+ return;
374
+ }
375
+
376
+ const prompt = promptInput.value;
377
+ const temperature = parseFloat(tempSlider.value);
378
+ const selectedSpeaker = selectedSpeakerIdStorage.value;
379
+ const sessionHash = Math.random().toString(36).substring(2);
380
+ const payload = { fn_index: FN_INDEX, data: [false, null, text, prompt, selectedSpeaker, temperature], event_data: null, session_hash: sessionHash };
381
+
382
+ try {
383
+ const joinQueueResponse = await fetch(JOIN_QUEUE_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });
384
+ if (!joinQueueResponse.ok) { throw new Error(`خطا در اتصال به صف (${joinQueueResponse.status})`); }
385
+
386
+ const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
387
+ const reader = dataResponse.body.getReader();
388
+ const decoder = new TextDecoder();
389
+ let finalFilePath = null;
390
+ let buffer = '';
391
+
392
+ while (true) {
393
+ const { value, done } = await reader.read();
394
+ if (done) break;
395
+ buffer += decoder.decode(value, { stream: true });
396
+ const lines = buffer.split('\n');
397
+ buffer = lines.pop();
398
+ for (const line of lines) {
399
+ if (!line.startsWith('data:')) continue;
400
+ try {
401
+ const data = JSON.parse(line.substring(5));
402
+ if (data.msg === 'process_completed') {
403
+ if (data.success && data.output.data && data.output.data[0] && (data.output.data[0].name || data.output.data[0].path)) {
404
+ finalFilePath = data.output.data[0].name || data.output.data[0].path;
405
+ }
406
+ break;
407
+ }
408
+ } catch (e) { /* ignore parse errors */ }
409
+ }
410
+ if (finalFilePath) break;
411
+ }
412
+
413
+ if (finalFilePath) {
414
+ const audioUrl = `${FILE_URL_BASE}${finalFilePath}`;
415
+ audioPlayer.src = audioUrl;
416
+ showResultState(true);
417
+ } else {
418
+ throw new Error('فایل صوتی از سرور دریافت نشد.');
419
+ }
420
+ } catch (error) {
421
+ console.error('یک خطا در فرآیند رخ داد:', error);
422
+ showResultState(false, `یک خطا رخ داد: ${error.message}`);
423
+ }
424
+ }
425
+
426
+ updateSelectedSpeakerDisplay(selectedSpeakerIdStorage.value);
427
+ form.addEventListener('submit', generateAudio);
428
+ });
429
+ </script>
430
+ </body>
431
+ </html>