HtmlEzmar / index.html
Hamed744's picture
Update index.html
06f7f41 verified
raw
history blame
32.5 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 Nova - نسل جدید تبدیل متن به صدا</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--app-font: 'Vazirmatn', sans-serif;
--app-bg: #F0F7FA; /* Very light cool gray/blue */
--panel-bg: #FFFFFF;
--panel-border: #E0E8EF;
--text-primary: #1A202C; /* Dark Gray */
--text-secondary: #4A5568; /* Medium Gray */
--accent-primary: #2563EB; /* Vibrant Blue */
--accent-primary-hover: #1D4ED8;
--accent-secondary: #0D9488; /* Teal */
--accent-secondary-hover: #0F766E;
--input-bg: #F7FAFC;
--input-border-focus: var(--accent-primary);
--radius-card: 20px;
--radius-input: 12px;
--shadow-subtle: 0 4px 12px -2px rgba(26, 32, 44, 0.06);
--shadow-medium: 0 8px 20px -4px rgba(26, 32, 44, 0.1);
--shadow-strong: 0 12px 30px -6px rgba(26, 32, 44, 0.15);
}
body {
font-family: var(--app-font);
direction: rtl;
background-color: var(--app-bg);
color: var(--text-primary);
font-size: 16px;
line-height: 1.7;
margin: 0;
padding: 2rem 0;
min-height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: flex;
justify-content: center;
align-items: flex-start;
}
.container {
max-width: 720px;
width: 90%;
margin: 0 auto;
}
.app-header {
padding: 0.5rem 0 2.5rem 0;
text-align: center;
margin-bottom: 1.5rem;
}
.app-header h1 {
font-size: 2.8em;
font-weight: 800;
margin:0 0 0.5rem 0;
color: var(--accent-primary);
}
.app-header p {
font-size: 1.15em;
color: var(--text-secondary);
margin-top:0;
opacity: 0.9;
}
.main-content {
padding: 2.5rem;
background-color: var(--panel-bg);
border-radius: var(--radius-card);
box-shadow: var(--shadow-medium);
border: 1px solid var(--panel-border);
}
.form-group { margin-bottom: 2.2rem; }
label {
display: block;
font-weight: 600;
color: var(--text-primary);
font-size: 1.05em;
margin-bottom: 0.75rem;
}
textarea, input[type="text"] {
width: 100%;
padding: 0.9rem 1.1rem;
border-radius: var(--radius-input);
border: 1px solid var(--panel-border);
background-color: var(--input-bg);
color: var(--text-primary);
box-shadow: var(--shadow-subtle);
font-family: var(--app-font);
font-size: 1rem;
box-sizing: border-box;
transition: all 0.25s ease-in-out;
}
textarea:focus, input[type="text"]:focus {
outline: none;
border-color: var(--input-border-focus);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2), var(--shadow-subtle);
background-color: #fff;
}
textarea { min-height: 110px; }
/* --- نمایش گوینده منتخب --- */
#selected-speaker-display { text-align: center; margin-top: 1rem; }
#selected-speaker-card {
display: inline-flex;
align-items: center;
background: linear-gradient(145deg, var(--input-bg), #fff);
border-radius: 99px;
padding: 10px 12px 10px 20px; /* R L T B */
box-shadow: var(--shadow-medium);
border: 1px solid var(--panel-border);
transition: all 0.3s ease;
cursor: pointer;
}
#selected-speaker-card:hover {
transform: translateY(-3px) scale(1.02);
box-shadow: var(--shadow-strong);
}
#selected-speaker-card img {
width: 75px; height: 75px;
border-radius: 50%;
object-fit: cover;
margin-left: 18px;
border: 3px solid var(--accent-secondary);
box-shadow: 0 0 12px -2px rgba(13, 148, 136, 0.5);
background-color: #e0e0e0;
}
#selected-speaker-info h3 { margin: 0; font-size: 1.35em; font-weight: 700; color: var(--text-primary); }
#selected-speaker-info p { margin: 4px 0 0; color: var(--text-secondary); font-size: 0.88em; }
#change-speaker-btn {
display: block; margin: 1.2rem auto 0;
padding: 10px 24px;
border-radius: var(--radius-input);
background-color: transparent;
border: 2px solid var(--accent-primary);
color: var(--accent-primary);
cursor: pointer;
font-family: var(--app-font); font-weight: 600;
transition: all 0.2s ease;
}
#change-speaker-btn:hover {
background-color: var(--accent-primary);
color: #fff;
transform: translateY(-2px);
box-shadow: 0 4px 10px -2px rgba(37, 99, 235, 0.3);
}
/* --- مودال گالری گویندگان --- */
#speaker-modal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background-color: rgba(26, 32, 44, 0.5);
backdrop-filter: blur(8px) saturate(150%);
display: none; align-items: center; justify-content: center;
z-index: 1000; opacity: 0;
transition: opacity 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
#speaker-modal.visible { display: flex; opacity: 1; }
.modal-content {
background: var(--panel-bg);
padding: 2rem;
border-radius: var(--radius-card);
width: 90%; max-width: 680px;
max-height: 85vh; overflow-y: auto;
transform: scale(0.92) translateY(20px);
transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.35s;
border: 1px solid var(--panel-border);
box-shadow: var(--shadow-strong);
opacity: 0;
}
#speaker-modal.visible .modal-content { transform: scale(1) translateY(0); opacity: 1;}
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; padding-bottom: 1rem; border-bottom: 1px solid var(--panel-border); }
.modal-header h2 { margin: 0; font-size: 1.6em; color: var(--accent-primary);}
.close-modal-btn { background: none; border: none; font-size: 2.4rem; cursor: pointer; color: var(--text-secondary); transition: all 0.2s ease; line-height: 1; }
.close-modal-btn:hover { color: var(--accent-primary); transform: rotate(90deg) scale(1.05); }
#speaker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(115px, 1fr)); gap: 1.3rem; }
@media (min-width: 576px) { #speaker-grid { grid-template-columns: repeat(4, 1fr); } }
.speaker-card { cursor: pointer; transition: all 0.25s ease-out; text-align: center; position: relative;}
.speaker-card .speaker-visual {
border: 3px solid transparent;
border-radius: var(--radius-card);
overflow: hidden;
box-shadow: var(--shadow-subtle);
position: relative;
background-color: #fff;
transition: all 0.25s ease-out;
}
.speaker-card:hover .speaker-visual {
transform: translateY(-5px) scale(1.03);
box-shadow: var(--shadow-medium);
}
.speaker-card input[type="radio"] { display: none; }
.speaker-card img {
width: 100%; height: 115px;
object-fit: cover; display: block;
background-color: #e9e9e9;
transition: transform 0.3s ease;
}
.speaker-card:hover img { transform: scale(1.05); }
.speaker-card .speaker-name { padding: 0.8rem 0.5rem; font-weight: 500; font-size: 0.92em; color: var(--text-secondary); }
.speaker-card input[type="radio"]:checked + .speaker-visual {
border-color: var(--accent-secondary);
box-shadow: 0 0 15px -3px rgba(13, 148, 136, 0.6);
}
.speaker-card input[type="radio"]:checked + .speaker-visual .speaker-name {
color: var(--accent-secondary);
font-weight: 700;
}
/* --- Slider & Button & Output --- */
.slider-container { display: flex; align-items: center; gap: 1.2rem; }
input[type="range"] {
flex-grow: 1; -webkit-appearance: none; appearance: none;
width: 100%; height: 8px;
background: #E2E8F0;
border-radius: 4px; outline: none;
cursor: pointer;
transition: background 0.2s;
}
input[type="range"]::-webkit-slider-runnable-track {
background: linear-gradient(to right, var(--accent-secondary) 0%, var(--accent-primary) 100%);
height: 8px;
border-radius: 4px;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 22px; height: 22px;
background: #fff;
border-radius: 50%; cursor: pointer;
border: 3px solid var(--accent-primary);
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
margin-top: -7px; /* Center thumb on track */
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.1);
box-shadow: 0 3px 8px rgba(37, 99, 235, 0.3);
}
input[type="range"]::-moz-range-thumb {
width: 22px; height: 22px;
background: #fff;
border-radius: 50%; cursor: pointer;
border: 3px solid var(--accent-primary);
box-shadow: 0 2px 6px rgba(0,0,0,0.15);
}
/* For Firefox, style the track directly */
input[type="range"]::-moz-range-track {
background: linear-gradient(to right, var(--accent-secondary) 0%, var(--accent-primary) 100%);
height: 8px;
border-radius: 4px;
border: none;
}
#temperature-value {
font-weight: 600; background-color: var(--input-bg);
padding: 0.4rem 1rem;
border-radius: 8px;
border: 1px solid var(--panel-border);
min-width: 45px; text-align: center;
color: var(--text-primary);
}
#generate-btn {
width: 100%; padding: 1.05rem 1.5rem;
font-size: 1.25em; font-weight: 700;
font-family: var(--app-font);
background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%);
color: #fff;
border: none;
border-radius: var(--radius-input);
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 5px 15px -4px rgba(37, 99, 235, 0.4), 0 5px 15px -4px rgba(13, 148, 136, 0.3);
position: relative;
overflow: hidden;
}
#generate-btn::before { /* Shine effect */
content: ''; position: absolute;
top: 0; left: -150%; width: 70%; height: 100%;
background: linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.4) 50%, rgba(255,255,255,0) 100%);
transform: skewX(-25deg);
transition: left 0.6s cubic-bezier(0.23, 1, 0.32, 1);
}
#generate-btn:hover::before { left: 150%; }
#generate-btn:hover:not(:disabled) {
transform: translateY(-3px);
box-shadow: 0 8px 20px -4px rgba(37, 99, 235, 0.5), 0 8px 20px -4px rgba(13, 148, 136, 0.4);
}
#generate-btn:disabled {
background: #A0AEC0; /* Lighter gray for disabled */
cursor: not-allowed; box-shadow: none; color: #E2E8F0;
transform: none;
}
#generate-btn:disabled::before { display: none; }
#output-section {
margin-top: 3rem;
padding: 2.5rem;
background-color: #fff;
border-radius: var(--radius-card);
min-height: 200px;
display: flex; align-items: center; justify-content: center;
flex-direction: column; gap: 1.5rem;
border: 1px dashed var(--panel-border);
transition: all 0.3s ease;
position: relative;
box-shadow: var(--shadow-subtle);
}
#status-message { font-weight: 500; color: var(--text-secondary); text-align: center; font-size: 1.1em; }
#audio-player { width: 100%; margin-top: 1rem; display: none; }
#audio-player::-webkit-media-controls-panel { background-color: #f9fafb; border-radius: 8px; }
#audio-player::-webkit-media-controls-play-button { color: var(--accent-primary); }
#audio-player::-webkit-media-controls-current-time-display,
#audio-player::-webkit-media-controls-time-remaining-display { color: var(--text-secondary); }
/* --- انیمیشن پردازش جدید: ذرات هوشمند --- */
#loading-animation-wrapper {
display: none; /* Hidden by default */
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2rem;
width: 100%;
min-height: 150px;
perspective: 800px; /* For 3D effect on particles if desired */
}
.smart-particles-loader {
width: 100px;
height: 100px;
position: relative;
animation: rotate-loader 10s linear infinite;
}
.particle {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--accent-primary);
box-shadow: 0 0 8px var(--accent-primary), 0 0 12px var(--accent-secondary);
opacity: 0;
animation: particle-orbit 2.5s ease-in-out infinite,
particle-fade 2.5s ease-in-out infinite;
}
.particle:nth-child(1) { top: 0; left: 50%; transform: translateX(-50%); animation-delay: 0s; }
.particle:nth-child(2) { top: 15%; left: 85%; transform: translate(-50%, -50%); animation-delay: -0.3s; background-color: var(--accent-secondary);}
.particle:nth-child(3) { top: 50%; left: 100%; transform: translateY(-50%); animation-delay: -0.6s; }
.particle:nth-child(4) { top: 85%; left: 85%; transform: translate(-50%, -50%); animation-delay: -0.9s; background-color: var(--accent-secondary);}
.particle:nth-child(5) { top: 100%; left: 50%; transform: translateX(-50%); animation-delay: -1.2s; }
.particle:nth-child(6) { top: 85%; left: 15%; transform: translate(-50%, -50%); animation-delay: -1.5s; background-color: var(--accent-secondary);}
.particle:nth-child(7) { top: 50%; left: 0; transform: translateY(-50%); animation-delay: -1.8s; }
.particle:nth-child(8) { top: 15%; left: 15%; transform: translate(-50%, -50%); animation-delay: -2.1s; background-color: var(--accent-secondary);}
@keyframes rotate-loader {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes particle-orbit {
0%, 100% { transform: scale(0.5); }
50% { transform: scale(1.2); }
}
@keyframes particle-fade {
0%, 100% { opacity: 0; }
20%, 80% { opacity: 1; }
}
#loading-text {
font-size: 1.2em;
font-weight: 600;
color: var(--text-primary);
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<header class="app-header">
<h1>آلفا TTS Nova</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="4" 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="selected-speaker-display">
<div id="selected-speaker-card" title="برای تغییر گوینده کلیک کنید">
<img id="selected-speaker-img" src="" alt="عکس گوینده">
<div id="selected-speaker-info">
<h3 id="selected-speaker-name"></h3>
<p>گوینده پیش‌فرض</p>
</div>
</div>
<button type="button" id="change-speaker-btn">مشاهده همه گویندگان</button>
</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>
<div id="loading-animation-wrapper">
<div class="smart-particles-loader">
<div class="particle"></div> <div class="particle"></div>
<div class="particle"></div> <div class="particle"></div>
<div class="particle"></div> <div class="particle"></div>
<div class="particle"></div> <div class="particle"></div>
</div>
<p id="loading-text">در حال پردازش هوشمند و تولید صدا...</p>
</div>
<audio id="audio-player" controls></audio>
</div>
</main>
</div>
<div id="speaker-modal">
<div class="modal-content">
<div class="modal-header">
<h2>گالری گویندگان آلفا نوا</h2>
<button type="button" class="close-modal-btn">×</button>
</div>
<div id="speaker-grid"></div>
</div>
</div>
<input type="hidden" id="selected_speaker_id_storage" value="Charon">
<script>
document.addEventListener('DOMContentLoaded', () => {
const HF_SPACE_URL = "https://hamed744-ttspro.hf.space";
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=`;
const FN_INDEX = 1;
const speakers = [
{ id: "Charon", name: "شهاب (مرد)" }, { id: "Zephyr", name: "آوا (زن)" }, { id: "Achird", name: "نوید (مرد)" }, { id: "Zubenelgenubi", name: "رویا (زن)" }, { id: "Vindemiatrix", name: "کیان (مرد)" }, { id: "Sadachbia", name: "پریسا (زن)" }, { id: "Sadaltager", name: "آرش (مرد)" }, { id: "Sulafat", name: "شبنم (زن)" }, { id: "Laomedeia", name: "سهیل (مرد)" }, { id: "Achernar", name: "مریم (زن)" }, { id: "Alnilam", name: "بهرام (مرد)" }, { id: "Schedar", name: "نگار (زن)" }, { id: "Gacrux", name: "فرید (مرد)" }, { id: "Pulcherrima", name: "سارا (زن)" }, { id: "Umbriel", name: "مانی (مرد)" }, { id: "Algieba", name: "آناهیتا (زن)" }, { id: "Despina", name: "دلنواز (زن)" }, { id: "Erinome", name: "رسا (مرد)" }, { id: "Algenib", name: "امید (مرد)" }, { id: "Rasalthgeti", name: "الهه (زن)" }, { id: "Orus", name: "بردیا (مرد)" }, { id: "Aoede", name: "ترانه (زن)" }, { id: "Callirrhoe", name: "نیما (مرد)" }, { id: "Autonoe", name: "هستی (زن)" }, { id: "Enceladus", name: "کامیار (مرد)" }, { id: "Iapetus", name: "ستاره (زن)" }, { id: "Puck", name: "پویا (مرد)" }, { id: "Kore", name: "مهتاب (زن)" }, { id: "Fenrir", name: "سام (مرد)" }, { id: "Leda", name: "لیدا (زن)" }
];
const form = document.getElementById('tts-form');
const textInput = document.getElementById('text-input');
const promptInput = document.getElementById('prompt-input');
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');
const loadingAnimationWrapper = document.getElementById('loading-animation-wrapper');
const selectedSpeakerIdStorage = document.getElementById('selected_speaker_id_storage');
const speakerModal = document.getElementById('speaker-modal');
const changeSpeakerBtn = document.getElementById('change-speaker-btn');
const closeModalBtn = document.querySelector('.close-modal-btn');
const speakerGridInModal = document.getElementById('speaker-grid');
const selectedSpeakerImgDisplay = document.getElementById('selected-speaker-img');
const selectedSpeakerNameDisplay = document.getElementById('selected-speaker-name');
const selectedSpeakerCard = document.getElementById('selected-speaker-card');
const selectedSpeakerInfoP = selectedSpeakerCard.querySelector('#selected-speaker-info p');
function getSpeakerById(id) {
return speakers.find(s => s.id === id);
}
function getImageUrl(speaker, index, size = 'medium') {
const gender = speaker.name.includes('(مرد)') ? 'men' : (speaker.name.includes('(زن)') ? 'women' : 'lego');
const imageIndex = (index * 13 + 7) % 100;
let portraitSizePath = 'thumb/';
if (size === 'large') portraitSizePath = '';
return `https://randomuser.me/api/portraits/${portraitSizePath}${gender}/${imageIndex}.jpg`;
}
function updateSelectedSpeakerDisplay(speakerId) {
const speaker = getSpeakerById(speakerId);
if (speaker) {
const speakerIndex = speakers.findIndex(s => s.id === speakerId);
selectedSpeakerImgDisplay.src = getImageUrl(speaker, speakerIndex, 'large');
selectedSpeakerImgDisplay.alt = `عکس گوینده ${speaker.name}`;
selectedSpeakerNameDisplay.textContent = speaker.name;
selectedSpeakerInfoP.textContent = "گوینده منتخب شما";
selectedSpeakerIdStorage.value = speaker.id;
}
}
function createSpeakerCardsInModal() {
speakerGridInModal.innerHTML = '';
speakers.forEach((speaker, index) => {
const card = document.createElement('label');
card.className = 'speaker-card';
card.setAttribute('for', `modal-speaker-${speaker.id}`);
const isChecked = speaker.id === selectedSpeakerIdStorage.value ? 'checked' : '';
card.innerHTML = `
<input type="radio" name="modal_speaker_selection" value="${speaker.id}" id="modal-speaker-${speaker.id}" ${isChecked}>
<div class="speaker-visual">
<img src="${getImageUrl(speaker, index, 'thumb')}" alt="${speaker.name}" loading="lazy">
<div class="speaker-name">${speaker.name}</div>
</div>
`;
card.addEventListener('click', () => {
updateSelectedSpeakerDisplay(speaker.id);
setTimeout(() => speakerModal.classList.remove('visible'), 250);
});
speakerGridInModal.appendChild(card);
});
}
function openSpeakerModal() {
createSpeakerCardsInModal();
speakerModal.classList.add('visible');
}
changeSpeakerBtn.addEventListener('click', openSpeakerModal);
selectedSpeakerCard.addEventListener('click', openSpeakerModal); // Allow clicking on card too
closeModalBtn.addEventListener('click', () => speakerModal.classList.remove('visible'));
speakerModal.addEventListener('click', (e) => {
if (e.target === speakerModal) {
speakerModal.classList.remove('visible');
}
});
tempSlider.addEventListener('input', () => { tempValueSpan.textContent = tempSlider.value; });
function showLoadingState() {
statusMessage.style.display = 'none';
audioPlayer.style.display = 'none';
audioPlayer.src = '';
loadingAnimationWrapper.style.display = 'flex';
generateBtn.disabled = true;
generateBtn.textContent = 'در حال پردازش...';
}
function showResultState(isSuccess, message = '') {
loadingAnimationWrapper.style.display = 'none';
if (isSuccess) {
statusMessage.style.display = 'none';
audioPlayer.style.display = 'block';
// Consider if autoplay is desired: audioPlayer.play();
} else {
statusMessage.textContent = message || 'خطایی رخ داد. لطفاً دوباره تلاش کنید.';
statusMessage.style.display = 'block';
audioPlayer.style.display = 'none';
}
generateBtn.disabled = false;
generateBtn.textContent = '🚀 تولید صدا با آلفا نوا';
}
async function generateAudio(event) {
event.preventDefault();
showLoadingState();
const text = textInput.value;
if (!text.trim()) {
showResultState(false, 'خطا: متن ورودی نمی‌تواند خالی باشد.');
return;
}
const promptVal = promptInput.value;
const temperatureVal = parseFloat(tempSlider.value);
const selectedSpeakerVal = selectedSpeakerIdStorage.value;
const sessionHash = Math.random().toString(36).substring(2);
const payload = {
fn_index: FN_INDEX,
data: [false, null, text, promptVal, selectedSpeakerVal, temperatureVal],
event_data: null,
session_hash: sessionHash
};
try {
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}).`);
}
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));
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) { /* Ignore JSON parse errors */ }
}
if (finalFilePath) break;
}
if (finalFilePath) {
const audioUrl = `${FILE_URL_BASE}${finalFilePath}`;
audioPlayer.src = audioUrl;
showResultState(true);
} else {
throw new Error('فایل صوتی از سرور دریافت نشد. پردازش ممکن است ناموفق بوده باشد.');
}
} catch (error) {
console.error('خطا در فرآیند تولید صدا:', error);
showResultState(false, `خطا: ${error.message}`);
}
}
updateSelectedSpeakerDisplay(selectedSpeakerIdStorage.value);
form.addEventListener('submit', generateAudio);
statusMessage.style.display = 'block';
loadingAnimationWrapper.style.display = 'none';
audioPlayer.style.display = 'none';
});
</script>
</body>
</html>