ai / lib /wordpress_export (9).html
Hamed744's picture
Upload wordpress_export (9).html
9194ea0 verified
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تولید صدای هوشمند با هوش مصنوعی | AI Sada</title>
<meta name="description" content="با AI Sada، متن فارسی خود را به صدایی طبیعی تبدیل کنید.">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Turnstile -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" async defer></script>
<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-secondary: #0FD4A8; --accent-secondary-hover: #0DA986;
--shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06);
--radius-card: 24px; --radius-btn: 14px; --radius-input: 12px;
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--danger: #E53E3E;
}
body { font-family: var(--app-font); direction: rtl; background-color: var(--app-bg); color: var(--text-primary); margin: 0; padding: 0; }
.page-wrapper { max-width: 820px; width: 92%; margin: 0 auto; padding: 2rem 0; }
.app-header { text-align: center; margin-bottom: 2rem; }
.app-header h1 { font-size: 2.2em; font-weight: 900; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 0.5rem; }
.main-content { background: var(--panel-bg); padding: 2rem; border-radius: var(--radius-card); box-shadow: var(--shadow-lg); border: 1px solid var(--panel-border); }
.form-group { margin-bottom: 1.5rem; }
label { display: block; font-weight: 700; margin-bottom: 0.5rem; }
textarea, input[type="text"], input[type="email"] { width: 100%; padding: 1rem; border-radius: var(--radius-input); border: 1px solid var(--input-border); background: var(--input-bg); font-family: inherit; font-size: 1rem; box-sizing: border-box; }
.generate-btn { width: 100%; padding: 1rem; background: linear-gradient(90deg, var(--accent-secondary), var(--accent-primary)); color: white; border: none; border-radius: var(--radius-btn); font-weight: 800; font-size: 1.1em; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 10px; transition: var(--transition-smooth); }
.generate-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.spinner { width: 20px; height: 20px; border: 3px solid rgba(255,255,255,0.3); border-top-color: #fff; border-radius: 50%; animation: spin 1s linear infinite; display: none; }
@keyframes spin { to { transform: rotate(360deg); } }
.output-section { margin-top: 2rem; background: var(--input-bg); padding: 1.5rem; border-radius: var(--radius-card); border: 2px dashed var(--input-border); text-align: center; min-height: 100px; display: flex; flex-direction: column; justify-content: center; }
.output-section.has-content { border-style: solid; background: #fff; border-color: var(--panel-border); }
.request-card { background: #fff; border: 1px solid var(--panel-border); padding: 1rem; border-radius: 16px; margin-bottom: 1rem; display: flex; flex-direction: column; gap: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.02); }
.card-header { display: flex; justify-content: space-between; align-items: center; }
.project-name { font-weight: 700; font-size: 1rem; }
.project-status { font-size: 0.8rem; padding: 2px 8px; border-radius: 4px; background: #eee; }
.status-processing { background: #EBF8FF; color: #2B6CB0; }
.status-completed { background: #F0FFF4; color: #2F855A; }
.status-failed { background: #FFF5F5; color: #C53030; }
.progress-bar { width: 100%; height: 6px; background: #eee; border-radius: 3px; overflow: hidden; margin-top: 5px; }
.progress-fill { height: 100%; background: var(--accent-primary); width: 0%; transition: width 0.3s; }
/* Upload Area */
.upload-area { border: 2px dashed var(--input-border); padding: 2rem; border-radius: var(--radius-input); cursor: pointer; background: var(--input-bg); transition: 0.3s; }
.upload-area:hover { border-color: var(--accent-primary); background: #fff; }
#file-preview { display: none; align-items: center; justify-content: space-between; background: #e0e7ff; padding: 10px; border-radius: 10px; margin-top: 10px; color: #3730a3; font-weight: 600; }
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); backdrop-filter: blur(5px); display: none; align-items: center; justify-content: center; z-index: 999; }
.modal-dialog { background: #fff; padding: 2rem; border-radius: 20px; width: 90%; max-width: 400px; position: relative; }
.close-modal-btn { position: absolute; top: 1rem; left: 1rem; background: none; border: none; font-size: 1.5rem; cursor: pointer; }
/* Audio Player Simple */
.simple-player { width: 100%; margin-top: 10px; }
.download-btn { display: block; margin-top: 5px; text-align: center; background: #f0fdf4; color: #166534; padding: 8px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 0.9rem; }
#cf-container-clone, #cf-container-login { margin: 1rem 0; display: flex; justify-content: center; }
/* Hide Standard View by default for this snippet focus */
#standard-view { display: none; }
#voice-clone-view { display: block; }
.nav-tabs { display: flex; justify-content: center; gap: 10px; margin-bottom: 1.5rem; }
.nav-btn { padding: 8px 16px; border: 1px solid var(--panel-border); background: #fff; border-radius: 20px; cursor: pointer; }
.nav-btn.active { background: var(--accent-primary); color: white; border-color: var(--accent-primary); }
#user-status-container { display: none; justify-content: center; gap: 10px; margin-bottom: 1rem; font-size: 0.9rem; font-weight: 600; }
</style>
</head>
<body>
<div class="page-wrapper">
<div class="app-container">
<header class="app-header">
<h1>هوش مصنوعی آلفا</h1>
<div id="user-status-container">
<span id="user-email-display"></span>
<button id="logout-btn" style="background:none;border:none;color:red;cursor:pointer;">خروج</button>
</div>
<button id="login-check-btn" class="generate-btn" style="width: auto; margin: 0 auto; padding: 0.5rem 1.5rem;">ورود / ثبت نام</button>
</header>
<div class="nav-tabs">
<button class="nav-btn" onclick="switchTab('standard')">متن به صدا</button>
<button class="nav-btn active" onclick="switchTab('clone')">شبیه‌سازی صدا</button>
</div>
<!-- STANDARD VIEW (Placeholder) -->
<div id="standard-view" style="text-align:center; padding: 2rem;">
<p>برای بخش متن به صدا، به کد اصلی مراجعه کنید. تمرکز این فایل روی رفع مشکل شبیه‌سازی است.</p>
</div>
<!-- VOICE CLONE VIEW -->
<div id="voice-clone-view">
<main class="main-content">
<form id="voice-clone-form" onsubmit="return false;">
<div class="form-group">
<label>📝 متن اصلی</label>
<textarea id="text-input-clone" placeholder="متنی که می‌خواهید با صدای خودتان خوانده شود..."></textarea>
</div>
<div class="form-group">
<label>🎤 صدای شما (مرجع)</label>
<label class="upload-area" id="upload-area">
<div>📂</div>
<p>فایل صوتی خود را اینجا بکشید یا کلیک کنید (۳ تا ۱۰ ثانیه، فرمت WAV/MP3)</p>
<input type="file" id="user-voice-input" accept="audio/*" style="display: none;">
</label>
<div id="file-preview">
<span id="file-name"></span>
<button type="button" id="remove-file-btn" style="background:none;border:none;cursor:pointer;"></button>
</div>
</div>
<div id="cf-container-clone"></div>
<button type="submit" id="generate-btn-clone" class="generate-btn">
<span class="btn-text">شروع پردازش</span>
<div class="spinner"></div>
</button>
</form>
<div style="margin-top: 2rem;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem;">
<h3>تاریخچه درخواست‌ها</h3>
<button id="clear-history" style="background:none;border:none;color:gray;cursor:pointer;">پاکسازی</button>
</div>
<div id="history-list"></div>
</div>
</main>
</div>
</div>
</div>
<!-- Login Modal -->
<div id="email-modal" class="modal-overlay">
<div class="modal-dialog">
<button class="close-modal-btn" onclick="document.getElementById('email-modal').classList.remove('visible')">×</button>
<h2>ورود به حساب</h2>
<form id="email-form">
<input type="email" id="login-email-input" placeholder="ایمیل خود را وارد کنید" required style="margin-bottom:1rem;">
<div id="cf-container-login"></div>
<button type="submit" class="generate-btn">ارسال کد</button>
</form>
<form id="code-form" style="display:none; margin-top:1rem;">
<input type="text" id="code-input" placeholder="کد تایید" required style="margin-bottom:1rem;">
<button type="submit" class="generate-btn">تایید</button>
</form>
</div>
</div>
<script>
// --- Config ---
const PROXY_URL = '/tts/proxy.php'; // Ensure this matches your PHP filename/path
let widgetIdClone, widgetIdLogin;
let currentUser = { email: localStorage.getItem('userEmail'), status: 'unknown' };
// --- UI Helpers ---
function switchTab(tab) {
document.getElementById('standard-view').style.display = tab === 'standard' ? 'block' : 'none';
document.getElementById('voice-clone-view').style.display = tab === 'clone' ? 'block' : 'none';
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
event.target.classList.add('active');
}
function updateAuthUI() {
if(currentUser.email) {
document.getElementById('user-email-display').textContent = currentUser.email;
document.getElementById('user-status-container').style.display = 'flex';
document.getElementById('login-check-btn').style.display = 'none';
} else {
document.getElementById('user-status-container').style.display = 'none';
document.getElementById('login-check-btn').style.display = 'block';
}
}
updateAuthUI();
// --- History Logic ---
function getJobs() { return JSON.parse(localStorage.getItem('aisada_jobs_v2') || '[]'); }
function saveJob(job) {
let jobs = getJobs();
const existingIndex = jobs.findIndex(j => j.id === job.id);
if(existingIndex > -1) jobs[existingIndex] = job;
else jobs.unshift(job);
localStorage.setItem('aisada_jobs_v2', JSON.stringify(jobs));
renderHistory();
}
function renderHistory() {
const list = document.getElementById('history-list');
list.innerHTML = '';
const jobs = getJobs();
jobs.forEach(job => {
let statusHtml = '';
let contentHtml = '';
if(job.status === 'completed') {
statusHtml = '<span class="project-status status-completed">تکمیل شد</span>';
const dlUrl = `${PROXY_URL}?endpoint=download-clone&filename=${job.filename}`;
contentHtml = `
<audio controls src="${dlUrl}" class="simple-player"></audio>
<a href="${dlUrl}" class="download-btn">دانلود فایل نهایی</a>
`;
} else if(job.status === 'failed') {
statusHtml = '<span class="project-status status-failed">خطا</span>';
contentHtml = `<p style="color:red;font-size:0.9rem;">خطا: ${job.error || 'ناشناخته'}</p>`;
} else {
statusHtml = '<span class="project-status status-processing">در حال پردازش</span>';
contentHtml = `
<div class="progress-bar"><div class="progress-fill" style="width:${job.progress || 10}%"></div></div>
<p style="font-size:0.8rem;color:gray;margin-top:5px;">${job.step_desc || 'در حال انجام کار...'}</p>
`;
}
const div = document.createElement('div');
div.className = 'request-card';
div.innerHTML = `
<div class="card-header">
<span class="project-name">${job.text_preview}</span>
${statusHtml}
</div>
${contentHtml}
`;
list.appendChild(div);
});
}
// --- File Handling ---
const fileInput = document.getElementById('user-voice-input');
const uploadArea = document.getElementById('upload-area');
document.getElementById('remove-file-btn').addEventListener('click', () => {
fileInput.value = '';
document.getElementById('file-preview').style.display = 'none';
uploadArea.style.display = 'block';
});
fileInput.addEventListener('change', () => {
if(fileInput.files[0]) {
document.getElementById('file-name').textContent = fileInput.files[0].name;
uploadArea.style.display = 'none';
document.getElementById('file-preview').style.display = 'flex';
}
});
uploadArea.addEventListener('click', () => fileInput.click());
// --- The Core Cloning Logic (Orchestrator) ---
async function runCloneProcess(text, file, turnstileToken) {
const jobId = 'job_' + Date.now();
const newJob = {
id: jobId,
text_preview: text.substring(0, 20) + '...',
status: 'processing',
progress: 5,
step_desc: 'تولید صدای پایه (TTS)...'
};
saveJob(newJob);
try {
// Step 1: Generate TTS
const ttsParams = {
text: text,
speaker: 'Charon', // Base speaker for raw audio
temperature: 0.1,
email: currentUser.email,
turnstile_token: turnstileToken,
fingerprint: 'browser_fp'
};
const ttsInitRes = await fetch(`${PROXY_URL}?endpoint=generate`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(ttsParams)
});
if(!ttsInitRes.ok) throw new Error('خطا در شروع تولید صدا');
const ttsInitData = await ttsInitRes.json();
const ttsJobId = ttsInitData.job_id;
// Step 2: Poll TTS
let ttsAudioUrl = null;
for(let i=0; i<30; i++) { // Max 90 seconds wait
newJob.progress = 10 + (i*2); // Fake progress up to 70%
newJob.step_desc = 'در حال ساخت صدای پایه...';
saveJob(newJob);
const pollRes = await fetch(`${PROXY_URL}?endpoint=check-tts-status`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({job_id: ttsJobId})
});
const pollData = await pollRes.json();
if(pollData.status === 'completed' && pollData.proxy_url) {
// For the proxy to download it, we might need the full URL or handle properly
// The React app uses: `${TTS_BASE_URL}${statusData.proxy_url}`
// Here we assume the proxy can handle the download if we fetch it client side
// But to upload via PHP, we need the Blob here.
// Fetch the actual audio blob from the TTS server (via proxy if needed, or direct if CORS allowed)
// The 'proxy_url' is usually relative e.g. /download/file.wav.
// We need to fetch it. Since user's PHP is simple, let's try fetching via the new 'download-clone' endpoint with url param if CORS fails
// Construct absolute URL for TTS
const ttsBase = 'https://ezmary-padgenpro2.hf.space';
ttsAudioUrl = ttsBase + pollData.proxy_url;
break;
}
if(pollData.status === 'failed') throw new Error('تولید صدای پایه ناموفق بود.');
await new Promise(r => setTimeout(r, 3000));
}
if(!ttsAudioUrl) throw new Error('تایم‌اوت در تولید صدا.');
// Step 3: Fetch TTS Blob
newJob.step_desc = 'دانلود صدای پایه...';
saveJob(newJob);
const ttsBlob = await fetch(ttsAudioUrl).then(r => r.blob());
// Step 4: Upload to VC (Source = TTS Blob, Ref = User File)
newJob.step_desc = 'ارسال به موتور شبیه‌سازی...';
newJob.progress = 75;
saveJob(newJob);
const formData = new FormData();
formData.append('email', currentUser.email);
formData.append('source_audio', ttsBlob, 'source.wav');
formData.append('ref_audio', file, 'ref.wav');
const vcUploadRes = await fetch(`${PROXY_URL}?endpoint=vc-upload`, {
method: 'POST',
body: formData
});
if(!vcUploadRes.ok) throw new Error('خطا در ارسال به سرور کلون');
const vcInitData = await vcUploadRes.json();
const vcJobId = vcInitData.job_id;
// Step 5: Poll VC
for(let i=0; i<30; i++) {
newJob.progress = 80 + i;
newJob.step_desc = 'نهایی‌سازی شبیه‌سازی...';
saveJob(newJob);
const vcPollRes = await fetch(`${PROXY_URL}?endpoint=vc-status`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
job_id: vcJobId,
total_chunks: vcInitData.total_chunks || 1,
chunks: vcInitData.chunks || []
})
});
const vcData = await vcPollRes.json();
if(vcData.status === 'completed') {
newJob.status = 'completed';
newJob.progress = 100;
newJob.filename = vcData.filename;
saveJob(newJob);
return;
}
if(vcData.status === 'failed') throw new Error('خطا در موتور شبیه‌سازی');
await new Promise(r => setTimeout(r, 3000));
}
throw new Error('تایم‌اوت در شبیه‌سازی نهایی.');
} catch(e) {
newJob.status = 'failed';
newJob.error = e.message;
saveJob(newJob);
}
}
// --- Form Handlers ---
document.getElementById('voice-clone-form').addEventListener('submit', async () => {
if(!currentUser.email) {
document.getElementById('email-modal').classList.add('visible');
return;
}
const text = document.getElementById('text-input-clone').value;
const file = document.getElementById('user-voice-input').files[0];
if(!text.trim() || !file) {
alert('لطفا متن و فایل صوتی را وارد کنید.');
return;
}
const token = turnstile.getResponse(widgetIdClone);
if(!token) { alert('کپچا را حل کنید'); return; }
// Start Process
const btn = document.getElementById('generate-btn-clone');
btn.disabled = true;
btn.querySelector('.btn-text').textContent = 'درخواست ارسال شد';
// Run Async
runCloneProcess(text, file, token).then(() => {
btn.disabled = false;
btn.querySelector('.btn-text').textContent = 'شروع پردازش';
turnstile.reset(widgetIdClone);
});
});
document.getElementById('clear-history').addEventListener('click', () => {
localStorage.removeItem('aisada_jobs_v2');
renderHistory();
});
// --- Init ---
renderHistory();
setTimeout(() => {
if(window.turnstile) {
widgetIdClone = turnstile.render('#cf-container-clone', { sitekey: '0x4AAAAAACJYw8vz3QHa-WFi' });
widgetIdLogin = turnstile.render('#cf-container-login', { sitekey: '0x4AAAAAACJYw8vz3QHa-WFi' });
}
}, 1000);
// Login Handlers
document.getElementById('login-check-btn').addEventListener('click', () => document.getElementById('email-modal').classList.add('visible'));
document.getElementById('logout-btn').addEventListener('click', () => {
localStorage.removeItem('userEmail');
location.reload();
});
// Auth Forms (Simplified for snippet)
document.getElementById('email-form').addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('login-email-input').value;
const token = turnstile.getResponse(widgetIdLogin);
if(!token) return alert('کپچا؟');
// Simulate Send Code
await fetch('/tts/send_code.php', {
method: 'POST',
body: JSON.stringify({email, turnstile_token: token})
});
document.getElementById('email-form').style.display='none';
document.getElementById('code-form').style.display='block';
});
document.getElementById('code-form').addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('login-email-input').value;
const code = document.getElementById('code-input').value;
const res = await fetch('/tts/verify_code.php', {
method: 'POST',
body: JSON.stringify({email, code})
});
const d = await res.json();
if(d.status === 'success') {
localStorage.setItem('userEmail', email);
currentUser.email = email;
currentUser.status = d.status_type || 'free';
updateAuthUI();
document.getElementById('email-modal').classList.remove('visible');
} else {
alert('کد اشتباه است');
}
});
</script>
</body>
</html>