Update index.html
Browse files- index.html +385 -363
index.html
CHANGED
@@ -2,437 +2,459 @@
|
|
2 |
<html lang="fa" dir="rtl">
|
3 |
<head>
|
4 |
<meta charset="UTF-8">
|
5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0
|
6 |
-
<title
|
7 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
8 |
<style>
|
9 |
-
|
10 |
-
|
11 |
-
@theme inline {
|
12 |
-
--color-background: var(--background);
|
13 |
-
--color-foreground: var(--foreground);
|
14 |
-
--radius-lg: var(--radius);
|
15 |
-
--popover: oklch(1 0 0);
|
16 |
-
--popover-foreground: oklch(0.145 0 0);
|
17 |
-
--border: oklch(0.922 0 0);
|
18 |
-
}
|
19 |
:root {
|
20 |
-
--
|
21 |
-
--
|
22 |
-
--
|
23 |
-
--
|
24 |
-
|
25 |
-
|
26 |
-
--
|
27 |
-
--
|
28 |
-
--
|
29 |
-
--
|
30 |
-
--border:
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
}
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
46 |
display: flex;
|
47 |
-
|
48 |
-
pointer-events: none; /* Wrapper itself doesn't catch events */
|
49 |
}
|
50 |
|
51 |
-
.
|
|
|
|
|
|
|
52 |
width: 100%;
|
53 |
-
|
54 |
-
border-width: 1px; /* Border for the popover container */
|
55 |
-
border-color: var(--border);
|
56 |
-
background-color: var(--popover);
|
57 |
-
color: var(--popover-foreground);
|
58 |
-
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
59 |
-
outline: none;
|
60 |
-
transition: opacity 0.3s ease-out, transform 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
61 |
-
opacity: 0;
|
62 |
-
transform: translateY(-100%) scale(0.9); /* Start off-screen top and slightly scaled down */
|
63 |
-
pointer-events: none; /* Content not interactive until 'open' */
|
64 |
}
|
65 |
|
66 |
-
.
|
67 |
-
|
68 |
-
|
69 |
-
|
|
|
|
|
|
|
|
|
70 |
}
|
71 |
-
|
72 |
-
.
|
73 |
-
|
74 |
-
font-
|
75 |
-
|
76 |
-
|
77 |
-
/* For dark mode, text color in notification should be dark on light blue */
|
78 |
-
color: oklch(0.145 0 0); /* Ensuring text readability on light blue bg */
|
79 |
}
|
80 |
-
|
81 |
-
|
82 |
-
|
|
|
|
|
|
|
83 |
}
|
84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
width: 100%;
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
|
|
|
|
|
|
|
|
|
|
97 |
}
|
98 |
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
background-color: #e5e7eb; /* bg-gray-200 */
|
104 |
-
cursor: pointer;
|
105 |
-
transition: background-color 0.2s;
|
106 |
}
|
107 |
-
|
108 |
-
|
|
|
|
|
109 |
}
|
110 |
-
.header-button svg { opacity: 0.7; stroke: #374151 /* gray-700 */; }
|
111 |
|
112 |
-
.
|
113 |
-
|
|
|
|
|
114 |
}
|
115 |
-
|
116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
}
|
118 |
-
.dark .header-button svg { opacity: 0.8; stroke: oklch(0.85 0 0); }
|
119 |
|
|
|
|
|
|
|
120 |
|
121 |
-
|
122 |
-
|
|
|
|
|
|
|
123 |
|
124 |
-
|
125 |
-
|
|
|
|
|
|
|
126 |
|
127 |
-
|
128 |
-
|
129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
}
|
131 |
-
.footer-controls.layout-default { justify-content: space-between; }
|
132 |
-
.footer-controls.layout-with-small-logo { justify-content: space-around; }
|
133 |
|
134 |
-
.
|
135 |
-
|
136 |
-
padding:
|
137 |
-
|
138 |
-
|
139 |
-
|
|
|
|
|
|
|
140 |
cursor: pointer;
|
141 |
-
transition:
|
142 |
-
|
143 |
-
|
144 |
-
transform: scale(1.05);
|
145 |
-
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -2px rgba(0,0,0,0.05);
|
146 |
-
}
|
147 |
-
.cam-button-color { background-color: #E0ECFF; } /* Now left */
|
148 |
-
.mic-button-color { background-color: #fecdd3; } /* Now right */
|
149 |
-
.dark .cam-button-color { background-color: #223355; } /* Darker blue */
|
150 |
-
.dark .mic-button-color { background-color: #5C2129; } /* Darker red */
|
151 |
-
|
152 |
-
|
153 |
-
/* Switch Camera Button Styles */
|
154 |
-
.switch-camera-button-container {
|
155 |
-
position: absolute;
|
156 |
-
bottom: calc(100% + 0.65rem); /* Adjusted spacing, was 0.75rem */
|
157 |
-
left: 50%;
|
158 |
-
z-index: 5;
|
159 |
-
opacity: 0;
|
160 |
-
transform: translateY(15px) scale(0.7) translateX(-50%); /* Ensure centered above */
|
161 |
-
pointer-events: none;
|
162 |
-
transition: opacity 0.35s cubic-bezier(0.68, -0.55, 0.27, 1.55), transform 0.35s cubic-bezier(0.68, -0.55, 0.27, 1.55);
|
163 |
-
transform-origin: center bottom; /* Animation origin */
|
164 |
-
}
|
165 |
-
.switch-camera-button-container.visible {
|
166 |
-
opacity: 1;
|
167 |
-
transform: translateY(0) scale(1) translateX(-50%);
|
168 |
-
pointer-events: auto;
|
169 |
-
}
|
170 |
-
.switch-camera-button-content {
|
171 |
-
width: 48px; /* Reduced from 52px */
|
172 |
-
height: 48px; /* Reduced from 52px */
|
173 |
-
background-color: var(--background);
|
174 |
-
border: 1px solid var(--border);
|
175 |
-
border-radius: 9999px;
|
176 |
display: flex;
|
177 |
align-items: center;
|
178 |
justify-content: center;
|
179 |
-
|
180 |
-
cursor: pointer;
|
181 |
-
transform-origin: center; /* For hover animation */
|
182 |
-
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
|
183 |
}
|
184 |
-
|
185 |
-
|
186 |
-
|
|
|
|
|
187 |
}
|
188 |
-
|
189 |
-
|
|
|
|
|
|
|
190 |
}
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
|
|
|
|
|
|
196 |
}
|
197 |
-
|
198 |
-
|
|
|
199 |
}
|
200 |
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
extend: {
|
207 |
-
colors: { /* ... (same as before) ... */ },
|
208 |
-
borderRadius: { /* ... (same as before) ... */ },
|
209 |
-
inset: { /* ... (same as before) ... */ },
|
210 |
-
keyframes: {
|
211 |
-
'popover-drop-in': {
|
212 |
-
'0%': { opacity: '0', transform: 'translateY(-100%) scale(0.9)' },
|
213 |
-
'70%': { opacity: '1', transform: 'translateY(5px) scale(1.02)' }, /* Slight overshoot */
|
214 |
-
'100%': { opacity: '1', transform: 'translateY(0) scale(1)' },
|
215 |
-
},
|
216 |
-
'popover-lift-out': {
|
217 |
-
'0%': { opacity: '1', transform: 'translateY(0) scale(1)' },
|
218 |
-
'100%': { opacity: '0', transform: 'translateY(-100%) scale(0.9)' },
|
219 |
-
}
|
220 |
-
},
|
221 |
-
animation: {
|
222 |
-
'popover-open-top-center': 'popover-drop-in 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55) forwards',
|
223 |
-
'popover-close-top-center': 'popover-lift-out 0.3s ease-in forwards',
|
224 |
-
}
|
225 |
-
}
|
226 |
-
}
|
227 |
}
|
228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
229 |
</head>
|
230 |
-
<body
|
231 |
-
|
232 |
-
<div class="
|
233 |
-
<
|
234 |
-
|
235 |
-
<
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
</div>
|
248 |
</div>
|
249 |
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
<
|
254 |
-
|
255 |
-
|
|
|
256 |
</div>
|
257 |
</div>
|
258 |
|
259 |
-
|
260 |
-
|
261 |
-
<
|
262 |
-
|
263 |
-
|
264 |
-
<!-- Footer Controls - Order Changed -->
|
265 |
-
<div id="footer-controls" class="footer-controls layout-default">
|
266 |
-
<!-- Cam Button Wrapper (Now on the Left) -->
|
267 |
-
<div id="cam-button-wrapper" class="relative flex justify-center">
|
268 |
-
<div id="cam-button" class="control-button cam-button-color">
|
269 |
-
<!-- Cam icon will be injected here by JS -->
|
270 |
-
</div>
|
271 |
-
<div id="switch-camera-button-container" class="switch-camera-button-container">
|
272 |
-
<button id="switch-camera-button" aria-label="Switch Camera" class="switch-camera-button-content">
|
273 |
-
<!-- New Switch Camera Icon -->
|
274 |
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
275 |
-
<path d="M11 19H4a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h5"/>
|
276 |
-
<path d="M13 5h7a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-5"/>
|
277 |
-
<path d="m17 12-3-3 3-3"/>
|
278 |
-
<path d="m7 12 3 3-3 3"/>
|
279 |
-
</svg>
|
280 |
-
</button>
|
281 |
-
</div>
|
282 |
-
</div>
|
283 |
-
|
284 |
-
<div id="small-logo-container" class="hidden"></div>
|
285 |
-
|
286 |
-
<!-- Mic Button (Now on the Right) -->
|
287 |
-
<div id="mic-button" class="control-button mic-button-color">
|
288 |
-
<!-- Mic icon will be injected here by JS -->
|
289 |
-
</div>
|
290 |
</div>
|
291 |
</div>
|
292 |
-
</div>
|
293 |
-
</div>
|
294 |
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
|
|
|
|
|
|
|
|
|
|
302 |
</div>
|
303 |
|
304 |
<script>
|
305 |
-
|
306 |
-
|
307 |
-
const
|
308 |
-
const
|
309 |
-
|
310 |
-
const
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
357 |
} else {
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
}
|
|
|
362 |
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
|
|
|
|
377 |
}
|
378 |
|
379 |
-
//
|
380 |
-
|
381 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
382 |
|
383 |
-
|
384 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
385 |
|
386 |
-
// Notification Popover Animation
|
387 |
-
if (isNotificationOpen) {
|
388 |
-
notificationPopover.classList.remove('animate-popover-close-top-center');
|
389 |
-
notificationPopover.classList.add('open');
|
390 |
-
notificationPopover.classList.add('animate-popover-open-top-center');
|
391 |
-
} else {
|
392 |
-
if (notificationPopover.classList.contains('open')) {
|
393 |
-
notificationPopover.classList.remove('animate-popover-open-top-center');
|
394 |
-
notificationPopover.classList.add('animate-popover-close-top-center');
|
395 |
-
setTimeout(() => {
|
396 |
-
if (!isNotificationOpen) { // Check state again before removing
|
397 |
-
notificationPopover.classList.remove('open');
|
398 |
-
}
|
399 |
-
}, 300); // Match close animation duration (0.3s)
|
400 |
} else {
|
401 |
-
|
402 |
-
|
403 |
-
notificationPopover.classList.remove('animate-popover-open-top-center');
|
404 |
-
notificationPopover.classList.remove('animate-popover-close-top-center');
|
405 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
406 |
}
|
407 |
}
|
|
|
|
|
408 |
|
409 |
-
|
410 |
-
|
411 |
-
isNotificationOpen = !isNotificationOpen;
|
412 |
-
updateUI();
|
413 |
-
});
|
414 |
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
isNotificationOpen = false;
|
419 |
-
updateUI();
|
420 |
-
}
|
421 |
});
|
422 |
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
switchCameraButton.addEventListener('click', () => {
|
427 |
-
alert('دکمه تعویض دوربین کلیک شد!');
|
428 |
-
// Future: Implement actual camera switching logic
|
429 |
});
|
430 |
|
431 |
-
//
|
432 |
-
|
433 |
|
434 |
-
// Dark mode toggle for testing (optional)
|
435 |
-
// document.documentElement.classList.add('dark'); // Uncomment to test dark mode
|
436 |
</script>
|
437 |
</body>
|
438 |
</html>
|
|
|
2 |
<html lang="fa" dir="rtl">
|
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: #2c3e50;
|
13 |
+
--app-header-grad-end: #3498db;
|
14 |
+
--app-panel-bg: #ffffff;
|
15 |
+
--app-input-bg: #f8f9fa;
|
16 |
+
--app-button-bg: #3498db;
|
17 |
+
--app-button-hover-bg: #2980b9;
|
18 |
+
--app-main-bg: #ecf0f1;
|
19 |
+
--app-text-primary: #2c3e50;
|
20 |
+
--app-text-secondary: #7f8c8d;
|
21 |
+
--app-border-color: #dee2e6;
|
22 |
+
--radius-card: 20px;
|
23 |
+
--radius-input: 12px;
|
24 |
+
--shadow-card: 0 10px 40px -10px rgba(0, 0, 0, 0.15);
|
25 |
+
--shadow-button: 0 4px 15px -3px rgba(52, 152, 219, 0.4);
|
26 |
+
}
|
27 |
+
|
28 |
+
body {
|
29 |
+
font-family: var(--app-font);
|
30 |
+
direction: rtl;
|
31 |
+
background-color: var(--app-main-bg);
|
32 |
+
color: var(--app-text-primary);
|
33 |
+
font-size: 16px;
|
34 |
+
line-height: 1.7;
|
35 |
+
margin: 0;
|
36 |
+
padding: 0;
|
37 |
+
min-height: 100vh;
|
38 |
display: flex;
|
39 |
+
flex-direction: column;
|
|
|
40 |
}
|
41 |
|
42 |
+
.container {
|
43 |
+
max-width: 720px;
|
44 |
+
margin: 0 auto;
|
45 |
+
padding: 20px;
|
46 |
width: 100%;
|
47 |
+
box-sizing: border-box;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
}
|
49 |
|
50 |
+
.app-header {
|
51 |
+
padding: 3.5rem 1.5rem 6rem 1.5rem;
|
52 |
+
text-align: center;
|
53 |
+
background-image: linear-gradient(135deg, var(--app-header-grad-start) 0%, var(--app-header-grad-end) 100%);
|
54 |
+
color: white;
|
55 |
+
border-bottom-left-radius: var(--radius-card);
|
56 |
+
border-bottom-right-radius: var(--radius-card);
|
57 |
+
box-shadow: 0 8px 25px -8px rgba(0, 0, 0, 0.2);
|
58 |
}
|
59 |
+
|
60 |
+
.app-header h1 {
|
61 |
+
font-size: 2.8em;
|
62 |
+
font-weight: 800;
|
63 |
+
margin: 0 0 0.5rem 0;
|
64 |
+
text-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
|
|
|
|
65 |
}
|
66 |
+
|
67 |
+
.app-header p {
|
68 |
+
font-size: 1.2em;
|
69 |
+
color: rgba(255,255,255,0.9);
|
70 |
+
margin-top: 0;
|
71 |
+
opacity: 0.95;
|
72 |
}
|
73 |
|
74 |
+
.main-panel {
|
75 |
+
padding: 2rem;
|
76 |
+
margin: -4.5rem auto 2rem auto;
|
77 |
+
width: 90%;
|
78 |
+
background-color: var(--app-panel-bg);
|
79 |
+
border-radius: var(--radius-card);
|
80 |
+
box-shadow: var(--shadow-card);
|
81 |
+
position: relative;
|
82 |
+
z-index: 10;
|
83 |
+
}
|
84 |
|
85 |
+
.form-group {
|
86 |
+
margin-bottom: 1.8rem;
|
87 |
+
}
|
88 |
+
|
89 |
+
label {
|
90 |
+
display: block;
|
91 |
+
font-weight: 700;
|
92 |
+
color: var(--app-text-primary);
|
93 |
+
font-size: 1em;
|
94 |
+
margin-bottom: 0.75rem;
|
95 |
+
}
|
96 |
+
|
97 |
+
textarea, select, input[type="range"] {
|
98 |
width: 100%;
|
99 |
+
padding: 0.8rem 1rem;
|
100 |
+
border-radius: var(--radius-input);
|
101 |
+
border: 1px solid var(--app-border-color);
|
102 |
+
background-color: var(--app-input-bg);
|
103 |
+
box-shadow: inset 0 1px 3px rgba(0,0,0,0.06);
|
104 |
+
font-family: var(--app-font);
|
105 |
+
font-size: 1rem;
|
106 |
+
transition: border-color 0.3s, box-shadow 0.3s;
|
107 |
+
box-sizing: border-box;
|
108 |
}
|
109 |
|
110 |
+
textarea:focus, select:focus {
|
111 |
+
outline: none;
|
112 |
+
border-color: var(--app-button-bg);
|
113 |
+
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
|
|
|
|
|
|
|
114 |
}
|
115 |
+
|
116 |
+
textarea {
|
117 |
+
resize: vertical;
|
118 |
+
min-height: 120px;
|
119 |
}
|
|
|
120 |
|
121 |
+
.speaker-selection {
|
122 |
+
display: flex;
|
123 |
+
gap: 1.5rem;
|
124 |
+
align-items: center;
|
125 |
}
|
126 |
+
|
127 |
+
.speaker-selection .dropdown-container {
|
128 |
+
flex-grow: 1;
|
129 |
+
}
|
130 |
+
|
131 |
+
.speaker-image-container {
|
132 |
+
width: 100px;
|
133 |
+
height: 100px;
|
134 |
+
border-radius: 50%;
|
135 |
+
overflow: hidden;
|
136 |
+
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
|
137 |
+
border: 3px solid white;
|
138 |
+
flex-shrink: 0;
|
139 |
+
transition: transform 0.3s ease-in-out;
|
140 |
}
|
|
|
141 |
|
142 |
+
.speaker-image-container:hover {
|
143 |
+
transform: scale(1.1);
|
144 |
+
}
|
145 |
|
146 |
+
#speakerImage {
|
147 |
+
width: 100%;
|
148 |
+
height: 100%;
|
149 |
+
object-fit: cover;
|
150 |
+
}
|
151 |
|
152 |
+
.slider-group {
|
153 |
+
display: flex;
|
154 |
+
align-items: center;
|
155 |
+
gap: 1rem;
|
156 |
+
}
|
157 |
|
158 |
+
input[type="range"] {
|
159 |
+
padding: 0;
|
160 |
+
flex-grow: 1;
|
161 |
+
}
|
162 |
+
|
163 |
+
#temperatureValue {
|
164 |
+
font-weight: 700;
|
165 |
+
background-color: var(--app-input-bg);
|
166 |
+
padding: 5px 12px;
|
167 |
+
border-radius: var(--radius-input);
|
168 |
+
min-width: 40px;
|
169 |
+
text-align: center;
|
170 |
}
|
|
|
|
|
171 |
|
172 |
+
.generate-button {
|
173 |
+
width: 100%;
|
174 |
+
padding: 1rem 1.5rem;
|
175 |
+
font-size: 1.2rem;
|
176 |
+
font-weight: 700;
|
177 |
+
color: white;
|
178 |
+
background: var(--app-button-bg);
|
179 |
+
border: none;
|
180 |
+
border-radius: var(--radius-input);
|
181 |
cursor: pointer;
|
182 |
+
transition: all 0.3s ease;
|
183 |
+
box-shadow: var(--shadow-button);
|
184 |
+
margin-top: 1rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
display: flex;
|
186 |
align-items: center;
|
187 |
justify-content: center;
|
188 |
+
gap: 10px;
|
|
|
|
|
|
|
189 |
}
|
190 |
+
|
191 |
+
.generate-button:hover:not(:disabled) {
|
192 |
+
background: var(--app-button-hover-bg);
|
193 |
+
transform: translateY(-2px);
|
194 |
+
box-shadow: 0 6px 20px -3px rgba(52, 152, 219, 0.6);
|
195 |
}
|
196 |
+
|
197 |
+
.generate-button:disabled {
|
198 |
+
background-color: #bdc3c7;
|
199 |
+
cursor: not-allowed;
|
200 |
+
box-shadow: none;
|
201 |
}
|
202 |
+
|
203 |
+
.loader {
|
204 |
+
width: 20px;
|
205 |
+
height: 20px;
|
206 |
+
border: 3px solid rgba(255, 255, 255, 0.3);
|
207 |
+
border-radius: 50%;
|
208 |
+
border-top-color: #fff;
|
209 |
+
animation: spin 1s ease-in-out infinite;
|
210 |
}
|
211 |
+
|
212 |
+
@keyframes spin {
|
213 |
+
to { transform: rotate(360deg); }
|
214 |
}
|
215 |
|
216 |
+
#audioResultContainer {
|
217 |
+
margin-top: 2rem;
|
218 |
+
padding-top: 2rem;
|
219 |
+
border-top: 1px solid var(--app-border-color);
|
220 |
+
display: none; /* Hidden by default */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
221 |
}
|
222 |
+
|
223 |
+
#audioPlayer {
|
224 |
+
width: 100%;
|
225 |
+
border-radius: var(--radius-input);
|
226 |
+
margin-top: 1rem;
|
227 |
+
}
|
228 |
+
|
229 |
+
#errorMessage {
|
230 |
+
color: #e74c3c;
|
231 |
+
background-color: #fbeae5;
|
232 |
+
padding: 1rem;
|
233 |
+
border-radius: var(--radius-input);
|
234 |
+
text-align: center;
|
235 |
+
display: none; /* Hidden by default */
|
236 |
+
margin-top: 1.5rem;
|
237 |
+
border: 1px solid #e74c3c;
|
238 |
+
}
|
239 |
+
|
240 |
+
</style>
|
241 |
</head>
|
242 |
+
<body>
|
243 |
+
|
244 |
+
<div class="container">
|
245 |
+
<header class="app-header">
|
246 |
+
<h1>Alpha TTS</h1>
|
247 |
+
<p>یک رابط کاربری زیبا برای تبدیل متن به صدا</p>
|
248 |
+
</header>
|
249 |
+
|
250 |
+
<main class="main-panel">
|
251 |
+
<div class="form-group">
|
252 |
+
<label for="text_to_speak">📝 متن فارسی برای تبدیل</label>
|
253 |
+
<textarea id="text_to_speak" placeholder="متن خود را اینجا وارد کنید...">این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است.</textarea>
|
254 |
+
</div>
|
255 |
+
|
256 |
+
<div class="form-group">
|
257 |
+
<label for="speech_prompt">🗣️ سبک و لحن گفتار (اختیاری)</label>
|
258 |
+
<textarea id="speech_prompt" rows="2" placeholder="مثال: با لحنی دوستانه و رسا صحبت کن.">با صدایی طبیعی و روان.</textarea>
|
|
|
259 |
</div>
|
260 |
|
261 |
+
<div class="form-group speaker-selection">
|
262 |
+
<div class="dropdown-container">
|
263 |
+
<label for="speaker_voice">🎤 انتخاب گوینده</label>
|
264 |
+
<select id="speaker_voice"></select>
|
265 |
+
</div>
|
266 |
+
<div class="speaker-image-container">
|
267 |
+
<img id="speakerImage" src="" alt="Speaker Avatar">
|
268 |
</div>
|
269 |
</div>
|
270 |
|
271 |
+
<div class="form-group">
|
272 |
+
<label for="temperature_slider">🌡️ میزان خلاقیت (Temperature)</label>
|
273 |
+
<div class="slider-group">
|
274 |
+
<input type="range" id="temperature_slider" min="0.1" max="1.5" step="0.05" value="0.9">
|
275 |
+
<span id="temperatureValue">0.90</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
276 |
</div>
|
277 |
</div>
|
|
|
|
|
278 |
|
279 |
+
<button id="generateButton" class="generate-button">
|
280 |
+
<span id="buttonText">🚀 تولید صدا</span>
|
281 |
+
<div id="buttonLoader" class="loader" style="display: none;"></div>
|
282 |
+
</button>
|
283 |
+
|
284 |
+
<div id="errorMessage"></div>
|
285 |
+
|
286 |
+
<div id="audioResultContainer">
|
287 |
+
<h2>خروجی صدا</h2>
|
288 |
+
<audio id="audioPlayer" controls></audio>
|
289 |
+
</div>
|
290 |
+
</main>
|
291 |
</div>
|
292 |
|
293 |
<script>
|
294 |
+
// --- 1. تعریف متغیرهای اصلی و دادهها ---
|
295 |
+
|
296 |
+
const SPACE_API_URL = "https://hamed744-ttspro.hf.space/run/predict"; // آدرس API اسپیس شما
|
297 |
+
const FN_INDEX = 1; // این ایندکس از لاگهای کنسول شما استخراج شده است
|
298 |
+
|
299 |
+
const SPEAKERS = [
|
300 |
+
"Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager",
|
301 |
+
"Sulafat", "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux",
|
302 |
+
"Pulcherrima", "Umbriel", "Algieba", "Despina", "Erinome", "Algenib",
|
303 |
+
"Rasalthgeti", "Orus", "Aoede", "Callirrhoe", "Autonoe", "Enceladus",
|
304 |
+
"Iapetus", "Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Leda"
|
305 |
+
];
|
306 |
+
|
307 |
+
// --- 2. انتخاب عناصر DOM ---
|
308 |
+
|
309 |
+
const textInput = document.getElementById('text_to_speak');
|
310 |
+
const promptInput = document.getElementById('speech_prompt');
|
311 |
+
const speakerSelect = document.getElementById('speaker_voice');
|
312 |
+
const speakerImage = document.getElementById('speakerImage');
|
313 |
+
const tempSlider = document.getElementById('temperature_slider');
|
314 |
+
const tempValueDisplay = document.getElementById('temperatureValue');
|
315 |
+
const generateBtn = document.getElementById('generateButton');
|
316 |
+
const buttonText = document.getElementById('buttonText');
|
317 |
+
const buttonLoader = document.getElementById('buttonLoader');
|
318 |
+
const audioContainer = document.getElementById('audioResultContainer');
|
319 |
+
const audioPlayer = document.getElementById('audioPlayer');
|
320 |
+
const errorMessageDiv = document.getElementById('errorMessage');
|
321 |
+
|
322 |
+
// --- 3. توابع کمکی ---
|
323 |
+
|
324 |
+
// تابعی برای بهروزرسانی تصویر گوینده
|
325 |
+
function updateSpeakerImage(speakerName) {
|
326 |
+
// از یک سرویس برای تولید تصاویر رندوم با تم کهکشان استفاده میکنیم
|
327 |
+
const imageUrl = `https://source.unsplash.com/150x150/?nebula,galaxy,${speakerName}`;
|
328 |
+
speakerImage.src = imageUrl;
|
329 |
+
speakerImage.alt = `Avatar for ${speakerName}`;
|
330 |
+
}
|
331 |
+
|
332 |
+
// تابعی برای پر کردن لیست گویندگان در Dropdown
|
333 |
+
function populateSpeakers() {
|
334 |
+
SPEAKERS.forEach(speaker => {
|
335 |
+
const option = document.createElement('option');
|
336 |
+
option.value = speaker;
|
337 |
+
option.textContent = speaker;
|
338 |
+
speakerSelect.appendChild(option);
|
339 |
+
});
|
340 |
+
// گوینده پیشفرض ر�� "Charon" قرار میدهیم
|
341 |
+
speakerSelect.value = "Charon";
|
342 |
+
updateSpeakerImage("Charon");
|
343 |
+
}
|
344 |
+
|
345 |
+
// نمایش یا پنهان کردن پیام خطا
|
346 |
+
function showError(message) {
|
347 |
+
errorMessageDiv.textContent = message;
|
348 |
+
errorMessageDiv.style.display = 'block';
|
349 |
+
audioContainer.style.display = 'none';
|
350 |
+
}
|
351 |
+
|
352 |
+
function hideError() {
|
353 |
+
errorMessageDiv.style.display = 'none';
|
354 |
+
}
|
355 |
+
|
356 |
+
// کنترل وضعیت دکمه (فعال/غیرفعال/لودینگ)
|
357 |
+
function setButtonState(state) { // state can be 'idle', 'loading', 'disabled'
|
358 |
+
if (state === 'loading') {
|
359 |
+
generateBtn.disabled = true;
|
360 |
+
buttonText.textContent = 'در حال پردازش...';
|
361 |
+
buttonLoader.style.display = 'block';
|
362 |
} else {
|
363 |
+
generateBtn.disabled = false;
|
364 |
+
buttonText.textContent = '🚀 تولید صدا';
|
365 |
+
buttonLoader.style.display = 'none';
|
366 |
}
|
367 |
+
}
|
368 |
|
369 |
+
// --- 4. منطق اصلی تولید صدا ---
|
370 |
+
|
371 |
+
async function generateAudio() {
|
372 |
+
hideError();
|
373 |
+
setButtonState('loading');
|
374 |
+
|
375 |
+
// دریافت مقادیر ورودی از کاربر
|
376 |
+
const text = textInput.value;
|
377 |
+
const prompt = promptInput.value;
|
378 |
+
const speaker = speakerSelect.value;
|
379 |
+
const temperature = parseFloat(tempSlider.value);
|
380 |
+
|
381 |
+
if (!text.trim()) {
|
382 |
+
showError("لطفاً متنی را برای تبدیل وارد کنید.");
|
383 |
+
setButtonState('idle');
|
384 |
+
return;
|
385 |
}
|
386 |
|
387 |
+
// ساختار دادهای که به API ارسال میشود (بر اساس تحلیل لاگ شما)
|
388 |
+
const payload = {
|
389 |
+
"fn_index": FN_INDEX,
|
390 |
+
"data": [
|
391 |
+
false, // use_file_input_cb
|
392 |
+
null, // uploaded_file_input
|
393 |
+
text, // text_to_speak_tb
|
394 |
+
prompt, // speech_prompt_tb
|
395 |
+
speaker, // speaker_voice_dd
|
396 |
+
temperature // temperature_slider
|
397 |
+
]
|
398 |
+
};
|
399 |
+
|
400 |
+
try {
|
401 |
+
const response = await fetch(SPACE_API_URL, {
|
402 |
+
method: 'POST',
|
403 |
+
headers: { 'Content-Type': 'application/json' },
|
404 |
+
body: JSON.stringify(payload)
|
405 |
+
});
|
406 |
+
|
407 |
+
if (!response.ok) {
|
408 |
+
throw new Error(`خطای شبکه: ${response.status} - ${response.statusText}`);
|
409 |
+
}
|
410 |
|
411 |
+
const result = await response.json();
|
412 |
+
|
413 |
+
// بر اساس ساختار پاسخ Gradio، مسیر فایل صوتی را استخراج میکنیم
|
414 |
+
// result.data[0] خروجی اولین کامپوننت (gr.Audio) است
|
415 |
+
const audioFileData = result.data[0];
|
416 |
+
|
417 |
+
if (audioFileData && audioFileData.name) {
|
418 |
+
// ساخت URL کامل فایل صوتی
|
419 |
+
const audioUrl = `https://hamed744-ttspro.hf.space/file=${audioFileData.name}`;
|
420 |
+
|
421 |
+
// نمایش پلیر و تنظیم منبع آن
|
422 |
+
audioPlayer.src = audioUrl;
|
423 |
+
audioContainer.style.display = 'block';
|
424 |
+
audioPlayer.load();
|
425 |
+
// audioPlayer.play(); // می��توانید این خط را برای پخش خودکار فعال کنید
|
426 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
427 |
} else {
|
428 |
+
console.error("پاسخ دریافتی ساختار مورد انتظار را ندارد:", result);
|
429 |
+
showError("پاسخ ناموفق از سرور. لطفاً کنسول مرورگر را برای جزئیات بررسی کنید.");
|
|
|
|
|
430 |
}
|
431 |
+
|
432 |
+
} catch (error) {
|
433 |
+
console.error("خطا در ارتباط با API:", error);
|
434 |
+
showError(`خطایی رخ داد: ${error.message}. لطفاً از روشن بودن اسپیس هاگینگ فیس مطمئن شوید.`);
|
435 |
+
} finally {
|
436 |
+
setButtonState('idle'); // بازگرداندن دکمه به حالت اولیه
|
437 |
}
|
438 |
}
|
439 |
+
|
440 |
+
// --- 5. ثبت Event Listener ها ---
|
441 |
|
442 |
+
// هنگام بارگذاری صفحه، لیست گویندگان را پر کن
|
443 |
+
document.addEventListener('DOMContentLoaded', populateSpeakers);
|
|
|
|
|
|
|
444 |
|
445 |
+
// با تغییر گوینده، تصویر را عوض کن
|
446 |
+
speakerSelect.addEventListener('change', () => {
|
447 |
+
updateSpeakerImage(speakerSelect.value);
|
|
|
|
|
|
|
448 |
});
|
449 |
|
450 |
+
// با حرکت اسلایدر، عدد کنار آن را بهروز کن
|
451 |
+
tempSlider.addEventListener('input', () => {
|
452 |
+
tempValueDisplay.textContent = parseFloat(tempSlider.value).toFixed(2);
|
|
|
|
|
|
|
453 |
});
|
454 |
|
455 |
+
// با کلیک روی دکمه، فرآیند تولید صدا را شروع کن
|
456 |
+
generateBtn.addEventListener('click', generateAudio);
|
457 |
|
|
|
|
|
458 |
</script>
|
459 |
</body>
|
460 |
</html>
|