ssasio commited on
Commit
8a298ea
·
verified ·
1 Parent(s): 7e624a7

Upload static/index.html

Browse files
Files changed (1) hide show
  1. static/index.html +457 -0
static/index.html ADDED
@@ -0,0 +1,457 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="bg">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>🎙️ VOX ANI TTS</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
9
+
10
+ :root {
11
+ --bg: #0f0f13;
12
+ --bg2: #17171e;
13
+ --bg3: #1e1e28;
14
+ --border: #2e2e3e;
15
+ --text: #e2e2ec;
16
+ --muted: #8888aa;
17
+ --accent: #7c6ef5;
18
+ --accent2: #5b4fd4;
19
+ --success: #4caf7d;
20
+ --danger: #e05555;
21
+ --warn: #e09030;
22
+ --radius: 10px;
23
+ --font: 'Segoe UI', system-ui, sans-serif;
24
+ }
25
+
26
+ body {
27
+ font-family: var(--font);
28
+ background: var(--bg);
29
+ color: var(--text);
30
+ min-height: 100vh;
31
+ display: flex;
32
+ flex-direction: column;
33
+ align-items: center;
34
+ padding: 24px 16px 60px;
35
+ }
36
+
37
+ h1 {
38
+ font-size: 1.7rem;
39
+ font-weight: 700;
40
+ letter-spacing: -0.5px;
41
+ margin-bottom: 4px;
42
+ background: linear-gradient(135deg, #a89af8, #7c6ef5);
43
+ -webkit-background-clip: text;
44
+ -webkit-text-fill-color: transparent;
45
+ }
46
+
47
+ .subtitle {
48
+ color: var(--muted);
49
+ font-size: .875rem;
50
+ margin-bottom: 24px;
51
+ }
52
+
53
+ .card {
54
+ background: var(--bg2);
55
+ border: 1px solid var(--border);
56
+ border-radius: var(--radius);
57
+ padding: 20px;
58
+ width: 100%;
59
+ max-width: 780px;
60
+ }
61
+
62
+ /* Key row */
63
+ .key-row {
64
+ display: flex;
65
+ gap: 10px;
66
+ margin-bottom: 20px;
67
+ max-width: 780px;
68
+ width: 100%;
69
+ }
70
+ .key-row input {
71
+ flex: 1;
72
+ }
73
+
74
+ /* Tabs */
75
+ .tabs {
76
+ display: flex;
77
+ gap: 4px;
78
+ margin-bottom: 20px;
79
+ max-width: 780px;
80
+ width: 100%;
81
+ }
82
+ .tab-btn {
83
+ flex: 1;
84
+ padding: 9px 6px;
85
+ border: 1px solid var(--border);
86
+ border-radius: var(--radius);
87
+ background: var(--bg2);
88
+ color: var(--muted);
89
+ cursor: pointer;
90
+ font-size: .82rem;
91
+ font-family: var(--font);
92
+ transition: all .15s;
93
+ }
94
+ .tab-btn.active {
95
+ background: var(--accent);
96
+ border-color: var(--accent);
97
+ color: #fff;
98
+ font-weight: 600;
99
+ }
100
+ .tab-btn:hover:not(.active) {
101
+ border-color: var(--accent);
102
+ color: var(--text);
103
+ }
104
+
105
+ .tab-panel { display: none; }
106
+ .tab-panel.active { display: block; }
107
+
108
+ /* Form elements */
109
+ label {
110
+ display: block;
111
+ font-size: .82rem;
112
+ color: var(--muted);
113
+ margin-bottom: 5px;
114
+ margin-top: 14px;
115
+ }
116
+ label:first-child { margin-top: 0; }
117
+
118
+ input[type=text],
119
+ input[type=password],
120
+ input[type=number],
121
+ select,
122
+ textarea {
123
+ width: 100%;
124
+ background: var(--bg3);
125
+ border: 1px solid var(--border);
126
+ border-radius: 7px;
127
+ color: var(--text);
128
+ font-family: var(--font);
129
+ font-size: .92rem;
130
+ padding: 9px 12px;
131
+ outline: none;
132
+ transition: border .15s;
133
+ }
134
+ input:focus, select:focus, textarea:focus {
135
+ border-color: var(--accent);
136
+ }
137
+ textarea { resize: vertical; min-height: 90px; }
138
+
139
+ .row { display: flex; gap: 12px; }
140
+ .row > * { flex: 1; }
141
+
142
+ /* Range */
143
+ .slider-row {
144
+ display: flex;
145
+ align-items: center;
146
+ gap: 10px;
147
+ }
148
+ .slider-row input[type=range] {
149
+ flex: 1;
150
+ accent-color: var(--accent);
151
+ padding: 0;
152
+ height: 4px;
153
+ cursor: pointer;
154
+ }
155
+ .slider-row .val {
156
+ min-width: 36px;
157
+ text-align: right;
158
+ font-size: .82rem;
159
+ color: var(--muted);
160
+ }
161
+
162
+ /* Accordion */
163
+ details {
164
+ border: 1px solid var(--border);
165
+ border-radius: var(--radius);
166
+ margin-top: 14px;
167
+ overflow: hidden;
168
+ }
169
+ summary {
170
+ padding: 11px 14px;
171
+ cursor: pointer;
172
+ font-size: .88rem;
173
+ color: var(--muted);
174
+ list-style: none;
175
+ display: flex;
176
+ align-items: center;
177
+ gap: 6px;
178
+ user-select: none;
179
+ }
180
+ summary::before { content: '▶'; font-size: .7rem; transition: transform .15s; }
181
+ details[open] summary::before { transform: rotate(90deg); }
182
+ .accordion-body {
183
+ padding: 0 14px 14px;
184
+ }
185
+
186
+ /* Buttons */
187
+ button {
188
+ display: inline-flex;
189
+ align-items: center;
190
+ justify-content: center;
191
+ gap: 6px;
192
+ border: none;
193
+ border-radius: 7px;
194
+ cursor: pointer;
195
+ font-family: var(--font);
196
+ font-size: .9rem;
197
+ font-weight: 600;
198
+ padding: 10px 18px;
199
+ transition: opacity .15s, transform .1s;
200
+ }
201
+ button:active { transform: scale(.97); }
202
+ button:disabled { opacity: .45; cursor: not-allowed; }
203
+
204
+ .btn-primary { background: var(--accent); color: #fff; }
205
+ .btn-primary:hover:not(:disabled) { background: var(--accent2); }
206
+ .btn-danger { background: var(--danger); color: #fff; }
207
+ .btn-danger:hover:not(:disabled) { opacity: .85; }
208
+ .btn-secondary { background: var(--bg3); border: 1px solid var(--border); color: var(--text); }
209
+ .btn-secondary:hover:not(:disabled) { border-color: var(--accent); }
210
+
211
+ .btn-row { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
212
+
213
+ /* Status */
214
+ .status {
215
+ margin-top: 12px;
216
+ padding: 9px 13px;
217
+ border-radius: 7px;
218
+ font-size: .88rem;
219
+ background: var(--bg3);
220
+ border: 1px solid var(--border);
221
+ color: var(--muted);
222
+ min-height: 38px;
223
+ display: flex;
224
+ align-items: center;
225
+ gap: 6px;
226
+ }
227
+ .status.ok { border-color: var(--success); color: var(--success); }
228
+ .status.err { border-color: var(--danger); color: var(--danger); }
229
+ .status.warn { border-color: var(--warn); color: var(--warn); }
230
+
231
+ /* Audio player */
232
+ audio {
233
+ width: 100%;
234
+ margin-top: 12px;
235
+ border-radius: 7px;
236
+ accent-color: var(--accent);
237
+ }
238
+
239
+ /* Voices list */
240
+ #voices-list, #manage-list {
241
+ background: var(--bg3);
242
+ border: 1px solid var(--border);
243
+ border-radius: 7px;
244
+ padding: 10px 12px;
245
+ min-height: 60px;
246
+ font-size: .85rem;
247
+ line-height: 1.8;
248
+ color: var(--muted);
249
+ margin-top: 6px;
250
+ white-space: pre-line;
251
+ }
252
+
253
+ /* Divider */
254
+ hr { border: none; border-top: 1px solid var(--border); margin: 20px 0; }
255
+
256
+ /* Checkbox */
257
+ .check-row {
258
+ display: flex;
259
+ align-items: center;
260
+ gap: 8px;
261
+ margin-top: 14px;
262
+ }
263
+ .check-row input[type=checkbox] {
264
+ width: 16px;
265
+ height: 16px;
266
+ accent-color: var(--accent);
267
+ cursor: pointer;
268
+ }
269
+ .check-row label {
270
+ margin: 0;
271
+ font-size: .9rem;
272
+ color: var(--text);
273
+ cursor: pointer;
274
+ }
275
+
276
+ .section-title {
277
+ font-weight: 600;
278
+ font-size: .95rem;
279
+ color: var(--text);
280
+ margin-bottom: 10px;
281
+ }
282
+
283
+ /* Spinner */
284
+ @keyframes spin { to { transform: rotate(360deg); } }
285
+ .spin { display: inline-block; animation: spin .7s linear infinite; }
286
+
287
+ /* Download link */
288
+ .dl-link {
289
+ display: inline-flex;
290
+ align-items: center;
291
+ gap: 6px;
292
+ color: var(--accent);
293
+ font-size: .88rem;
294
+ text-decoration: none;
295
+ margin-top: 8px;
296
+ }
297
+ .dl-link:hover { text-decoration: underline; }
298
+ </style>
299
+ </head>
300
+ <body>
301
+
302
+ <h1>🎙️ VOX ANI TTS</h1>
303
+ <p class="subtitle">Neural text-to-speech with voice cloning</p>
304
+
305
+ <!-- API Key -->
306
+ <div class="key-row">
307
+ <input type="password" id="api-key" placeholder="🔑 API Key" oninput="onKeyChange()">
308
+ </div>
309
+
310
+ <!-- Tabs -->
311
+ <div class="tabs">
312
+ <button class="tab-btn active" onclick="showTab('synth')">🔊 Синтез</button>
313
+ <button class="tab-btn" onclick="showTab('clone')">🎤 Клониране</button>
314
+ <button class="tab-btn" onclick="showTab('manage')">🗑️ Управление</button>
315
+ <button class="tab-btn" onclick="showTab('encode')">🔐 Encode Key</button>
316
+ </div>
317
+
318
+ <!-- ───────────── TAB 1: SYNTHESIZE ───────────── -->
319
+ <div class="card tab-panel active" id="tab-synth">
320
+ <label for="synth-text">Текст за синтезиране</label>
321
+ <textarea id="synth-text" placeholder="Въведете текст…"></textarea>
322
+
323
+ <label for="voice-select">Глас</label>
324
+ <select id="voice-select"><option value="">— зарежда се —</option></select>
325
+
326
+ <label>Reference аудио (по избор — замества избрания глас)</label>
327
+ <input type="file" id="ref-audio-synth" accept="audio/*" style="color:var(--muted);font-size:.85rem">
328
+
329
+ <details>
330
+ <summary>⚙️ Параметри на генерацията</summary>
331
+ <div class="accordion-body">
332
+ <label>Temperature</label>
333
+ <div class="slider-row">
334
+ <input type="range" min="0.05" max="1.0" step="0.05" value="0.3" id="temperature"
335
+ oninput="document.getElementById('temperature-val').textContent=this.value">
336
+ <span class="val" id="temperature-val">0.3</span>
337
+ </div>
338
+ <label>Top-K</label>
339
+ <div class="slider-row">
340
+ <input type="range" min="10" max="500" step="10" value="250" id="top-k"
341
+ oninput="document.getElementById('top-k-val').textContent=this.value">
342
+ <span class="val" id="top-k-val">250</span>
343
+ </div>
344
+ <label>Top-P</label>
345
+ <div class="slider-row">
346
+ <input type="range" min="0.5" max="1.0" step="0.05" value="0.95" id="top-p"
347
+ oninput="document.getElementById('top-p-val').textContent=this.value">
348
+ <span class="val" id="top-p-val">0.95</span>
349
+ </div>
350
+ </div>
351
+ </details>
352
+
353
+ <div class="btn-row">
354
+ <button class="btn-primary" onclick="synthesize()" id="synth-btn">🔊 Генерирай</button>
355
+ </div>
356
+ <div class="status" id="synth-status">Готов</div>
357
+ <audio id="synth-audio" controls style="display:none"></audio>
358
+ </div>
359
+
360
+ <!-- ───────────── TAB 2: CLONE ───────────── -->
361
+ <div class="card tab-panel" id="tab-clone">
362
+ <label for="voice-name">Име на гласа</label>
363
+ <input type="text" id="voice-name" placeholder="напр. Иван">
364
+
365
+ <label>Reference аудио (WAV / MP3, мин. 5 сек.)</label>
366
+ <input type="file" id="ref-audio-clone" accept="audio/*" style="color:var(--muted);font-size:.85rem">
367
+
368
+ <details open>
369
+ <summary>🎛️ Почистване на аудио (препоръчително)</summary>
370
+ <div class="accordion-body">
371
+ <div class="check-row">
372
+ <input type="checkbox" id="do-enhance" checked onchange="toggleEnhance()">
373
+ <label for="do-enhance">Активирай почистване на гласа</label>
374
+ </div>
375
+ <div id="enhance-sliders">
376
+ <label>🔇 Noise reduction</label>
377
+ <div class="slider-row">
378
+ <input type="range" min="0" max="1" step="0.05" value="0.75" id="denoise"
379
+ oninput="document.getElementById('denoise-val').textContent=this.value">
380
+ <span class="val" id="denoise-val">0.75</span>
381
+ </div>
382
+ <label>🐍 De-essing (dB)</label>
383
+ <div class="slider-row">
384
+ <input type="range" min="0" max="12" step="0.5" value="6" id="deess"
385
+ oninput="document.getElementById('deess-val').textContent=this.value">
386
+ <span class="val" id="deess-val">6</span>
387
+ </div>
388
+ <label>🔥 Warming (dB)</label>
389
+ <div class="slider-row">
390
+ <input type="range" min="0" max="6" step="0.5" value="2.5" id="warm"
391
+ oninput="document.getElementById('warm-val').textContent=this.value">
392
+ <span class="val" id="warm-val">2.5</span>
393
+ </div>
394
+ </div>
395
+ </div>
396
+ </details>
397
+
398
+ <div class="btn-row">
399
+ <button class="btn-primary" onclick="cloneVoice()" id="clone-btn">💾 Запази гласа</button>
400
+ </div>
401
+ <div class="status" id="clone-status">Готов</div>
402
+
403
+ <label style="margin-top:16px">Запазени гласове</label>
404
+ <div id="voices-list">—</div>
405
+ </div>
406
+
407
+ <!-- ───────────── TAB 3: MANAGE ───────────── -->
408
+ <div class="card tab-panel" id="tab-manage">
409
+
410
+ <p class="section-title">💾 Свали глас като JSON</p>
411
+ <label for="dl-voice">Избери глас</label>
412
+ <select id="dl-voice"><option value="">— зарежда се —</option></select>
413
+ <div class="btn-row">
414
+ <button class="btn-secondary" onclick="downloadVoice()" id="dl-btn">⬇️ Свали JSON</button>
415
+ </div>
416
+ <div class="status" id="dl-status">—</div>
417
+
418
+ <hr>
419
+
420
+ <p class="section-title">🗑️ Изтрий глас</p>
421
+ <label for="del-voice">Избери глас за изтриване</label>
422
+ <select id="del-voice"><option value="">— зарежда се —</option></select>
423
+ <div class="btn-row">
424
+ <button class="btn-danger" onclick="deleteVoice()" id="del-btn">🗑️ Изтрий</button>
425
+ </div>
426
+ <div class="status" id="del-status">—</div>
427
+
428
+ <label style="margin-top:16px">Текущ списък</label>
429
+ <div id="manage-list">—</div>
430
+ </div>
431
+
432
+ <!-- ───────────── TAB 4: ENCODE KEY ───────────── -->
433
+ <div class="card tab-panel" id="tab-encode">
434
+ <p class="section-title">🔒 Encode API Key</p>
435
+ <label for="raw-key">Реален ключ</label>
436
+ <input type="password" id="raw-key" placeholder="Въведи реалния API ключ">
437
+ <div class="btn-row">
438
+ <button class="btn-primary" onclick="encodeKey()">🔒 Encode</button>
439
+ </div>
440
+ <label style="margin-top:16px">Кодиран ключ (за app.py)</label>
441
+ <input type="text" id="encoded-out" readonly placeholder="резултатът ще се появи тук" onclick="this.select()">
442
+
443
+ <hr>
444
+
445
+ <p class="section-title">🔓 Decode API Key</p>
446
+ <label for="encoded-key">Кодиран ключ</label>
447
+ <input type="text" id="encoded-key" placeholder="Постави кодирания ключ">
448
+ <div class="btn-row">
449
+ <button class="btn-secondary" onclick="decodeKey()">🔓 Decode</button>
450
+ </div>
451
+ <label style="margin-top:16px">Декодиран ключ</label>
452
+ <input type="text" id="decoded-out" readonly placeholder="резултатът ще се появи тук" onclick="this.select()">
453
+ </div>
454
+
455
+ <script src="/static/app.js"></script>
456
+ </body>
457
+ </html>