Spaces:
Running
Running
Update main.ts
Browse files
main.ts
CHANGED
@@ -15,78 +15,123 @@ async function fetchVoiceList() {
|
|
15 |
}, {});
|
16 |
}
|
17 |
|
18 |
-
async function
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
}
|
89 |
|
|
|
90 |
function validateContentType(req: Request, expected: string) {
|
91 |
const contentType = req.headers.get("Content-Type");
|
92 |
if (contentType !== expected) {
|
@@ -137,261 +182,6 @@ async function handleDemoRequest(req: Request) {
|
|
137 |
<meta charset="UTF-8" />
|
138 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
139 |
<title>tts</title>
|
140 |
-
<link
|
141 |
-
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;700&display=swap"
|
142 |
-
rel="stylesheet"
|
143 |
-
/>
|
144 |
-
<style>
|
145 |
-
:root {
|
146 |
-
--primary-color: #6c8bd6;
|
147 |
-
--primary-light: #a2b3e3;
|
148 |
-
--primary-dark: #3d5b8f;
|
149 |
-
--secondary-color: #f08080;
|
150 |
-
--text-color: #333;
|
151 |
-
--text-secondary: #777;
|
152 |
-
--bg-color: #fff;
|
153 |
-
}
|
154 |
-
body {
|
155 |
-
font-family: "Noto Sans SC", "Arial", sans-serif;
|
156 |
-
color: var(--text-color);
|
157 |
-
margin: 0;
|
158 |
-
padding: 0;
|
159 |
-
display: flex;
|
160 |
-
justify-content: center;
|
161 |
-
background-color: #fafafa;
|
162 |
-
background-image: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
163 |
-
position: relative;
|
164 |
-
overflow: hidden;
|
165 |
-
}
|
166 |
-
body::before {
|
167 |
-
content: "";
|
168 |
-
position: absolute;
|
169 |
-
top: 0;
|
170 |
-
left: 0;
|
171 |
-
right: 0;
|
172 |
-
bottom: 0;
|
173 |
-
background: repeating-radial-gradient(
|
174 |
-
circle at 50% 50%,
|
175 |
-
rgba(255, 255, 255, 0.8) 0%,
|
176 |
-
rgba(255, 255, 255, 0.8) 2%,
|
177 |
-
transparent 2%,
|
178 |
-
transparent 4%,
|
179 |
-
rgba(255, 255, 255, 0.8) 4%,
|
180 |
-
rgba(255, 255, 255, 0.8) 6%,
|
181 |
-
transparent 6%,
|
182 |
-
transparent 8%,
|
183 |
-
rgba(255, 255, 255, 0.8) 8%,
|
184 |
-
rgba(255, 255, 255, 0.8) 10%,
|
185 |
-
transparent 10%
|
186 |
-
),
|
187 |
-
repeating-linear-gradient(
|
188 |
-
45deg,
|
189 |
-
#d4f4ff 0%,
|
190 |
-
#d4f4ff 5%,
|
191 |
-
#e6f9ff 5%,
|
192 |
-
#e6f9ff 10%,
|
193 |
-
#f0faff 10%,
|
194 |
-
#f0faff 15%,
|
195 |
-
#e6f9ff 15%,
|
196 |
-
#e6f9ff 20%,
|
197 |
-
#d4f4ff 20%,
|
198 |
-
#d4f4ff 25%
|
199 |
-
);
|
200 |
-
background-blend-mode: multiply;
|
201 |
-
opacity: 0.8;
|
202 |
-
z-index: -1;
|
203 |
-
animation: glitch 15s infinite;
|
204 |
-
}
|
205 |
-
.container {
|
206 |
-
display: flex;
|
207 |
-
max-width: 1200px;
|
208 |
-
width: 100%;
|
209 |
-
margin: 40px;
|
210 |
-
background: #fff;
|
211 |
-
border-radius: 12px;
|
212 |
-
position: relative;
|
213 |
-
background-color: rgba(255, 255, 255, 0.8);
|
214 |
-
z-index: 1;
|
215 |
-
}
|
216 |
-
@keyframes glitch {
|
217 |
-
0% {
|
218 |
-
background-position: 0 0, 0 0;
|
219 |
-
filter: hue-rotate(0deg);
|
220 |
-
}
|
221 |
-
50% {
|
222 |
-
background-position: 10px 10px, -10px 10px;
|
223 |
-
filter: hue-rotate(360deg);
|
224 |
-
}
|
225 |
-
100% {
|
226 |
-
background-position: 0 0, 0 0;
|
227 |
-
filter: hue-rotate(0deg);
|
228 |
-
}
|
229 |
-
}
|
230 |
-
.input-area,
|
231 |
-
.output-area {
|
232 |
-
padding: 30px;
|
233 |
-
width: 50%;
|
234 |
-
}
|
235 |
-
.input-area {
|
236 |
-
border-right: 1px solid #e0e0e0;
|
237 |
-
}
|
238 |
-
h1 {
|
239 |
-
font-size: 36px;
|
240 |
-
color: var(--primary-color);
|
241 |
-
margin-bottom: 30px;
|
242 |
-
}
|
243 |
-
.filter-section {
|
244 |
-
margin-bottom: 30px;
|
245 |
-
}
|
246 |
-
.filter-section label {
|
247 |
-
display: block;
|
248 |
-
font-size: 16px;
|
249 |
-
color: var(--text-secondary);
|
250 |
-
margin-bottom: 10px;
|
251 |
-
}
|
252 |
-
.filter-section input {
|
253 |
-
font-size: 16px;
|
254 |
-
padding: 10px 15px;
|
255 |
-
border: 2px solid var(--primary-light);
|
256 |
-
border-radius: 8px;
|
257 |
-
outline: none;
|
258 |
-
transition: border-color 0.3s, box-shadow 0.3s;
|
259 |
-
width: 100%;
|
260 |
-
box-sizing: border-box;
|
261 |
-
}
|
262 |
-
.filter-section input:focus {
|
263 |
-
border-color: var(--primary-color);
|
264 |
-
box-shadow: 0 0 0 2px var(--primary-light);
|
265 |
-
}
|
266 |
-
.slider-container {
|
267 |
-
margin-bottom: 30px;
|
268 |
-
}
|
269 |
-
.slider-container label {
|
270 |
-
display: block;
|
271 |
-
font-size: 16px;
|
272 |
-
color: var(--text-secondary);
|
273 |
-
margin-bottom: 10px;
|
274 |
-
}
|
275 |
-
.slider {
|
276 |
-
-webkit-appearance: none;
|
277 |
-
width: 100%;
|
278 |
-
height: 10px;
|
279 |
-
border-radius: 5px;
|
280 |
-
background: linear-gradient(
|
281 |
-
to right,
|
282 |
-
var(--secondary-color) 0%,
|
283 |
-
var(--primary-color) 50%,
|
284 |
-
var(--primary-light) 100%
|
285 |
-
);
|
286 |
-
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1),
|
287 |
-
0 1px rgba(255, 255, 255, 0.1);
|
288 |
-
outline: none;
|
289 |
-
opacity: 0.7;
|
290 |
-
-webkit-transition: 0.2s;
|
291 |
-
transition: opacity 0.2s;
|
292 |
-
margin-bottom: 10px;
|
293 |
-
}
|
294 |
-
.slider:hover {
|
295 |
-
opacity: 1;
|
296 |
-
}
|
297 |
-
.slider::-webkit-slider-thumb {
|
298 |
-
-webkit-appearance: none;
|
299 |
-
appearance: none;
|
300 |
-
width: 20px;
|
301 |
-
height: 20px;
|
302 |
-
border-radius: 50%;
|
303 |
-
background: #fff;
|
304 |
-
border: 2px solid var(--primary-color);
|
305 |
-
cursor: pointer;
|
306 |
-
}
|
307 |
-
.slider::-moz-range-thumb {
|
308 |
-
width: 20px;
|
309 |
-
height: 20px;
|
310 |
-
border-radius: 50%;
|
311 |
-
background: #fff;
|
312 |
-
border: 2px solid var(--primary-color);
|
313 |
-
cursor: pointer;
|
314 |
-
}
|
315 |
-
.slider-value {
|
316 |
-
font-size: 14px;
|
317 |
-
color: var(--text-secondary);
|
318 |
-
}
|
319 |
-
.textarea-container {
|
320 |
-
margin-bottom: 30px;
|
321 |
-
}
|
322 |
-
.textarea-container label {
|
323 |
-
display: block;
|
324 |
-
font-size: 18px;
|
325 |
-
margin-bottom: 10px;
|
326 |
-
}
|
327 |
-
.textarea-container textarea {
|
328 |
-
width: 100%;
|
329 |
-
padding: 10px;
|
330 |
-
font-size: 16px;
|
331 |
-
border: 2px solid var(--primary-light);
|
332 |
-
border-radius: 8px;
|
333 |
-
outline: none;
|
334 |
-
resize: vertical;
|
335 |
-
transition: border-color 0.3s, box-shadow 0.3s;
|
336 |
-
box-sizing: border-box;
|
337 |
-
height: 200px;
|
338 |
-
}
|
339 |
-
.textarea-container textarea:focus {
|
340 |
-
border-color: var(--primary-color);
|
341 |
-
box-shadow: 0 0 0 2px var(--primary-light);
|
342 |
-
}
|
343 |
-
.voice-group {
|
344 |
-
margin-bottom: 20px;
|
345 |
-
border: 2px solid var(--primary-light);
|
346 |
-
border-radius: 12px;
|
347 |
-
overflow: hidden;
|
348 |
-
cursor: move;
|
349 |
-
background: #fff;
|
350 |
-
}
|
351 |
-
.voice-header {
|
352 |
-
padding: 15px 20px;
|
353 |
-
font-size: 18px;
|
354 |
-
background: var(--primary-light);
|
355 |
-
color: #fff;
|
356 |
-
cursor: pointer;
|
357 |
-
display: flex;
|
358 |
-
justify-content: space-between;
|
359 |
-
align-items: center;
|
360 |
-
}
|
361 |
-
.voice-header:hover {
|
362 |
-
background: var(--primary-color);
|
363 |
-
}
|
364 |
-
.voice-buttons {
|
365 |
-
padding: 20px;
|
366 |
-
display: none;
|
367 |
-
gap: 12px;
|
368 |
-
flex-wrap: wrap;
|
369 |
-
}
|
370 |
-
.voice-button {
|
371 |
-
background: var(--secondary-color);
|
372 |
-
color: #fff;
|
373 |
-
border: none;
|
374 |
-
padding: 10px 20px;
|
375 |
-
border-radius: 50px;
|
376 |
-
cursor: pointer;
|
377 |
-
transition: filter 0.3s;
|
378 |
-
}
|
379 |
-
.voice-button:hover {
|
380 |
-
filter: brightness(0.9);
|
381 |
-
}
|
382 |
-
.chevron {
|
383 |
-
transition: transform 0.3s;
|
384 |
-
}
|
385 |
-
.voice-group.open .voice-buttons {
|
386 |
-
display: flex;
|
387 |
-
}
|
388 |
-
.voice-group.open .chevron {
|
389 |
-
transform: rotate(180deg);
|
390 |
-
}
|
391 |
-
.dragging {
|
392 |
-
opacity: 0.5;
|
393 |
-
}
|
394 |
-
</style>
|
395 |
</head>
|
396 |
<body>
|
397 |
<div class="container">
|
@@ -426,9 +216,7 @@ async function handleDemoRequest(req: Request) {
|
|
426 |
</div>
|
427 |
<div class="textarea-container">
|
428 |
<label for="inputText">текст:</label
|
429 |
-
><textarea id="inputText"
|
430 |
-
|
431 |
-
Пусть ваши сердца наполняются радостью и гордостью за нас, когда вы видите, как мы смиренно платим налоги и штрафы, как мы покорно следуем вашим указаниям и ограничениям! Ведь мы - ваши верные слуги, готовые на все ради вашего благополучия и процветания!</textarea>
|
432 |
</div>
|
433 |
</div>
|
434 |
<div class="output-area">
|
|
|
15 |
}, {});
|
16 |
}
|
17 |
|
18 |
+
async function handleDemoRequest(req: Request) {
|
19 |
+
const html = `<!DOCTYPE html>
|
20 |
+
<html lang="en">
|
21 |
+
<head>
|
22 |
+
<meta charset="UTF-8" />
|
23 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
24 |
+
<title>tts</title>
|
25 |
+
</head>
|
26 |
+
<body>
|
27 |
+
<div class="container">
|
28 |
+
<div class="input-area">
|
29 |
+
<div class="slider-container">
|
30 |
+
<label for="rate">скорость:</label
|
31 |
+
><input
|
32 |
+
type="range"
|
33 |
+
min="-1"
|
34 |
+
max="1"
|
35 |
+
step="0.1"
|
36 |
+
value="-0.1"
|
37 |
+
class="slider"
|
38 |
+
id="rate"
|
39 |
+
/>
|
40 |
+
<div class="slider-value" id="rateValue">-0.1</div>
|
41 |
+
<label for="pitch">тон:</label
|
42 |
+
><input
|
43 |
+
type="range"
|
44 |
+
min="-1"
|
45 |
+
max="1"
|
46 |
+
step="0.1"
|
47 |
+
value="0.1"
|
48 |
+
class="slider"
|
49 |
+
id="pitch"
|
50 |
+
/>
|
51 |
+
<div class="slider-value" id="pitchValue">0.1</div>
|
52 |
+
</div>
|
53 |
+
<div class="textarea-container">
|
54 |
+
<label for="inputText">текст:</label
|
55 |
+
><textarea id="inputText">Привет, хочешь я расскажу сказку?</textarea>
|
56 |
+
</div>
|
57 |
+
<div class="dropdown-container">
|
58 |
+
<label for="voiceSelect">выберите голос:</label>
|
59 |
+
<select id="voiceSelect">
|
60 |
+
<option value="ava">ava</option>
|
61 |
+
<option value="andrew">andrew</option>
|
62 |
+
<option value="emma">emma</option>
|
63 |
+
<option value="brian">brian</option>
|
64 |
+
<option value="vivienne">vivienne</option>
|
65 |
+
<option value="remy">remy</option>
|
66 |
+
<option value="seraphina">seraphina</option>
|
67 |
+
<option value="florian">florian</option>
|
68 |
+
<option value="dmitry">dmitry</option>
|
69 |
+
<option value="svetlana">svetlana</option>
|
70 |
+
</select>
|
71 |
+
</div>
|
72 |
+
<button id="synthesizeButton">Синтезировать</button>
|
73 |
+
</div>
|
74 |
+
<div class="output-area">
|
75 |
+
<h1>Результат</h1>
|
76 |
+
<div id="audioPlayerContainer"></div>
|
77 |
+
</div>
|
78 |
+
</div>
|
79 |
+
<script>
|
80 |
+
let audio = null;
|
81 |
+
|
82 |
+
document.getElementById('synthesizeButton').addEventListener('click', () => {
|
83 |
+
const text = document.getElementById('inputText').value || 'Hello world';
|
84 |
+
const rate = document.getElementById('rate').value || '-0.1';
|
85 |
+
const pitch = document.getElementById('pitch').value || '0.1';
|
86 |
+
const voice = \`rate:\${rate}|pitch:\${pitch}\`;
|
87 |
+
const model = document.getElementById('voiceSelect').value;
|
88 |
+
|
89 |
+
if (audio) {
|
90 |
+
audio.pause();
|
91 |
+
audio.currentTime = 0;
|
92 |
+
}
|
93 |
|
94 |
+
fetch('/v1/audio/speech', {
|
95 |
+
method: 'POST',
|
96 |
+
headers: { 'Content-Type': 'application/json' },
|
97 |
+
body: JSON.stringify({ model, input: text, voice })
|
98 |
+
})
|
99 |
+
.then(response => response.blob())
|
100 |
+
.then(blob => {
|
101 |
+
const audioUrl = URL.createObjectURL(blob);
|
102 |
+
const audioPlayerContainer = document.getElementById('audioPlayerContainer');
|
103 |
+
|
104 |
+
if (audio) {
|
105 |
+
audio.src = audioUrl;
|
106 |
+
} else {
|
107 |
+
audio = new Audio(audioUrl);
|
108 |
+
audioPlayerContainer.appendChild(audio);
|
109 |
+
}
|
110 |
+
|
111 |
+
audio.play();
|
112 |
+
});
|
113 |
+
});
|
114 |
+
|
115 |
+
const rateSlider = document.getElementById('rate');
|
116 |
+
const rateValue = document.getElementById('rateValue');
|
117 |
+
rateSlider.oninput = function() {
|
118 |
+
rateValue.innerHTML = this.value;
|
119 |
+
};
|
120 |
+
|
121 |
+
const pitchSlider = document.getElementById('pitch');
|
122 |
+
const pitchValue = document.getElementById('pitchValue');
|
123 |
+
pitchSlider.oninput = function() {
|
124 |
+
pitchValue.innerHTML = this.value;
|
125 |
+
};
|
126 |
+
</script>
|
127 |
+
</body></html>`;
|
128 |
+
|
129 |
+
return new Response(html, {
|
130 |
+
headers: { "Content-Type": "text/html" },
|
131 |
+
});
|
132 |
}
|
133 |
|
134 |
+
|
135 |
function validateContentType(req: Request, expected: string) {
|
136 |
const contentType = req.headers.get("Content-Type");
|
137 |
if (contentType !== expected) {
|
|
|
182 |
<meta charset="UTF-8" />
|
183 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
184 |
<title>tts</title>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
185 |
</head>
|
186 |
<body>
|
187 |
<div class="container">
|
|
|
216 |
</div>
|
217 |
<div class="textarea-container">
|
218 |
<label for="inputText">текст:</label
|
219 |
+
><textarea id="inputText">Привет, хочешь я расскажу сказку?</textarea>
|
|
|
|
|
220 |
</div>
|
221 |
</div>
|
222 |
<div class="output-area">
|