Veu3 / index.html
Hamed744's picture
Update index.html
b51dc15 verified
raw
history blame
15.2 kB
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alpha TTS - رابط کاربری نهایی</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;700;800&display=swap');
:root{--app-font:'Vazirmatn',sans-serif;--app-header-grad-start:#2980b9;--app-header-grad-end:#2ecc71;--app-panel-bg:#FFFFFF;--app-input-bg:#F7F7F7;--app-button-bg:#2979FF;--app-main-bg:linear-gradient(170deg, #E0F2FE 0%, #F3E8FF 100%);--app-text-primary:#333;--app-text-secondary:#555;--app-border-color:#E0E0E0;--radius-card:20px;--radius-input:12px;--shadow-card:0 10px 30px -5px rgba(0,0,0,0.1);--shadow-button:0 4px 10px -2px rgba(41,121,255,0.5);--speaker-selected-border:3px solid var(--app-button-bg);}
body{font-family:var(--app-font);direction:rtl;background:var(--app-main-bg);color:var(--app-text-primary);font-size:16px;line-height:1.65;margin:0;padding:0;min-height:100vh;display:flex;flex-direction:column;}
.container{max-width:900px;width:95%;margin:0 auto;padding-bottom:40px;}
.app-header{padding:3rem 1.5rem 5rem 1.5rem;text-align:center;background-image:linear-gradient(135deg, var(--app-header-grad-start) 0%, var(--app-header-grad-end) 100%);color:white;border-bottom-left-radius:var(--radius-card);border-bottom-right-radius:var(--radius-card);box-shadow:0 6px 20px -5px rgba(0,0,0,0.2);}
.app-header h1{font-size:2.8em;font-weight:800;margin:0 0 .5rem 0;text-shadow:0 2px 4px rgba(0,0,0,0.15);}
.app-header p{font-size:1.2em;color:rgba(255,255,255,0.9);margin-top:0;opacity:.9;}
.main-content{padding:2.5rem;margin:-3.5rem auto 2rem auto;background-color:var(--app-panel-bg);border-radius:var(--radius-card);box-shadow:var(--shadow-card);}
.form-group{margin-bottom:2rem;}
label{display:block;font-weight:700;color:var(--app-text-primary);font-size:1.1em;margin-bottom:.8rem;}
textarea,input[type=text]{width:100%;padding:1rem;border-radius:var(--radius-input);border:1px solid var(--app-border-color);background-color:var(--app-input-bg);box-shadow:inset 0 1px 2px rgba(0,0,0,0.05);font-family:var(--app-font);font-size:1rem;box-sizing:border-box;transition:all .2s ease-in-out;}
textarea:focus,input[type=text]:focus{outline:none;border-color:var(--app-button-bg);box-shadow:0 0 0 3px rgba(41,121,255,0.2);}
#speaker-grid{display:grid;grid-template-columns:repeat(auto-fill, minmax(120px, 1fr));gap:1.5rem;}
.speaker-card{cursor:pointer;border:3px solid transparent;border-radius:var(--radius-card);overflow:hidden;text-align:center;transition:all .3s ease;box-shadow:0 4px 15px rgba(0,0,0,0.08);position:relative;}
.speaker-card:hover{transform:translateY(-5px);box-shadow:0 8px 25px rgba(0,0,0,0.12);}
.speaker-card input[type=radio]{display:none;}
.speaker-card img{width:100%;height:120px;object-fit:cover;display:block;background-color:#eee;}
.speaker-card .speaker-name{padding:.8rem .5rem;font-weight:500;background-color:rgba(255,255,255,0.8);backdrop-filter:blur(5px);transition:background-color .3s ease;}
.speaker-card input[type=radio]:checked + .speaker-visual{border-color:var(--app-button-bg);box-shadow:0 8px 25px rgba(41,121,255,0.3);transform:translateY(-5px);}
.speaker-card input[type=radio]:checked + .speaker-visual .speaker-name{background-color:var(--app-button-bg);color:white;font-weight:700;}
.slider-container{display:flex;align-items:center;gap:1rem;}
input[type=range]{flex-grow:1;cursor:pointer;}
#temperature-value{font-weight:bold;background-color:var(--app-input-bg);padding:.2rem .8rem;border-radius:8px;border:1px solid var(--app-border-color);min-width:40px;text-align:center;}
#generate-btn{width:100%;padding:1rem 1.5rem;font-size:1.2em;font-weight:700;font-family:var(--app-font);background:var(--app-button-bg);color:white;border:none;border-radius:var(--radius-input);cursor:pointer;transition:all .3s ease;box-shadow:var(--shadow-button);}
#generate-btn:hover:not(:disabled){filter:brightness(1.1);transform:translateY(-2px);box-shadow:0 6px 12px -3px rgba(41,121,255,0.6);}
#generate-btn:disabled{background-color:#999;cursor:not-allowed;box-shadow:none;}
#output-section{margin-top:2.5rem;padding:1.5rem;background-color:var(--app-input-bg);border-radius:var(--radius-card);min-height:100px;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:1rem;}
#status-message{font-weight:500;color:var(--app-text-secondary);}
#audio-player{width:100%;margin-top:1rem;display:none;}
</style>
</head>
<body>
<div class="container">
<header class="app-header">
<h1>Alpha TTS</h1>
<p>جادوی تبدیل متن به صدا با رابط کاربری سفارشی شما</p>
</header>
<main class="main-content">
<form id="tts-form">
<div class="form-group">
<label for="text-input">📝 متن برای تبدیل</label>
<textarea id="text-input" rows="5" placeholder="اینجا متن خود را به فارسی وارد کنید...">این یک آزمایش برای بررسی کیفیت صدای تولید شده توسط هوش مصنوعی آلفا است.</textarea>
</div>
<div class="form-group">
<label for="prompt-input">🗣️ سبک و لحن گفتار (اختیاری)</label>
<input type="text" id="prompt-input" value="با صدایی طبیعی و روان." placeholder="مثال: با لحنی شاد و پرانرژی">
</div>
<div class="form-group">
<label>🎤 گوینده و لهجه را انتخاب کنید</label>
<div id="speaker-grid"></div>
</div>
<div class="form-group">
<label for="temperature-slider">🌡️ میزان خلاقیت صدا (0.1 تا 1.5)</label>
<div class="slider-container">
<input type="range" id="temperature-slider" min="0.1" max="1.5" step="0.05" value="0.9">
<span id="temperature-value">0.9</span>
</div>
</div>
<button type="submit" id="generate-btn">🚀 تولید و پخش صدا</button>
</form>
<div id="output-section">
<div id="status-message">خروجی صدا در اینجا نمایش داده می‌شود</div>
<audio id="audio-player" controls></audio>
</div>
</main>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
// *** مسیرهای صحیح با پیشوند /gradio_api/ بر اساس لاگ‌ها ***
const JOIN_QUEUE_URL = `${HF_SPACE_URL}/gradio_api/queue/join`;
const GET_DATA_URL_BASE = `${HF_SPACE_URL}/gradio_api/queue/data`;
const FILE_URL_BASE = `${HF_SPACE_URL}/gradio_api/file=`;
// fn_index ها معمولا از 0 شروع میشن، اما با توجه به لاگ های شما، از 1 استفاده می کنیم
const FN_INDEX = 1;
const speakers = ["Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager", "Sulafat", "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux", "Pulcherrima", "Umbriel", "Algieba", "Despina", "Erinome", "Algenib", "Rasalthgeti", "Orus", "Aoede", "Callirrhoe", "Autonoe", "Enceladus", "Iapetus", "Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Leda"];
const form = document.getElementById('tts-form');
const textInput = document.getElementById('text-input');
const promptInput = document.getElementById('prompt-input');
const speakerGrid = document.getElementById('speaker-grid');
const tempSlider = document.getElementById('temperature-slider');
const tempValueSpan = document.getElementById('temperature-value');
const generateBtn = document.getElementById('generate-btn');
const statusMessage = document.getElementById('status-message');
const audioPlayer = document.getElementById('audio-player');
function createSpeakerCards() {
speakers.forEach((speakerName) => {
const card = document.createElement('label');
card.className = 'speaker-card';
card.setAttribute('for', `speaker-${speakerName}`);
const imageUrl = `https://picsum.photos/seed/${speakerName}/200/200`;
const isChecked = speakerName === 'Charon' ? 'checked' : '';
card.innerHTML = `<input type="radio" name="speaker" value="${speakerName}" id="speaker-${speakerName}" ${isChecked}><div class="speaker-visual"><img src="${imageUrl}" alt="عکس گوینده ${speakerName}"><div class="speaker-name">${speakerName}</div></div>`;
speakerGrid.appendChild(card);
});
}
tempSlider.addEventListener('input', () => { tempValueSpan.textContent = tempSlider.value; });
async function generateAudio(event) {
event.preventDefault();
generateBtn.disabled = true;
generateBtn.textContent = 'در حال پردازش...';
statusMessage.textContent = 'در حال ارسال درخواست به سرور...';
audioPlayer.style.display = 'none';
audioPlayer.src = '';
const text = textInput.value;
const prompt = promptInput.value;
const temperature = parseFloat(tempSlider.value);
const selectedSpeaker = document.querySelector('input[name="speaker"]:checked').value;
const sessionHash = Math.random().toString(36).substring(2);
if (!text.trim()) {
statusMessage.textContent = 'خطا: متن ورودی نمی‌تواند خالی باشد.';
generateBtn.disabled = false;
generateBtn.textContent = '🚀 تولید و پخش صدا';
return;
}
const payload = {
fn_index: FN_INDEX,
data: [false, null, text, prompt, selectedSpeaker, temperature],
event_data: null,
session_hash: sessionHash
};
try {
// 1. اتصال به صف
const joinQueueResponse = await fetch(JOIN_QUEUE_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
if (!joinQueueResponse.ok) {
const errorBody = await joinQueueResponse.text();
throw new Error(`خطا در اتصال به صف (${joinQueueResponse.status}): ${errorBody}`);
}
console.log("با موفقیت به صف متصل شد. در انتظار داده...");
statusMessage.textContent = 'در انتظار نتیجه از سرور...';
// 2. گوش دادن برای دریافت نتیجه
const dataResponse = await fetch(`${GET_DATA_URL_BASE}?session_hash=${sessionHash}`);
const reader = dataResponse.body.getReader();
const decoder = new TextDecoder();
let finalFilePath = null;
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
if (!line.startsWith('data:')) continue;
try {
const data = JSON.parse(line.substring(5));
console.log('پیام دریافتی از سرور:', data);
if (data.msg === 'process_generating') {
statusMessage.textContent = 'سرور در حال تولید فایل صوتی است...';
}
if (data.msg === 'process_completed') {
if (data.success && data.output.data && data.output.data[0] && (data.output.data[0].name || data.output.data[0].path)) {
finalFilePath = data.output.data[0].name || data.output.data[0].path;
} else {
console.error("ساختار پیام موفقیت مورد انتظار نبود:", data);
}
break;
}
} catch (e) {
console.warn("خطا در پارس کردن خط:", line, e);
}
}
if (finalFilePath) break;
}
if (finalFilePath) {
statusMessage.textContent = 'فایل صوتی با موفقیت دریافت شد!';
const audioUrl = `${FILE_URL_BASE}${finalFilePath}`;
console.log("آدرس نهایی فایل صوتی:", audioUrl);
audioPlayer.src = audioUrl;
audioPlayer.style.display = 'block';
audioPlayer.play();
} else {
throw new Error('فایل صوتی از سرور دریافت نشد. کنسول را برای اطلاعات بیشتر بررسی کنید.');
}
} catch (error) {
console.error('یک خطا در فرآیند رخ داد:', error);
statusMessage.textContent = `یک خطا رخ داد: ${error.message}`;
} finally {
generateBtn.disabled = false;
generateBtn.textContent = '🚀 تولید و پخش صدا';
}
}
createSpeakerCards();
form.addEventListener('submit', generateAudio);
});
</script>
</body>
</html>