Gemini-Audio / templates /index.html
SolarumAsteridion's picture
Create templates/index.html
65d58ea verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gemini Text-to-Speech</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<!-- Google Material Icons -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 600px;
}
h1 {
color: #333;
font-size: 32px;
font-weight: 600;
margin-bottom: 10px;
text-align: center;
}
.subtitle {
color: #666;
text-align: center;
margin-bottom: 30px;
font-weight: 300;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
color: #555;
font-weight: 500;
margin-bottom: 8px;
font-size: 14px;
}
textarea {
width: 100%;
padding: 12px 16px;
border: 2px solid #e1e1e1;
border-radius: 10px;
font-size: 16px;
font-family: 'Inter', sans-serif;
resize: vertical;
transition: border-color 0.3s;
min-height: 120px;
}
textarea:focus {
outline: none;
border-color: #667eea;
}
.options {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
select {
width: 100%;
padding: 12px 16px;
border: 2px solid #e1e1e1;
border-radius: 10px;
font-size: 14px;
background: white;
cursor: pointer;
transition: border-color 0.3s;
}
select:focus {
outline: none;
border-color: #667eea;
}
.btn {
width: 100%;
padding: 14px 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
}
.btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
}
.btn-secondary {
background: #f5f5f5;
color: #333;
margin-top: 10px;
}
.btn-secondary:hover {
background: #ebebeb;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
}
.audio-player {
margin-top: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 10px;
display: none;
}
.audio-player.show {
display: block;
}
audio {
width: 100%;
margin-top: 10px;
}
.error {
color: #d32f2f;
font-size: 14px;
margin-top: 10px;
display: none;
}
.loading {
display: none;
text-align: center;
margin-top: 20px;
}
.spinner {
border: 3px solid #f3f3f3;
border-radius: 50%;
border-top: 3px solid #667eea;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<h1>Gemini Text-to-Speech</h1>
<p class="subtitle">Convert your text to natural speech with AI</p>
<form id="ttsForm">
<div class="form-group">
<label for="text">Enter your text</label>
<textarea
id="text"
name="text"
placeholder="Type or paste your text here..."
required
>Hello, welcome to our service. How may I help you today?</textarea>
</div>
<div class="options">
<div class="form-group">
<label for="voice">Voice</label>
<select id="voice" name="voice">
<option value="Zephyr">Zephyr</option>
<option value="Puck">Puck</option>
<option value="Leda">Leda</option>
</select>
</div>
<div class="form-group">
<label for="accent">Accent</label>
<select id="accent" name="accent">
<option value="hindi">Indian (Hindi)</option>
<option value="neutral">Neutral</option>
<option value="american">American</option>
<option value="british">British</option>
</select>
</div>
</div>
<button type="submit" class="btn" id="generateBtn">
<span class="material-icons">record_voice_over</span>
Generate Speech
</button>
</form>
<div class="error" id="error"></div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p style="margin-top: 10px; color: #666;">Generating speech...</p>
</div>
<div class="audio-player" id="audioPlayer">
<h3 style="margin-bottom: 10px; color: #333;">Generated Audio</h3>
<audio id="audioElement" controls></audio>
<button class="btn btn-secondary" id="downloadBtn">
<span class="material-icons">download</span>
Download Audio
</button>
</div>
</div>
<script>
const form = document.getElementById('ttsForm');
const generateBtn = document.getElementById('generateBtn');
const audioPlayer = document.getElementById('audioPlayer');
const audioElement = document.getElementById('audioElement');
const errorDiv = document.getElementById('error');
const loading = document.getElementById('loading');
const downloadBtn = document.getElementById('downloadBtn');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const text = document.getElementById('text').value;
const voice = document.getElementById('voice').value;
const accent = document.getElementById('accent').value;
if (!text.trim()) {
showError('Please enter some text');
return;
}
// Hide error and audio player
errorDiv.style.display = 'none';
audioPlayer.classList.remove('show');
// Show loading
loading.style.display = 'block';
generateBtn.disabled = true;
try {
const response = await fetch('/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ text, voice, accent }),
});
const data = await response.json();
if (response.ok && data.success) {
// Convert base64 to blob
const audioData = atob(data.audio);
const arrayBuffer = new ArrayBuffer(audioData.length);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < audioData.length; i++) {
view[i] = audioData.charCodeAt(i);
}
const blob = new Blob([arrayBuffer], { type: 'audio/wav' });
const url = URL.createObjectURL(blob);
audioElement.src = url;
audioPlayer.classList.add('show');
} else {
showError(data.error || 'Failed to generate audio');
}
} catch (error) {
showError('Network error. Please try again.');
} finally {
loading.style.display = 'none';
generateBtn.disabled = false;
}
});
downloadBtn.addEventListener('click', () => {
window.open('/download', '_blank');
});
function showError(message) {
errorDiv.textContent = message;
errorDiv.style.display = 'block';
}
</script>
</body>
</html>