Spaces:
Running
Running
| <html lang="fa" dir="rtl"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI Video Studio</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;700;800&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --app-font: 'Vazirmatn', sans-serif; | |
| --app-bg: #f8f9fc; | |
| --panel-bg: #fff; | |
| --text-primary: #1a202c; | |
| --text-secondary: #626f86; | |
| --accent-primary: #4a6cfa; | |
| --accent-secondary: #0fd4a8; | |
| --accent-premium: #FFC107; | |
| --danger-color: #e53e3e; | |
| --warning-color: #FFA000; | |
| --radius-card: 24px; | |
| --radius-btn: 14px; | |
| } | |
| body { font-family: var(--app-font); background-color: var(--app-bg); color: var(--text-primary); margin: 0; padding: 2.5rem 1rem; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; } | |
| .container { max-width: 820px; width: 100%; } | |
| header { text-align: center; margin-bottom: 1.5rem; } | |
| h1 { font-size: 2.8rem; font-weight: 800; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } | |
| .subtitle { font-size: 1.1rem; color: var(--text-secondary); margin-top: 0.5rem; } | |
| #subscription-badge { display: none; padding: 6px 16px; border-radius: 20px; font-size: 0.9em; font-weight: 700; margin-top: 1rem; letter-spacing: 0.5px; display: inline-block; } | |
| #subscription-badge.free-badge { background: linear-gradient(45deg, #6c757d, #495057); color: white; } | |
| #subscription-badge.paid-badge { background: linear-gradient(45deg, var(--accent-premium), #ffca2c); color: #333; } | |
| main { padding: 2.5rem; background-color: var(--panel-bg); border-radius: var(--radius-card); box-shadow: 0 20px 25px -5px rgba(26,32,44,.07); } | |
| .hidden { display: none ; } | |
| input[type="email"] { width: 100%; padding: 1rem; border-radius: 12px; border: 1px solid #e1e7ef; background-color: #f6f8fb; font-size: 1rem; box-sizing: border-box; text-align: left; direction: ltr; } | |
| button { display: flex; align-items: center; justify-content: center; width: 100%; padding: 1rem; font-size: 1.1rem; font-weight: 700; color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; margin-top: 1rem; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); transition: transform 0.2s; } | |
| button:hover:not(:disabled) { transform: translateY(-2px); } | |
| button:disabled { background: #8a94a6; cursor: not-allowed; } | |
| .user-panel { background: #f6f8fb; padding: 1.5rem; border-radius: var(--radius-btn); text-align: center; margin-bottom: 2rem; border: 1px solid #eaf0ff; } | |
| .user-info-header { display: flex; justify-content: center; align-items: center; gap: 10px; margin-bottom: 0.5rem; font-weight: 500; font-size: 1rem; color: var(--text-secondary); } | |
| #user-email-display { font-weight: 700; font-size: 1.1rem; color: var(--accent-primary); margin-bottom: 1rem; word-break: break-all; } | |
| #edit-email-btn { background: none; border: none; cursor: pointer; padding: 0; margin: 0; width: 24px; height: 24px; color: var(--text-secondary); transition: color 0.2s; } | |
| #edit-email-btn:hover { color: var(--accent-primary); } | |
| .user-panel p { margin: 0.5rem 0; font-weight: 500; font-size: 1.1rem; } | |
| .user-panel .credit-value { font-weight: 700; color: var(--accent-primary); } | |
| .credit-btn-wrapper { text-align: center; margin-top: 1.5rem; } | |
| .credit-btn { display: inline-flex; align-items: center; gap: 8px; background: #eaf0ff; color: var(--accent-primary); width: auto; padding: 0.6rem 1.8rem; font-size: 0.95rem; font-weight: 700; margin: 0; border-radius: var(--radius-btn); box-shadow: 0 2px 5px rgba(74, 108, 250, 0.1); transition: all 0.2s ease-in-out; } | |
| .credit-btn:hover { background-color: var(--accent-primary); color: white; transform: translateY(-2px); box-shadow: 0 4px 10px rgba(74, 108, 250, 0.2); } | |
| .modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); backdrop-filter: blur(5px); display: flex; align-items: center; justify-content: center; z-index: 1000; opacity: 0; pointer-events: none; transition: opacity 0.3s ease-in-out; } | |
| .modal-overlay.visible { opacity: 1; pointer-events: auto; } | |
| .modal-content { background: #fff; padding: 2.5rem; border-radius: var(--radius-card); width: 90%; max-width: 420px; transform: scale(0.95); transition: transform 0.3s ease-in-out; text-align: center; box-shadow: 0 10px 30px rgba(0,0,0,0.1); } | |
| .modal-overlay.visible .modal-content { transform: scale(1); } | |
| .modal-icon { width: 50px; height: 50px; margin: 0 auto 1.5rem; background-color: rgba(255, 160, 0, 0.1); color: var(--warning-color); border-radius: 50%; display: flex; align-items: center; justify-content: center; } | |
| .modal-icon svg { width: 28px; height: 28px; } | |
| .modal-title { font-size: 1.5rem; font-weight: 800; color: var(--text-primary); margin-bottom: 0.5rem; } | |
| .modal-subtitle { font-size: 1rem; color: var(--text-secondary); margin-bottom: 1rem; } | |
| .modal-text { font-size: 1rem; font-weight: 500; color: var(--text-secondary); line-height: 1.8; margin: 0 0 2rem 0; } | |
| #modal-premium-info { display: none; font-size: 0.9rem; color: var(--accent-primary); background-color: #eaf0ff; padding: 0.8rem; border-radius: 12px; margin-bottom: 2rem; line-height: 1.7; } | |
| .modal-buttons { display: flex; gap: 1rem; width: 100%; } | |
| .modal-button { flex: 1; padding: 0.8rem; font-family: var(--app-font); font-weight: 600; font-size: 1rem; cursor: pointer; transition: all 0.2s ease; border: none; border-radius: var(--radius-btn); } | |
| .modal-button.primary { background: var(--accent-primary); color: white; } | |
| .modal-button.primary:hover { background: #3553D6; } | |
| .modal-button.secondary { background: #e2e8f0; color: var(--text-secondary); } | |
| .modal-button.secondary:hover { background: #cbd5e0; } | |
| .package { border: 2px solid #e1e7ef; padding: 1rem; border-radius: var(--radius-btn); margin-bottom: 1rem; cursor: pointer; transition: all 0.2s; text-align: center; } | |
| .package:hover { border-color: var(--accent-primary); background: #f6f8fb; } | |
| .package.processing { background-color: #e2e8f0; cursor: not-allowed; pointer-events: none; } | |
| .package h4 { margin: 0 0 0.5rem 0; color: var(--text-primary); font-size: 1.2rem; } | |
| .package .price-container { display: flex; justify-content: center; align-items: baseline; gap: 8px; } | |
| .package .current-price { color: var(--text-primary); font-size: 1rem; font-weight: bold; } | |
| .package .original-price { color: var(--danger-color); font-size: 0.85rem; text-decoration: line-through; } | |
| .form-group { margin-bottom: 2rem; } .form-label { font-weight: 700; font-size: 1rem; margin-bottom: 1rem; display: block; } .tabs { display: flex; border-bottom: 1px solid #EAEFF7; margin-bottom: 2rem; } .tab-button { background: none; border: none; outline: none; cursor: pointer; padding: 1rem 1.5rem; font-size: 1rem; font-weight: 700; color: #626F86; border-bottom: 3px solid transparent; } .tab-button.active { color: var(--text-primary); border-bottom-color: var(--accent-primary); } .tab-content { display: none; } .tab-content.active { display: block; } textarea { width: 100%; padding: 1rem; border-radius: 12px; border: 1px solid #E1E7EF; background-color: #F6f8fb; font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; min-height: 120px; } | |
| #image-drop-zone { border: 2px dashed #E1E7EF; border-radius: 12px; padding: 2rem; text-align: center; cursor: pointer; display: block; width: 100%; box-sizing: border-box; } | |
| #image-preview-container img { max-width: 100%; max-height: 200px; border-radius: 12px; margin-top: 1rem; } #results-container { min-height: 300px; position: relative; padding: 1rem; background-color: #F6F8FB; border-radius: var(--radius-card); display: flex; flex-direction: column; align-items: center; justify-content: center; margin-top: 2.5rem; } #status { display: none; width: 100%; text-align: center; font-weight: 600; padding: 1rem; border-radius: 12px; margin-bottom: 1rem; } .status-error { background-color: rgba(229, 62, 62, 0.1); color: var(--danger-color); } | |
| #video-container { width: 100%; text-align: center; } /* تغییر برای وسطچین کردن محتوا */ | |
| #video-container video { width: 100%; border-radius: var(--radius-card); display: block; } | |
| #loading-animation { text-align: center; padding: 2rem; width: 100%; box-sizing: border-box;} | |
| #loading-animation p { font-weight: 600; color: var(--text-secondary); margin-bottom: 1.5rem; } | |
| .progress-bar-container { width: 100%; background-color: #e2e8f0; border-radius: 10px; overflow: hidden; } | |
| .progress-bar { height: 12px; width: 0%; background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary)); border-radius: 10px; animation: fillProgress 180s linear forwards; } | |
| @keyframes fillProgress { from { width: 0%; } to { width: 100%; } } | |
| .download-btn { | |
| background: var(--accent-primary); | |
| color: white; | |
| width: auto; | |
| padding: 0.8rem 2rem; | |
| margin-top: 1.5rem; | |
| font-size: 1rem; | |
| display: inline-flex; | |
| gap: 8px; | |
| } | |
| .download-btn:hover { background: #3553D6; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1>استودیو ویدیو هوش مصنوعی Veo3.1</h1> | |
| <p class="subtitle">ایدههای خود را به ویدیوهای خیرهکننده تبدیل کنید.</p> | |
| <div id="subscription-badge"></div> | |
| </header> | |
| <main> | |
| <div id="login-section"> | |
| <p>برای استفاده از سرویس، لطفا ایمیل خود را وارد کنید.</p> | |
| <div class="form-group"> | |
| <input type="email" id="email-input" placeholder="[email protected]" required> | |
| </div> | |
| <button id="login-btn">ورود / ثبتنام</button> | |
| </div> | |
| <div id="app-section" class="hidden"> | |
| <div class="user-panel"> | |
| <div class="user-info-header"> | |
| <span>کاربر:</span> | |
| <button id="edit-email-btn" title="ویرایش ایمیل"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg> | |
| </button> | |
| </div> | |
| <div id="user-email-display"></div> | |
| <p>اعتبار شما: <span id="user-credits-display" class="credit-value">0</span></p> | |
| <div class="credit-btn-wrapper"> | |
| <button id="add-credit-btn" class="credit-btn"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg> | |
| افزایش اعتبار | |
| </button> | |
| </div> | |
| </div> | |
| <div class="tabs"> | |
| <button class="tab-button active" onclick="openTab(event, 'text-to-video')">متن به ویدیو</button> | |
| <button class="tab-button" onclick="openTab(event, 'image-to-video')">تصویر به ویدیو</button> | |
| </div> | |
| <div id="text-to-video" class="tab-content active"> | |
| <form id="text-form"> | |
| <div class="form-group"><label for="text-prompt" class="form-label">دستور ساخت</label><textarea id="text-prompt" name="prompt" rows="5" placeholder="مثال: یک گربه فضانورد که در میان ستارگان شناور است..." required></textarea></div> | |
| <button type="submit"><span>ساخت ویدیو (کسر ۱ اعتبار)</span></button> | |
| </form> | |
| </div> | |
| <div id="image-to-video" class="tab-content"> | |
| <form id="image-form"> | |
| <div class="form-group"> | |
| <label class="form-label">آپلود تصویر</label> | |
| <label id="image-drop-zone" for="imageFileInput"><p>تصویر را اینجا بکشید یا برای انتخاب کلیک کنید</p></label> | |
| <input type="file" id="imageFileInput" name="image" accept="image/*" hidden required> | |
| <div id="image-preview-container"></div> | |
| </div> | |
| <div class="form-group"><label for="image-prompt" class="form-label">دستور حرکت</label><textarea id="image-prompt" name="prompt" rows="4" placeholder="مثال: زوم آهسته به بیرون در حالی که برگها میرقصند..." required></textarea></div> | |
| <button type="submit"><span>متحرکسازی (کسر ۱ اعتبار)</span></button> | |
| </form> | |
| </div> | |
| <div id="results-container"> | |
| <div id="status"></div><div id="video-container"></div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <!-- Credit Purchase Modal --> | |
| <div id="creditModal" class="modal-overlay"> | |
| <div class="modal-content"> | |
| <h3 id="modal-title">افزایش اعتبار</h3> | |
| <p id="modal-subtitle">هر یک اعتبار = یک ویدیو ۸ ثانیهای</p> | |
| <p id="modal-premium-info">شما کاربر اشتراکی هستید به همین خاطر قیمتها برای شما با تخفیف لحاظ شده است. برای کاربرانی که اشتراک ندارند تخفیف وجود ندارد.</p> | |
| <div class="package" data-package="1-credit"><h4>۱ اعتبار</h4><div class="price-container"><span class="original-price"></span><span class="current-price"></span></div></div> | |
| <div class="package" data-package="5-credits"><h4>۵ اعتبار</h4><div class="price-container"><span class="original-price"></span><span class="current-price"></span></div></div> | |
| <div class="package" data-package="50-credits"><h4>۵۰ اعتبار</h4><div class="price-container"><span class="original-price"></span><span class="current-price"></span></div></div> | |
| <button id="close-modal-btn" style="background: #6c757d; margin-top: 2rem;">انصراف</button> | |
| </div> | |
| </div> | |
| <!-- Payment Confirmation Modal --> | |
| <div id="payment-confirm-modal" class="modal-overlay"> | |
| <div class="modal-content"> | |
| <div class="modal-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg></div> | |
| <h3 class="modal-title" style="font-size: 1.3rem;">نکات مهم قبل از پرداخت</h3> | |
| <p class="modal-text"> | |
| قبل از پرداخت فیلترشکن خود را <strong>خاموش کنید</strong>. | |
| <br><br> | |
| بعد از پرداخت موفق، برنامه را یک بار کامل ببندید و دوباره باز کنید تا اعتبار به حساب شما اضافه شود. | |
| <br><br> | |
| اعتبارات خریداری شده هم برای <strong>Veo3.1</strong> قابل استفاده است هم برای <strong>Sora</strong>. | |
| </p> | |
| <div class="modal-buttons"> | |
| <button class="modal-button secondary" id="cancel-payment-btn">انصراف</button> | |
| <button class="modal-button primary" id="proceed-payment-btn">تایید و پرداخت</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Edit Email Confirmation Modal --> | |
| <div id="edit-email-confirm-modal" class="modal-overlay"> | |
| <div class="modal-content"> | |
| <div class="modal-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg></div> | |
| <h3 class="modal-title" style="font-size: 1.3rem;">تغییر ایمیل</h3> | |
| <p class="modal-text">آیا از تغییر ایمیل خود و ورود با حساب کاربری جدید مطمئن هستید؟</p> | |
| <div class="modal-buttons"> | |
| <button class="modal-button secondary" id="cancel-edit-email-btn">انصراف</button> | |
| <button class="modal-button primary" id="proceed-edit-email-btn">تایید و ویرایش</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const loginSection = document.getElementById('login-section'); | |
| const appSection = document.getElementById('app-section'); | |
| const emailInput = document.getElementById('email-input'); | |
| const loginBtn = document.getElementById('login-btn'); | |
| const userEmailDisplay = document.getElementById('user-email-display'); | |
| const userCreditsDisplay = document.getElementById('user-credits-display'); | |
| const editEmailBtn = document.getElementById('edit-email-btn'); | |
| const statusDiv = document.getElementById('status'); | |
| const videoContainer = document.getElementById('video-container'); | |
| const creditModal = document.getElementById('creditModal'); | |
| const subscriptionBadge = document.getElementById('subscription-badge'); | |
| const modalPremiumInfo = document.getElementById('modal-premium-info'); | |
| const paymentConfirmModal = document.getElementById('payment-confirm-modal'); | |
| const proceedPaymentBtn = document.getElementById('proceed-payment-btn'); | |
| const cancelPaymentBtn = document.getElementById('cancel-payment-btn'); | |
| const editEmailConfirmModal = document.getElementById('edit-email-confirm-modal'); | |
| const proceedEditEmailBtn = document.getElementById('proceed-edit-email-btn'); | |
| const cancelEditEmailBtn = document.getElementById('cancel-edit-email-btn'); | |
| let currentUserEmail = ''; | |
| let userSubscriptionStatus = 'free'; | |
| let pendingPaymentUrl = ''; | |
| const PREMIUM_PAGE_ID = '1149636'; | |
| const API_URL = 'https://www.aisada.ir/hf/api.php'; | |
| const prices = { | |
| free: { credit: 25000, pack50: 800000 }, | |
| paid: { credit: 20000, pack50: 700000 } | |
| }; | |
| function updateUIForSubscription() { | |
| if (userSubscriptionStatus === 'paid') { | |
| subscriptionBadge.textContent = 'کاربر اشتراکی'; | |
| subscriptionBadge.className = 'paid-badge'; | |
| modalPremiumInfo.style.display = 'block'; | |
| } else { | |
| subscriptionBadge.textContent = 'کاربر رایگان'; | |
| subscriptionBadge.className = 'free-badge'; | |
| modalPremiumInfo.style.display = 'none'; | |
| } | |
| updatePricingModal(); | |
| } | |
| function updatePricingModal() { | |
| const isPaid = userSubscriptionStatus === 'paid'; | |
| const pricePerCredit = isPaid ? prices.paid.credit : prices.free.credit; | |
| const formatPrice = (price) => price.toLocaleString('fa-IR'); | |
| const pkg1 = document.querySelector('.package[data-package="1-credit"]'); | |
| pkg1.querySelector('.current-price').textContent = `${formatPrice(pricePerCredit)} تومان`; | |
| const pkg1Original = pkg1.querySelector('.original-price'); | |
| if (isPaid) { pkg1Original.textContent = `${formatPrice(prices.free.credit)} تومان`; pkg1Original.style.display = 'inline'; } else { pkg1Original.style.display = 'none'; } | |
| const pkg5 = document.querySelector('.package[data-package="5-credits"]'); | |
| pkg5.querySelector('.current-price').textContent = `${formatPrice(pricePerCredit * 5)} تومان`; | |
| const pkg5Original = pkg5.querySelector('.original-price'); | |
| if (isPaid) { pkg5Original.textContent = `${formatPrice(prices.free.credit * 5)} تومان`; pkg5Original.style.display = 'inline'; } else { pkg5Original.style.display = 'none'; } | |
| const pkg50 = document.querySelector('.package[data-package="50-credits"]'); | |
| pkg50.querySelector('.current-price').textContent = `${formatPrice(isPaid ? prices.paid.pack50 : prices.free.pack50)} تومان`; | |
| const pkg50Original = pkg50.querySelector('.original-price'); | |
| if (isPaid) { pkg50Original.textContent = `${formatPrice(prices.free.pack50)} تومان`; pkg50Original.style.display = 'inline'; } else { pkg50Original.style.display = 'none'; } | |
| } | |
| window.addEventListener('message', (event) => { | |
| if (event.data?.type === 'USER_DATA_RESPONSE') { | |
| let isPaidUser = false; | |
| if (event.data.payload) { | |
| try { | |
| const userObject = JSON.parse(event.data.payload); | |
| isPaidUser = userObject?.isLogin && userObject.accessible_pages?.some(p => p == PREMIUM_PAGE_ID); | |
| } catch (e) { console.error("Failed to parse user data from parent."); } | |
| } | |
| userSubscriptionStatus = isPaidUser ? 'paid' : 'free'; | |
| updateUIForSubscription(); | |
| } | |
| }); | |
| window.addEventListener('DOMContentLoaded', () => { | |
| parent.postMessage({ type: 'REQUEST_USER_DATA' }, '*'); | |
| const savedEmail = localStorage.getItem('loggedInEmail'); | |
| if (savedEmail) { emailInput.value = savedEmail; checkUserStatus(savedEmail); } | |
| updateUIForSubscription(); | |
| }); | |
| document.querySelectorAll('.package').forEach(pkg => { | |
| pkg.addEventListener('click', async (e) => { | |
| const button = e.currentTarget; | |
| if (button.classList.contains('processing')) return; | |
| document.querySelectorAll('.package').forEach(p => p.classList.add('processing')); | |
| const originalHTML = button.innerHTML; | |
| button.innerHTML = '<h4>در حال ایجاد لینک...</h4>'; | |
| try { | |
| const response = await fetch(API_URL, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ action: 'request_payment', email: currentUserEmail, package: button.dataset.package, user_status: userSubscriptionStatus }) | |
| }); | |
| if (!response.ok) throw new Error('Payment request failed.'); | |
| const data = await response.json(); | |
| if (data.payment_url) { | |
| pendingPaymentUrl = data.payment_url; | |
| creditModal.classList.remove('visible'); | |
| paymentConfirmModal.classList.add('visible'); | |
| } else { alert('خطا: ' + (data.error || 'نامشخص')); } | |
| } catch (error) { | |
| alert('خطا در ارتباط با سرور پرداخت.'); | |
| } finally { | |
| document.querySelectorAll('.package').forEach(p => p.classList.remove('processing')); | |
| button.innerHTML = originalHTML; | |
| } | |
| }); | |
| }); | |
| proceedPaymentBtn.addEventListener('click', () => { | |
| if (pendingPaymentUrl) { | |
| parent.postMessage({ type: 'NAVIGATE_TO_URL', url: pendingPaymentUrl }, '*'); | |
| paymentConfirmModal.classList.remove('visible'); | |
| pendingPaymentUrl = ''; | |
| } | |
| }); | |
| cancelPaymentBtn.addEventListener('click', () => { | |
| paymentConfirmModal.classList.remove('visible'); | |
| pendingPaymentUrl = ''; | |
| creditModal.classList.add('visible'); | |
| }); | |
| paymentConfirmModal.addEventListener('click', (e) => { if (e.target === paymentConfirmModal) { paymentConfirmModal.classList.remove('visible'); pendingPaymentUrl = ''; } }); | |
| editEmailBtn.addEventListener('click', () => { | |
| editEmailConfirmModal.classList.add('visible'); | |
| }); | |
| proceedEditEmailBtn.addEventListener('click', () => { | |
| editEmailConfirmModal.classList.remove('visible'); | |
| appSection.classList.add('hidden'); | |
| loginSection.classList.remove('hidden'); | |
| emailInput.value = currentUserEmail; | |
| emailInput.focus(); | |
| }); | |
| cancelEditEmailBtn.addEventListener('click', () => { | |
| editEmailConfirmModal.classList.remove('visible'); | |
| }); | |
| editEmailConfirmModal.addEventListener('click', (e) => { if (e.target === editEmailConfirmModal) { editEmailConfirmModal.classList.remove('visible'); } }); | |
| async function checkUserStatus(email) { if (!email) return; currentUserEmail = email; localStorage.setItem('loggedInEmail', email); userEmailDisplay.textContent = email; loginSection.classList.add('hidden'); appSection.classList.remove('hidden'); userCreditsDisplay.textContent = '...'; try { const response = await fetch(`${API_URL}?action=check_credits&email=${encodeURIComponent(email)}`); if (!response.ok) throw new Error('Network error'); const data = await response.json(); userCreditsDisplay.textContent = data.credits ?? '0'; } catch (error) { userCreditsDisplay.textContent = 'خطا'; } } | |
| async function handleFormSubmit(event, endpoint) { | |
| event.preventDefault(); | |
| const form = event.target; | |
| const button = form.querySelector('button[type="submit"]'); | |
| button.disabled = true; | |
| button.querySelector('span').textContent = '۱. دریافت مجوز ساخت...'; | |
| statusDiv.style.display = 'none'; | |
| videoContainer.innerHTML = ''; | |
| let jwt_token = ''; | |
| try { | |
| const response = await fetch(API_URL, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ action: 'request_generation_token', email: currentUserEmail }) }); | |
| const tokenData = await response.json(); | |
| if (!response.ok || tokenData.status !== 'ok') { throw new Error(tokenData.message || 'اعتبار کافی نیست یا خطای سرور رخ داده است.'); } | |
| jwt_token = tokenData.token; | |
| button.querySelector('span').textContent = '۲. ارسال به سرور ساخت...'; | |
| } catch (error) { | |
| showStatus(`❌ ${error.message}`, true); | |
| button.disabled = false; | |
| button.querySelector('span').textContent = form.id === 'text-form' ? 'ساخت ویدیو (کسر ۱ اعتبار)' : 'متحرکسازی (کسر ۱ اعتبار)'; | |
| checkUserStatus(currentUserEmail); | |
| return; | |
| } | |
| const formData = new FormData(form); | |
| formData.append('token', jwt_token); | |
| try { | |
| videoContainer.innerHTML = `<div id="loading-animation"><p>در حال پردازش هوش مصنوعی... این فرآیند ممکن است تا ۳ دقیقه طول بکشد.</p><div class="progress-bar-container"><div class="progress-bar"></div></div></div>`; | |
| const response = await fetch(endpoint, { method: 'POST', body: formData }); | |
| if (!response.ok) { | |
| const errorData = await response.json(); | |
| throw new Error(errorData.detail || `خطای سرور ساخت: ${response.status}`); | |
| } | |
| const deductionToken = response.headers.get('X-Deduction-Token'); | |
| const videoBlob = await response.blob(); | |
| const videoUrl = URL.createObjectURL(videoBlob); | |
| const videoElement = document.createElement('video'); | |
| videoElement.src = videoUrl; | |
| videoElement.controls = true; | |
| videoElement.autoplay = true; | |
| videoElement.loop = true; | |
| const downloadButton = document.createElement('button'); | |
| downloadButton.className = 'download-btn'; | |
| downloadButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg><span>دانلود ویدیو</span>`; | |
| downloadButton.onclick = () => { | |
| parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: videoUrl }, '*'); | |
| }; | |
| videoElement.addEventListener('canplay', () => { | |
| if (deductionToken && !videoElement.dataset.deducted) { | |
| videoElement.dataset.deducted = 'true'; | |
| finalizeDeduction(deductionToken); | |
| } | |
| }, { once: true }); | |
| videoContainer.innerHTML = ''; | |
| videoContainer.appendChild(videoElement); | |
| videoContainer.appendChild(downloadButton); | |
| showStatus('✅ ویدیو با موفقیت ساخته شد!', false); | |
| } catch (error) { | |
| showStatus(`❌ ${error.message} (اعتباری کسر نشد)`, true); | |
| } finally { | |
| button.disabled = false; | |
| button.querySelector('span').textContent = form.id === 'text-form' ? 'ساخت ویدیو (کسر ۱ اعتبار)' : 'متحرکسازی (کسر ۱ اعتبار)'; | |
| } | |
| } | |
| async function finalizeDeduction(deductionToken) { | |
| console.log("Finalizing deduction with token:", deductionToken); | |
| try { | |
| const response = await fetch(API_URL, { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify({ action: 'finalize_deduction', deduction_token: deductionToken }) | |
| }); | |
| const result = await response.json(); | |
| if (result.status === 'success') { | |
| showStatus('✅ ویدیو با موفقیت ساخته شد! یک اعتبار شما کسر گردید. ویدیو رو دانلود و ذخیره کنید چون در اینجا باقی نمی ماند.', false); | |
| } else { | |
| showStatus(`✅ ویدیو ساخته شد اما در کسر اعتبار خطا رخ داد: ${result.message}`, true); | |
| } | |
| } catch (error) { | |
| console.error("Error sending deduction request:", error); | |
| } finally { | |
| checkUserStatus(currentUserEmail); | |
| } | |
| } | |
| loginBtn.addEventListener('click', () => { const email = emailInput.value.trim().toLowerCase(); if (email && email.includes('@')) checkUserStatus(email); else alert('لطفا یک ایمیل معتبر وارد کنید.'); }); | |
| document.getElementById('add-credit-btn').addEventListener('click', () => creditModal.classList.add('visible')); | |
| document.getElementById('close-modal-btn').addEventListener('click', () => creditModal.classList.remove('visible')); | |
| creditModal.addEventListener('click', (e) => { if(e.target === creditModal) creditModal.classList.remove('visible'); }); | |
| document.getElementById('text-form').addEventListener('submit', (e) => handleFormSubmit(e, '/text-to-video/')); | |
| document.getElementById('image-form').addEventListener('submit', (e) => handleFormSubmit(e, '/image-to-video/')); | |
| function showStatus(message, isError = false) { if (isError) { const loadingAnim = document.getElementById('loading-animation'); if (loadingAnim) loadingAnim.remove(); } statusDiv.innerHTML = message; statusDiv.style.display = 'block'; statusDiv.className = isError ? 'status-error' : ''; } | |
| function openTab(evt, tabName) { document.querySelectorAll(".tab-content, .tab-button").forEach(el => el.classList.remove("active")); document.getElementById(tabName).classList.add("active"); evt.currentTarget.classList.add("active"); } | |
| const dropZone = document.getElementById('image-drop-zone'); const fileInput = document.getElementById('imageFileInput'); const previewContainer = document.getElementById('image-preview-container'); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => dropZone.addEventListener(eventName, e => { e.preventDefault(); e.stopPropagation(); })); dropZone.addEventListener('drop', e => { if (e.dataTransfer.files.length) { fileInput.files = e.dataTransfer.files; handleFiles(fileInput.files); } }); fileInput.addEventListener('change', () => handleFiles(fileInput.files)); | |
| function handleFiles(files) { const file = files[0]; if (!file || !file.type.startsWith('image/')) { previewContainer.innerHTML = ''; dropZone.querySelector('p').textContent = "فرمت نامعتبر است. لطفاً تصویر انتخاب کنید."; fileInput.value = ''; return; } const img = document.createElement('img'); img.src = URL.createObjectURL(file); previewContainer.innerHTML = ''; previewContainer.appendChild(img); dropZone.querySelector('p').textContent = `فایل انتخاب شد: ${file.name}`; } | |
| </script> | |
| </body> | |
| </html> |