Update templates/index.html
Browse files- templates/index.html +83 -413
templates/index.html
CHANGED
|
@@ -51,7 +51,6 @@
|
|
| 51 |
@keyframes ring-rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
| 52 |
@keyframes background-pan { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
|
| 53 |
@keyframes igadlmAlphaV2fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
|
| 54 |
-
@keyframes fill-progress { from { width: 0%; } to { width: 100%; } }
|
| 55 |
@keyframes badge-fade-in { from { opacity: 0; transform: translateY(-10px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
| 56 |
|
| 57 |
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; }
|
|
@@ -87,7 +86,6 @@
|
|
| 87 |
#image-drop-zone.has-image #imagePreview { display: block; }
|
| 88 |
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; }
|
| 89 |
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); }
|
| 90 |
-
|
| 91 |
.slider-group { margin-top: 2rem; margin-bottom: 1.5rem; }
|
| 92 |
.slider-label-container { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; }
|
| 93 |
.slider-label { font-weight: 600; color: var(--text-secondary); font-size: 0.95rem; }
|
|
@@ -100,7 +98,6 @@
|
|
| 100 |
input[type="range"]:hover { box-shadow: inset 0 1px 3px rgba(0,0,0,0.1), 0 0 0 4px var(--accent-primary-glow); }
|
| 101 |
input[type="range"]:hover::-webkit-slider-thumb { box-shadow: 0 0 0 10px var(--accent-primary-glow), var(--shadow-md); transform: scale(1.1); }
|
| 102 |
input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.2); box-shadow: 0 0 0 12px var(--accent-primary-glow), var(--shadow-md); }
|
| 103 |
-
|
| 104 |
#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; }
|
| 105 |
#generateButton svg { width: 24px; height: 24px; margin-left: 4px; filter: drop-shadow(0 0 5px rgba(255,255,255,0.5)); }
|
| 106 |
#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); }
|
|
@@ -113,26 +110,13 @@
|
|
| 113 |
.status-message { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 1rem; margin-bottom: 0.5rem; border-radius: var(--radius-input); font-size: 0.9rem; background: var(--panel-bg); border: 1px solid var(--panel-border); color: var(--text-secondary); }
|
| 114 |
.status-message.success { border-left: 4px solid var(--success-color); color: var(--success-color); }
|
| 115 |
.status-message.error { border-left: 4px solid var(--danger-color); color: var(--danger-color); }
|
| 116 |
-
.status-icon { width: 18px; height: 18px; }
|
| 117 |
#aiLoader { display: none; align-items: center; justify-content: center; }
|
| 118 |
-
.generator-container { position: relative; width: 400px; max-width: 100%; height: 300px; border
|
| 119 |
-
@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); } }
|
| 120 |
-
.noise-layer, .sketch-layer, .building-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
|
| 121 |
-
/* FIX xmlns در SVG اینجا اعمال شده */
|
| 122 |
-
.noise-layer { background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"><rect width="100" height="100" fill="none"/><filter id="noise"><feTurbulence type="fractalNoise" baseFrequency="0.5" numOctaves="4" stitchTiles="stitch"/></filter><rect width="100%" height="100%" filter="url(%23noise)" opacity="0.6"/></svg>') repeat; opacity: 1; animation: fade-noise 7s infinite ease-in-out; }
|
| 123 |
-
@keyframes fade-noise { 0% { opacity: 1; filter: blur(5px); } 30% { opacity: 0.8; filter: blur(2px); } 100% { opacity: 0; filter: blur(0px); } }
|
| 124 |
-
.sketch-layer { filter: grayscale(1) contrast(1.5) blur(3px); opacity: 0; animation: reveal-sketch 7s infinite ease-in-out; }
|
| 125 |
-
@keyframes reveal-sketch { 0% { opacity: 0; } 20% { opacity: 1; } 60% { opacity: 0.5; } 100% { opacity: 0; } }
|
| 126 |
-
.building-layer { filter: blur(15px); opacity: 0; animation: denoise-color 7s infinite ease-in-out; }
|
| 127 |
-
@keyframes denoise-color { 0% { opacity: 0; } 40% { opacity: 0.6; filter: blur(5px); } 100% { opacity: 1; filter: blur(0px); } }
|
| 128 |
-
.pixel-grid { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: repeating-linear-gradient(0deg, transparent 0 1px, rgba(255,255,255,0.1) 1px 2px), repeating-linear-gradient(90deg, transparent 0 1px, rgba(255,255,255,0.1) 1px 2px); opacity: 1; animation: dissolve-grid 7s infinite ease-in-out; }
|
| 129 |
-
@keyframes dissolve-grid { 0% { opacity: 1; } 70% { opacity: 0.5; } 100% { opacity: 0; } }
|
| 130 |
-
.particles { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle, rgba(56, 189, 248, 0.2) 0%, transparent 50%); animation: flow-particles 7s infinite cubic-bezier(0.4, 0, 0.6, 1); }
|
| 131 |
-
@keyframes flow-particles { 0% { transform: translate(0, 0) scale(1); opacity: 0.5; } 50% { transform: translate(10px, -15px) scale(1.05); opacity: 0.8; } 100% { transform: translate(0, 0) scale(1); opacity: 0.5; } }
|
| 132 |
.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;}
|
| 133 |
@keyframes glow-text { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } }
|
| 134 |
-
.progress-bar { position: absolute; bottom:
|
| 135 |
-
.progress-bar
|
| 136 |
#outputVideo { width: 100%; max-width: 500px; border-radius: var(--radius-input); margin: 1rem auto; display: block; background-color: #000; box-shadow: var(--shadow-md); }
|
| 137 |
#finalSeed { text-align: center; color: var(--text-tertiary); font-size: 0.85rem; margin-top: 1rem; }
|
| 138 |
.video-controls { display: flex; justify-content: center; gap: 1rem; margin-top: 1.5rem; }
|
|
@@ -149,16 +133,14 @@
|
|
| 149 |
.gpu-error-message-container .error-content ul { list-style-type: "▫️ "; padding-right: 20px; margin-top: 10px; }
|
| 150 |
.gpu-error-message-container .error-content li { margin-bottom: 6px; }
|
| 151 |
.gpu-error-actions { display: flex; gap: 15px; margin-top: 25px; }
|
|
|
|
| 152 |
.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; }
|
| 153 |
.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; }
|
| 154 |
-
|
| 155 |
#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; }
|
| 156 |
#subscription-status-badge.free-badge { background: linear-gradient(45deg, #6c757d, #495057); color: white; box-shadow: 0 4px 10px rgba(108, 117, 125, 0.3); }
|
| 157 |
#subscription-status-badge.paid-badge { background: linear-gradient(45deg, var(--accent-premium), #ffca2c); color: #333; box-shadow: 0 4px 10px var(--accent-premium-glow); }
|
| 158 |
-
|
| 159 |
#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; }
|
| 160 |
#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; }
|
| 161 |
-
|
| 162 |
@media (max-width: 768px) { main { padding: 1.5rem; } h1 { font-size: 2.2rem; } .generator-container { height: 250px; } .text-overlay { font-size: 18px; } }
|
| 163 |
</style>
|
| 164 |
</head>
|
|
@@ -169,14 +151,8 @@
|
|
| 169 |
<div class="header-content">
|
| 170 |
<div class="ai-orb-container">
|
| 171 |
<div class="orb-background"></div>
|
| 172 |
-
<div class="orb-core">
|
| 173 |
-
|
| 174 |
-
<path d="M8 5v14l11-7z"></path>
|
| 175 |
-
</svg>
|
| 176 |
-
</div>
|
| 177 |
-
<div class="orb-ring one"></div>
|
| 178 |
-
<div class="orb-ring two"></div>
|
| 179 |
-
<div class="orb-ring three"></div>
|
| 180 |
</div>
|
| 181 |
<h1>استودیو ویدیو هوش مصنوعی</h1>
|
| 182 |
<p class="subtitle">تصاویر خود را با قدرت هوش مصنوعی به ویدیو سینمایی تبدیل کنید</p>
|
|
@@ -187,10 +163,7 @@
|
|
| 187 |
|
| 188 |
<main>
|
| 189 |
<div class="form-group">
|
| 190 |
-
<div class="form-label">
|
| 191 |
-
<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>
|
| 192 |
-
۱. تصویر خود را انتخاب کنید
|
| 193 |
-
</div>
|
| 194 |
<label id="image-drop-zone" for="imageFile">
|
| 195 |
<div class="upload-content">
|
| 196 |
<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>
|
|
@@ -202,12 +175,8 @@
|
|
| 202 |
</div>
|
| 203 |
|
| 204 |
<div class="form-group">
|
| 205 |
-
<label for="prompt" class="form-label">
|
| 206 |
-
<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>
|
| 207 |
-
۲. دستور ساخت ویدیو را بنویسید
|
| 208 |
-
</label>
|
| 209 |
<textarea id="prompt" rows="4" placeholder="مثال: زوم آهسته به بیرون در حالی که برگها در باد میرقصند و نور خورشید از بین درختان میتابد"></textarea>
|
| 210 |
-
|
| 211 |
<div class="slider-group">
|
| 212 |
<div class="slider-label-container">
|
| 213 |
<label for="durationSlider" class="slider-label">مدت زمان ویدیو</label>
|
|
@@ -219,52 +188,33 @@
|
|
| 219 |
<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>
|
| 220 |
</div>
|
| 221 |
</div>
|
| 222 |
-
|
| 223 |
<div id="credit-info-section"></div>
|
| 224 |
<button id="upgrade-button">⭐️ ارتقا به نسخه کامل و نامحدود</button>
|
| 225 |
-
|
| 226 |
-
<button id="generateButton">
|
| 227 |
-
<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>
|
| 228 |
-
<span>ساخت ویدیو جادویی</span>
|
| 229 |
-
</button>
|
| 230 |
</div>
|
| 231 |
|
| 232 |
<div class="form-group">
|
| 233 |
-
<div class="form-label">
|
| 234 |
-
<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>
|
| 235 |
-
۳. نتیجه را ببینید
|
| 236 |
-
</div>
|
| 237 |
<div id="result-container">
|
| 238 |
<div id="statusSection">
|
| 239 |
<div id="statusMessages"></div>
|
| 240 |
<div id="aiLoader" style="display: none;">
|
| 241 |
<div class="generator-container">
|
| 242 |
-
<div class="noise-layer"></div>
|
| 243 |
-
<div class="sketch-layer"></div>
|
| 244 |
-
<div class="building-layer"></div>
|
| 245 |
-
<div class="pixel-grid"></div>
|
| 246 |
-
<div class="particles"></div>
|
| 247 |
<div class="text-overlay">در حال پردازش ویدیو...</div>
|
| 248 |
-
<div class="progress-bar">
|
|
|
|
|
|
|
| 249 |
</div>
|
| 250 |
</div>
|
| 251 |
</div>
|
| 252 |
-
|
| 253 |
<div id="outputSection">
|
| 254 |
<video id="outputVideo" controls preload="metadata" playsinline></video>
|
| 255 |
<p id="finalSeed"></p>
|
| 256 |
<div class="video-controls">
|
| 257 |
-
<button id="btnDownloadVideo" class="video-button primary" style="display: none;">
|
| 258 |
-
|
| 259 |
-
دانلود ویدیو
|
| 260 |
-
</button>
|
| 261 |
-
<button id="btnRestart" class="video-button">
|
| 262 |
-
<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>
|
| 263 |
-
ساخت ویدیو جدید
|
| 264 |
-
</button>
|
| 265 |
</div>
|
| 266 |
</div>
|
| 267 |
-
|
| 268 |
<div id="criticalErrorSection"></div>
|
| 269 |
</div>
|
| 270 |
</div>
|
|
@@ -275,128 +225,20 @@
|
|
| 275 |
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
|
| 276 |
|
| 277 |
(function() {
|
| 278 |
-
// === START: بخش مدیریت اعتبار و ارتباط با والد ===
|
| 279 |
-
let userSubscriptionStatus = 'free';
|
| 280 |
let userFingerprint = null;
|
| 281 |
let countdownInterval = null;
|
| 282 |
const PREMIUM_PAGE_ID = '1149636';
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
} catch (e) { components.push("canvas-error"); }
|
| 291 |
-
const fingerprintString = components.join('---');
|
| 292 |
-
let hash = 0;
|
| 293 |
-
for (let i = 0; i < fingerprintString.length; i++) {
|
| 294 |
-
hash = ((hash << 5) - hash) + fingerprintString.charCodeAt(i);
|
| 295 |
-
hash |= 0;
|
| 296 |
-
}
|
| 297 |
-
return 'fp_' + Math.abs(hash).toString(16);
|
| 298 |
-
}
|
| 299 |
-
|
| 300 |
-
function isUserPaid(userObject) {
|
| 301 |
-
return userObject && userObject.isLogin && userObject.accessible_pages &&
|
| 302 |
-
(userObject.accessible_pages.includes(PREMIUM_PAGE_ID) || userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID)));
|
| 303 |
-
}
|
| 304 |
-
|
| 305 |
-
function updateUIForSubscriptionStatus(status) {
|
| 306 |
-
userSubscriptionStatus = status;
|
| 307 |
-
const creditInfoDiv = document.getElementById('credit-info-section');
|
| 308 |
-
const upgradeBtn = document.getElementById('upgrade-button');
|
| 309 |
-
const genBtn = document.getElementById('generateButton');
|
| 310 |
-
const subscriptionBadge = document.getElementById('subscription-status-badge');
|
| 311 |
-
|
| 312 |
-
if (status === 'paid') {
|
| 313 |
-
subscriptionBadge.textContent = 'نسخه نامحدود';
|
| 314 |
-
subscriptionBadge.className = 'paid-badge';
|
| 315 |
-
creditInfoDiv.style.display = 'none';
|
| 316 |
-
upgradeBtn.style.display = 'none';
|
| 317 |
-
genBtn.disabled = false;
|
| 318 |
-
if(countdownInterval) clearInterval(countdownInterval);
|
| 319 |
-
} else {
|
| 320 |
-
subscriptionBadge.textContent = 'نسخه رایگان';
|
| 321 |
-
subscriptionBadge.className = 'free-badge';
|
| 322 |
-
checkFreeUserCredit();
|
| 323 |
-
}
|
| 324 |
-
subscriptionBadge.style.display = 'inline-block';
|
| 325 |
-
}
|
| 326 |
-
|
| 327 |
-
async function checkFreeUserCredit() {
|
| 328 |
-
if (!userFingerprint) return;
|
| 329 |
-
const creditInfoDiv = document.getElementById('credit-info-section');
|
| 330 |
-
const upgradeBtn = document.getElementById('upgrade-button');
|
| 331 |
-
const genBtn = document.getElementById('generateButton');
|
| 332 |
-
|
| 333 |
-
try {
|
| 334 |
-
const response = await fetch('/api/check-credit', {
|
| 335 |
-
method: 'POST',
|
| 336 |
-
headers: {'Content-Type': 'application/json'},
|
| 337 |
-
body: JSON.stringify({ fingerprint: userFingerprint })
|
| 338 |
-
});
|
| 339 |
-
const result = await response.json();
|
| 340 |
-
|
| 341 |
-
creditInfoDiv.style.display = 'block';
|
| 342 |
-
if (result.limit_reached) {
|
| 343 |
-
genBtn.disabled = true;
|
| 344 |
-
upgradeBtn.style.display = 'block';
|
| 345 |
-
startCountdown(result.reset_timestamp);
|
| 346 |
-
} else {
|
| 347 |
-
genBtn.disabled = false;
|
| 348 |
-
upgradeBtn.style.display = 'none';
|
| 349 |
-
if(countdownInterval) clearInterval(countdownInterval);
|
| 350 |
-
creditInfoDiv.textContent = `شما ${result.credits_remaining} اعتبار ساخت ویدیوی رایگان در این هفته دارید.`;
|
| 351 |
-
}
|
| 352 |
-
} catch (error) {
|
| 353 |
-
creditInfoDiv.textContent = "خطا در بررسی اعتبار.";
|
| 354 |
-
genBtn.disabled = true;
|
| 355 |
-
}
|
| 356 |
-
}
|
| 357 |
-
|
| 358 |
-
function startCountdown(resetTimestamp) {
|
| 359 |
-
if (countdownInterval) clearInterval(countdownInterval);
|
| 360 |
-
const creditInfoDiv = document.getElementById('credit-info-section');
|
| 361 |
-
const updateTimer = () => {
|
| 362 |
-
const timeLeft = Math.max(0, resetTimestamp - (Date.now() / 1000));
|
| 363 |
-
if (timeLeft === 0) {
|
| 364 |
-
clearInterval(countdownInterval);
|
| 365 |
-
creditInfoDiv.textContent = 'در حال بروزرسانی اعتبار...';
|
| 366 |
-
setTimeout(checkFreeUserCredit, 2000);
|
| 367 |
-
return;
|
| 368 |
-
}
|
| 369 |
-
const d = Math.floor(timeLeft / 86400);
|
| 370 |
-
const h = Math.floor((timeLeft % 86400) / 3600);
|
| 371 |
-
const m = Math.floor((timeLeft % 3600) / 60);
|
| 372 |
-
creditInfoDiv.textContent = `اعتبار شما تمام شده. زمان باقیمانده: ${d} روز و ${h} ساعت و ${m} دقیقه`;
|
| 373 |
-
};
|
| 374 |
-
updateTimer();
|
| 375 |
-
countdownInterval = setInterval(updateTimer, 60000);
|
| 376 |
-
}
|
| 377 |
-
|
| 378 |
-
window.addEventListener('message', (event) => {
|
| 379 |
-
if (event.data && event.data.type === 'USER_DATA_RESPONSE') {
|
| 380 |
-
if (event.data.error || !event.data.payload) {
|
| 381 |
-
updateUIForSubscriptionStatus('free');
|
| 382 |
-
return;
|
| 383 |
-
}
|
| 384 |
-
try {
|
| 385 |
-
const userObject = JSON.parse(event.data.payload);
|
| 386 |
-
const status = isUserPaid(userObject) ? 'paid' : 'free';
|
| 387 |
-
updateUIForSubscriptionStatus(status);
|
| 388 |
-
} catch (e) {
|
| 389 |
-
updateUIForSubscriptionStatus('free');
|
| 390 |
-
}
|
| 391 |
-
}
|
| 392 |
-
});
|
| 393 |
-
|
| 394 |
-
document.getElementById('upgrade-button').addEventListener('click', () => {
|
| 395 |
-
parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM' }, '*');
|
| 396 |
-
});
|
| 397 |
// === END: بخش مدیریت اعتبار ===
|
| 398 |
|
| 399 |
-
|
| 400 |
// DOM Elements
|
| 401 |
const resultContainer = document.getElementById('result-container');
|
| 402 |
const imageFileInput = document.getElementById('imageFile');
|
|
@@ -410,7 +252,7 @@
|
|
| 410 |
const statusSection = document.getElementById('statusSection');
|
| 411 |
const statusMessagesDiv = document.getElementById('statusMessages');
|
| 412 |
const aiLoader = document.getElementById('aiLoader');
|
| 413 |
-
const
|
| 414 |
const loaderTextOverlay = document.querySelector('#aiLoader .text-overlay');
|
| 415 |
const outputSection = document.getElementById('outputSection');
|
| 416 |
const btnRestart = document.getElementById('btnRestart');
|
|
@@ -418,48 +260,24 @@
|
|
| 418 |
const durationSlider = document.getElementById('durationSlider');
|
| 419 |
const durationValueDisplay = document.getElementById('durationValue');
|
| 420 |
|
| 421 |
-
// ====== تنظیمات مدلها ======
|
| 422 |
const VIDEO_SPACE_NAME = "mrbui1990/wan2-2-fp8da-aoti-faster";
|
| 423 |
-
|
| 424 |
-
// مترجم: بهجای SSE از Gradio Client استفاده میکنیم (پایدار روی موبایل)
|
| 425 |
const TRANSLATOR_SPACE_NAME = "hamed744/translate-tts-aloha";
|
| 426 |
-
const FN_INDEX_TRANSLATE_SPEAK = 1;
|
| 427 |
-
const DEFAULT_TTS_VOICE_TRANSLATOR = "انگلیسی (آمریکا) - جنی (زن)";
|
| 428 |
-
|
| 429 |
const DEFAULT_NEGATIVE_PROMPT = "色调艳丽, 过曝, 静态, 细节模糊不清, 字幕, 风格, 作品, 画作, 画面, 静止, 整体发灰, 最差质量, 低质量, JPEG压缩残留, 丑陋的, 残缺的, 多余的手指, 画得不好的手部, 画得不好的脸部, 畸形的, 毁容的, 形态畸形的肢体, 手指融合, 静止不动的画面, 杂乱的背景, 三条腿, 背景人很多, 倒着走";
|
| 430 |
const DEFAULT_PROMPT_TEXT = "";
|
| 431 |
const TRANSPARENT_PIXEL_SRC = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
| 432 |
const DEFAULT_VIDEO_DURATION = 4.3;
|
| 433 |
-
|
| 434 |
-
// --- TIMEOUTS ---
|
| 435 |
-
const GENERATION_TIMEOUT = 150000; // 2.5 دقیقه برای ساخت ویدیو
|
| 436 |
-
const TRANSLATION_TIMEOUT = 20000; // 20 ثانیه برای ترجمه
|
| 437 |
|
| 438 |
let lastAttemptedVideoPayload = null;
|
| 439 |
|
| 440 |
-
function updateSliderStyle(slider) {
|
| 441 |
-
const percentage = ((slider.value - slider.min) / (slider.max - slider.min)) * 100;
|
| 442 |
-
const colorStops = `var(--accent-primary), var(--accent-secondary)`;
|
| 443 |
-
slider.style.background = `linear-gradient(to left, ${colorStops} ${percentage}%, var(--input-bg) ${percentage}%)`;
|
| 444 |
-
}
|
| 445 |
-
|
| 446 |
-
function generateRandomHash(length = 11) {
|
| 447 |
-
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15).substring(0, length - 7);
|
| 448 |
-
}
|
| 449 |
|
| 450 |
-
async function retryLastAttempt() {
|
| 451 |
-
if (!lastAttemptedVideoPayload || !lastAttemptedVideoPayload.imageSourceFile || !lastAttemptedVideoPayload.persianPrompt) {
|
| 452 |
-
showCriticalError("اطلاعات کافی برای تلاش مجدد وجود ندارد. لطفاً از نو شروع کنید.");
|
| 453 |
-
return;
|
| 454 |
-
}
|
| 455 |
-
generateButton.click();
|
| 456 |
-
}
|
| 457 |
|
| 458 |
-
function showCriticalError(message
|
| 459 |
const lowerCaseMessage = (message || "").toLowerCase();
|
| 460 |
if (lowerCaseMessage.includes("gpu") || lowerCaseMessage.includes("quota") || lowerCaseMessage.includes("exceeded") || lowerCaseMessage.includes("limit") || lowerCaseMessage.includes("capacity") || lowerCaseMessage.includes("queue full")) {
|
| 461 |
-
|
| 462 |
-
criticalErrorSection.innerHTML = detailedGpuErrorHtml;
|
| 463 |
document.getElementById('gpuErrorGoBackBtn').addEventListener('click', hideCriticalError);
|
| 464 |
document.getElementById('gpuErrorRetryBtn').addEventListener('click', retryLastAttempt);
|
| 465 |
} else {
|
|
@@ -475,161 +293,54 @@
|
|
| 475 |
generateButton.disabled = false;
|
| 476 |
}
|
| 477 |
|
| 478 |
-
function hideCriticalError() {
|
| 479 |
-
criticalErrorSection.classList.remove('active');
|
| 480 |
-
criticalErrorSection.innerHTML = '';
|
| 481 |
-
}
|
| 482 |
-
|
| 483 |
function setAiLoaderText(text) { if (loaderTextOverlay) loaderTextOverlay.textContent = text; }
|
| 484 |
-
|
| 485 |
-
function
|
| 486 |
-
|
| 487 |
-
resultContainer.classList.add('active');
|
| 488 |
-
statusSection.classList.add('active');
|
| 489 |
-
const messageDiv = document.createElement('div');
|
| 490 |
-
messageDiv.className = `status-message ${type}`;
|
| 491 |
-
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>';
|
| 492 |
-
messageDiv.innerHTML = `${iconSvg}<span class="status-text">${message}</span>`;
|
| 493 |
-
statusMessagesDiv.insertBefore(messageDiv, statusMessagesDiv.firstChild);
|
| 494 |
-
}
|
| 495 |
-
|
| 496 |
-
function clearStatusMessages() {
|
| 497 |
-
statusMessagesDiv.innerHTML = '';
|
| 498 |
-
if(loaderProgressBar) {
|
| 499 |
-
loaderProgressBar.classList.remove('animate-progress');
|
| 500 |
-
loaderProgressBar.style.width = '0%';
|
| 501 |
-
}
|
| 502 |
-
aiLoader.style.display = 'none';
|
| 503 |
-
if (outputVideo.src && outputVideo.src.startsWith('blob:')) {
|
| 504 |
-
URL.revokeObjectURL(outputVideo.src);
|
| 505 |
-
}
|
| 506 |
-
outputVideo.src = '';
|
| 507 |
-
finalSeedElement.textContent = '';
|
| 508 |
-
btnDownloadVideo.style.display = 'none';
|
| 509 |
-
}
|
| 510 |
-
|
| 511 |
-
function initializeForm() {
|
| 512 |
-
hideCriticalError();
|
| 513 |
-
resultContainer.classList.remove('active');
|
| 514 |
-
statusSection.classList.remove('active');
|
| 515 |
-
outputSection.classList.remove('active');
|
| 516 |
-
clearStatusMessages();
|
| 517 |
-
lastAttemptedVideoPayload = null;
|
| 518 |
-
imageFileInput.value = '';
|
| 519 |
-
imagePreview.src = TRANSPARENT_PIXEL_SRC;
|
| 520 |
-
imageDropZone.classList.remove('has-image');
|
| 521 |
-
promptInput.value = DEFAULT_PROMPT_TEXT;
|
| 522 |
-
generateButton.disabled = false;
|
| 523 |
-
durationSlider.value = DEFAULT_VIDEO_DURATION;
|
| 524 |
-
durationValueDisplay.textContent = `${parseFloat(DEFAULT_VIDEO_DURATION).toFixed(1)} ثانیه`;
|
| 525 |
-
updateSliderStyle(durationSlider);
|
| 526 |
-
updateUIForSubscriptionStatus(userSubscriptionStatus);
|
| 527 |
-
}
|
| 528 |
|
| 529 |
-
|
| 530 |
-
async function translatePersianToEnglish(persianText, { timeoutMs = TRANSLATION_TIMEOUT } = {}) {
|
| 531 |
-
if (!persianText?.trim()) throw new Error("متن ترجمه خالی است.");
|
| 532 |
-
|
| 533 |
-
if (aiLoader.style.display !== 'none') setAiLoaderText("در حال ترجمه دستور شما...");
|
| 534 |
-
const translator = await Client.connect(TRANSLATOR_SPACE_NAME);
|
| 535 |
-
|
| 536 |
-
const predictPromise = translator.predict(FN_INDEX_TRANSLATE_SPEAK, [
|
| 537 |
-
persianText,
|
| 538 |
-
DEFAULT_TTS_VOICE_TRANSLATOR,
|
| 539 |
-
0, 0, 0
|
| 540 |
-
]);
|
| 541 |
-
|
| 542 |
-
const timeoutPromise = new Promise((_, reject) =>
|
| 543 |
-
setTimeout(() => reject(new Error("مهلت ترجمه به پایان رسید.")), timeoutMs)
|
| 544 |
-
);
|
| 545 |
-
|
| 546 |
-
const resp = await Promise.race([predictPromise, timeoutPromise]);
|
| 547 |
-
|
| 548 |
-
const english = resp?.data?.[0];
|
| 549 |
-
if (!english) throw new Error("ترجمه نامعتبر بود.");
|
| 550 |
-
return english;
|
| 551 |
-
}
|
| 552 |
|
| 553 |
async function handleSuccessfulVideoGeneration(result) {
|
| 554 |
aiLoader.style.display = 'none';
|
| 555 |
-
if (!result?.data?.[0]?.video?.url) {
|
| 556 |
-
showCriticalError('پاسخ دریافتی از سرور ویدیو معتبر نیست.');
|
| 557 |
-
return;
|
| 558 |
-
}
|
| 559 |
-
|
| 560 |
if (userSubscriptionStatus === 'free') {
|
| 561 |
-
try {
|
| 562 |
-
addStatusMessage('ویدیو ساخته شد، در حال ثبت اعتبار...', 'info');
|
| 563 |
-
const creditResponse = await fetch('/api/use-credit', {
|
| 564 |
-
method: 'POST',
|
| 565 |
-
headers: {'Content-Type': 'application/json'},
|
| 566 |
-
body: JSON.stringify({ fingerprint: userFingerprint })
|
| 567 |
-
});
|
| 568 |
-
if (!creditResponse.ok) {
|
| 569 |
-
console.warn("Credit deduction failed AFTER video generation.");
|
| 570 |
-
}
|
| 571 |
-
checkFreeUserCredit();
|
| 572 |
-
} catch (creditError) {
|
| 573 |
-
console.error("Critical error deducting credit:", creditError);
|
| 574 |
-
}
|
| 575 |
}
|
| 576 |
-
|
| 577 |
addStatusMessage('ویدیو آماده شد! 🎉', 'success');
|
| 578 |
const videoUrl = result.data[0].video.url;
|
| 579 |
const usedSeed = result.data[1];
|
| 580 |
-
setTimeout(() => {
|
| 581 |
-
statusSection.classList.remove('active');
|
| 582 |
-
outputSection.classList.add('active');
|
| 583 |
-
outputVideo.src = videoUrl;
|
| 584 |
-
finalSeedElement.textContent = `Seed: ${usedSeed}`;
|
| 585 |
-
prepareCustomDownloadLink(videoUrl);
|
| 586 |
-
}, 300);
|
| 587 |
generateButton.disabled = false;
|
| 588 |
}
|
| 589 |
|
| 590 |
-
function prepareCustomDownloadLink(videoUrl) {
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 595 |
}
|
| 596 |
-
|
| 597 |
-
document.addEventListener('DOMContentLoaded', async () => {
|
| 598 |
-
initializeForm();
|
| 599 |
-
userFingerprint = await getBrowserFingerprint();
|
| 600 |
-
parent.postMessage({ type: 'REQUEST_USER_DATA' }, '*');
|
| 601 |
-
updateSliderStyle(durationSlider);
|
| 602 |
-
});
|
| 603 |
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
durationValueDisplay.textContent = `${value} ثانیه`;
|
| 607 |
-
updateSliderStyle(durationSlider);
|
| 608 |
-
});
|
| 609 |
-
|
| 610 |
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(e => imageDropZone.addEventListener(e, ev => ev.preventDefault()));
|
| 611 |
['dragenter', 'dragover'].forEach(e => imageDropZone.addEventListener(e, () => { if (!imageDropZone.classList.contains('has-image')) imageDropZone.classList.add('drag-over'); }));
|
| 612 |
['dragleave', 'drop'].forEach(e => imageDropZone.addEventListener(e, () => imageDropZone.classList.remove('drag-over')));
|
| 613 |
imageDropZone.addEventListener('drop', e => { if (e.dataTransfer.files.length > 0) { imageFileInput.files = e.dataTransfer.files; imageFileInput.dispatchEvent(new Event('change')); } });
|
| 614 |
btnRestart.addEventListener('click', initializeForm);
|
| 615 |
-
|
| 616 |
-
imageFileInput.addEventListener('change', function(event) {
|
| 617 |
-
const file = event.target.files[0];
|
| 618 |
-
hideCriticalError();
|
| 619 |
-
if (file) {
|
| 620 |
-
if (!file.type.startsWith('image/')) {
|
| 621 |
-
addStatusMessage('فرمت نامعتبر', 'error');
|
| 622 |
-
initializeForm();
|
| 623 |
-
} else {
|
| 624 |
-
const reader = new FileReader();
|
| 625 |
-
reader.onload = (e) => { imagePreview.src = e.target.result; imageDropZone.classList.add('has-image'); };
|
| 626 |
-
reader.readAsDataURL(file);
|
| 627 |
-
}
|
| 628 |
-
} else {
|
| 629 |
-
imagePreview.src = TRANSPARENT_PIXEL_SRC;
|
| 630 |
-
imageDropZone.classList.remove('has-image');
|
| 631 |
-
}
|
| 632 |
-
});
|
| 633 |
|
| 634 |
const proceedWithVideoGeneration = async () => {
|
| 635 |
const persianPrompt = promptInput.value.trim();
|
|
@@ -639,49 +350,37 @@
|
|
| 639 |
if (!persianPrompt) { addStatusMessage('لطفاً توضیحات را وارد کنید.', 'error'); return; }
|
| 640 |
if (!imageFile) { addStatusMessage('لطفاً تصویر انتخاب کنید.', 'error'); return; }
|
| 641 |
|
| 642 |
-
clearStatusMessages();
|
| 643 |
-
hideCriticalError();
|
| 644 |
-
generateButton.disabled = true;
|
| 645 |
-
resultContainer.classList.add('active');
|
| 646 |
-
statusSection.classList.add('active');
|
| 647 |
-
aiLoader.style.display = 'flex';
|
| 648 |
-
loaderProgressBar.classList.add('animate-progress');
|
| 649 |
-
outputSection.classList.remove('active');
|
| 650 |
-
addStatusMessage('در حال آماده سازی...', 'info');
|
| 651 |
-
|
| 652 |
lastAttemptedVideoPayload = { persianPrompt, imageSourceFile: imageFile, duration };
|
| 653 |
|
| 654 |
-
// ---- مرحله ترجمه با فالبک ----
|
| 655 |
let promptForVideo = persianPrompt;
|
| 656 |
try {
|
| 657 |
-
const englishPrompt = await translatePersianToEnglish(persianPrompt
|
| 658 |
addStatusMessage(`ترجمه: ${englishPrompt}`, 'info');
|
| 659 |
promptForVideo = englishPrompt;
|
| 660 |
} catch (e) {
|
| 661 |
-
addStatusMessage('ترجمه به انگلیسی ناموفق
|
| 662 |
}
|
| 663 |
|
| 664 |
try {
|
| 665 |
-
setAiLoaderText("در حال
|
| 666 |
const client = await Client.connect(VIDEO_SPACE_NAME);
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
const videoGenerationPromise = client.predict(0, [
|
| 673 |
-
imageFile, // 0: input_image
|
| 674 |
-
promptForVideo, // 1: prompt
|
| 675 |
-
6, // 2: steps
|
| 676 |
-
DEFAULT_NEGATIVE_PROMPT, // 3: negative_prompt
|
| 677 |
-
duration, // 4: duration_seconds
|
| 678 |
-
1, // 5: guidance_scale
|
| 679 |
-
1, // 6: guidance_scale_2
|
| 680 |
-
0, // 7: seed (0 = random)
|
| 681 |
-
true // 8: randomize_seed
|
| 682 |
]);
|
| 683 |
|
| 684 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 685 |
await handleSuccessfulVideoGeneration(result);
|
| 686 |
|
| 687 |
} catch (errorCaught) {
|
|
@@ -693,50 +392,21 @@
|
|
| 693 |
|
| 694 |
generateButton.addEventListener('click', async () => {
|
| 695 |
if (generateButton.disabled) return;
|
| 696 |
-
|
| 697 |
-
if (userSubscriptionStatus === 'paid') {
|
| 698 |
-
await proceedWithVideoGeneration();
|
| 699 |
-
} else {
|
| 700 |
try {
|
| 701 |
-
const response = await fetch('/api/check-credit', {
|
| 702 |
-
method: 'POST',
|
| 703 |
-
headers: {'Content-Type': 'application/json'},
|
| 704 |
-
body: JSON.stringify({ fingerprint: userFingerprint })
|
| 705 |
-
});
|
| 706 |
const result = await response.json();
|
| 707 |
-
if(result.limit_reached) {
|
| 708 |
-
updateUIForSubscriptionStatus('free');
|
| 709 |
-
return;
|
| 710 |
-
}
|
| 711 |
await proceedWithVideoGeneration();
|
| 712 |
-
|
| 713 |
-
} catch (err) {
|
| 714 |
-
showCriticalError(`خطا در سیستم اعتبار: ${err.message}`);
|
| 715 |
-
}
|
| 716 |
}
|
| 717 |
});
|
| 718 |
})();
|
| 719 |
</script>
|
| 720 |
|
| 721 |
<script>
|
| 722 |
-
// --- Header Animation Script ---
|
| 723 |
-
document.addEventListener('DOMContentLoaded', () => {
|
| 724 |
-
const canvas = document.getElementById('neural-network-canvas');
|
| 725 |
-
if (!canvas) return;
|
| 726 |
-
const header = canvas.parentElement;
|
| 727 |
-
const ctx = canvas.getContext('2d');
|
| 728 |
-
let particles = [];
|
| 729 |
-
const particleCount = 20; const maxDistance = 100;
|
| 730 |
-
const computedStyles = getComputedStyle(document.documentElement);
|
| 731 |
-
const particleColor = computedStyles.getPropertyValue('--accent-primary').trim();
|
| 732 |
-
const lineColor = computedStyles.getPropertyValue('--text-tertiary').trim();
|
| 733 |
-
function resizeCanvas() { canvas.width = header.clientWidth; canvas.height = header.clientHeight; init(); }
|
| 734 |
-
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(); } }
|
| 735 |
-
function init() { particles = []; for (let i = 0; i < particleCount; i++) { particles.push(new Particle()); } }
|
| 736 |
-
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; }
|
| 737 |
-
function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); connectParticles(); requestAnimationFrame(animate); }
|
| 738 |
-
window.addEventListener('resize', resizeCanvas); resizeCanvas(); animate();
|
| 739 |
-
});
|
| 740 |
</script>
|
| 741 |
</body>
|
| 742 |
</html>
|
|
|
|
| 51 |
@keyframes ring-rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
| 52 |
@keyframes background-pan { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
|
| 53 |
@keyframes igadlmAlphaV2fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
|
|
|
|
| 54 |
@keyframes badge-fade-in { from { opacity: 0; transform: translateY(-10px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
| 55 |
|
| 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; }
|
|
|
|
| 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 |
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); }
|
|
|
|
| 110 |
.status-message { display: flex; align-items: center; gap: 0.75rem; padding: 0.6rem 1rem; margin-bottom: 0.5rem; border-radius: var(--radius-input); font-size: 0.9rem; background: var(--panel-bg); border: 1px solid var(--panel-border); color: var(--text-secondary); }
|
| 111 |
.status-message.success { border-left: 4px solid var(--success-color); color: var(--success-color); }
|
| 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 |
.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;}
|
| 117 |
@keyframes glow-text { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } }
|
| 118 |
+
.progress-bar-container { position: absolute; bottom: 20px; left: 10%; width: 80%; height: 8px; background-color: rgba(255, 255, 255, 0.2); border-radius: 4px; }
|
| 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 |
.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; }
|
| 140 |
#subscription-status-badge.free-badge { background: linear-gradient(45deg, #6c757d, #495057); color: white; box-shadow: 0 4px 10px rgba(108, 117, 125, 0.3); }
|
| 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 |
<div class="header-content">
|
| 152 |
<div class="ai-orb-container">
|
| 153 |
<div class="orb-background"></div>
|
| 154 |
+
<div class="orb-core"><svg class="play-icon" viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path d="M8 5v14l11-7z"></path></svg></div>
|
| 155 |
+
<div class="orb-ring one"></div><div class="orb-ring two"></div><div class="orb-ring three"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
</div>
|
| 157 |
<h1>استودیو ویدیو هوش مصنوعی</h1>
|
| 158 |
<p class="subtitle">تصاویر خود را با قدرت هوش مصنوعی به ویدیو سینمایی تبدیل کنید</p>
|
|
|
|
| 163 |
|
| 164 |
<main>
|
| 165 |
<div class="form-group">
|
| 166 |
+
<div class="form-label"><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>۱. تصویر خود را انتخاب کنید</div>
|
|
|
|
|
|
|
|
|
|
| 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 |
</div>
|
| 176 |
|
| 177 |
<div class="form-group">
|
| 178 |
+
<label for="prompt" class="form-label"><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>۲. دستور ساخت ویدیو را بنویسید</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 |
<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 |
+
<button id="generateButton"><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><span>ساخت ویدیو جادویی</span></button>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
</div>
|
| 195 |
|
| 196 |
<div class="form-group">
|
| 197 |
+
<div class="form-label"><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>۳. نتیجه را ببینید</div>
|
|
|
|
|
|
|
|
|
|
| 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-container">
|
| 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;"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>دانلود ویدیو</button>
|
| 215 |
+
<button id="btnRestart" class="video-button"><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>ساخت ویدیو جدید</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
</div>
|
| 217 |
</div>
|
|
|
|
| 218 |
<div id="criticalErrorSection"></div>
|
| 219 |
</div>
|
| 220 |
</div>
|
|
|
|
| 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 |
+
async function getBrowserFingerprint() { const components = [navigator.userAgent, navigator.language, screen.width + 'x' + screen.height, new Date().getTimezoneOffset()]; try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); ctx.fillText("a1b2c3d4e5f6g7h8i9j0", 2, 15); components.push(canvas.toDataURL()); } catch (e) { components.push("canvas-error"); } const fingerprintString = components.join('---'); let hash = 0; for (let i = 0; i < fingerprintString.length; i++) { hash = ((hash << 5) - hash) + fingerprintString.charCodeAt(i); hash |= 0; } return 'fp_' + Math.abs(hash).toString(16); }
|
| 234 |
+
function isUserPaid(userObject) { return userObject && userObject.isLogin && userObject.accessible_pages && (userObject.accessible_pages.includes(PREMIUM_PAGE_ID) || userObject.accessible_pages.includes(parseInt(PREMIUM_PAGE_ID))); }
|
| 235 |
+
function updateUIForSubscriptionStatus(status) { userSubscriptionStatus = status; const creditInfoDiv = document.getElementById('credit-info-section'); const upgradeBtn = document.getElementById('upgrade-button'); const genBtn = document.getElementById('generateButton'); const subscriptionBadge = document.getElementById('subscription-status-badge'); if (status === 'paid') { subscriptionBadge.textContent = 'نسخه نامحدود'; subscriptionBadge.className = 'paid-badge'; creditInfoDiv.style.display = 'none'; upgradeBtn.style.display = 'none'; genBtn.disabled = false; if(countdownInterval) clearInterval(countdownInterval); } else { subscriptionBadge.textContent = 'نسخه رایگان'; subscriptionBadge.className = 'free-badge'; checkFreeUserCredit(); } subscriptionBadge.style.display = 'inline-block'; }
|
| 236 |
+
async function checkFreeUserCredit() { if (!userFingerprint) return; const creditInfoDiv = document.getElementById('credit-info-section'); const upgradeBtn = document.getElementById('upgrade-button'); const genBtn = document.getElementById('generateButton'); try { const response = await fetch('/api/check-credit', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ fingerprint: userFingerprint }) }); const result = await response.json(); creditInfoDiv.style.display = 'block'; if (result.limit_reached) { genBtn.disabled = true; upgradeBtn.style.display = 'block'; startCountdown(result.reset_timestamp); } else { genBtn.disabled = false; upgradeBtn.style.display = 'none'; if(countdownInterval) clearInterval(countdownInterval); creditInfoDiv.textContent = `شما ${result.credits_remaining} اعتبار ساخت ویدیوی رایگان در این هفته دارید.`; } } catch (error) { creditInfoDiv.textContent = "خطا در بررسی اعتبار."; genBtn.disabled = true; } }
|
| 237 |
+
function startCountdown(resetTimestamp) { if (countdownInterval) clearInterval(countdownInterval); const creditInfoDiv = document.getElementById('credit-info-section'); const updateTimer = () => { const timeLeft = Math.max(0, resetTimestamp - (Date.now() / 1000)); if (timeLeft === 0) { clearInterval(countdownInterval); creditInfoDiv.textContent = 'در حال بروزرسانی اعتبار...'; setTimeout(checkFreeUserCredit, 2000); return; } const d = Math.floor(timeLeft / 86400); const h = Math.floor((timeLeft % 86400) / 3600); const m = Math.floor((timeLeft % 3600) / 60); creditInfoDiv.textContent = `اعتبار شما تمام شده. زمان باقیمانده: ${d} روز و ${h} ساعت و ${m} دقیقه`; }; updateTimer(); countdownInterval = setInterval(updateTimer, 60000); }
|
| 238 |
+
window.addEventListener('message', (event) => { if (event.data && event.data.type === 'USER_DATA_RESPONSE') { if (event.data.error || !event.data.payload) { updateUIForSubscriptionStatus('free'); return; } try { const userObject = JSON.parse(event.data.payload); const status = isUserPaid(userObject) ? 'paid' : 'free'; updateUIForSubscriptionStatus(status); } catch (e) { updateUIForSubscriptionStatus('free'); } } });
|
| 239 |
+
document.getElementById('upgrade-button').addEventListener('click', () => { parent.postMessage({ type: 'NAVIGATE_TO_PREMIUM' }, '*'); });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
// === END: بخش مدیریت اعتبار ===
|
| 241 |
|
|
|
|
| 242 |
// DOM Elements
|
| 243 |
const resultContainer = document.getElementById('result-container');
|
| 244 |
const imageFileInput = document.getElementById('imageFile');
|
|
|
|
| 252 |
const statusSection = document.getElementById('statusSection');
|
| 253 |
const statusMessagesDiv = document.getElementById('statusMessages');
|
| 254 |
const aiLoader = document.getElementById('aiLoader');
|
| 255 |
+
const loaderProgressBarFill = document.querySelector('.progress-bar-fill');
|
| 256 |
const loaderTextOverlay = document.querySelector('#aiLoader .text-overlay');
|
| 257 |
const outputSection = document.getElementById('outputSection');
|
| 258 |
const btnRestart = document.getElementById('btnRestart');
|
|
|
|
| 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 |
+
function updateSliderStyle(slider) { const percentage = ((slider.value - slider.min) / (slider.max - slider.min)) * 100; const colorStops = `var(--accent-primary), var(--accent-secondary)`; slider.style.background = `linear-gradient(to left, ${colorStops} ${percentage}%, var(--input-bg) ${percentage}%)`; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
+
async function retryLastAttempt() { if (!lastAttemptedVideoPayload || !lastAttemptedVideoPayload.imageSourceFile || !lastAttemptedVideoPayload.persianPrompt) { showCriticalError("اطلاعات کافی برای تلاش مجدد وجود ندارد. لطفاً از نو شروع کنید."); return; } generateButton.click(); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
+
function showCriticalError(message) {
|
| 278 |
const lowerCaseMessage = (message || "").toLowerCase();
|
| 279 |
if (lowerCaseMessage.includes("gpu") || lowerCaseMessage.includes("quota") || lowerCaseMessage.includes("exceeded") || lowerCaseMessage.includes("limit") || lowerCaseMessage.includes("capacity") || lowerCaseMessage.includes("queue full")) {
|
| 280 |
+
criticalErrorSection.innerHTML = `<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>`;
|
|
|
|
| 281 |
document.getElementById('gpuErrorGoBackBtn').addEventListener('click', hideCriticalError);
|
| 282 |
document.getElementById('gpuErrorRetryBtn').addEventListener('click', retryLastAttempt);
|
| 283 |
} else {
|
|
|
|
| 293 |
generateButton.disabled = false;
|
| 294 |
}
|
| 295 |
|
| 296 |
+
function hideCriticalError() { criticalErrorSection.classList.remove('active'); criticalErrorSection.innerHTML = ''; }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
function setAiLoaderText(text) { if (loaderTextOverlay) loaderTextOverlay.textContent = text; }
|
| 298 |
+
function addStatusMessage(message, type = 'info') { if (criticalErrorSection.classList.contains('active') && type === 'error') return; resultContainer.classList.add('active'); statusSection.classList.add('active'); const messageDiv = document.createElement('div'); messageDiv.className = `status-message ${type}`; 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>'; messageDiv.innerHTML = `${iconSvg}<span class="status-text">${message}</span>`; statusMessagesDiv.insertBefore(messageDiv, statusMessagesDiv.firstChild); }
|
| 299 |
+
function clearStatusMessages() { statusMessagesDiv.innerHTML = ''; if(loaderProgressBarFill) loaderProgressBarFill.style.width = '0%'; aiLoader.style.display = 'none'; if (outputVideo.src && outputVideo.src.startsWith('blob:')) { URL.revokeObjectURL(outputVideo.src); } outputVideo.src = ''; finalSeedElement.textContent = ''; btnDownloadVideo.style.display = 'none'; }
|
| 300 |
+
function initializeForm() { hideCriticalError(); resultContainer.classList.remove('active'); statusSection.classList.remove('active'); outputSection.classList.remove('active'); clearStatusMessages(); lastAttemptedVideoPayload = null; imageFileInput.value = ''; imagePreview.src = TRANSPARENT_PIXEL_SRC; imageDropZone.classList.remove('has-image'); promptInput.value = DEFAULT_PROMPT_TEXT; generateButton.disabled = false; durationSlider.value = DEFAULT_VIDEO_DURATION; durationValueDisplay.textContent = `${parseFloat(DEFAULT_VIDEO_DURATION).toFixed(1)} ثانیه`; updateSliderStyle(durationSlider); updateUIForSubscriptionStatus(userSubscriptionStatus); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
+
async function translatePersianToEnglish(persianText) { if (!persianText?.trim()) throw new Error("متن ترجمه خالی است."); setAiLoaderText("در حال ترجمه دستور شما..."); const translator = await Client.connect(TRANSLATOR_SPACE_NAME); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error("مهلت ترجمه به پایان رسید.")), TRANSLATION_TIMEOUT)); const predictPromise = translator.predict(1, [persianText, "انگلیسی (آمریکا) - جنی (زن)", 0, 0, 0]); const resp = await Promise.race([predictPromise, timeoutPromise]); const english = resp?.data?.[0]; if (!english) throw new Error("ترجمه نامعتبر بود."); return english; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
|
| 304 |
async function handleSuccessfulVideoGeneration(result) {
|
| 305 |
aiLoader.style.display = 'none';
|
| 306 |
+
if (!result?.data?.[0]?.video?.url) { showCriticalError('پاسخ دریافتی از سرور ویدیو معتبر نیست.'); return; }
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
if (userSubscriptionStatus === 'free') {
|
| 308 |
+
try { addStatusMessage('ویدیو ساخته شد، در حال ثبت اعتبار...', 'info'); const creditResponse = await fetch('/api/use-credit', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ fingerprint: userFingerprint }) }); if (!creditResponse.ok) console.warn("Credit deduction failed AFTER video generation."); checkFreeUserCredit(); } catch (creditError) { console.error("Critical error deducting credit:", creditError); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 309 |
}
|
|
|
|
| 310 |
addStatusMessage('ویدیو آماده شد! 🎉', 'success');
|
| 311 |
const videoUrl = result.data[0].video.url;
|
| 312 |
const usedSeed = result.data[1];
|
| 313 |
+
setTimeout(() => { statusSection.classList.remove('active'); outputSection.classList.add('active'); outputVideo.src = videoUrl; finalSeedElement.textContent = `Seed: ${usedSeed}`; prepareCustomDownloadLink(videoUrl); }, 300);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 314 |
generateButton.disabled = false;
|
| 315 |
}
|
| 316 |
|
| 317 |
+
function prepareCustomDownloadLink(videoUrl) { btnDownloadVideo.style.display = 'flex'; btnDownloadVideo.onclick = () => { parent.postMessage({ type: 'DOWNLOAD_REQUEST', url: videoUrl }, '*'); }; }
|
| 318 |
+
|
| 319 |
+
// --- تابع جدید برای مدیریت پیشرفت ---
|
| 320 |
+
function handleProgressUpdate(progressData) {
|
| 321 |
+
if (!progressData || progressData.length === 0) return;
|
| 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 |
+
document.addEventListener('DOMContentLoaded', async () => { initializeForm(); userFingerprint = await getBrowserFingerprint(); parent.postMessage({ type: 'REQUEST_USER_DATA' }, '*'); updateSliderStyle(durationSlider); });
|
| 337 |
+
durationSlider.addEventListener('input', () => { const value = parseFloat(durationSlider.value).toFixed(1); durationValueDisplay.textContent = `${value} ثانیه`; updateSliderStyle(durationSlider); });
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
imageFileInput.addEventListener('change', function(event) { const file = event.target.files[0]; hideCriticalError(); if (file) { if (!file.type.startsWith('image/')) { addStatusMessage('فرمت نامعتبر', 'error'); initializeForm(); } else { const reader = new FileReader(); reader.onload = (e) => { imagePreview.src = e.target.result; imageDropZone.classList.add('has-image'); }; reader.readAsDataURL(file); } } else { imagePreview.src = TRANSPARENT_PIXEL_SRC; imageDropZone.classList.remove('has-image'); } });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
|
| 345 |
const proceedWithVideoGeneration = async () => {
|
| 346 |
const persianPrompt = promptInput.value.trim();
|
|
|
|
| 350 |
if (!persianPrompt) { addStatusMessage('لطفاً توضیحات را وارد کنید.', 'error'); return; }
|
| 351 |
if (!imageFile) { addStatusMessage('لطفاً تصویر انتخاب کنید.', 'error'); return; }
|
| 352 |
|
| 353 |
+
clearStatusMessages(); hideCriticalError(); generateButton.disabled = true; resultContainer.classList.add('active'); statusSection.classList.add('active'); aiLoader.style.display = 'flex'; outputSection.classList.remove('active'); addStatusMessage('در حال آماده سازی...', 'info');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
lastAttemptedVideoPayload = { persianPrompt, imageSourceFile: imageFile, duration };
|
| 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('ترجمه به انگلیسی ناموفق بود؛ با همان متن فارسی ادامه میدهیم.', 'error');
|
| 363 |
}
|
| 364 |
|
| 365 |
try {
|
| 366 |
+
setAiLoaderText("در حال اتصال به سرور ویدیو...");
|
| 367 |
const client = await Client.connect(VIDEO_SPACE_NAME);
|
| 368 |
+
addStatusMessage('اتصال به سرور ویدیو برقرار شد.', 'info');
|
| 369 |
+
|
| 370 |
+
const job = client.predict(0, [
|
| 371 |
+
imageFile, promptForVideo, 6, DEFAULT_NEGATIVE_PROMPT, duration, 1, 1, 0, true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
]);
|
| 373 |
|
| 374 |
+
job.on("status", (status) => {
|
| 375 |
+
if (status.stage === "pending") {
|
| 376 |
+
addStatusMessage(`در صف قرار گرفتید. نفر ${status.queue_position} هستید. زمان تخمینی: ${Math.round(status.queue_eta)} ثانیه`, 'info');
|
| 377 |
+
setAiLoaderText(`در صف... (نفر ${status.queue_position})`);
|
| 378 |
+
} else if (status.stage === "generating") {
|
| 379 |
+
handleProgressUpdate(status.progress_data);
|
| 380 |
+
}
|
| 381 |
+
});
|
| 382 |
+
|
| 383 |
+
const result = await job;
|
| 384 |
await handleSuccessfulVideoGeneration(result);
|
| 385 |
|
| 386 |
} catch (errorCaught) {
|
|
|
|
| 392 |
|
| 393 |
generateButton.addEventListener('click', async () => {
|
| 394 |
if (generateButton.disabled) return;
|
| 395 |
+
if (userSubscriptionStatus === 'paid') { await proceedWithVideoGeneration(); } else {
|
|
|
|
|
|
|
|
|
|
| 396 |
try {
|
| 397 |
+
const response = await fetch('/api/check-credit', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({ fingerprint: userFingerprint }) });
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
const result = await response.json();
|
| 399 |
+
if(result.limit_reached) { updateUIForSubscriptionStatus('free'); return; }
|
|
|
|
|
|
|
|
|
|
| 400 |
await proceedWithVideoGeneration();
|
| 401 |
+
} catch (err) { showCriticalError(`خطا در سیستم اعتبار: ${err.message}`); }
|
|
|
|
|
|
|
|
|
|
| 402 |
}
|
| 403 |
});
|
| 404 |
})();
|
| 405 |
</script>
|
| 406 |
|
| 407 |
<script>
|
| 408 |
+
// --- Header Animation Script (بدون تغییر) ---
|
| 409 |
+
document.addEventListener('DOMContentLoaded', () => { const canvas = document.getElementById('neural-network-canvas'); if (!canvas) return; const header = canvas.parentElement; const ctx = canvas.getContext('2d'); let particles = []; const particleCount = 20; const maxDistance = 100; const computedStyles = getComputedStyle(document.documentElement); const particleColor = computedStyles.getPropertyValue('--accent-primary').trim(); const lineColor = computedStyles.getPropertyValue('--text-tertiary').trim(); function resizeCanvas() { canvas.width = header.clientWidth; canvas.height = header.clientHeight; init(); } 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(); } } function init() { particles = []; for (let i = 0; i < particleCount; i++) { particles.push(new Particle()); } } 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; } function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(particle => { particle.update(); particle.draw(); }); connectParticles(); requestAnimationFrame(animate); } window.addEventListener('resize', resizeCanvas); resizeCanvas(); animate(); });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 410 |
</script>
|
| 411 |
</body>
|
| 412 |
</html>
|