SolarumAsteridion commited on
Commit
65d58ea
·
verified ·
1 Parent(s): 40b0211

Create templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +330 -0
templates/index.html ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Gemini Text-to-Speech</title>
7
+
8
+ <!-- Google Fonts -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
12
+
13
+ <!-- Google Material Icons -->
14
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
15
+
16
+ <style>
17
+ * {
18
+ margin: 0;
19
+ padding: 0;
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ body {
24
+ font-family: 'Inter', sans-serif;
25
+ min-height: 100vh;
26
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ padding: 20px;
31
+ }
32
+
33
+ .container {
34
+ background: rgba(255, 255, 255, 0.95);
35
+ backdrop-filter: blur(10px);
36
+ border-radius: 20px;
37
+ padding: 40px;
38
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
39
+ width: 100%;
40
+ max-width: 600px;
41
+ }
42
+
43
+ h1 {
44
+ color: #333;
45
+ font-size: 32px;
46
+ font-weight: 600;
47
+ margin-bottom: 10px;
48
+ text-align: center;
49
+ }
50
+
51
+ .subtitle {
52
+ color: #666;
53
+ text-align: center;
54
+ margin-bottom: 30px;
55
+ font-weight: 300;
56
+ }
57
+
58
+ .form-group {
59
+ margin-bottom: 20px;
60
+ }
61
+
62
+ label {
63
+ display: block;
64
+ color: #555;
65
+ font-weight: 500;
66
+ margin-bottom: 8px;
67
+ font-size: 14px;
68
+ }
69
+
70
+ textarea {
71
+ width: 100%;
72
+ padding: 12px 16px;
73
+ border: 2px solid #e1e1e1;
74
+ border-radius: 10px;
75
+ font-size: 16px;
76
+ font-family: 'Inter', sans-serif;
77
+ resize: vertical;
78
+ transition: border-color 0.3s;
79
+ min-height: 120px;
80
+ }
81
+
82
+ textarea:focus {
83
+ outline: none;
84
+ border-color: #667eea;
85
+ }
86
+
87
+ .options {
88
+ display: grid;
89
+ grid-template-columns: 1fr 1fr;
90
+ gap: 20px;
91
+ margin-bottom: 30px;
92
+ }
93
+
94
+ select {
95
+ width: 100%;
96
+ padding: 12px 16px;
97
+ border: 2px solid #e1e1e1;
98
+ border-radius: 10px;
99
+ font-size: 14px;
100
+ background: white;
101
+ cursor: pointer;
102
+ transition: border-color 0.3s;
103
+ }
104
+
105
+ select:focus {
106
+ outline: none;
107
+ border-color: #667eea;
108
+ }
109
+
110
+ .btn {
111
+ width: 100%;
112
+ padding: 14px 24px;
113
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
114
+ color: white;
115
+ border: none;
116
+ border-radius: 10px;
117
+ font-size: 16px;
118
+ font-weight: 500;
119
+ cursor: pointer;
120
+ display: flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ gap: 8px;
124
+ transition: transform 0.2s, box-shadow 0.2s;
125
+ }
126
+
127
+ .btn:hover {
128
+ transform: translateY(-2px);
129
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
130
+ }
131
+
132
+ .btn:disabled {
133
+ opacity: 0.7;
134
+ cursor: not-allowed;
135
+ transform: none;
136
+ }
137
+
138
+ .btn-secondary {
139
+ background: #f5f5f5;
140
+ color: #333;
141
+ margin-top: 10px;
142
+ }
143
+
144
+ .btn-secondary:hover {
145
+ background: #ebebeb;
146
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
147
+ }
148
+
149
+ .audio-player {
150
+ margin-top: 30px;
151
+ padding: 20px;
152
+ background: #f8f9fa;
153
+ border-radius: 10px;
154
+ display: none;
155
+ }
156
+
157
+ .audio-player.show {
158
+ display: block;
159
+ }
160
+
161
+ audio {
162
+ width: 100%;
163
+ margin-top: 10px;
164
+ }
165
+
166
+ .error {
167
+ color: #d32f2f;
168
+ font-size: 14px;
169
+ margin-top: 10px;
170
+ display: none;
171
+ }
172
+
173
+ .loading {
174
+ display: none;
175
+ text-align: center;
176
+ margin-top: 20px;
177
+ }
178
+
179
+ .spinner {
180
+ border: 3px solid #f3f3f3;
181
+ border-radius: 50%;
182
+ border-top: 3px solid #667eea;
183
+ width: 40px;
184
+ height: 40px;
185
+ animation: spin 1s linear infinite;
186
+ margin: 0 auto;
187
+ }
188
+
189
+ @keyframes spin {
190
+ 0% { transform: rotate(0deg); }
191
+ 100% { transform: rotate(360deg); }
192
+ }
193
+ </style>
194
+ </head>
195
+ <body>
196
+ <div class="container">
197
+ <h1>Gemini Text-to-Speech</h1>
198
+ <p class="subtitle">Convert your text to natural speech with AI</p>
199
+
200
+ <form id="ttsForm">
201
+ <div class="form-group">
202
+ <label for="text">Enter your text</label>
203
+ <textarea
204
+ id="text"
205
+ name="text"
206
+ placeholder="Type or paste your text here..."
207
+ required
208
+ >Hello, welcome to our service. How may I help you today?</textarea>
209
+ </div>
210
+
211
+ <div class="options">
212
+ <div class="form-group">
213
+ <label for="voice">Voice</label>
214
+ <select id="voice" name="voice">
215
+ <option value="Zephyr">Zephyr</option>
216
+ <option value="Puck">Puck</option>
217
+ <option value="Leda">Leda</option>
218
+ </select>
219
+ </div>
220
+
221
+ <div class="form-group">
222
+ <label for="accent">Accent</label>
223
+ <select id="accent" name="accent">
224
+ <option value="hindi">Indian (Hindi)</option>
225
+ <option value="neutral">Neutral</option>
226
+ <option value="american">American</option>
227
+ <option value="british">British</option>
228
+ </select>
229
+ </div>
230
+ </div>
231
+
232
+ <button type="submit" class="btn" id="generateBtn">
233
+ <span class="material-icons">record_voice_over</span>
234
+ Generate Speech
235
+ </button>
236
+ </form>
237
+
238
+ <div class="error" id="error"></div>
239
+
240
+ <div class="loading" id="loading">
241
+ <div class="spinner"></div>
242
+ <p style="margin-top: 10px; color: #666;">Generating speech...</p>
243
+ </div>
244
+
245
+ <div class="audio-player" id="audioPlayer">
246
+ <h3 style="margin-bottom: 10px; color: #333;">Generated Audio</h3>
247
+ <audio id="audioElement" controls></audio>
248
+ <button class="btn btn-secondary" id="downloadBtn">
249
+ <span class="material-icons">download</span>
250
+ Download Audio
251
+ </button>
252
+ </div>
253
+ </div>
254
+
255
+ <script>
256
+ const form = document.getElementById('ttsForm');
257
+ const generateBtn = document.getElementById('generateBtn');
258
+ const audioPlayer = document.getElementById('audioPlayer');
259
+ const audioElement = document.getElementById('audioElement');
260
+ const errorDiv = document.getElementById('error');
261
+ const loading = document.getElementById('loading');
262
+ const downloadBtn = document.getElementById('downloadBtn');
263
+
264
+ form.addEventListener('submit', async (e) => {
265
+ e.preventDefault();
266
+
267
+ const text = document.getElementById('text').value;
268
+ const voice = document.getElementById('voice').value;
269
+ const accent = document.getElementById('accent').value;
270
+
271
+ if (!text.trim()) {
272
+ showError('Please enter some text');
273
+ return;
274
+ }
275
+
276
+ // Hide error and audio player
277
+ errorDiv.style.display = 'none';
278
+ audioPlayer.classList.remove('show');
279
+
280
+ // Show loading
281
+ loading.style.display = 'block';
282
+ generateBtn.disabled = true;
283
+
284
+ try {
285
+ const response = await fetch('/generate', {
286
+ method: 'POST',
287
+ headers: {
288
+ 'Content-Type': 'application/json',
289
+ },
290
+ body: JSON.stringify({ text, voice, accent }),
291
+ });
292
+
293
+ const data = await response.json();
294
+
295
+ if (response.ok && data.success) {
296
+ // Convert base64 to blob
297
+ const audioData = atob(data.audio);
298
+ const arrayBuffer = new ArrayBuffer(audioData.length);
299
+ const view = new Uint8Array(arrayBuffer);
300
+ for (let i = 0; i < audioData.length; i++) {
301
+ view[i] = audioData.charCodeAt(i);
302
+ }
303
+
304
+ const blob = new Blob([arrayBuffer], { type: 'audio/wav' });
305
+ const url = URL.createObjectURL(blob);
306
+
307
+ audioElement.src = url;
308
+ audioPlayer.classList.add('show');
309
+ } else {
310
+ showError(data.error || 'Failed to generate audio');
311
+ }
312
+ } catch (error) {
313
+ showError('Network error. Please try again.');
314
+ } finally {
315
+ loading.style.display = 'none';
316
+ generateBtn.disabled = false;
317
+ }
318
+ });
319
+
320
+ downloadBtn.addEventListener('click', () => {
321
+ window.open('/download', '_blank');
322
+ });
323
+
324
+ function showError(message) {
325
+ errorDiv.textContent = message;
326
+ errorDiv.style.display = 'block';
327
+ }
328
+ </script>
329
+ </body>
330
+ </html>