Update templates/index.html
Browse files- templates/index.html +421 -99
templates/index.html
CHANGED
|
@@ -56,7 +56,7 @@
|
|
| 56 |
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; }
|
| 57 |
.container { max-width: 820px; width: 100%; }
|
| 58 |
header { position: relative; text-align: center; margin-bottom: 2.5rem; padding: 2rem 0; animation: fadeIn 0.8s 0.1s ease-out backwards; overflow: hidden; }
|
| 59 |
-
#neural-network-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; }
|
| 60 |
.header-content { position: relative; z-index: 2; }
|
| 61 |
.ai-orb-container { width: 150px; height: 150px; margin: 0 auto 1rem; position: relative; display: flex; align-items: center; justify-content: center; }
|
| 62 |
.orb-background { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse at center, rgba(74, 108, 250, 0.15) 0%, transparent 70%), linear-gradient(45deg, rgba(15, 212, 168, 0.05), rgba(74, 108, 250, 0.05)); border-radius: 50%; animation: background-pan 12s ease-in-out infinite; background-size: 200% 200%; }
|
|
@@ -86,6 +86,7 @@
|
|
| 86 |
#image-drop-zone.has-image #imagePreview { display: block; }
|
| 87 |
textarea { width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 1px solid var(--input-border); background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset; font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; transition: var(--transition-smooth); min-height: 120px; resize: vertical; }
|
| 88 |
textarea:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg); }
|
|
|
|
| 89 |
.slider-group { margin-top: 2rem; margin-bottom: 1.5rem; }
|
| 90 |
.slider-label-container { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }
|
| 91 |
.slider-label { font-weight: 600; color: var(--text-secondary); font-size: 0.95rem; }
|
|
@@ -98,6 +99,7 @@
|
|
| 98 |
input[type="range"]:hover { box-shadow: inset 0 1px 3px rgba(0,0,0,0.1), 0 0 0 4px var(--accent-primary-glow); }
|
| 99 |
input[type="range"]:hover::-webkit-slider-thumb { box-shadow: 0 0 0 10px var(--accent-primary-glow), var(--shadow-md); transform: scale(1.1); }
|
| 100 |
input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.2); box-shadow: 0 0 0 12px var(--accent-primary-glow), var(--shadow-md); }
|
|
|
|
| 101 |
#generateButton { display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; padding: 1.1rem; font-size: 1.2rem; font-weight: 700; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px rgba(15, 212, 168, 0.25); margin-top: 1rem; }
|
| 102 |
#generateButton svg { width: 24px; height: 24px; margin-left: 4px; filter: drop-shadow(0 0 5px rgba(255,255,255,0.5)); }
|
| 103 |
#generateButton:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px rgba(15, 212, 168, 0.3); }
|
|
@@ -112,11 +114,11 @@
|
|
| 112 |
.status-message.error { border-left: 4px solid var(--danger-color); color: var(--danger-color); }
|
| 113 |
.status-icon { width: 18px; height: 18px; flex-shrink: 0; }
|
| 114 |
#aiLoader { display: none; align-items: center; justify-content: center; }
|
| 115 |
-
.generator-container { position: relative; width: 400px; max-width: 100%; height: 300px; border-radius: 20px; overflow: hidden; background-color: #161b22; color: #f0f6fc; }
|
| 116 |
-
|
|
|
|
| 117 |
@keyframes glow-text { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } }
|
| 118 |
-
.progress-bar-
|
| 119 |
-
.progress-bar-fill { height: 100%; width: 0%; background: linear-gradient(to right, #38bdf8, #bb86fc, #facc15); border-radius: 4px; transition: width 0.3s ease-out; }
|
| 120 |
#outputVideo { width: 100%; max-width: 500px; border-radius: var(--radius-input); margin: 1rem auto; display: block; background-color: #000; box-shadow: var(--shadow-md); }
|
| 121 |
#finalSeed { text-align: center; color: var(--text-tertiary); font-size: 0.85rem; margin-top: 1rem; }
|
| 122 |
.video-controls { display: flex; justify-content: center; gap: 1rem; margin-top: 1.5rem; }
|
|
@@ -133,7 +135,6 @@
|
|
| 133 |
.gpu-error-message-container .error-content ul { list-style-type: "▫️ "; padding-right: 20px; margin-top: 10px; }
|
| 134 |
.gpu-error-message-container .error-content li { margin-bottom: 6px; }
|
| 135 |
.gpu-error-actions { display: flex; gap: 15px; margin-top: 25px; }
|
| 136 |
-
.gpu-error-actions .action-button { padding: 12px 20px; font-size: 1em; font-weight: 600; border-radius: var(--igadlm-alpha-border-radius-md); border: none; cursor: pointer; transition: all 0.2s; }
|
| 137 |
.gpu-error-actions .action-button.back-button { background-color: var(--panel-bg); color: var(--text-tertiary); border: 1px solid var(--panel-border); flex-grow: 0.45; }
|
| 138 |
.gpu-error-actions .action-button.retry-button { background: linear-gradient(60deg, var(--igadlm-alpha-color-primary) 0%, var(--igadlm-alpha-color-secondary) 100%); color: white; flex-grow: 1; }
|
| 139 |
#subscription-status-badge { display: none; padding: 6px 16px; border-radius: 20px; font-size: 0.9em; font-weight: 700; margin-top: 1.25rem; letter-spacing: 0.5px; animation: badge-fade-in 0.6s 0.5s ease-out backwards; }
|
|
@@ -141,6 +142,13 @@
|
|
| 141 |
#subscription-status-badge.paid-badge { background: linear-gradient(45deg, var(--accent-premium), #ffca2c); color: #333; box-shadow: 0 4px 10px var(--accent-premium-glow); }
|
| 142 |
#credit-info-section { margin-top: 1.5rem; margin-bottom: 0.5rem; padding: 1rem; background-color: var(--input-bg); border-radius: var(--radius-input); text-align: center; font-size: 0.95rem; font-weight: 500; color: var(--text-secondary); border: 1px solid var(--input-border); display: none; }
|
| 143 |
#upgrade-button { margin-top: 1rem; width: 100%; padding: 1rem; font-size: 1.1rem; font-weight: 700; background: linear-gradient(95deg, #FFD54F, #FFC107 100%); color: #212529; border: none; border-radius: var(--radius-btn); cursor: pointer; box-shadow: 0 8px 20px -5px rgba(255, 193, 7, 0.3); display: none; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
@media (max-width: 768px) { main { padding: 1.5rem; } h1 { font-size: 2.2rem; } .generator-container { height: 250px; } .text-overlay { font-size: 18px; } }
|
| 145 |
</style>
|
| 146 |
</head>
|
|
@@ -151,8 +159,14 @@
|
|
| 151 |
<div class="header-content">
|
| 152 |
<div class="ai-orb-container">
|
| 153 |
<div class="orb-background"></div>
|
| 154 |
-
<div class="orb-core">
|
| 155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
</div>
|
| 157 |
<h1>استودیو ویدیو هوش مصنوعی</h1>
|
| 158 |
<p class="subtitle">تصاویر خود را با قدرت هوش مصنوعی به ویدیو سینمایی تبدیل کنید</p>
|
|
@@ -163,7 +177,10 @@
|
|
| 163 |
|
| 164 |
<main>
|
| 165 |
<div class="form-group">
|
| 166 |
-
<div class="form-label">
|
|
|
|
|
|
|
|
|
|
| 167 |
<label id="image-drop-zone" for="imageFile">
|
| 168 |
<div class="upload-content">
|
| 169 |
<div class="upload-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg></div>
|
|
@@ -175,8 +192,12 @@
|
|
| 175 |
</div>
|
| 176 |
|
| 177 |
<div class="form-group">
|
| 178 |
-
<label for="prompt" class="form-label">
|
|
|
|
|
|
|
|
|
|
| 179 |
<textarea id="prompt" rows="4" placeholder="مثال: زوم آهسته به بیرون در حالی که برگها در باد میرقصند و نور خورشید از بین درختان میتابد"></textarea>
|
|
|
|
| 180 |
<div class="slider-group">
|
| 181 |
<div class="slider-label-container">
|
| 182 |
<label for="durationSlider" class="slider-label">مدت زمان ویدیو</label>
|
|
@@ -188,34 +209,55 @@
|
|
| 188 |
<svg class="slider-icon" xmlns="http://www.w3.org/2000/svg" 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><polyline points="12 6 12 12 16 14"></polyline></svg>
|
| 189 |
</div>
|
| 190 |
</div>
|
|
|
|
| 191 |
<div id="credit-info-section"></div>
|
| 192 |
<button id="upgrade-button">⭐️ ارتقا به نسخه کامل و نامحدود</button>
|
| 193 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
</div>
|
| 195 |
|
| 196 |
<div class="form-group">
|
| 197 |
-
<div class="form-label">
|
|
|
|
|
|
|
|
|
|
| 198 |
<div id="result-container">
|
| 199 |
<div id="statusSection">
|
| 200 |
<div id="statusMessages"></div>
|
| 201 |
<div id="aiLoader" style="display: none;">
|
| 202 |
<div class="generator-container">
|
| 203 |
<div class="text-overlay">در حال پردازش ویدیو...</div>
|
| 204 |
-
<div class="progress-bar-
|
| 205 |
-
<div class="progress-bar-fill"></div>
|
| 206 |
-
</div>
|
| 207 |
</div>
|
| 208 |
</div>
|
| 209 |
</div>
|
|
|
|
| 210 |
<div id="outputSection">
|
| 211 |
<video id="outputVideo" controls preload="metadata" playsinline></video>
|
| 212 |
<p id="finalSeed"></p>
|
| 213 |
<div class="video-controls">
|
| 214 |
-
<button id="btnDownloadVideo" class="video-button primary" style="display: none;">
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
</div>
|
| 217 |
</div>
|
|
|
|
| 218 |
<div id="criticalErrorSection"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
</div>
|
| 220 |
</div>
|
| 221 |
</main>
|
|
@@ -225,20 +267,128 @@
|
|
| 225 |
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
|
| 226 |
|
| 227 |
(function() {
|
| 228 |
-
// === START: بخش مدیریت اعتبار و ارتباط با والد
|
| 229 |
-
let userSubscriptionStatus = 'free';
|
| 230 |
let userFingerprint = null;
|
| 231 |
let countdownInterval = null;
|
| 232 |
const PREMIUM_PAGE_ID = '1149636';
|
| 233 |
-
|
| 234 |
-
function
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
// === END: بخش مدیریت اعتبار ===
|
| 241 |
|
|
|
|
| 242 |
// DOM Elements
|
| 243 |
const resultContainer = document.getElementById('result-container');
|
| 244 |
const imageFileInput = document.getElementById('imageFile');
|
|
@@ -252,37 +402,55 @@
|
|
| 252 |
const statusSection = document.getElementById('statusSection');
|
| 253 |
const statusMessagesDiv = document.getElementById('statusMessages');
|
| 254 |
const aiLoader = document.getElementById('aiLoader');
|
| 255 |
-
const
|
| 256 |
const loaderTextOverlay = document.querySelector('#aiLoader .text-overlay');
|
| 257 |
const outputSection = document.getElementById('outputSection');
|
| 258 |
const btnRestart = document.getElementById('btnRestart');
|
| 259 |
const btnDownloadVideo = document.getElementById('btnDownloadVideo');
|
| 260 |
const durationSlider = document.getElementById('durationSlider');
|
| 261 |
const durationValueDisplay = document.getElementById('durationValue');
|
|
|
|
|
|
|
|
|
|
| 262 |
|
|
|
|
| 263 |
const VIDEO_SPACE_NAME = "mrbui1990/wan2-2-fp8da-aoti-faster";
|
| 264 |
const TRANSLATOR_SPACE_NAME = "hamed744/translate-tts-aloha";
|
|
|
|
|
|
|
|
|
|
| 265 |
const DEFAULT_NEGATIVE_PROMPT = "色调艳丽, 过曝, 静态, 细节模糊不清, 字幕, 风格, 作品, 画作, 画面, 静止, 整体发灰, 最差质量, 低质量, JPEG压缩残留, 丑陋的, 残缺的, 多余的手指, 画得不好的手部, 画得不好的脸部, 畸形的, 毁容的, 形态畸形的肢体, 手指融合, 静止不动的画面, 杂乱的背景, 三条腿, 背景人很多, 倒着走";
|
| 266 |
const DEFAULT_PROMPT_TEXT = "";
|
| 267 |
const TRANSPARENT_PIXEL_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
| 268 |
const DEFAULT_VIDEO_DURATION = 4.3;
|
| 269 |
-
const TRANSLATION_TIMEOUT = 20000;
|
| 270 |
|
| 271 |
let lastAttemptedVideoPayload = null;
|
| 272 |
-
|
| 273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
-
async function retryLastAttempt() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
function showCriticalError(message) {
|
| 278 |
-
|
| 279 |
if (lowerCaseMessage.includes("gpu") || lowerCaseMessage.includes("quota") || lowerCaseMessage.includes("exceeded") || lowerCaseMessage.includes("limit") || lowerCaseMessage.includes("capacity") || lowerCaseMessage.includes("queue full")) {
|
| 280 |
-
|
| 281 |
-
|
|
|
|
| 282 |
document.getElementById('gpuErrorRetryBtn').addEventListener('click', retryLastAttempt);
|
| 283 |
} else {
|
| 284 |
criticalErrorSection.innerHTML = `<div id="criticalErrorMessage" style="color: var(--danger-color); font-weight: 500; margin-bottom: 1.5rem; line-height: 1.6;"><p>${message}</p></div><div class="video-controls"><button id="simpleErrorGoBackBtn" class="video-button">بازگشت</button><button id="simpleErrorRetryBtn" class="video-button primary">تلاش مجدد</button></div>`;
|
| 285 |
-
document.getElementById('simpleErrorGoBackBtn').addEventListener('click',
|
| 286 |
document.getElementById('simpleErrorRetryBtn').addEventListener('click', retryLastAttempt);
|
| 287 |
}
|
| 288 |
resultContainer.classList.add('active');
|
|
@@ -291,122 +459,276 @@
|
|
| 291 |
outputSection.classList.remove('active');
|
| 292 |
aiLoader.style.display = 'none';
|
| 293 |
generateButton.disabled = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
}
|
| 295 |
|
| 296 |
-
function
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
-
async function translatePersianToEnglish(persianText) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
|
| 304 |
-
|
| 305 |
aiLoader.style.display = 'none';
|
| 306 |
-
if (!
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
if (userSubscriptionStatus === 'free') {
|
| 308 |
-
|
|
|
|
| 309 |
}
|
|
|
|
| 310 |
addStatusMessage('ویدیو آماده شد! 🎉', 'success');
|
| 311 |
-
const videoUrl =
|
| 312 |
-
const usedSeed =
|
| 313 |
-
setTimeout(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
generateButton.disabled = false;
|
|
|
|
| 315 |
}
|
| 316 |
|
| 317 |
-
function prepareCustomDownloadLink(videoUrl) {
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
const p = progressData[0];
|
| 323 |
-
const percentage = p.length > 0 ? (p.index / p.length) * 100 : 0;
|
| 324 |
-
|
| 325 |
-
if(loaderProgressBarFill) loaderProgressBarFill.style.width = `${percentage}%`;
|
| 326 |
-
|
| 327 |
-
let statusText = "در حال پردازش ویدیو...";
|
| 328 |
-
if (p.desc === "ZeroGPU init") {
|
| 329 |
-
statusText = `مرحله ۱/۲: آمادهسازی GPU... (${Math.round(percentage)}%)`;
|
| 330 |
-
} else if (p.desc === null) { // This is the main processing step
|
| 331 |
-
statusText = `مرحله ۲/۲: ساخت فریمها... (${Math.round(percentage)}%)`;
|
| 332 |
-
}
|
| 333 |
-
setAiLoaderText(statusText);
|
| 334 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
|
| 336 |
-
|
| 337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(e => imageDropZone.addEventListener(e, ev => ev.preventDefault()));
|
| 339 |
['dragenter', 'dragover'].forEach(e => imageDropZone.addEventListener(e, () => { if (!imageDropZone.classList.contains('has-image')) imageDropZone.classList.add('drag-over'); }));
|
| 340 |
['dragleave', 'drop'].forEach(e => imageDropZone.addEventListener(e, () => imageDropZone.classList.remove('drag-over')));
|
| 341 |
imageDropZone.addEventListener('drop', e => { if (e.dataTransfer.files.length > 0) { imageFileInput.files = e.dataTransfer.files; imageFileInput.dispatchEvent(new Event('change')); } });
|
| 342 |
btnRestart.addEventListener('click', initializeForm);
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
|
| 350 |
if (!persianPrompt) { addStatusMessage('لطفاً توضیحات را وارد کنید.', 'error'); return; }
|
| 351 |
if (!imageFile) { addStatusMessage('لطفاً تصویر انتخاب کنید.', 'error'); return; }
|
| 352 |
|
| 353 |
-
clearStatusMessages();
|
| 354 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
|
| 356 |
let promptForVideo = persianPrompt;
|
| 357 |
try {
|
|
|
|
| 358 |
const englishPrompt = await translatePersianToEnglish(persianPrompt);
|
| 359 |
addStatusMessage(`ترجمه: ${englishPrompt}`, 'info');
|
| 360 |
promptForVideo = englishPrompt;
|
| 361 |
} catch (e) {
|
| 362 |
-
addStatusMessage('ترجمه
|
| 363 |
}
|
| 364 |
|
| 365 |
try {
|
| 366 |
-
setAiLoaderText("در حال اتصال به سرور ویدیو...");
|
| 367 |
const client = await Client.connect(VIDEO_SPACE_NAME);
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
const job = client.predict(0, [
|
| 371 |
imageFile, promptForVideo, 6, DEFAULT_NEGATIVE_PROMPT, duration, 1, 1, 0, true
|
| 372 |
]);
|
| 373 |
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
}
|
| 381 |
});
|
| 382 |
|
| 383 |
-
const result = await job;
|
| 384 |
-
await handleSuccessfulVideoGeneration(result);
|
| 385 |
-
|
| 386 |
} catch (errorCaught) {
|
| 387 |
-
showCriticalError(`خطا: ${errorCaught.message}`);
|
| 388 |
-
} finally {
|
| 389 |
generateButton.disabled = false;
|
| 390 |
}
|
| 391 |
};
|
| 392 |
|
| 393 |
generateButton.addEventListener('click', async () => {
|
| 394 |
if (generateButton.disabled) return;
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
} catch (err) { showCriticalError(`خطا در سیستم اعتبار: ${err.message}`); }
|
| 402 |
}
|
| 403 |
});
|
| 404 |
})();
|
| 405 |
</script>
|
| 406 |
|
| 407 |
<script>
|
| 408 |
-
// --- Header Animation Script
|
| 409 |
-
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
</script>
|
| 411 |
</body>
|
| 412 |
</html>
|
|
|
|
| 56 |
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; }
|
| 57 |
.container { max-width: 820px; width: 100%; }
|
| 58 |
header { position: relative; text-align: center; margin-bottom: 2.5rem; padding: 2rem 0; animation: fadeIn 0.8s 0.1s ease-out backwards; overflow: hidden; }
|
| 59 |
+
#neural-network-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; opacity: 0.7; }
|
| 60 |
.header-content { position: relative; z-index: 2; }
|
| 61 |
.ai-orb-container { width: 150px; height: 150px; margin: 0 auto 1rem; position: relative; display: flex; align-items: center; justify-content: center; }
|
| 62 |
.orb-background { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse at center, rgba(74, 108, 250, 0.15) 0%, transparent 70%), linear-gradient(45deg, rgba(15, 212, 168, 0.05), rgba(74, 108, 250, 0.05)); border-radius: 50%; animation: background-pan 12s ease-in-out infinite; background-size: 200% 200%; }
|
|
|
|
| 86 |
#image-drop-zone.has-image #imagePreview { display: block; }
|
| 87 |
textarea { width: 100%; padding: 1rem 1.2rem; border-radius: var(--radius-input); border: 1px solid var(--input-border); background-color: var(--input-bg); color: var(--text-primary); box-shadow: var(--shadow-sm) inset; font-family: var(--app-font); font-size: 1rem; box-sizing: border-box; transition: var(--transition-smooth); min-height: 120px; resize: vertical; }
|
| 88 |
textarea:focus { outline: none; border-color: var(--accent-primary); box-shadow: 0 0 0 3px var(--accent-primary-glow), var(--shadow-sm) inset; background-color: var(--panel-bg); }
|
| 89 |
+
|
| 90 |
.slider-group { margin-top: 2rem; margin-bottom: 1.5rem; }
|
| 91 |
.slider-label-container { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }
|
| 92 |
.slider-label { font-weight: 600; color: var(--text-secondary); font-size: 0.95rem; }
|
|
|
|
| 99 |
input[type="range"]:hover { box-shadow: inset 0 1px 3px rgba(0,0,0,0.1), 0 0 0 4px var(--accent-primary-glow); }
|
| 100 |
input[type="range"]:hover::-webkit-slider-thumb { box-shadow: 0 0 0 10px var(--accent-primary-glow), var(--shadow-md); transform: scale(1.1); }
|
| 101 |
input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.2); box-shadow: 0 0 0 12px var(--accent-primary-glow), var(--shadow-md); }
|
| 102 |
+
|
| 103 |
#generateButton { display: flex; align-items: center; justify-content: center; gap: 0.75rem; width: 100%; padding: 1.1rem; font-size: 1.2rem; font-weight: 700; background: linear-gradient(95deg, var(--accent-secondary) 0%, var(--accent-primary) 100%); color: #fff; border: none; border-radius: var(--radius-btn); cursor: pointer; transition: all 0.3s ease; box-shadow: 0 6px 12px -3px var(--accent-primary-glow), 0 6px 12px -3px rgba(15, 212, 168, 0.25); margin-top: 1rem; }
|
| 104 |
#generateButton svg { width: 24px; height: 24px; margin-left: 4px; filter: drop-shadow(0 0 5px rgba(255,255,255,0.5)); }
|
| 105 |
#generateButton:hover:not(:disabled) { transform: translateY(-5px) scale(1.02); box-shadow: 0 8px 20px -4px var(--accent-primary-glow), 0 8px 20px -4px rgba(15, 212, 168, 0.3); }
|
|
|
|
| 114 |
.status-message.error { border-left: 4px solid var(--danger-color); color: var(--danger-color); }
|
| 115 |
.status-icon { width: 18px; height: 18px; flex-shrink: 0; }
|
| 116 |
#aiLoader { display: none; align-items: center; justify-content: center; }
|
| 117 |
+
.generator-container { position: relative; width: 400px; max-width: 100%; height: 300px; border: 2px solid #38bdf8; border-radius: 20px; overflow: hidden; box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); animation: pulse-loader 5s infinite cubic-bezier(0.4, 0, 0.6, 1); background-color: #161b22; color: #f0f6fc; }
|
| 118 |
+
@keyframes pulse-loader { 0% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } 50% { box-shadow: 0 0 60px rgba(56, 189, 248, 0.7); } 100% { box-shadow: 0 0 40px rgba(56, 189, 248, 0.3); } }
|
| 119 |
+
.text-overlay { position: absolute; top: 45%; left: 50%; transform: translate(-50%, -50%); font-size: 24px; font-weight: 700; text-shadow: 0 0 20px rgba(56, 189, 248, 0.8); animation: glow-text 7s infinite ease-in-out; font-family: var(--app-font); width: 90%; text-align: center; transition: var(--transition-smooth); }
|
| 120 |
@keyframes glow-text { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } }
|
| 121 |
+
.progress-bar-loader { position: absolute; bottom: 0; left: 0; width: 0%; height: 6px; background: linear-gradient(to right, #38bdf8, #bb86fc, #facc15); transition: width 0.4s ease-out; }
|
|
|
|
| 122 |
#outputVideo { width: 100%; max-width: 500px; border-radius: var(--radius-input); margin: 1rem auto; display: block; background-color: #000; box-shadow: var(--shadow-md); }
|
| 123 |
#finalSeed { text-align: center; color: var(--text-tertiary); font-size: 0.85rem; margin-top: 1rem; }
|
| 124 |
.video-controls { display: flex; justify-content: center; gap: 1rem; margin-top: 1.5rem; }
|
|
|
|
| 135 |
.gpu-error-message-container .error-content ul { list-style-type: "▫️ "; padding-right: 20px; margin-top: 10px; }
|
| 136 |
.gpu-error-message-container .error-content li { margin-bottom: 6px; }
|
| 137 |
.gpu-error-actions { display: flex; gap: 15px; margin-top: 25px; }
|
|
|
|
| 138 |
.gpu-error-actions .action-button.back-button { background-color: var(--panel-bg); color: var(--text-tertiary); border: 1px solid var(--panel-border); flex-grow: 0.45; }
|
| 139 |
.gpu-error-actions .action-button.retry-button { background: linear-gradient(60deg, var(--igadlm-alpha-color-primary) 0%, var(--igadlm-alpha-color-secondary) 100%); color: white; flex-grow: 1; }
|
| 140 |
#subscription-status-badge { display: none; padding: 6px 16px; border-radius: 20px; font-size: 0.9em; font-weight: 700; margin-top: 1.25rem; letter-spacing: 0.5px; animation: badge-fade-in 0.6s 0.5s ease-out backwards; }
|
|
|
|
| 142 |
#subscription-status-badge.paid-badge { background: linear-gradient(45deg, var(--accent-premium), #ffca2c); color: #333; box-shadow: 0 4px 10px var(--accent-premium-glow); }
|
| 143 |
#credit-info-section { margin-top: 1.5rem; margin-bottom: 0.5rem; padding: 1rem; background-color: var(--input-bg); border-radius: var(--radius-input); text-align: center; font-size: 0.95rem; font-weight: 500; color: var(--text-secondary); border: 1px solid var(--input-border); display: none; }
|
| 144 |
#upgrade-button { margin-top: 1rem; width: 100%; padding: 1rem; font-size: 1.1rem; font-weight: 700; background: linear-gradient(95deg, #FFD54F, #FFC107 100%); color: #212529; border: none; border-radius: var(--radius-btn); cursor: pointer; box-shadow: 0 8px 20px -5px rgba(255, 193, 7, 0.3); display: none; }
|
| 145 |
+
|
| 146 |
+
.json-log-container { display: none; margin-top: 1.5rem; }
|
| 147 |
+
.json-log-container h3 { font-size: 1rem; color: var(--text-secondary); margin-bottom: 0.75rem; }
|
| 148 |
+
.json-log-pre { background-color: #0d1117; color: #c9d1d9; padding: 1rem; border-radius: var(--radius-input); max-height: 250px; overflow-y: auto; font-family: monospace; font-size: 0.8rem; white-space: pre-wrap; word-break: break-all; }
|
| 149 |
+
#copyLogBtn { margin-top: 0.75rem; background-color: var(--text-secondary); }
|
| 150 |
+
#copyLogBtn:hover { background-color: var(--text-primary); }
|
| 151 |
+
|
| 152 |
@media (max-width: 768px) { main { padding: 1.5rem; } h1 { font-size: 2.2rem; } .generator-container { height: 250px; } .text-overlay { font-size: 18px; } }
|
| 153 |
</style>
|
| 154 |
</head>
|
|
|
|
| 159 |
<div class="header-content">
|
| 160 |
<div class="ai-orb-container">
|
| 161 |
<div class="orb-background"></div>
|
| 162 |
+
<div class="orb-core">
|
| 163 |
+
<svg class="play-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
| 164 |
+
<path d="M8 5v14l11-7z"></path>
|
| 165 |
+
</svg>
|
| 166 |
+
</div>
|
| 167 |
+
<div class="orb-ring one"></div>
|
| 168 |
+
<div class="orb-ring two"></div>
|
| 169 |
+
<div class="orb-ring three"></div>
|
| 170 |
</div>
|
| 171 |
<h1>استودیو ویدیو هوش مصنوعی</h1>
|
| 172 |
<p class="subtitle">تصاویر خود را با قدرت هوش مصنوعی به ویدیو سینمایی تبدیل کنید</p>
|
|
|
|
| 177 |
|
| 178 |
<main>
|
| 179 |
<div class="form-group">
|
| 180 |
+
<div class="form-label">
|
| 181 |
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4Z"/></svg>
|
| 182 |
+
۱. تصویر خود را انتخاب کنید
|
| 183 |
+
</div>
|
| 184 |
<label id="image-drop-zone" for="imageFile">
|
| 185 |
<div class="upload-content">
|
| 186 |
<div class="upload-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg></div>
|
|
|
|
| 192 |
</div>
|
| 193 |
|
| 194 |
<div class="form-group">
|
| 195 |
+
<label for="prompt" class="form-label">
|
| 196 |
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>
|
| 197 |
+
۲. دستور ساخت ویدیو را بنویسید
|
| 198 |
+
</label>
|
| 199 |
<textarea id="prompt" rows="4" placeholder="مثال: زوم آهسته به بیرون در حالی که برگها در باد میرقصند و نور خورشید از بین درختان میتابد"></textarea>
|
| 200 |
+
|
| 201 |
<div class="slider-group">
|
| 202 |
<div class="slider-label-container">
|
| 203 |
<label for="durationSlider" class="slider-label">مدت زمان ویدیو</label>
|
|
|
|
| 209 |
<svg class="slider-icon" xmlns="http://www.w3.org/2000/svg" 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><polyline points="12 6 12 12 16 14"></polyline></svg>
|
| 210 |
</div>
|
| 211 |
</div>
|
| 212 |
+
|
| 213 |
<div id="credit-info-section"></div>
|
| 214 |
<button id="upgrade-button">⭐️ ارتقا به نسخه کامل و نامحدود</button>
|
| 215 |
+
|
| 216 |
+
<button id="generateButton">
|
| 217 |
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L12 8L17 10L12 12L10 17L8 12L3 10L8 8L10 3z"/></svg>
|
| 218 |
+
<span>ساخت ویدیو جادویی</span>
|
| 219 |
+
</button>
|
| 220 |
</div>
|
| 221 |
|
| 222 |
<div class="form-group">
|
| 223 |
+
<div class="form-label">
|
| 224 |
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3L12 8L17 10L12 12L10 17L8 12L3 10L8 8L10 3z"/><path d="M21 14l-1.5 3-3-1.5 3-3 1.5 3z"/><path d="M19.5 2.5l-3 1.5 1.5 3 3-1.5-1.5-3z"/></svg>
|
| 225 |
+
۳. نتیجه را ببینید
|
| 226 |
+
</div>
|
| 227 |
<div id="result-container">
|
| 228 |
<div id="statusSection">
|
| 229 |
<div id="statusMessages"></div>
|
| 230 |
<div id="aiLoader" style="display: none;">
|
| 231 |
<div class="generator-container">
|
| 232 |
<div class="text-overlay">در حال پردازش ویدیو...</div>
|
| 233 |
+
<div class="progress-bar-loader"></div>
|
|
|
|
|
|
|
| 234 |
</div>
|
| 235 |
</div>
|
| 236 |
</div>
|
| 237 |
+
|
| 238 |
<div id="outputSection">
|
| 239 |
<video id="outputVideo" controls preload="metadata" playsinline></video>
|
| 240 |
<p id="finalSeed"></p>
|
| 241 |
<div class="video-controls">
|
| 242 |
+
<button id="btnDownloadVideo" class="video-button primary" style="display: none;">
|
| 243 |
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
|
| 244 |
+
دانلود ویدیو
|
| 245 |
+
</button>
|
| 246 |
+
<button id="btnRestart" class="video-button">
|
| 247 |
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
|
| 248 |
+
ساخت ویدیو جدید
|
| 249 |
+
</button>
|
| 250 |
</div>
|
| 251 |
</div>
|
| 252 |
+
|
| 253 |
<div id="criticalErrorSection"></div>
|
| 254 |
+
|
| 255 |
+
<div id="json-log-container" class="json-log-container">
|
| 256 |
+
<h3>لاگ کامل JSON</h3>
|
| 257 |
+
<pre id="json-log-pre" class="json-log-pre"></pre>
|
| 258 |
+
<button id="copyLogBtn" class="video-button">کپی لاگ</button>
|
| 259 |
+
</div>
|
| 260 |
+
|
| 261 |
</div>
|
| 262 |
</div>
|
| 263 |
</main>
|
|
|
|
| 267 |
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
|
| 268 |
|
| 269 |
(function() {
|
| 270 |
+
// === START: بخش مدیریت اعتبار و ارتباط با والد ===
|
| 271 |
+
let userSubscriptionStatus = 'free'; // 'free' or 'paid'
|
| 272 |
let userFingerprint = null;
|
| 273 |
let countdownInterval = null;
|
| 274 |
const PREMIUM_PAGE_ID = '1149636';
|
| 275 |
+
|
| 276 |
+
async function getBrowserFingerprint() {
|
| 277 |
+
const components = [navigator.userAgent, navigator.language, screen.width + 'x' + screen.height, new Date().getTimezoneOffset()];
|
| 278 |
+
try {
|
| 279 |
+
const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d');
|
| 280 |
+
ctx.fillText("a1b2c3d4e5f6g7h8i9j0", 2, 15);
|
| 281 |
+
components.push(canvas.toDataURL());
|
| 282 |
+
} catch (e) { components.push("canvas-error"); }
|
| 283 |
+
const fingerprintString = components.join('---');
|
| 284 |
+
let hash = 0;
|
| 285 |
+
for (let i = 0; i < fingerprintString.length; i++) {
|
| 286 |
+
hash = ((hash << 5) - hash) + fingerprintString.charCodeAt(i);
|
| 287 |
+
hash |= 0;
|
| 288 |
+
}
|
| 289 |
+
return 'fp_' + Math.abs(hash).toString(16);
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
function isUserPaid(userObject) {
|
| 293 |
+
return userObject && userObject.isLogin && userObject.accessible_pages &&
|
| 294 |
+
(userObject.accessible_pages.includes(PREMIUM_PAGE_ID) || userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)));
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
function updateUIForSubscriptionStatus(status) {
|
| 298 |
+
userSubscriptionStatus = status;
|
| 299 |
+
const creditInfoDiv = document.getElementById('credit-info-section');
|
| 300 |
+
const upgradeBtn = document.getElementById('upgrade-button');
|
| 301 |
+
const genBtn = document.getElementById('generateButton');
|
| 302 |
+
const subscriptionBadge = document.getElementById('subscription-status-badge');
|
| 303 |
+
|
| 304 |
+
if (status === 'paid') {
|
| 305 |
+
subscriptionBadge.textContent = 'نسخه نامحدود';
|
| 306 |
+
subscriptionBadge.className = 'paid-badge';
|
| 307 |
+
creditInfoDiv.style.display = 'none';
|
| 308 |
+
upgradeBtn.style.display = 'none';
|
| 309 |
+
genBtn.disabled = false;
|
| 310 |
+
if(countdownInterval) clearInterval(countdownInterval);
|
| 311 |
+
} else {
|
| 312 |
+
subscriptionBadge.textContent = 'نسخه رایگان';
|
| 313 |
+
subscriptionBadge.className = 'free-badge';
|
| 314 |
+
checkFreeUserCredit();
|
| 315 |
+
}
|
| 316 |
+
subscriptionBadge.style.display = 'inline-block';
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
async function checkFreeUserCredit() {
|
| 320 |
+
if (!userFingerprint) return;
|
| 321 |
+
const creditInfoDiv = document.getElementById('credit-info-section');
|
| 322 |
+
const upgradeBtn = document.getElementById('upgrade-button');
|
| 323 |
+
const genBtn = document.getElementById('generateButton');
|
| 324 |
+
|
| 325 |
+
try {
|
| 326 |
+
const response = await fetch('/api/check-credit', {
|
| 327 |
+
method: 'POST',
|
| 328 |
+
headers: {'Content-Type': 'application/json'},
|
| 329 |
+
body: JSON.stringify({ fingerprint: userFingerprint })
|
| 330 |
+
});
|
| 331 |
+
const result = await response.json();
|
| 332 |
+
|
| 333 |
+
creditInfoDiv.style.display = 'block';
|
| 334 |
+
if (result.limit_reached) {
|
| 335 |
+
genBtn.disabled = true;
|
| 336 |
+
upgradeBtn.style.display = 'block';
|
| 337 |
+
startCountdown(result.reset_timestamp);
|
| 338 |
+
} else {
|
| 339 |
+
genBtn.disabled = false;
|
| 340 |
+
upgradeBtn.style.display = 'none';
|
| 341 |
+
if(countdownInterval) clearInterval(countdownInterval);
|
| 342 |
+
creditInfoDiv.textContent = `شما ${result.credits_remaining} اعتبار ساخت ویدیوی رایگان در این هفته دارید.`;
|
| 343 |
+
}
|
| 344 |
+
} catch (error) {
|
| 345 |
+
creditInfoDiv.textContent = "توجه: سیستم اعتبار در این نسخه آزمایشی فعال است و نیازی به پرداخت نیست.";
|
| 346 |
+
genBtn.disabled = false;
|
| 347 |
+
}
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
function startCountdown(resetTimestamp) {
|
| 351 |
+
if (countdownInterval) clearInterval(countdownInterval);
|
| 352 |
+
const creditInfoDiv = document.getElementById('credit-info-section');
|
| 353 |
+
const updateTimer = () => {
|
| 354 |
+
const timeLeft = Math.max(0, resetTimestamp - (Date.now() / 1000));
|
| 355 |
+
if (timeLeft === 0) {
|
| 356 |
+
clearInterval(countdownInterval);
|
| 357 |
+
creditInfoDiv.textContent = 'در حال بروزرسانی اعتبار...';
|
| 358 |
+
setTimeout(checkFreeUserCredit, 2000);
|
| 359 |
+
return;
|
| 360 |
+
}
|
| 361 |
+
const d = Math.floor(timeLeft / 86400);
|
| 362 |
+
const h = Math.floor((timeLeft % 86400) / 3600);
|
| 363 |
+
const m = Math.floor((timeLeft % 3600) / 60);
|
| 364 |
+
creditInfoDiv.textContent = `اعتبار شما تمام شده. زمان باقیمانده: ${d} روز و ${h} ساعت و ${m} دقیقه`;
|
| 365 |
+
};
|
| 366 |
+
updateTimer();
|
| 367 |
+
countdownInterval = setInterval(updateTimer, 60000);
|
| 368 |
+
}
|
| 369 |
+
|
| 370 |
+
window.addEventListener('message', (event) => {
|
| 371 |
+
if (event.data && event.data.type === 'USER_DATA_RESPONSE') {
|
| 372 |
+
if (event.data.error || !event.data.payload) {
|
| 373 |
+
updateUIForSubscriptionStatus('free');
|
| 374 |
+
return;
|
| 375 |
+
}
|
| 376 |
+
try {
|
| 377 |
+
const userObject = JSON.parse(event.data.payload);
|
| 378 |
+
const status = isUserPaid(userObject) ? 'paid' : 'free';
|
| 379 |
+
updateUIForSubscriptionStatus(status);
|
| 380 |
+
} catch (e) {
|
| 381 |
+
updateUIForSubscriptionStatus('free');
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
+
});
|
| 385 |
+
|
| 386 |
+
document.getElementById('upgrade-button').addEventListener('click', () => {
|
| 387 |
+
parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM' }, '*');
|
| 388 |
+
});
|
| 389 |
// === END: بخش مدیریت اعتبار ===
|
| 390 |
|
| 391 |
+
|
| 392 |
// DOM Elements
|
| 393 |
const resultContainer = document.getElementById('result-container');
|
| 394 |
const imageFileInput = document.getElementById('imageFile');
|
|
|
|
| 402 |
const statusSection = document.getElementById('statusSection');
|
| 403 |
const statusMessagesDiv = document.getElementById('statusMessages');
|
| 404 |
const aiLoader = document.getElementById('aiLoader');
|
| 405 |
+
const loaderProgressBar = document.querySelector('#aiLoader .progress-bar-loader');
|
| 406 |
const loaderTextOverlay = document.querySelector('#aiLoader .text-overlay');
|
| 407 |
const outputSection = document.getElementById('outputSection');
|
| 408 |
const btnRestart = document.getElementById('btnRestart');
|
| 409 |
const btnDownloadVideo = document.getElementById('btnDownloadVideo');
|
| 410 |
const durationSlider = document.getElementById('durationSlider');
|
| 411 |
const durationValueDisplay = document.getElementById('durationValue');
|
| 412 |
+
const jsonLogContainer = document.getElementById('json-log-container');
|
| 413 |
+
const jsonLogPre = document.getElementById('json-log-pre');
|
| 414 |
+
const copyLogBtn = document.getElementById('copyLogBtn');
|
| 415 |
|
| 416 |
+
// ====== تنظیمات مدلها ======
|
| 417 |
const VIDEO_SPACE_NAME = "mrbui1990/wan2-2-fp8da-aoti-faster";
|
| 418 |
const TRANSLATOR_SPACE_NAME = "hamed744/translate-tts-aloha";
|
| 419 |
+
const FN_INDEX_TRANSLATE_SPEAK = 1;
|
| 420 |
+
const DEFAULT_TTS_VOICE_TRANSLATOR = "انگلیسی (آمریکا) - جنی (زن)";
|
| 421 |
+
|
| 422 |
const DEFAULT_NEGATIVE_PROMPT = "色调艳丽, 过曝, 静态, 细节模糊不清, 字幕, 风格, 作品, 画作, 画面, 静止, 整体发灰, 最差质量, 低质量, JPEG压缩残留, 丑陋的, 残缺的, 多余的手指, 画得不好的手部, 画得不好的脸部, 畸形的, 毁容的, 形态畸形的肢体, 手指融合, 静止不动的画面, 杂乱的背景, 三条腿, 背景人很多, 倒着走";
|
| 423 |
const DEFAULT_PROMPT_TEXT = "";
|
| 424 |
const TRANSPARENT_PIXEL_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
| 425 |
const DEFAULT_VIDEO_DURATION = 4.3;
|
|
|
|
| 426 |
|
| 427 |
let lastAttemptedVideoPayload = null;
|
| 428 |
+
let currentSubmission = null;
|
| 429 |
+
|
| 430 |
+
function updateSliderStyle(slider) {
|
| 431 |
+
const percentage = ((slider.value - slider.min) / (slider.max - slider.min)) * 100;
|
| 432 |
+
const colorStops = `var(--accent-primary), var(--accent-secondary)`;
|
| 433 |
+
slider.style.background = `linear-gradient(to left, ${colorStops} ${percentage}%, var(--input-bg) ${percentage}%)`;
|
| 434 |
+
}
|
| 435 |
|
| 436 |
+
async function retryLastAttempt() {
|
| 437 |
+
if (!lastAttemptedVideoPayload || !lastAttemptedVideoPayload.imageSourceFile || !lastAttemptedVideoPayload.persianPrompt) {
|
| 438 |
+
showCriticalError("اطلاعات کافی برای تلاش مجدد وجود ندارد. لطفاً از نو شروع کنید.");
|
| 439 |
+
return;
|
| 440 |
+
}
|
| 441 |
+
proceedWithVideoGeneration(true);
|
| 442 |
+
}
|
| 443 |
|
| 444 |
function showCriticalError(message) {
|
| 445 |
+
const lowerCaseMessage = (message || "").toLowerCase();
|
| 446 |
if (lowerCaseMessage.includes("gpu") || lowerCaseMessage.includes("quota") || lowerCaseMessage.includes("exceeded") || lowerCaseMessage.includes("limit") || lowerCaseMessage.includes("capacity") || lowerCaseMessage.includes("queue full")) {
|
| 447 |
+
const detailedGpuErrorHtml = `<div class="gpu-error-message-container"><h2><span class="icon">💡</span> راهنمای رفع محدودیت ویدیو</h2><div class="error-content"><strong class="error-message-title">ارور محدودیت استفاده از GPU</strong><p>مدلهای هوش مصنوعی برای اجرا به کارتهای گرافیک بسیار قدرتمند نیاز دارند که این امر گاهی محدودیتهایی جی پی یو را در استفاده از آنها ایجاد میکند. با این حال، خبر خوب این است که این محدودیتها با استفاده از یک ترفند ساده قابل رفع هستند.</p><p><strong>برای رفع محدودیت هر بار اینگونه عمل کنید:</strong></p><ul><li>اگر از اینترنت سیمکارت استفاده میکنید، فیلترشکن خود را حتما خاموش کنید.</li><li>به مدت ۱۰ تا ۱۵ ثانیه، گوشی خود را در حالت هواپیما قرار دهید و سپس آن را غیرفعال کنید. این محدودیت با همین روش برداشته میشود</li><li>اگر از وای فای استفاده میکنید، مودم خود را یک بار خاموش و روشن کنید (بهتر است برای استفاده نامحدود از برنامه از اینترنت سیم کارت استفاده کنید).</li></ul><p>خلاصه: هربار این صفحه اومد اینگونه عمل کنید از اینترنت سیم کارت استفاده کنید، فیلتر شکن خاموش باشه، یک یا دوبار حالت هواپیما رو روشن و خاموش کنید. تلاش مجدداً بزنید این ارور بر طرف میشه، با این روش میشه بصورت نامحدود با این برنامه ویدیو ساخت☘️</p></div><div class="gpu-error-actions"><button class="action-button back-button" id="gpuErrorGoBackBtn">بازگشت</button><button class="action-button retry-button" id="gpuErrorRetryBtn">تلاش مجدد</button></div></div>`;
|
| 448 |
+
criticalErrorSection.innerHTML = detailedGpuErrorHtml;
|
| 449 |
+
document.getElementById('gpuErrorGoBackBtn').addEventListener('click', initializeForm);
|
| 450 |
document.getElementById('gpuErrorRetryBtn').addEventListener('click', retryLastAttempt);
|
| 451 |
} else {
|
| 452 |
criticalErrorSection.innerHTML = `<div id="criticalErrorMessage" style="color: var(--danger-color); font-weight: 500; margin-bottom: 1.5rem; line-height: 1.6;"><p>${message}</p></div><div class="video-controls"><button id="simpleErrorGoBackBtn" class="video-button">بازگشت</button><button id="simpleErrorRetryBtn" class="video-button primary">تلاش مجدد</button></div>`;
|
| 453 |
+
document.getElementById('simpleErrorGoBackBtn').addEventListener('click', initializeForm);
|
| 454 |
document.getElementById('simpleErrorRetryBtn').addEventListener('click', retryLastAttempt);
|
| 455 |
}
|
| 456 |
resultContainer.classList.add('active');
|
|
|
|
| 459 |
outputSection.classList.remove('active');
|
| 460 |
aiLoader.style.display = 'none';
|
| 461 |
generateButton.disabled = false;
|
| 462 |
+
jsonLogContainer.style.display = 'block';
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
function setAiLoaderState(text, percentage = null) {
|
| 466 |
+
if (loaderTextOverlay) loaderTextOverlay.textContent = text;
|
| 467 |
+
if (loaderProgressBar) {
|
| 468 |
+
if (percentage !== null) {
|
| 469 |
+
loaderProgressBar.style.width = `${percentage}%`;
|
| 470 |
+
}
|
| 471 |
+
}
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
function appendJsonToLog(data) {
|
| 475 |
+
if (jsonLogPre.textContent) {
|
| 476 |
+
jsonLogPre.textContent += '\n\n// ---- پیام جدید ---- //\n\n';
|
| 477 |
+
}
|
| 478 |
+
jsonLogPre.textContent += JSON.stringify(data, null, 2);
|
| 479 |
+
jsonLogPre.parentElement.scrollTop = jsonLogPre.parentElement.scrollHeight;
|
| 480 |
}
|
| 481 |
|
| 482 |
+
function addStatusMessage(message, type = 'info') {
|
| 483 |
+
if (criticalErrorSection.classList.contains('active') && type === 'error') return;
|
| 484 |
+
resultContainer.classList.add('active');
|
| 485 |
+
statusSection.classList.add('active');
|
| 486 |
+
const messageDiv = document.createElement('div');
|
| 487 |
+
messageDiv.className = `status-message ${type}`;
|
| 488 |
+
const iconSvg = type === 'success' ? '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>' : type === 'error' ? '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>' : '<svg class="status-icon" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>';
|
| 489 |
+
messageDiv.innerHTML = `${iconSvg}<span class="status-text">${message}</span>`;
|
| 490 |
+
statusMessagesDiv.insertBefore(messageDiv, statusMessagesDiv.firstChild);
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
function clearStatusMessages() {
|
| 494 |
+
statusMessagesDiv.innerHTML = '';
|
| 495 |
+
setAiLoaderState("در حال پردازش...", 0);
|
| 496 |
+
aiLoader.style.display = 'none';
|
| 497 |
+
if (outputVideo.src && outputVideo.src.startsWith('blob:')) {
|
| 498 |
+
URL.revokeObjectURL(outputVideo.src);
|
| 499 |
+
}
|
| 500 |
+
outputVideo.src = '';
|
| 501 |
+
finalSeedElement.textContent = '';
|
| 502 |
+
btnDownloadVideo.style.display = 'none';
|
| 503 |
+
jsonLogContainer.style.display = 'none';
|
| 504 |
+
jsonLogPre.textContent = '';
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
function initializeForm() {
|
| 508 |
+
if (currentSubmission) {
|
| 509 |
+
try { currentSubmission.cancel(); } catch (e) {}
|
| 510 |
+
currentSubmission = null;
|
| 511 |
+
}
|
| 512 |
+
criticalErrorSection.classList.remove('active');
|
| 513 |
+
criticalErrorSection.innerHTML = '';
|
| 514 |
+
resultContainer.classList.remove('active');
|
| 515 |
+
statusSection.classList.remove('active');
|
| 516 |
+
outputSection.classList.remove('active');
|
| 517 |
+
clearStatusMessages();
|
| 518 |
+
lastAttemptedVideoPayload = null;
|
| 519 |
+
imageFileInput.value = '';
|
| 520 |
+
imagePreview.src = TRANSPARENT_PIXEL_SRC;
|
| 521 |
+
imageDropZone.classList.remove('has-image');
|
| 522 |
+
promptInput.value = DEFAULT_PROMPT_TEXT;
|
| 523 |
+
generateButton.disabled = false;
|
| 524 |
+
durationSlider.value = DEFAULT_VIDEO_DURATION;
|
| 525 |
+
durationValueDisplay.textContent = `${parseFloat(DEFAULT_VIDEO_DURATION).toFixed(1)} ثانیه`;
|
| 526 |
+
updateSliderStyle(durationSlider);
|
| 527 |
+
updateUIForSubscriptionStatus(userSubscriptionStatus);
|
| 528 |
+
}
|
| 529 |
|
| 530 |
+
async function translatePersianToEnglish(persianText) {
|
| 531 |
+
if (!persianText?.trim()) throw new Error("متن ترجمه خالی است.");
|
| 532 |
+
setAiLoaderState("در حال ترجمه دستور شما...");
|
| 533 |
+
const translator = await Client.connect(TRANSLATOR_SPACE_NAME);
|
| 534 |
+
const resp = await translator.predict(FN_INDEX_TRANSLATE_SPEAK, [persianText, DEFAULT_TTS_VOICE_TRANSLATOR, 0, 0, 0]);
|
| 535 |
+
const english = resp?.data?.[0];
|
| 536 |
+
if (!english) throw new Error("ترجمه نامعتبر بود.");
|
| 537 |
+
return english;
|
| 538 |
+
}
|
| 539 |
|
| 540 |
+
function handleSuccessfulVideoGeneration(resultData) {
|
| 541 |
aiLoader.style.display = 'none';
|
| 542 |
+
if (!resultData?.[0]?.video?.url) {
|
| 543 |
+
showCriticalError('پاسخ دریافتی از سرور ویدیو معتبر نیست.');
|
| 544 |
+
return;
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
if (userSubscriptionStatus === 'free') {
|
| 548 |
+
// Mock credit usage
|
| 549 |
+
checkFreeUserCredit();
|
| 550 |
}
|
| 551 |
+
|
| 552 |
addStatusMessage('ویدیو آماده شد! 🎉', 'success');
|
| 553 |
+
const videoUrl = resultData[0].video.url;
|
| 554 |
+
const usedSeed = resultData[1];
|
| 555 |
+
setTimeout(() => {
|
| 556 |
+
statusSection.classList.remove('active');
|
| 557 |
+
outputSection.classList.add('active');
|
| 558 |
+
outputVideo.src = videoUrl;
|
| 559 |
+
finalSeedElement.textContent = `Seed: ${usedSeed}`;
|
| 560 |
+
prepareCustomDownloadLink(videoUrl);
|
| 561 |
+
}, 300);
|
| 562 |
generateButton.disabled = false;
|
| 563 |
+
jsonLogContainer.style.display = 'block';
|
| 564 |
}
|
| 565 |
|
| 566 |
+
function prepareCustomDownloadLink(videoUrl) {
|
| 567 |
+
btnDownloadVideo.style.display = 'flex';
|
| 568 |
+
btnDownloadVideo.onclick = () => {
|
| 569 |
+
parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: videoUrl }, '*');
|
| 570 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
}
|
| 572 |
+
|
| 573 |
+
document.addEventListener('DOMContentLoaded', async () => {
|
| 574 |
+
initializeForm();
|
| 575 |
+
userFingerprint = await getBrowserFingerprint();
|
| 576 |
+
parent.postMessage({ type: 'REQUEST_USER_DATA' }, '*');
|
| 577 |
+
updateSliderStyle(durationSlider);
|
| 578 |
+
});
|
| 579 |
|
| 580 |
+
durationSlider.addEventListener('input', () => {
|
| 581 |
+
const value = parseFloat(durationSlider.value).toFixed(1);
|
| 582 |
+
durationValueDisplay.textContent = `${value} ثانیه`;
|
| 583 |
+
updateSliderStyle(durationSlider);
|
| 584 |
+
});
|
| 585 |
+
|
| 586 |
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(e => imageDropZone.addEventListener(e, ev => ev.preventDefault()));
|
| 587 |
['dragenter', 'dragover'].forEach(e => imageDropZone.addEventListener(e, () => { if (!imageDropZone.classList.contains('has-image')) imageDropZone.classList.add('drag-over'); }));
|
| 588 |
['dragleave', 'drop'].forEach(e => imageDropZone.addEventListener(e, () => imageDropZone.classList.remove('drag-over')));
|
| 589 |
imageDropZone.addEventListener('drop', e => { if (e.dataTransfer.files.length > 0) { imageFileInput.files = e.dataTransfer.files; imageFileInput.dispatchEvent(new Event('change')); } });
|
| 590 |
btnRestart.addEventListener('click', initializeForm);
|
| 591 |
+
|
| 592 |
+
imageFileInput.addEventListener('change', function(event) {
|
| 593 |
+
const file = event.target.files[0];
|
| 594 |
+
if (file) {
|
| 595 |
+
if (!file.type.startsWith('image/')) {
|
| 596 |
+
showCriticalError('فرمت فایل نامعتبر است. لطفاً یک تصویر انتخاب کنید.');
|
| 597 |
+
initializeForm();
|
| 598 |
+
} else {
|
| 599 |
+
criticalErrorSection.classList.remove('active');
|
| 600 |
+
criticalErrorSection.innerHTML = '';
|
| 601 |
+
const reader = new FileReader();
|
| 602 |
+
reader.onload = (e) => { imagePreview.src = e.target.result; imageDropZone.classList.add('has-image'); };
|
| 603 |
+
reader.readAsDataURL(file);
|
| 604 |
+
}
|
| 605 |
+
} else {
|
| 606 |
+
imagePreview.src = TRANSPARENT_PIXEL_SRC;
|
| 607 |
+
imageDropZone.classList.remove('has-image');
|
| 608 |
+
}
|
| 609 |
+
});
|
| 610 |
+
|
| 611 |
+
copyLogBtn.addEventListener('click', () => {
|
| 612 |
+
navigator.clipboard.writeText(jsonLogPre.textContent).then(() => {
|
| 613 |
+
const originalText = copyLogBtn.textContent;
|
| 614 |
+
copyLogBtn.textContent = 'کپی شد!';
|
| 615 |
+
setTimeout(() => { copyLogBtn.textContent = originalText; }, 2000);
|
| 616 |
+
});
|
| 617 |
+
});
|
| 618 |
+
|
| 619 |
+
const proceedWithVideoGeneration = async (isRetry = false) => {
|
| 620 |
+
let persianPrompt, imageFile, duration;
|
| 621 |
+
|
| 622 |
+
if (isRetry) {
|
| 623 |
+
persianPrompt = lastAttemptedVideoPayload.persianPrompt;
|
| 624 |
+
imageFile = lastAttemptedVideoPayload.imageSourceFile;
|
| 625 |
+
duration = lastAttemptedVideoPayload.duration;
|
| 626 |
+
} else {
|
| 627 |
+
persianPrompt = promptInput.value.trim();
|
| 628 |
+
imageFile = imageFileInput.files[0];
|
| 629 |
+
duration = parseFloat(durationSlider.value);
|
| 630 |
+
}
|
| 631 |
|
| 632 |
if (!persianPrompt) { addStatusMessage('لطفاً توضیحات را وارد کنید.', 'error'); return; }
|
| 633 |
if (!imageFile) { addStatusMessage('لطفاً تصویر انتخاب کنید.', 'error'); return; }
|
| 634 |
|
| 635 |
+
clearStatusMessages();
|
| 636 |
+
criticalErrorSection.classList.remove('active');
|
| 637 |
+
criticalErrorSection.innerHTML = '';
|
| 638 |
+
generateButton.disabled = true;
|
| 639 |
+
resultContainer.classList.add('active');
|
| 640 |
+
statusSection.classList.add('active');
|
| 641 |
+
aiLoader.style.display = 'flex';
|
| 642 |
+
outputSection.classList.remove('active');
|
| 643 |
+
addStatusMessage('در حال آماده سازی...', 'info');
|
| 644 |
+
|
| 645 |
+
if (!isRetry) {
|
| 646 |
+
lastAttemptedVideoPayload = { persianPrompt, imageSourceFile: imageFile, duration };
|
| 647 |
+
}
|
| 648 |
|
| 649 |
let promptForVideo = persianPrompt;
|
| 650 |
try {
|
| 651 |
+
addStatusMessage('در حال ترجمه متن به انگلیسی...', 'info');
|
| 652 |
const englishPrompt = await translatePersianToEnglish(persianPrompt);
|
| 653 |
addStatusMessage(`ترجمه: ${englishPrompt}`, 'info');
|
| 654 |
promptForVideo = englishPrompt;
|
| 655 |
} catch (e) {
|
| 656 |
+
addStatusMessage('ترجمه ناموفق بود؛ با متن فارسی ادامه میدهیم.', 'error');
|
| 657 |
}
|
| 658 |
|
| 659 |
try {
|
|
|
|
| 660 |
const client = await Client.connect(VIDEO_SPACE_NAME);
|
| 661 |
+
currentSubmission = client.submit(0, [
|
|
|
|
|
|
|
| 662 |
imageFile, promptForVideo, 6, DEFAULT_NEGATIVE_PROMPT, duration, 1, 1, 0, true
|
| 663 |
]);
|
| 664 |
|
| 665 |
+
currentSubmission.on("status", (status) => {
|
| 666 |
+
appendJsonToLog(status); // Always log the raw data
|
| 667 |
+
switch(status.code) {
|
| 668 |
+
case "QUEUED":
|
| 669 |
+
setAiLoaderState(`در صف... نفر جلوی شما: ${status.queue_size - 1}. زمان تخمینی: ${Math.round(status.eta)} ثانیه`, 0);
|
| 670 |
+
break;
|
| 671 |
+
case "ESTIMATING":
|
| 672 |
+
setAiLoaderState(`در حال تخمین زمان...`, 0);
|
| 673 |
+
break;
|
| 674 |
+
case "SENDING_DATA":
|
| 675 |
+
case "PROCESSING":
|
| 676 |
+
if (status.progress_data) {
|
| 677 |
+
const p = status.progress_data[0];
|
| 678 |
+
const percentage = p.total ? (p.index / p.total) * 100 : 0;
|
| 679 |
+
const desc = p.desc || 'در حال پردازش...';
|
| 680 |
+
setAiLoaderState(`${desc} (${Math.round(percentage)}%)`, percentage);
|
| 681 |
+
} else {
|
| 682 |
+
setAiLoaderState("پردازش در سرور شروع شد...");
|
| 683 |
+
}
|
| 684 |
+
break;
|
| 685 |
+
case "COMPLETE":
|
| 686 |
+
handleSuccessfulVideoGeneration(status.data);
|
| 687 |
+
break;
|
| 688 |
+
case "GENERIC_ERROR":
|
| 689 |
+
showCriticalError(`خطا در پردازش: ${status.message}`);
|
| 690 |
+
break;
|
| 691 |
}
|
| 692 |
});
|
| 693 |
|
|
|
|
|
|
|
|
|
|
| 694 |
} catch (errorCaught) {
|
| 695 |
+
showCriticalError(`خطای اتصال: ${errorCaught.message}`);
|
|
|
|
| 696 |
generateButton.disabled = false;
|
| 697 |
}
|
| 698 |
};
|
| 699 |
|
| 700 |
generateButton.addEventListener('click', async () => {
|
| 701 |
if (generateButton.disabled) return;
|
| 702 |
+
|
| 703 |
+
if (userSubscriptionStatus === 'paid') {
|
| 704 |
+
await proceedWithVideoGeneration();
|
| 705 |
+
} else {
|
| 706 |
+
// In demo mode, we assume credit is available
|
| 707 |
+
await proceedWithVideoGeneration();
|
|
|
|
| 708 |
}
|
| 709 |
});
|
| 710 |
})();
|
| 711 |
</script>
|
| 712 |
|
| 713 |
<script>
|
| 714 |
+
// --- Header Animation Script ---
|
| 715 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 716 |
+
const canvas = document.getElementById('neural-network-canvas');
|
| 717 |
+
if (!canvas) return;
|
| 718 |
+
const header = canvas.parentElement;
|
| 719 |
+
const ctx = canvas.getContext('2d');
|
| 720 |
+
let particles = [];
|
| 721 |
+
const particleCount = 20; const maxDistance = 100;
|
| 722 |
+
const computedStyles = getComputedStyle(document.documentElement);
|
| 723 |
+
const particleColor = computedStyles.getPropertyValue('--accent-primary').trim();
|
| 724 |
+
const lineColor = computedStyles.getPropertyValue('--text-tertiary').trim();
|
| 725 |
+
function resizeCanvas() { canvas.width = header.clientWidth; canvas.height = header.clientHeight; init(); }
|
| 726 |
+
class Particle { constructor() { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; this.vx = (Math.random() - 0.5) * 0.3; this.vy = (Math.random() - 0.5) * 0.3; this.radius = 1.2; } update() { this.x += this.vx; this.y += this.vy; if (this.x < 0 || this.x > canvas.width) this.vx *= -1; if (this.y < 0 || this.y > canvas.height) this.vy *= -1; } draw() { ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fillStyle = particleColor; ctx.fill(); } }
|
| 727 |
+
function init() { particles = []; for (let i = 0; i < particleCount; i++) { particles.push(new Particle()); } }
|
| 728 |
+
function connectParticles() { for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < maxDistance) { ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.strokeStyle = lineColor; ctx.lineWidth = 0.2; ctx.globalAlpha = 1 - distance / maxDistance; ctx.stroke(); } } } ctx.globalAlpha = 1; }
|
| 729 |
+
function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); connectParticles(); requestAnimationFrame(animate); }
|
| 730 |
+
window.addEventListener('resize', resizeCanvas); resizeCanvas(); animate();
|
| 731 |
+
});
|
| 732 |
</script>
|
| 733 |
</body>
|
| 734 |
</html>
|