|
|
<!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، متن فارسی خود را به صدایی طبیعی تبدیل کنید."> |
|
|
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
|
|
|
<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 { 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; } |
|
|
|
|
|
|
|
|
.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; } |
|
|
|
|
|
|
|
|
#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> |
|
|
|
|
|
|
|
|
<div id="standard-view" style="text-align:center; padding: 2rem;"> |
|
|
<p>برای بخش متن به صدا، به کد اصلی مراجعه کنید. تمرکز این فایل روی رفع مشکل شبیهسازی است.</p> |
|
|
</div> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
|
|
|
<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> |
|
|
|
|
|
const PROXY_URL = '/tts/proxy.php'; |
|
|
let widgetIdClone, widgetIdLogin; |
|
|
let currentUser = { email: localStorage.getItem('userEmail'), status: 'unknown' }; |
|
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
|
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); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
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()); |
|
|
|
|
|
|
|
|
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 { |
|
|
|
|
|
const ttsParams = { |
|
|
text: text, |
|
|
speaker: 'Charon', |
|
|
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; |
|
|
|
|
|
|
|
|
let ttsAudioUrl = null; |
|
|
for(let i=0; i<30; i++) { |
|
|
newJob.progress = 10 + (i*2); |
|
|
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) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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('تایماوت در تولید صدا.'); |
|
|
|
|
|
|
|
|
newJob.step_desc = 'دانلود صدای پایه...'; |
|
|
saveJob(newJob); |
|
|
const ttsBlob = await fetch(ttsAudioUrl).then(r => r.blob()); |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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; } |
|
|
|
|
|
|
|
|
const btn = document.getElementById('generate-btn-clone'); |
|
|
btn.disabled = true; |
|
|
btn.querySelector('.btn-text').textContent = 'درخواست ارسال شد'; |
|
|
|
|
|
|
|
|
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(); |
|
|
}); |
|
|
|
|
|
|
|
|
renderHistory(); |
|
|
setTimeout(() => { |
|
|
if(window.turnstile) { |
|
|
widgetIdClone = turnstile.render('#cf-container-clone', { sitekey: '0x4AAAAAACJYw8vz3QHa-WFi' }); |
|
|
widgetIdLogin = turnstile.render('#cf-container-login', { sitekey: '0x4AAAAAACJYw8vz3QHa-WFi' }); |
|
|
} |
|
|
}, 1000); |
|
|
|
|
|
|
|
|
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(); |
|
|
}); |
|
|
|
|
|
|
|
|
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('کپچا؟'); |
|
|
|
|
|
|
|
|
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> |