|
<!DOCTYPE html> |
|
<html lang="fa" dir="rtl"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>تبدیل متن به صدا - Gemini TTS Pro</title> |
|
<style> |
|
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800;900&display=swap'); |
|
|
|
:root { |
|
--app-font: 'Vazirmatn', sans-serif; |
|
--app-bg: #f8f9fc; |
|
--panel-bg: #ffffff; |
|
--panel-border: #eaeff7; |
|
--input-bg: #f6f8fb; |
|
--input-border: #e1e7ef; |
|
--text-primary: #1a202c; |
|
--text-secondary: #626f86; |
|
--text-tertiary: #8a94a6; |
|
--accent-primary: #4a6cfa; |
|
--accent-primary-hover: #3553d6; |
|
--accent-primary-glow: rgba(74, 108, 250, 0.25); |
|
--accent-secondary: #0fd4a8; |
|
--accent-secondary-hover: #0da986; |
|
--waveform-color-active: var(--accent-primary); |
|
--waveform-color-inactive: #d0d9e6; |
|
--shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03); |
|
--shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04); |
|
--shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05); |
|
--shadow-xl: 0 20px 25px -5px rgba(26, 32, 44, 0.07), 0 8px 10px -6px rgba(26, 32, 44, 0.05); |
|
--radius-card: 24px; |
|
--radius-btn: 14px; |
|
--radius-input: 12px; |
|
--transition-fast: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); |
|
--transition-smooth: all 0.35s cubic-bezier(0.4, 0, 0.2, 1); |
|
} |
|
|
|
body { |
|
font-family: var(--app-font); |
|
direction: rtl; |
|
background-color: var(--app-bg); |
|
color: var(--text-primary); |
|
margin: 0; |
|
padding: 2rem; |
|
min-height: 100vh; |
|
display: flex; |
|
justify-content: center; |
|
align-items: flex-start; |
|
} |
|
|
|
.container { |
|
max-width: 800px; |
|
width: 100%; |
|
} |
|
|
|
.app-header { |
|
text-align: center; |
|
margin-bottom: 2rem; |
|
} |
|
|
|
.app-header h1 { |
|
font-size: 2.2rem; |
|
color: var(--accent-primary); |
|
margin-bottom: 0.5rem; |
|
} |
|
|
|
.app-header p { |
|
color: var(--text-secondary); |
|
margin-top: 0; |
|
} |
|
|
|
.main-panel { |
|
background-color: var(--panel-bg); |
|
border-radius: var(--radius-card); |
|
padding: 2rem; |
|
box-shadow: var(--shadow-xl); |
|
border: 1px solid var(--panel-border); |
|
} |
|
|
|
.form-group { |
|
margin-bottom: 1.5rem; |
|
} |
|
|
|
label { |
|
display: block; |
|
font-weight: 600; |
|
margin-bottom: 0.5rem; |
|
color: var(--text-primary); |
|
} |
|
|
|
textarea, select { |
|
width: 100%; |
|
padding: 0.8rem; |
|
border-radius: var(--radius-input); |
|
border: 1px solid var(--input-border); |
|
background-color: var(--input-bg); |
|
font-family: var(--app-font); |
|
resize: vertical; |
|
transition: var(--transition-smooth); |
|
} |
|
|
|
textarea:focus, select:focus { |
|
outline: none; |
|
border-color: var(--accent-primary); |
|
box-shadow: 0 0 0 3px var(--accent-primary-glow); |
|
} |
|
|
|
textarea { |
|
min-height: 120px; |
|
} |
|
|
|
.char-counter { |
|
font-size: 0.85rem; |
|
color: var(--text-secondary); |
|
text-align: left; |
|
margin-top: 0.5rem; |
|
} |
|
|
|
.char-counter .count { |
|
font-weight: 600; |
|
color: var(--accent-primary); |
|
} |
|
|
|
.btn { |
|
background-color: var(--accent-primary); |
|
color: white; |
|
border: none; |
|
padding: 0.8rem 1.5rem; |
|
border-radius: var(--radius-btn); |
|
font-family: var(--app-font); |
|
font-weight: 600; |
|
cursor: pointer; |
|
transition: var(--transition-smooth); |
|
width: 100%; |
|
font-size: 1rem; |
|
} |
|
|
|
.btn:hover { |
|
background-color: var(--accent-primary-hover); |
|
transform: translateY(-2px); |
|
box-shadow: var(--shadow-md); |
|
} |
|
|
|
.btn:disabled { |
|
background-color: var(--text-tertiary); |
|
cursor: not-allowed; |
|
transform: none; |
|
box-shadow: none; |
|
} |
|
|
|
.output-section { |
|
margin-top: 2rem; |
|
padding: 1.5rem; |
|
background-color: var(--input-bg); |
|
border-radius: var(--radius-card); |
|
border: 2px dashed var(--input-border); |
|
text-align: center; |
|
min-height: 150px; |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: center; |
|
align-items: center; |
|
} |
|
|
|
.output-section.has-audio { |
|
background-color: var(--panel-bg); |
|
border: 1px solid var(--panel-border); |
|
} |
|
|
|
.status-message { |
|
color: var(--text-secondary); |
|
} |
|
|
|
.loading-spinner { |
|
display: none; |
|
width: 40px; |
|
height: 40px; |
|
border: 4px solid rgba(0, 0, 0, 0.1); |
|
border-radius: 50%; |
|
border-top-color: var(--accent-primary); |
|
animation: spin 1s linear infinite; |
|
margin-bottom: 1rem; |
|
} |
|
|
|
@keyframes spin { |
|
to { transform: rotate(360deg); } |
|
} |
|
|
|
.audio-player { |
|
width: 100%; |
|
margin-top: 1rem; |
|
display: none; |
|
} |
|
|
|
.audio-controls { |
|
display: flex; |
|
justify-content: center; |
|
gap: 1rem; |
|
margin-top: 1rem; |
|
} |
|
|
|
.audio-controls button { |
|
background: none; |
|
border: none; |
|
cursor: pointer; |
|
font-size: 1.2rem; |
|
color: var(--text-primary); |
|
} |
|
|
|
.error-message { |
|
color: #e74c3c; |
|
margin-top: 1rem; |
|
display: none; |
|
} |
|
|
|
@media (max-width: 600px) { |
|
body { |
|
padding: 1rem; |
|
} |
|
|
|
.main-panel { |
|
padding: 1.5rem; |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<header class="app-header"> |
|
<h1>تبدیل متن به صدا با هوش مصنوعی</h1> |
|
<p>متن خود را وارد کنید و به صدای طبیعی تبدیل کنید</p> |
|
</header> |
|
|
|
<main class="main-panel"> |
|
<form id="tts-form"> |
|
<div class="form-group"> |
|
<label for="text-input">متن فارسی</label> |
|
<textarea id="text-input" placeholder="متن خود را اینجا وارد کنید..."></textarea> |
|
<div class="char-counter"> |
|
<span class="count">0</span> / 50000 نویسه |
|
</div> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label for="prompt-input">راهنمای لحن (اختیاری)</label> |
|
<textarea id="prompt-input" placeholder="مثال: با لحنی شاد و پرانرژی" rows="2"></textarea> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label for="voice-select">انتخاب صدا</label> |
|
<select id="voice-select"> |
|
<option value="Charon">چارون (مرد - پیشفرض)</option> |
|
<option value="Zephyr">زفیر (زن - ملایم)</option> |
|
<option value="Achird">آچیرد (مرد - جوان)</option> |
|
<option value="Zubenelgenubi">زوبن الجنوبی (مرد - گرم)</option> |
|
<option value="Vindemiatrix">ویندیمیاتریکس (زن - رسمی)</option> |
|
<option value="Sadachbia">ساداخبیا (مرد - شاداب)</option> |
|
</select> |
|
</div> |
|
|
|
<div class="form-group"> |
|
<label for="temperature-slider">سطح خلاقیت صدا (0.1 تا 1.5)</label> |
|
<input type="range" id="temperature-slider" min="0.1" max="1.5" step="0.1" value="0.9"> |
|
<div style="text-align: center; margin-top: 0.5rem;"> |
|
<span id="temperature-value">0.9</span> |
|
</div> |
|
</div> |
|
|
|
<button type="submit" class="btn" id="generate-btn">تبدیل به صدا</button> |
|
</form> |
|
|
|
<div class="output-section" id="output-section"> |
|
<div class="status-message" id="status-message">صدای تولید شده در اینجا نمایش داده میشود</div> |
|
<div class="loading-spinner" id="loading-spinner"></div> |
|
<audio controls class="audio-player" id="audio-player"></audio> |
|
<div class="error-message" id="error-message"></div> |
|
</div> |
|
</main> |
|
</div> |
|
|
|
<script> |
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
const API_URL = 'https://www.aisada.ir/api/v1/audio/generate'; |
|
const API_KEY = 'sk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4'; |
|
|
|
|
|
const form = document.getElementById('tts-form'); |
|
const textInput = document.getElementById('text-input'); |
|
const promptInput = document.getElementById('prompt-input'); |
|
const voiceSelect = document.getElementById('voice-select'); |
|
const temperatureSlider = document.getElementById('temperature-slider'); |
|
const temperatureValue = document.getElementById('temperature-value'); |
|
const generateBtn = document.getElementById('generate-btn'); |
|
const outputSection = document.getElementById('output-section'); |
|
const statusMessage = document.getElementById('status-message'); |
|
const loadingSpinner = document.getElementById('loading-spinner'); |
|
const audioPlayer = document.getElementById('audio-player'); |
|
const errorMessage = document.getElementById('error-message'); |
|
const charCount = document.querySelector('.char-counter .count'); |
|
|
|
|
|
temperatureSlider.addEventListener('input', function() { |
|
temperatureValue.textContent = this.value; |
|
}); |
|
|
|
|
|
textInput.addEventListener('input', function() { |
|
const count = this.value.length; |
|
charCount.textContent = count; |
|
charCount.style.color = count > 50000 ? 'red' : 'var(--accent-primary)'; |
|
}); |
|
|
|
|
|
form.addEventListener('submit', async function(e) { |
|
e.preventDefault(); |
|
|
|
const text = textInput.value.trim(); |
|
if (!text) { |
|
showError('لطفاً متن مورد نظر را وارد کنید'); |
|
return; |
|
} |
|
|
|
if (text.length > 50000) { |
|
showError('متن نمیتواند بیشتر از 50000 نویسه باشد'); |
|
return; |
|
} |
|
|
|
startLoading(); |
|
|
|
try { |
|
const response = await fetch(API_URL, { |
|
method: 'POST', |
|
headers: { |
|
'Authorization': `Bearer ${API_KEY}`, |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ |
|
text: text, |
|
prompt: promptInput.value.trim(), |
|
voice: voiceSelect.value, |
|
temperature: parseFloat(temperatureSlider.value) |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
if (response.ok) { |
|
if (data.success) { |
|
showAudio(data.data.audio_url); |
|
} else { |
|
showError(data.error?.message || 'خطایی در پردازش رخ داد'); |
|
} |
|
} else { |
|
showError(data.error?.message || `خطای سرور: ${response.status}`); |
|
} |
|
} catch (error) { |
|
showError('خطا در ارتباط با سرور: ' + error.message); |
|
} finally { |
|
stopLoading(); |
|
} |
|
}); |
|
|
|
|
|
function startLoading() { |
|
generateBtn.disabled = true; |
|
generateBtn.textContent = 'در حال پردازش...'; |
|
loadingSpinner.style.display = 'block'; |
|
statusMessage.style.display = 'none'; |
|
errorMessage.style.display = 'none'; |
|
audioPlayer.style.display = 'none'; |
|
outputSection.classList.remove('has-audio'); |
|
} |
|
|
|
function stopLoading() { |
|
generateBtn.disabled = false; |
|
generateBtn.textContent = 'تبدیل به صدا'; |
|
loadingSpinner.style.display = 'none'; |
|
} |
|
|
|
function showAudio(audioUrl) { |
|
audioPlayer.src = audioUrl; |
|
audioPlayer.style.display = 'block'; |
|
outputSection.classList.add('has-audio'); |
|
statusMessage.style.display = 'none'; |
|
errorMessage.style.display = 'none'; |
|
|
|
|
|
setTimeout(() => { |
|
audioPlayer.play().catch(e => { |
|
console.log('اتوماتیک پخش نشد:', e); |
|
}); |
|
}, 500); |
|
} |
|
|
|
function showError(message) { |
|
errorMessage.textContent = message; |
|
errorMessage.style.display = 'block'; |
|
statusMessage.style.display = 'none'; |
|
audioPlayer.style.display = 'none'; |
|
outputSection.classList.remove('has-audio'); |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |