Update index.html
Browse files- 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
|
7 |
<style>
|
8 |
-
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800
|
9 |
|
10 |
:root {
|
11 |
--app-font: 'Vazirmatn', sans-serif;
|
12 |
-
--app-bg: #
|
13 |
-
--panel-bg: #
|
14 |
-
--panel-border: #
|
15 |
-
--text-primary: #
|
16 |
-
--text-secondary: #
|
17 |
-
--accent-primary: #
|
18 |
-
--accent-primary-hover: #
|
19 |
-
--accent-secondary: #
|
20 |
-
--accent-secondary-hover: #
|
|
|
|
|
21 |
|
22 |
-
--radius-card:
|
23 |
-
--radius-input:
|
24 |
-
--shadow-
|
25 |
-
--shadow-
|
26 |
-
--shadow-
|
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.
|
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;
|
44 |
-
padding-top: 3rem;
|
45 |
-
padding-bottom: 3rem;
|
46 |
}
|
47 |
|
48 |
.container {
|
49 |
-
max-width:
|
50 |
width: 90%;
|
51 |
margin: 0 auto;
|
52 |
}
|
53 |
|
54 |
.app-header {
|
55 |
-
padding:
|
56 |
text-align: center;
|
57 |
-
margin-bottom:
|
58 |
}
|
59 |
.app-header h1 {
|
60 |
-
font-size:
|
61 |
font-weight: 800;
|
62 |
margin:0 0 0.5rem 0;
|
63 |
-
|
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.
|
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:
|
80 |
border: 1px solid var(--panel-border);
|
81 |
}
|
82 |
|
83 |
-
.form-group { margin-bottom: 2.
|
84 |
label {
|
85 |
display: block;
|
86 |
font-weight: 600;
|
87 |
color: var(--text-primary);
|
88 |
font-size: 1.05em;
|
89 |
-
margin-bottom: 0.
|
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:
|
97 |
color: var(--text-primary);
|
98 |
-
box-shadow:
|
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(--
|
107 |
-
box-shadow:
|
108 |
-
background-color: #
|
109 |
}
|
110 |
-
textarea { min-height:
|
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,
|
118 |
border-radius: 99px;
|
119 |
-
padding: 12px
|
120 |
-
box-shadow:
|
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:
|
127 |
}
|
128 |
#selected-speaker-card img {
|
129 |
-
width:
|
130 |
border-radius: 50%;
|
131 |
object-fit: cover;
|
132 |
-
margin-left:
|
133 |
border: 3px solid var(--accent-secondary);
|
134 |
-
box-shadow: 0 0
|
135 |
-
background-color: #
|
136 |
}
|
137 |
-
#selected-speaker-info h3 { margin: 0; font-size: 1.
|
138 |
-
#selected-speaker-info p { margin: 4px 0 0; color: var(--text-secondary); font-size: 0.
|
|
|
139 |
#change-speaker-btn {
|
140 |
-
display: block; margin:
|
141 |
-
padding: 10px
|
142 |
border-radius: var(--radius-input);
|
143 |
background-color: transparent;
|
144 |
-
border:
|
145 |
color: var(--accent-primary);
|
146 |
cursor: pointer;
|
147 |
-
font-family: var(--app-font); font-weight:
|
148 |
transition: all 0.2s ease;
|
149 |
}
|
150 |
#change-speaker-btn:hover {
|
151 |
-
background-color:
|
152 |
-
|
153 |
-
transform: translateY(-
|
|
|
154 |
}
|
155 |
|
156 |
/* --- مودال گالری گویندگان --- */
|
157 |
#speaker-modal {
|
158 |
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
159 |
-
background-color: rgba(
|
160 |
-
backdrop-filter: blur(
|
161 |
display: none; align-items: center; justify-content: center;
|
162 |
z-index: 1000; opacity: 0;
|
163 |
-
transition: opacity 0.
|
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:
|
171 |
max-height: 85vh; overflow-y: auto;
|
172 |
-
transform: scale(0.
|
173 |
-
transition: transform 0.
|
174 |
border: 1px solid var(--panel-border);
|
175 |
-
box-shadow:
|
|
|
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.
|
180 |
-
.close-modal-btn { background: none; border: none; font-size: 2.
|
181 |
-
.close-modal-btn:hover { color: var(--
|
182 |
|
183 |
-
#speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(
|
184 |
@media (min-width: 576px) { #speaker-grid { grid-template-columns: repeat(4, 1fr); } }
|
185 |
-
.speaker-card { cursor: pointer; transition: all 0.
|
186 |
.speaker-card .speaker-visual {
|
187 |
border: 3px solid transparent;
|
188 |
border-radius: var(--radius-card);
|
189 |
overflow: hidden;
|
190 |
-
box-shadow:
|
191 |
position: relative;
|
192 |
-
background-color: #
|
193 |
-
transition: all 0.
|
194 |
}
|
195 |
.speaker-card:hover .speaker-visual {
|
196 |
transform: translateY(-5px) scale(1.03);
|
197 |
-
box-shadow:
|
198 |
}
|
199 |
.speaker-card input[type="radio"] { display: none; }
|
200 |
.speaker-card img {
|
201 |
-
width: 100%; height:
|
202 |
object-fit: cover; display: block;
|
203 |
-
background-color: #
|
204 |
-
|
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
|
|
|
211 |
.speaker-card input[type="radio"]:checked + .speaker-visual {
|
212 |
border-color: var(--accent-secondary);
|
213 |
-
box-shadow: 0 0
|
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:
|
225 |
-
background:
|
226 |
-
border-radius:
|
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
|
237 |
-
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
}
|
249 |
#temperature-value {
|
250 |
-
font-weight:
|
251 |
-
padding: 0.
|
252 |
border-radius: 8px;
|
253 |
border: 1px solid var(--panel-border);
|
254 |
-
min-width:
|
255 |
color: var(--text-primary);
|
256 |
}
|
257 |
|
258 |
#generate-btn {
|
259 |
-
width: 100%; padding: 1.
|
260 |
-
font-size: 1.
|
261 |
font-family: var(--app-font);
|
262 |
-
background: linear-gradient(
|
263 |
-
color: #
|
264 |
border: none;
|
265 |
border-radius: var(--radius-input);
|
266 |
cursor: pointer;
|
267 |
transition: all 0.3s ease;
|
268 |
-
box-shadow: 0
|
269 |
position: relative;
|
270 |
overflow: hidden;
|
271 |
}
|
272 |
-
#generate-btn::before {
|
273 |
-
content: '';
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
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
|
286 |
}
|
287 |
#generate-btn:disabled {
|
288 |
-
background:
|
289 |
-
cursor: not-allowed; box-shadow: none; color: #
|
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:
|
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 |
-
|
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;
|
311 |
-
#audio-player::-webkit-media-controls-panel { background-color:
|
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:
|
322 |
width: 100%;
|
323 |
-
min-height: 150px;
|
|
|
324 |
}
|
325 |
|
326 |
-
.
|
327 |
-
width:
|
328 |
-
height:
|
329 |
position: relative;
|
330 |
-
|
331 |
}
|
332 |
-
|
333 |
-
.
|
334 |
-
.aurora-loader::after {
|
335 |
-
content: "";
|
336 |
position: absolute;
|
337 |
-
|
338 |
-
|
339 |
-
width: 100%;
|
340 |
-
height: 100%;
|
341 |
border-radius: 50%;
|
342 |
-
background:
|
343 |
-
|
344 |
-
|
345 |
-
animation:
|
|
|
346 |
}
|
347 |
|
348 |
-
.
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
354 |
}
|
355 |
|
356 |
-
@keyframes
|
357 |
-
0% { transform:
|
358 |
-
50% { transform:
|
359 |
-
100% { transform: translate(-50%, -50%) rotate(360deg) scale(0.8); }
|
360 |
}
|
361 |
-
@keyframes
|
362 |
-
0% {
|
363 |
-
|
364 |
}
|
365 |
|
366 |
#loading-text {
|
367 |
-
font-size: 1.
|
368 |
-
font-weight:
|
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
|
381 |
</header>
|
382 |
|
383 |
<main class="main-content">
|
384 |
<form id="tts-form">
|
385 |
<div class="form-group">
|
386 |
-
<label for="text-input">📝 متن
|
387 |
-
<textarea id="text-input" rows="4" placeholder="متن خود را
|
388 |
</div>
|
389 |
<div class="form-group">
|
390 |
-
<label for="prompt-input">🗣️ سبک و لحن گفتار (
|
391 |
-
<input type="text" id="prompt-input" value="با صدایی
|
392 |
</div>
|
393 |
|
394 |
<div class="form-group">
|
395 |
-
<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
|
402 |
</div>
|
403 |
</div>
|
404 |
-
<button type="button" id="change-speaker-btn"
|
405 |
</div>
|
406 |
</div>
|
407 |
|
408 |
<div class="form-group">
|
409 |
-
<label for="temperature-slider">🌡️ میزان خلاقیت و
|
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"
|
417 |
</form>
|
418 |
|
419 |
<div id="output-section">
|
420 |
-
<div id="status-message"
|
421 |
<div id="loading-animation-wrapper">
|
422 |
-
<div class="
|
423 |
-
|
|
|
|
|
|
|
|
|
|
|
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
|
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') {
|
478 |
const gender = speaker.name.includes('(مرد)') ? 'men' : (speaker.name.includes('(زن)') ? 'women' : 'lego');
|
479 |
-
const imageIndex = (index *
|
480 |
-
|
481 |
-
|
482 |
-
if (size === 'large') portraitSize = ''; // API uses no path for largest, just /men/idx.jpg
|
483 |
|
484 |
-
return `https://randomuser.me/api/portraits/${
|
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);
|
516 |
});
|
517 |
|
518 |
speakerGridInModal.appendChild(card);
|
519 |
});
|
520 |
}
|
521 |
|
522 |
-
|
523 |
createSpeakerCardsInModal();
|
524 |
speakerModal.classList.add('visible');
|
525 |
-
}
|
526 |
-
|
527 |
-
|
528 |
-
|
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) {
|
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();
|
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
|
576 |
-
const
|
577 |
-
const
|
578 |
const sessionHash = Math.random().toString(36).substring(2);
|
579 |
|
580 |
const payload = {
|
581 |
fn_index: FN_INDEX,
|
582 |
-
data: [false, null, text,
|
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(`خطا در
|
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
|
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);
|
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';
|