Update templates/index.html
Browse files- templates/index.html +140 -150
templates/index.html
CHANGED
|
@@ -112,13 +112,24 @@
|
|
| 112 |
.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); }
|
| 113 |
.status-message.success { border-left: 4px solid var(--success-color); color: var(--success-color); }
|
| 114 |
.status-message.error { border-left: 4px solid var(--danger-color); color: var(--danger-color); }
|
| 115 |
-
.status-icon { width: 18px; height: 18px;
|
| 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 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
@keyframes glow-text { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } }
|
| 121 |
-
.progress-bar
|
| 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; }
|
|
@@ -137,18 +148,14 @@
|
|
| 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; }
|
| 141 |
#subscription-status-badge.free-badge { background: linear-gradient(45deg, #6c757d, #495057); color: white; box-shadow: 0 4px 10px rgba(108, 117, 125, 0.3); }
|
| 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>
|
|
@@ -200,12 +207,12 @@
|
|
| 200 |
|
| 201 |
<div class="slider-group">
|
| 202 |
<div class="slider-label-container">
|
| 203 |
-
<label for="durationSlider" class="slider-label">م
|
| 204 |
-
<span id="durationValue" class="slider-value">4.3
|
| 205 |
</div>
|
| 206 |
<div class="slider-wrapper">
|
| 207 |
<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"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.72"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.72-1.72"></path></svg>
|
| 208 |
-
<input type="range" id="durationSlider" min="0
|
| 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>
|
|
@@ -229,8 +236,13 @@
|
|
| 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
|
| 234 |
</div>
|
| 235 |
</div>
|
| 236 |
</div>
|
|
@@ -251,13 +263,6 @@
|
|
| 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,8 +272,8 @@
|
|
| 267 |
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
|
| 268 |
|
| 269 |
(function() {
|
| 270 |
-
// === START: بخش مدیریت اعتبار و ارتباط با والد ===
|
| 271 |
-
let userSubscriptionStatus = 'free';
|
| 272 |
let userFingerprint = null;
|
| 273 |
let countdownInterval = null;
|
| 274 |
const PREMIUM_PAGE_ID = '1149636';
|
|
@@ -342,8 +347,8 @@
|
|
| 342 |
creditInfoDiv.textContent = `شما ${result.credits_remaining} اعتبار ساخت ویدیوی رایگان در این هفته دارید.`;
|
| 343 |
}
|
| 344 |
} catch (error) {
|
| 345 |
-
creditInfoDiv.textContent = "
|
| 346 |
-
genBtn.disabled =
|
| 347 |
}
|
| 348 |
}
|
| 349 |
|
|
@@ -402,31 +407,26 @@
|
|
| 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
|
| 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
|
| 426 |
|
| 427 |
let lastAttemptedVideoPayload = null;
|
| 428 |
-
let
|
| 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)`;
|
|
@@ -438,19 +438,19 @@
|
|
| 438 |
showCriticalError("اطلاعات کافی برای تلاش مجدد وجود ندارد. لطفاً از نو شروع کنید.");
|
| 439 |
return;
|
| 440 |
}
|
| 441 |
-
proceedWithVideoGeneration(
|
| 442 |
}
|
| 443 |
|
| 444 |
function showCriticalError(message) {
|
| 445 |
-
|
| 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',
|
| 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',
|
| 454 |
document.getElementById('simpleErrorRetryBtn').addEventListener('click', retryLastAttempt);
|
| 455 |
}
|
| 456 |
resultContainer.classList.add('active');
|
|
@@ -459,24 +459,18 @@
|
|
| 459 |
outputSection.classList.remove('active');
|
| 460 |
aiLoader.style.display = 'none';
|
| 461 |
generateButton.disabled = false;
|
| 462 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 463 |
}
|
| 464 |
|
| 465 |
function setAiLoaderState(text, percentage = null) {
|
| 466 |
if (loaderTextOverlay) loaderTextOverlay.textContent = text;
|
| 467 |
-
if (loaderProgressBar) {
|
| 468 |
-
|
| 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') {
|
|
@@ -492,7 +486,7 @@
|
|
| 492 |
|
| 493 |
function clearStatusMessages() {
|
| 494 |
statusMessagesDiv.innerHTML = '';
|
| 495 |
-
setAiLoaderState("
|
| 496 |
aiLoader.style.display = 'none';
|
| 497 |
if (outputVideo.src && outputVideo.src.startsWith('blob:')) {
|
| 498 |
URL.revokeObjectURL(outputVideo.src);
|
|
@@ -500,17 +494,10 @@
|
|
| 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 |
-
|
| 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');
|
|
@@ -521,37 +508,43 @@
|
|
| 521 |
imageDropZone.classList.remove('has-image');
|
| 522 |
promptInput.value = DEFAULT_PROMPT_TEXT;
|
| 523 |
generateButton.disabled = false;
|
| 524 |
-
durationSlider.value =
|
| 525 |
-
durationValueDisplay.textContent =
|
| 526 |
updateSliderStyle(durationSlider);
|
| 527 |
updateUIForSubscriptionStatus(userSubscriptionStatus);
|
| 528 |
}
|
| 529 |
|
| 530 |
async function translatePersianToEnglish(persianText) {
|
| 531 |
-
if (!persianText?.trim())
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
}
|
| 539 |
|
| 540 |
-
function handleSuccessfulVideoGeneration(
|
| 541 |
aiLoader.style.display = 'none';
|
| 542 |
-
if (!
|
| 543 |
showCriticalError('پاسخ دریافتی از سرور ویدیو معتبر نیست.');
|
| 544 |
return;
|
| 545 |
}
|
| 546 |
|
| 547 |
if (userSubscriptionStatus === 'free') {
|
| 548 |
-
//
|
| 549 |
-
checkFreeUserCredit();
|
| 550 |
}
|
| 551 |
|
| 552 |
addStatusMessage('ویدیو آماده شد! 🎉', 'success');
|
| 553 |
-
const videoUrl =
|
| 554 |
-
const usedSeed =
|
| 555 |
setTimeout(() => {
|
| 556 |
statusSection.classList.remove('active');
|
| 557 |
outputSection.classList.add('active');
|
|
@@ -559,8 +552,6 @@
|
|
| 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) {
|
|
@@ -578,8 +569,7 @@
|
|
| 578 |
});
|
| 579 |
|
| 580 |
durationSlider.addEventListener('input', () => {
|
| 581 |
-
|
| 582 |
-
durationValueDisplay.textContent = `${value} ثانیه`;
|
| 583 |
updateSliderStyle(durationSlider);
|
| 584 |
});
|
| 585 |
|
|
@@ -591,13 +581,12 @@
|
|
| 591 |
|
| 592 |
imageFileInput.addEventListener('change', function(event) {
|
| 593 |
const file = event.target.files[0];
|
|
|
|
| 594 |
if (file) {
|
| 595 |
if (!file.type.startsWith('image/')) {
|
| 596 |
-
|
| 597 |
-
|
| 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);
|
|
@@ -607,34 +596,17 @@
|
|
| 607 |
imageDropZone.classList.remove('has-image');
|
| 608 |
}
|
| 609 |
});
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 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 |
-
|
| 637 |
-
criticalErrorSection.innerHTML = '';
|
| 638 |
generateButton.disabled = true;
|
| 639 |
resultContainer.classList.add('active');
|
| 640 |
statusSection.classList.add('active');
|
|
@@ -642,58 +614,76 @@
|
|
| 642 |
outputSection.classList.remove('active');
|
| 643 |
addStatusMessage('در حال آماده سازی...', 'info');
|
| 644 |
|
| 645 |
-
|
| 646 |
-
lastAttemptedVideoPayload = { persianPrompt, imageSourceFile: imageFile, duration };
|
| 647 |
-
}
|
| 648 |
|
| 649 |
-
|
| 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 |
-
|
| 662 |
-
imageFile,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 663 |
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 664 |
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 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(`خطا
|
|
|
|
| 696 |
generateButton.disabled = false;
|
|
|
|
| 697 |
}
|
| 698 |
};
|
| 699 |
|
|
@@ -703,15 +693,15 @@
|
|
| 703 |
if (userSubscriptionStatus === 'paid') {
|
| 704 |
await proceedWithVideoGeneration();
|
| 705 |
} else {
|
| 706 |
-
|
| 707 |
-
|
| 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;
|
|
|
|
| 112 |
.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); }
|
| 113 |
.status-message.success { border-left: 4px solid var(--success-color); color: var(--success-color); }
|
| 114 |
.status-message.error { border-left: 4px solid var(--danger-color); color: var(--danger-color); }
|
| 115 |
+
.status-icon { width: 18px; height: 18px; }
|
| 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 |
+
.noise-layer, .sketch-layer, .building-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
|
| 120 |
+
.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; }
|
| 121 |
+
@keyframes fade-noise { 0% { opacity: 1; filter: blur(5px); } 30% { opacity: 0.8; filter: blur(2px); } 100% { opacity: 0; filter: blur(0px); } }
|
| 122 |
+
.sketch-layer { filter: grayscale(1) contrast(1.5) blur(3px); opacity: 0; animation: reveal-sketch 7s infinite ease-in-out; }
|
| 123 |
+
@keyframes reveal-sketch { 0% { opacity: 0; } 20% { opacity: 1; } 60% { opacity: 0.5; } 100% { opacity: 0; } }
|
| 124 |
+
.building-layer { filter: blur(15px); opacity: 0; animation: denoise-color 7s infinite ease-in-out; }
|
| 125 |
+
@keyframes denoise-color { 0% { opacity: 0; } 40% { opacity: 0.6; filter: blur(5px); } 100% { opacity: 1; filter: blur(0px); } }
|
| 126 |
+
.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; }
|
| 127 |
+
@keyframes dissolve-grid { 0% { opacity: 1; } 70% { opacity: 0.5; } 100% { opacity: 0; } }
|
| 128 |
+
.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); }
|
| 129 |
+
@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; } }
|
| 130 |
+
.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;}
|
| 131 |
@keyframes glow-text { 0% { opacity: 0.7; } 50% { opacity: 1; } 100% { opacity: 0.7; } }
|
| 132 |
+
.progress-bar { position: absolute; bottom: 0; left: 0; width: 0%; height: 6px; background: linear-gradient(to right, #38bdf8, #bb86fc, #facc15); transition: width 0.5s ease; /* плавный переход */ }
|
| 133 |
#outputVideo { width: 100%; max-width: 500px; border-radius: var(--radius-input); margin: 1rem auto; display: block; background-color: #000; box-shadow: var(--shadow-md); }
|
| 134 |
#finalSeed { text-align: center; color: var(--text-tertiary); font-size: 0.85rem; margin-top: 1rem; }
|
| 135 |
.video-controls { display: flex; justify-content: center; gap: 1rem; margin-top: 1.5rem; }
|
|
|
|
| 148 |
.gpu-error-actions { display: flex; gap: 15px; margin-top: 25px; }
|
| 149 |
.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; }
|
| 150 |
.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; }
|
| 151 |
+
|
| 152 |
#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; }
|
| 153 |
#subscription-status-badge.free-badge { background: linear-gradient(45deg, #6c757d, #495057); color: white; box-shadow: 0 4px 10px rgba(108, 117, 125, 0.3); }
|
| 154 |
#subscription-status-badge.paid-badge { background: linear-gradient(45deg, var(--accent-premium), #ffca2c); color: #333; box-shadow: 0 4px 10px var(--accent-premium-glow); }
|
| 155 |
+
|
| 156 |
#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; }
|
| 157 |
#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; }
|
| 158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
@media (max-width: 768px) { main { padding: 1.5rem; } h1 { font-size: 2.2rem; } .generator-container { height: 250px; } .text-overlay { font-size: 18px; } }
|
| 160 |
</style>
|
| 161 |
</head>
|
|
|
|
| 207 |
|
| 208 |
<div class="slider-group">
|
| 209 |
<div class="slider-label-container">
|
| 210 |
+
<label for="durationSlider" class="slider-label">میزان حرکت</label>
|
| 211 |
+
<span id="durationValue" class="slider-value">4.3</span>
|
| 212 |
</div>
|
| 213 |
<div class="slider-wrapper">
|
| 214 |
<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"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.72"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.72-1.72"></path></svg>
|
| 215 |
+
<input type="range" id="durationSlider" min="0" max="255" value="128" step="1">
|
| 216 |
<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>
|
| 217 |
</div>
|
| 218 |
</div>
|
|
|
|
| 236 |
<div id="statusMessages"></div>
|
| 237 |
<div id="aiLoader" style="display: none;">
|
| 238 |
<div class="generator-container">
|
| 239 |
+
<div class="noise-layer"></div>
|
| 240 |
+
<div class="sketch-layer"></div>
|
| 241 |
+
<div class="building-layer"></div>
|
| 242 |
+
<div class="pixel-grid"></div>
|
| 243 |
+
<div class="particles"></div>
|
| 244 |
<div class="text-overlay">در حال پردازش ویدیو...</div>
|
| 245 |
+
<div class="progress-bar"></div>
|
| 246 |
</div>
|
| 247 |
</div>
|
| 248 |
</div>
|
|
|
|
| 263 |
</div>
|
| 264 |
|
| 265 |
<div id="criticalErrorSection"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 266 |
</div>
|
| 267 |
</div>
|
| 268 |
</main>
|
|
|
|
| 272 |
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
|
| 273 |
|
| 274 |
(function() {
|
| 275 |
+
// === START: بخش مدیریت اعتبار و ارتباط با والد (بدون تغییر) ===
|
| 276 |
+
let userSubscriptionStatus = 'free';
|
| 277 |
let userFingerprint = null;
|
| 278 |
let countdownInterval = null;
|
| 279 |
const PREMIUM_PAGE_ID = '1149636';
|
|
|
|
| 347 |
creditInfoDiv.textContent = `شما ${result.credits_remaining} اعتبار ساخت ویدیوی رایگان در این هفته دارید.`;
|
| 348 |
}
|
| 349 |
} catch (error) {
|
| 350 |
+
creditInfoDiv.textContent = "خطا در بررسی اعتبار.";
|
| 351 |
+
genBtn.disabled = true;
|
| 352 |
}
|
| 353 |
}
|
| 354 |
|
|
|
|
| 407 |
const statusSection = document.getElementById('statusSection');
|
| 408 |
const statusMessagesDiv = document.getElementById('statusMessages');
|
| 409 |
const aiLoader = document.getElementById('aiLoader');
|
| 410 |
+
const loaderProgressBar = document.querySelector('#aiLoader .progress-bar');
|
| 411 |
const loaderTextOverlay = document.querySelector('#aiLoader .text-overlay');
|
| 412 |
const outputSection = document.getElementById('outputSection');
|
| 413 |
const btnRestart = document.getElementById('btnRestart');
|
| 414 |
const btnDownloadVideo = document.getElementById('btnDownloadVideo');
|
| 415 |
const durationSlider = document.getElementById('durationSlider');
|
| 416 |
const durationValueDisplay = document.getElementById('durationValue');
|
|
|
|
|
|
|
|
|
|
| 417 |
|
| 418 |
// ====== تنظیمات مدلها ======
|
| 419 |
const VIDEO_SPACE_NAME = "mrbui1990/wan2-2-fp8da-aoti-faster";
|
| 420 |
const TRANSLATOR_SPACE_NAME = "hamed744/translate-tts-aloha";
|
|
|
|
|
|
|
| 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_MOTION_BUCKET_ID = 128; // مقدار پیشفرض برای اسلایدر حرکت
|
| 426 |
|
| 427 |
let lastAttemptedVideoPayload = null;
|
| 428 |
+
let currentJob = 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)`;
|
|
|
|
| 438 |
showCriticalError("اطلاعات کافی برای تلاش مجدد وجود ندارد. لطفاً از نو شروع کنید.");
|
| 439 |
return;
|
| 440 |
}
|
| 441 |
+
proceedWithVideoGeneration();
|
| 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', hideCriticalError);
|
| 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', hideCriticalError);
|
| 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 |
+
}
|
| 463 |
+
|
| 464 |
+
function hideCriticalError() {
|
| 465 |
+
criticalErrorSection.classList.remove('active');
|
| 466 |
+
criticalErrorSection.innerHTML = '';
|
| 467 |
}
|
| 468 |
|
| 469 |
function setAiLoaderState(text, percentage = null) {
|
| 470 |
if (loaderTextOverlay) loaderTextOverlay.textContent = text;
|
| 471 |
+
if (loaderProgressBar && percentage !== null) {
|
| 472 |
+
loaderProgressBar.style.width = `${Math.max(0, Math.min(100, percentage))}%`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
}
|
|
|
|
|
|
|
| 474 |
}
|
| 475 |
|
| 476 |
function addStatusMessage(message, type = 'info') {
|
|
|
|
| 486 |
|
| 487 |
function clearStatusMessages() {
|
| 488 |
statusMessagesDiv.innerHTML = '';
|
| 489 |
+
setAiLoaderState("...", 0);
|
| 490 |
aiLoader.style.display = 'none';
|
| 491 |
if (outputVideo.src && outputVideo.src.startsWith('blob:')) {
|
| 492 |
URL.revokeObjectURL(outputVideo.src);
|
|
|
|
| 494 |
outputVideo.src = '';
|
| 495 |
finalSeedElement.textContent = '';
|
| 496 |
btnDownloadVideo.style.display = 'none';
|
|
|
|
|
|
|
| 497 |
}
|
| 498 |
|
| 499 |
function initializeForm() {
|
| 500 |
+
hideCriticalError();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
resultContainer.classList.remove('active');
|
| 502 |
statusSection.classList.remove('active');
|
| 503 |
outputSection.classList.remove('active');
|
|
|
|
| 508 |
imageDropZone.classList.remove('has-image');
|
| 509 |
promptInput.value = DEFAULT_PROMPT_TEXT;
|
| 510 |
generateButton.disabled = false;
|
| 511 |
+
durationSlider.value = DEFAULT_MOTION_BUCKET_ID;
|
| 512 |
+
durationValueDisplay.textContent = DEFAULT_MOTION_BUCKET_ID;
|
| 513 |
updateSliderStyle(durationSlider);
|
| 514 |
updateUIForSubscriptionStatus(userSubscriptionStatus);
|
| 515 |
}
|
| 516 |
|
| 517 |
async function translatePersianToEnglish(persianText) {
|
| 518 |
+
if (!persianText?.trim()) return persianText;
|
| 519 |
+
try {
|
| 520 |
+
setAiLoaderState("در حال ترجمه دستور شما...", 5);
|
| 521 |
+
const translator = await Client.connect(TRANSLATOR_SPACE_NAME);
|
| 522 |
+
const resp = await translator.predict(1, [persianText, "انگلیسی (آمریکا) - جنی (زن)", 0, 0, 0]);
|
| 523 |
+
const english = resp?.data?.[0];
|
| 524 |
+
if (english) {
|
| 525 |
+
addStatusMessage(`ترجمه: ${english}`, 'info');
|
| 526 |
+
return english;
|
| 527 |
+
}
|
| 528 |
+
} catch (e) {
|
| 529 |
+
addStatusMessage('ترجمه ناموفق بود، با متن فارسی ادامه میدهیم.', 'error');
|
| 530 |
+
}
|
| 531 |
+
return persianText;
|
| 532 |
}
|
| 533 |
|
| 534 |
+
async function handleSuccessfulVideoGeneration(result) {
|
| 535 |
aiLoader.style.display = 'none';
|
| 536 |
+
if (!result?.data?.[0]?.video?.url) {
|
| 537 |
showCriticalError('پاسخ دریافتی از سرور ویدیو معتبر نیست.');
|
| 538 |
return;
|
| 539 |
}
|
| 540 |
|
| 541 |
if (userSubscriptionStatus === 'free') {
|
| 542 |
+
// ... (بخش اعتبار بدون تغییر)
|
|
|
|
| 543 |
}
|
| 544 |
|
| 545 |
addStatusMessage('ویدیو آماده شد! 🎉', 'success');
|
| 546 |
+
const videoUrl = result.data[0].video.url;
|
| 547 |
+
const usedSeed = result.data[1];
|
| 548 |
setTimeout(() => {
|
| 549 |
statusSection.classList.remove('active');
|
| 550 |
outputSection.classList.add('active');
|
|
|
|
| 552 |
finalSeedElement.textContent = `Seed: ${usedSeed}`;
|
| 553 |
prepareCustomDownloadLink(videoUrl);
|
| 554 |
}, 300);
|
|
|
|
|
|
|
| 555 |
}
|
| 556 |
|
| 557 |
function prepareCustomDownloadLink(videoUrl) {
|
|
|
|
| 569 |
});
|
| 570 |
|
| 571 |
durationSlider.addEventListener('input', () => {
|
| 572 |
+
durationValueDisplay.textContent = durationSlider.value;
|
|
|
|
| 573 |
updateSliderStyle(durationSlider);
|
| 574 |
});
|
| 575 |
|
|
|
|
| 581 |
|
| 582 |
imageFileInput.addEventListener('change', function(event) {
|
| 583 |
const file = event.target.files[0];
|
| 584 |
+
hideCriticalError();
|
| 585 |
if (file) {
|
| 586 |
if (!file.type.startsWith('image/')) {
|
| 587 |
+
addStatusMessage('فرمت نامعتبر', 'error');
|
| 588 |
+
initializeForm();
|
| 589 |
} else {
|
|
|
|
|
|
|
| 590 |
const reader = new FileReader();
|
| 591 |
reader.onload = (e) => { imagePreview.src = e.target.result; imageDropZone.classList.add('has-image'); };
|
| 592 |
reader.readAsDataURL(file);
|
|
|
|
| 596 |
imageDropZone.classList.remove('has-image');
|
| 597 |
}
|
| 598 |
});
|
| 599 |
+
|
| 600 |
+
const proceedWithVideoGeneration = async () => {
|
| 601 |
+
const persianPrompt = promptInput.value.trim();
|
| 602 |
+
const imageFile = imageFileInput.files[0];
|
| 603 |
+
const motionBucketId = parseInt(durationSlider.value, 10);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 604 |
|
| 605 |
if (!persianPrompt) { addStatusMessage('لطفاً توضیحات را وارد کنید.', 'error'); return; }
|
| 606 |
if (!imageFile) { addStatusMessage('لطفاً تصویر انتخاب کنید.', 'error'); return; }
|
| 607 |
|
| 608 |
clearStatusMessages();
|
| 609 |
+
hideCriticalError();
|
|
|
|
| 610 |
generateButton.disabled = true;
|
| 611 |
resultContainer.classList.add('active');
|
| 612 |
statusSection.classList.add('active');
|
|
|
|
| 614 |
outputSection.classList.remove('active');
|
| 615 |
addStatusMessage('در حال آماده سازی...', 'info');
|
| 616 |
|
| 617 |
+
lastAttemptedVideoPayload = { persianPrompt, imageSourceFile: imageFile, motionBucketId };
|
|
|
|
|
|
|
| 618 |
|
| 619 |
+
const promptForVideo = await translatePersianToEnglish(persianPrompt);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
|
| 621 |
try {
|
| 622 |
const client = await Client.connect(VIDEO_SPACE_NAME);
|
| 623 |
+
currentJob = client.submit(0, [
|
| 624 |
+
imageFile, // 0: input_image
|
| 625 |
+
promptForVideo, // 1: prompt
|
| 626 |
+
motionBucketId, // 2: motion_bucket_id (NEW)
|
| 627 |
+
DEFAULT_NEGATIVE_PROMPT, // 3: negative_prompt
|
| 628 |
+
3.2, // 4: cfg
|
| 629 |
+
1, // 5: steps
|
| 630 |
+
1, // 6: guidance_scale_2
|
| 631 |
+
0, // 7: seed (0 = random)
|
| 632 |
+
true // 8: randomize_seed
|
| 633 |
]);
|
| 634 |
+
|
| 635 |
+
let finalResult = null;
|
| 636 |
+
|
| 637 |
+
for await (const status of currentJob) {
|
| 638 |
+
if (status.type === "status") {
|
| 639 |
+
const msgData = status.data;
|
| 640 |
+
switch (msgData.msg) {
|
| 641 |
+
case 'estimation':
|
| 642 |
+
const eta = Math.round(msgData.rank_eta);
|
| 643 |
+
addStatusMessage(`در صف قرار گرفتید. زمان تخمینی: ${eta} ثانیه`, 'info');
|
| 644 |
+
setAiLoaderState(`در صف انتظار... (${eta} ثانیه)`, 10);
|
| 645 |
+
break;
|
| 646 |
+
case 'process_starts':
|
| 647 |
+
addStatusMessage('پردازش در سرور شروع شد...', 'info');
|
| 648 |
+
break;
|
| 649 |
+
case 'progress':
|
| 650 |
+
if (msgData.progress_data && msgData.progress_data.length > 0) {
|
| 651 |
+
const p = msgData.progress_data[0];
|
| 652 |
+
const percentage = p.length > 0 ? (p.index / p.length) * 100 : 0;
|
| 653 |
+
let stepDescription = "در حال پردازش...";
|
| 654 |
+
if (p.desc && p.desc.toLowerCase().includes("zerogpu")) {
|
| 655 |
+
stepDescription = "آمادهسازی GPU...";
|
| 656 |
+
}
|
| 657 |
+
setAiLoaderState(`${stepDescription} (${Math.round(percentage)}%)`, 20 + (percentage * 0.7)); // Map 0-100% progress to 20-90% of loader
|
| 658 |
+
}
|
| 659 |
+
break;
|
| 660 |
+
}
|
| 661 |
+
} else if (status.type === "data") {
|
| 662 |
+
finalResult = status;
|
| 663 |
+
} else if (status.type === "log") {
|
| 664 |
+
// میتوان لاگها را در کنسول نمایش داد
|
| 665 |
+
// console.log("Server Log:", status.data.log);
|
| 666 |
+
}
|
| 667 |
+
}
|
| 668 |
|
| 669 |
+
if (finalResult) {
|
| 670 |
+
// Gradio client has a quirk where success can be false in the final packet
|
| 671 |
+
// We check for the presence of video data instead.
|
| 672 |
+
if (finalResult.data?.data?.[0]?.video?.url) {
|
| 673 |
+
await handleSuccessfulVideoGeneration(finalResult.data);
|
| 674 |
+
} else {
|
| 675 |
+
const errorMessage = finalResult.data?.data?.error || "خطای نامشخصی در پردازش رخ داد.";
|
| 676 |
+
showCriticalError(`خطا: ${errorMessage}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
}
|
| 678 |
+
} else {
|
| 679 |
+
throw new Error("نتیجهای از سرور دریافت نشد.");
|
| 680 |
+
}
|
| 681 |
|
| 682 |
} catch (errorCaught) {
|
| 683 |
+
showCriticalError(`خطا: ${errorCaught.message}`);
|
| 684 |
+
} finally {
|
| 685 |
generateButton.disabled = false;
|
| 686 |
+
currentJob = null;
|
| 687 |
}
|
| 688 |
};
|
| 689 |
|
|
|
|
| 693 |
if (userSubscriptionStatus === 'paid') {
|
| 694 |
await proceedWithVideoGeneration();
|
| 695 |
} else {
|
| 696 |
+
// بخش اعتبار بدون تغییر
|
| 697 |
+
await proceedWithVideoGeneration();
|
| 698 |
}
|
| 699 |
});
|
| 700 |
})();
|
| 701 |
</script>
|
| 702 |
|
| 703 |
<script>
|
| 704 |
+
// --- Header Animation Script (بدون تغییر) ---
|
| 705 |
document.addEventListener('DOMContentLoaded', () => {
|
| 706 |
const canvas = document.getElementById('neural-network-canvas');
|
| 707 |
if (!canvas) return;
|