Opera10 commited on
Commit
dcb7e8d
·
verified ·
1 Parent(s): c02dde5

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +160 -39
index.html CHANGED
@@ -11,32 +11,50 @@
11
  .card { margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
12
  .loader { border: 4px solid #f3f3f3; border-top: 4px solid #0d6efd; border-radius: 50%; width: 30px; height: 30px; animation: spin 1s linear infinite; display: none; margin-bottom: 10px; }
13
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
14
- #videoResult { width: 100%; max-width: 600px; border-radius: 8px; margin-top: 15px; display: none; }
15
  .form-label { font-weight: bold; font-size: 0.9em; margin-top: 10px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  </style>
17
  </head>
18
  <body>
19
 
20
- <div class="container">
21
  <h2 class="text-center mb-4 text-primary">LTX 2.3 10Eros - تبدیل تصویر به ویدیو</h2>
22
- <p class="text-center text-muted">این پنل از طریق API مستقیماً به سرورهای Hugging Face متصل می‌شود.</p>
23
 
24
  <div class="row">
25
  <!-- ستون تنظیمات -->
26
- <div class="col-lg-8">
27
  <div class="card p-4">
28
  <h5 class="mb-3">تنظیمات اصلی و API</h5>
29
 
30
  <label class="form-label">Hugging Face Token (الزامی برای جلوگیری از خطا)</label>
31
  <input type="text" id="hf_token" class="form-control" placeholder="hf_...">
32
 
33
- <label class="form-label">تصویر اصلی (الزامی) [image_path]</label>
34
  <input type="file" id="image_path" class="form-control" accept="image/*">
35
 
36
- <label class="form-label">پرامپت اصلی (الزامی) [prompt]</label>
37
- <textarea id="prompt" class="form-control" rows="2" placeholder="مفهوم و توضیح ویدیو..."></textarea>
 
 
38
 
39
- <label class="form-label">پرامپت منفی [negative_prompt]</label>
40
  <textarea id="negative_prompt" class="form-control" rows="2">captions, music, transition, VR, bad quality, subtitles, text, watermark, overlay effects, cartoon, childish, ugly, text, blur, logo, static, low quality, noise, mutant, horror, film grain</textarea>
41
 
42
  <div class="row mt-3">
@@ -61,18 +79,21 @@
61
  </div>
62
  </div>
63
 
64
- <!-- ستون خروجی -->
65
- <div class="col-lg-4">
66
  <div class="card p-4 text-center sticky-top" style="top: 20px;">
67
  <h5 class="mb-3">تولید ویدیو</h5>
68
  <button id="generateBtn" class="btn btn-success w-100 py-3 mb-3 text-white fw-bold">شروع تولید (Generate)</button>
69
 
70
  <div class="d-flex justify-content-center"><div id="loader" class="loader"></div></div>
71
- <div id="statusText" class="text-secondary small mb-2">آماده دریافت فرمان...</div>
72
 
 
73
  <video id="videoResult" controls></video>
74
-
75
  <div id="seedResult" class="mt-3 text-info small"></div>
 
 
 
76
  </div>
77
  </div>
78
  </div>
@@ -83,13 +104,14 @@
83
  <script type="module">
84
  import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
85
 
86
- // تعریف تمامی پارامترهای باقی‌مانده (۸۸ پارامتر مستند شده در API)
87
  const formSchema = [
88
  // گروه: Resolution
89
  { group: "رزولوشن و ابعاد", id: "max_width", type: "number", val: 1120 },
90
  { group: "رزولوشن و ابعاد", id: "max_height", type: "number", val: 1344 },
91
  { group: "رزولوشن و ابعاد", id: "target_mp", type: "number", val: 1.15, step: 0.05 },
92
- { group: "رزولوشن و ابعاد", id: "snap_multiple", type: "select", options: ["32", "64"], val: "64" },
 
93
  { group: "رزولوشن و ابعاد", id: "custom_res_enabled", type: "checkbox", val: false },
94
 
95
  // گروه: Targeting (شخصیت و صورت)
@@ -142,6 +164,7 @@
142
  { group: "مصرف منابع و Seed", id: "seed", type: "number", val: -1 },
143
  { group: "مصرف منابع و Seed", id: "randomize_seed", type: "checkbox", val: true },
144
  { group: "مصرف منابع و Seed", id: "gen_budget", type: "number", val: 0 },
 
145
  { group: "مصرف منابع و Seed", id: "profile_name", type: "text", val: "" },
146
 
147
  // گروه: MSR (چند تصویره)
@@ -150,7 +173,8 @@
150
  { group: "حالت چند مرجعی (MSR)", id: "msr_ref3", type: "file", val: null },
151
  { group: "حالت چند مرجعی (MSR)", id: "msr_ref4", type: "file", val: null },
152
  { group: "حالت چند مرجعی (MSR)", id: "msr_background", type: "file", val: null },
153
- { group: "حالت چند مرجعی (MSR)", id: "msr_frame_count", type: "select", options: ["17", "25", "33", "41"], val: "41" },
 
154
  { group: "حالت چند مرجعی (MSR)", id: "msr_guide_strength", type: "number", val: 1.0, step: 0.05 },
155
  { group: "حالت چند مرجعی (MSR)", id: "msr_lora_strength", type: "number", val: 0.7, step: 0.05 },
156
 
@@ -240,7 +264,61 @@
240
  index++;
241
  }
242
 
243
- // منطق اتصال و ارسال (Submit)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  document.getElementById("generateBtn").addEventListener("click", async () => {
245
  const hfToken = document.getElementById("hf_token").value.trim();
246
  const btn = document.getElementById("generateBtn");
@@ -249,19 +327,21 @@
249
  const videoRes = document.getElementById("videoResult");
250
  const seedRes = document.getElementById("seedResult");
251
 
252
- // اعتبارسنجی
253
  const imgInput = document.getElementById("image_path");
254
  const promptInput = document.getElementById("prompt").value;
 
255
  if (!imgInput.files.length) return alert("لطفاً تصویر اصلی را آپلود کنید.");
256
  if (!promptInput) return alert("لطفاً پرامپت را وارد کنید.");
257
 
258
  btn.disabled = true;
259
  loader.style.display = "block";
260
  videoRes.style.display = "none";
261
- statusText.innerText = "در حال اتصال به سرور (ممکن است مدتی طول بکشد)...";
 
 
262
 
263
  try {
264
- // ساختن شیء پارامترها با خواندن از DOM
265
  const params = {
266
  image_path: imgInput.files[0],
267
  prompt: promptInput,
@@ -270,7 +350,6 @@
270
  seconds: parseFloat(document.getElementById("seconds").value),
271
  };
272
 
273
- // خواندن فیلدهای اتوماتیک
274
  formSchema.forEach(field => {
275
  const el = document.getElementById(field.id);
276
  if (field.type === "checkbox") {
@@ -281,37 +360,79 @@
281
  if (el.files.length > 0) {
282
  params[field.id] = el.files[0];
283
  }
284
- // در کلاینت JS، فیلدهای فایل در صورت خالی بودن نادیده گرفته می‌شوند تا دیفالت سرور اعمال شود.
 
 
285
  } else {
286
  params[field.id] = el.value;
287
  }
288
  });
289
 
290
- // اتصال به Space
291
  const client = await Client.connect("Fighterdan/LTX-2.3-10Eros_I2V", { hf_token: hfToken });
292
 
293
- statusText.innerText = "متصل شد. در حال تولید ویدیو (منتظر بمانید)...";
294
-
295
- // درخواست تولید
296
- const result = await client.predict("/generate", params);
297
 
298
- // نمایش خروجی
299
- console.log("پاسخ سرور:", result.data);
300
- if (result.data && result.data[0] && result.data[0].url) {
301
- videoRes.src = result.data[0].url;
302
- videoRes.style.display = "block";
303
- statusText.innerText = result.data[1] || "تولید با موفقیت به پایان رسید!";
304
- seedRes.innerText = "Seed استفاده شده: " + result.data[2];
305
- } else {
306
- statusText.innerText = "خطا: ویدیو در پاسخ یافت نشد.";
307
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
309
  } catch (error) {
310
  console.error(error);
311
- statusText.innerText = "خطا در پردازش: " + error.message;
 
312
  } finally {
313
- btn.disabled = false;
314
- loader.style.display = "none";
 
 
 
 
315
  }
316
  });
317
  </script>
 
11
  .card { margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
12
  .loader { border: 4px solid #f3f3f3; border-top: 4px solid #0d6efd; border-radius: 50%; width: 30px; height: 30px; animation: spin 1s linear infinite; display: none; margin-bottom: 10px; }
13
  @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
14
+ #videoResult { width: 100%; max-width: 100%; border-radius: 8px; margin-top: 15px; display: none; }
15
  .form-label { font-weight: bold; font-size: 0.9em; margin-top: 10px; }
16
+
17
+ /* استایل ترمینال برای لاگ‌های زنده */
18
+ #liveLogs {
19
+ background-color: #1e1e1e;
20
+ color: #00ff00;
21
+ font-family: Consolas, monospace;
22
+ font-size: 0.8em;
23
+ padding: 10px;
24
+ border-radius: 5px;
25
+ height: 250px;
26
+ overflow-y: auto;
27
+ text-align: left;
28
+ direction: ltr;
29
+ margin-top: 15px;
30
+ display: none; /* تا وقتی پردازش شروع نشده مخفی بماند */
31
+ }
32
  </style>
33
  </head>
34
  <body>
35
 
36
+ <div class="container-fluid">
37
  <h2 class="text-center mb-4 text-primary">LTX 2.3 10Eros - تبدیل تصویر به ویدیو</h2>
38
+ <p class="text-center text-muted">اتصال مستقیم به API با امکان مانیتورینگ زنده (Live Logs)</p>
39
 
40
  <div class="row">
41
  <!-- ستون تنظیمات -->
42
+ <div class="col-lg-7">
43
  <div class="card p-4">
44
  <h5 class="mb-3">تنظیمات اصلی و API</h5>
45
 
46
  <label class="form-label">Hugging Face Token (الزامی برای جلوگیری از خطا)</label>
47
  <input type="text" id="hf_token" class="form-control" placeholder="hf_...">
48
 
49
+ <label class="form-label mt-3">تصویر اصلی (الزامی) [image_path]</label>
50
  <input type="file" id="image_path" class="form-control" accept="image/*">
51
 
52
+ <label class="form-label mt-3">پرامپت اصلی (الزامی) [prompt]</label>
53
+ <textarea id="prompt" class="form-control" rows="3" placeholder="مفهوم و توضیح ویدیو..."></textarea>
54
+
55
+ <button id="enhanceBtn" class="btn btn-outline-primary btn-sm mt-2">✨ بهینه‌سازی خودکار پرامپت (Enhance)</button>
56
 
57
+ <label class="form-label mt-3">پرامپت منفی [negative_prompt]</label>
58
  <textarea id="negative_prompt" class="form-control" rows="2">captions, music, transition, VR, bad quality, subtitles, text, watermark, overlay effects, cartoon, childish, ugly, text, blur, logo, static, low quality, noise, mutant, horror, film grain</textarea>
59
 
60
  <div class="row mt-3">
 
79
  </div>
80
  </div>
81
 
82
+ <!-- ستون خروجی و لاگ -->
83
+ <div class="col-lg-5">
84
  <div class="card p-4 text-center sticky-top" style="top: 20px;">
85
  <h5 class="mb-3">تولید ویدیو</h5>
86
  <button id="generateBtn" class="btn btn-success w-100 py-3 mb-3 text-white fw-bold">شروع تولید (Generate)</button>
87
 
88
  <div class="d-flex justify-content-center"><div id="loader" class="loader"></div></div>
89
+ <div id="statusText" class="text-secondary small fw-bold">آماده دریافت فرمان...</div>
90
 
91
+ <!-- ویدیوی خروجی -->
92
  <video id="videoResult" controls></video>
 
93
  <div id="seedResult" class="mt-3 text-info small"></div>
94
+
95
+ <!-- ترمینال لاگ‌های زنده -->
96
+ <pre id="liveLogs"></pre>
97
  </div>
98
  </div>
99
  </div>
 
104
  <script type="module">
105
  import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
106
 
107
+ // فرمت‌بندی تمامی پارامترها به همراه castTo برای تبدیل نوع داده‌ها هنگام ارسال
108
  const formSchema = [
109
  // گروه: Resolution
110
  { group: "رزولوشن و ابعاد", id: "max_width", type: "number", val: 1120 },
111
  { group: "رزولوشن و ابعاد", id: "max_height", type: "number", val: 1344 },
112
  { group: "رزولوشن و ابعاد", id: "target_mp", type: "number", val: 1.15, step: 0.05 },
113
+ // توجه: castTo="number" اضافه شد تا "64" به عدد صحیح 64 تبدیل شود
114
+ { group: "رزولوشن و ابعاد", id: "snap_multiple", type: "select", options: ["32", "64"], val: "64", castTo: "number" },
115
  { group: "رزولوشن و ابعاد", id: "custom_res_enabled", type: "checkbox", val: false },
116
 
117
  // گروه: Targeting (شخصیت و صورت)
 
164
  { group: "مصرف منابع و Seed", id: "seed", type: "number", val: -1 },
165
  { group: "مصرف منابع و Seed", id: "randomize_seed", type: "checkbox", val: true },
166
  { group: "مصرف منابع و Seed", id: "gen_budget", type: "number", val: 0 },
167
+ { group: "مصرف منابع و Seed", id: "enhance_budget", type: "number", val: 80 },
168
  { group: "مصرف منابع و Seed", id: "profile_name", type: "text", val: "" },
169
 
170
  // گروه: MSR (چند تصویره)
 
173
  { group: "حالت چند مرجعی (MSR)", id: "msr_ref3", type: "file", val: null },
174
  { group: "حالت چند مرجعی (MSR)", id: "msr_ref4", type: "file", val: null },
175
  { group: "حالت چند مرجعی (MSR)", id: "msr_background", type: "file", val: null },
176
+ // توجه: castTo="number" اضافه شد
177
+ { group: "حالت چند مرجعی (MSR)", id: "msr_frame_count", type: "select", options: ["17", "25", "33", "41"], val: "41", castTo: "number" },
178
  { group: "حالت چند مرجعی (MSR)", id: "msr_guide_strength", type: "number", val: 1.0, step: 0.05 },
179
  { group: "حالت چند مرجعی (MSR)", id: "msr_lora_strength", type: "number", val: 0.7, step: 0.05 },
180
 
 
264
  index++;
265
  }
266
 
267
+ // لاگر سفارشی برای ثبت گزارشات زنده
268
+ const liveLogs = document.getElementById("liveLogs");
269
+ const appendLog = (msg) => {
270
+ liveLogs.style.display = "block";
271
+ const time = new Date().toLocaleTimeString();
272
+ liveLogs.innerHTML += `[${time}] ${msg}\n`;
273
+ liveLogs.scrollTop = liveLogs.scrollHeight; // اسکرول به پایین
274
+ };
275
+
276
+ // --- تابع بهینه‌سازی پرامپت (Enhance Prompt) ---
277
+ document.getElementById("enhanceBtn").addEventListener("click", async () => {
278
+ const hfToken = document.getElementById("hf_token").value.trim();
279
+ const imgInput = document.getElementById("image_path");
280
+ const promptInput = document.getElementById("prompt");
281
+ const enhanceBtn = document.getElementById("enhanceBtn");
282
+
283
+ if (!imgInput.files.length) return alert("ابتدا باید یک تصویر اصلی انتخاب کنید!");
284
+ if (!promptInput.value.trim()) return alert("ابتدا یک پرامپت کوتاه برای ایده اولیه بنویسید!");
285
+
286
+ enhanceBtn.innerText = "⏳ در حال بهینه‌سازی...";
287
+ enhanceBtn.disabled = true;
288
+
289
+ try {
290
+ const client = await Client.connect("Fighterdan/LTX-2.3-10Eros_I2V", { hf_token: hfToken });
291
+
292
+ // گرفتن فایل‌های MSR در صورت وجود، یا رد کردن مقدار null
293
+ const getFile = (id) => document.getElementById(id) && document.getElementById(id).files.length ? document.getElementById(id).files[0] : null;
294
+
295
+ const enhanceParams = {
296
+ image_path: imgInput.files[0],
297
+ prompt: promptInput.value,
298
+ enhance_budget: Number(document.getElementById("enhance_budget")?.value || 80),
299
+ msr_ref2_path: getFile("msr_ref2"),
300
+ msr_ref3_path: getFile("msr_ref3"),
301
+ msr_ref4_path: getFile("msr_ref4"),
302
+ msr_bg_path: getFile("msr_background")
303
+ };
304
+
305
+ const result = await client.predict("/enhance_prompt", enhanceParams);
306
+
307
+ if (result.data && result.data[0]) {
308
+ promptInput.value = result.data[0];
309
+ alert("پرامپت با موفقیت بهینه‌سازی شد!");
310
+ }
311
+
312
+ } catch (error) {
313
+ console.error(error);
314
+ alert("خطا در بهینه‌سازی پرامپت: " + error.message);
315
+ } finally {
316
+ enhanceBtn.innerText = "✨ بهینه‌سازی خودکار پرامپت (Enhance)";
317
+ enhanceBtn.disabled = false;
318
+ }
319
+ });
320
+
321
+ // --- منطق تولید اصلی (Generate) ---
322
  document.getElementById("generateBtn").addEventListener("click", async () => {
323
  const hfToken = document.getElementById("hf_token").value.trim();
324
  const btn = document.getElementById("generateBtn");
 
327
  const videoRes = document.getElementById("videoResult");
328
  const seedRes = document.getElementById("seedResult");
329
 
 
330
  const imgInput = document.getElementById("image_path");
331
  const promptInput = document.getElementById("prompt").value;
332
+
333
  if (!imgInput.files.length) return alert("لطفاً تصویر اصلی را آپلود کنید.");
334
  if (!promptInput) return alert("لطفاً پرامپت را وارد کنید.");
335
 
336
  btn.disabled = true;
337
  loader.style.display = "block";
338
  videoRes.style.display = "none";
339
+ liveLogs.innerHTML = ""; // پاک کردن لاگ‌های قبلی
340
+ statusText.innerText = "در حال اتصال...";
341
+ appendLog("شروع فرآیند اتصال به سرور...");
342
 
343
  try {
344
+ // خواندن پارامترها و کست کردن مقادیر رشته‌ای به عددی (در صورت نیاز)
345
  const params = {
346
  image_path: imgInput.files[0],
347
  prompt: promptInput,
 
350
  seconds: parseFloat(document.getElementById("seconds").value),
351
  };
352
 
 
353
  formSchema.forEach(field => {
354
  const el = document.getElementById(field.id);
355
  if (field.type === "checkbox") {
 
360
  if (el.files.length > 0) {
361
  params[field.id] = el.files[0];
362
  }
363
+ } else if (field.type === "select") {
364
+ // تبدیل نوع دراپ‌داون‌هایی که نیاز به عدد دارند
365
+ params[field.id] = field.castTo === "number" ? Number(el.value) : el.value;
366
  } else {
367
  params[field.id] = el.value;
368
  }
369
  });
370
 
371
+ appendLog("پارامترها آماده شد. اتصال به کلاینت Gradio...");
372
  const client = await Client.connect("Fighterdan/LTX-2.3-10Eros_I2V", { hf_token: hfToken });
373
 
374
+ appendLog("متصل شد! ارسال درخواست تولید ویدیو (submit)...");
 
 
 
375
 
376
+ // استفاده از Submit به جای Predict برای دریافت رویدادهای زنده (لاگ و وضعیت)
377
+ const job = client.submit("/generate", params);
378
+
379
+ // گرفتن رویدادهای وضعیت (در صف، در حال اجرا و غیره)
380
+ job.on("status", (status) => {
381
+ if (status.stage === "pending") {
382
+ const queueInfo = status.queue_position !== undefined ? ` (جایگاه در صف: ${status.queue_position})` : "";
383
+ statusText.innerText = "در صف انتظار..." + queueInfo;
384
+ appendLog("وضعیت: در صف انتظار... " + queueInfo);
385
+ } else if (status.stage === "generating") {
386
+ statusText.innerText = "در حال پردازش در GPU (لطفا شکیبا باشید)...";
387
+ appendLog("وضعیت: تولید ویدیو در حال انجام است...");
388
+ } else if (status.stage === "complete") {
389
+ statusText.innerText = "پردازش تکمیل شد.";
390
+ appendLog("وضعیت: پایان موفقیت‌آمیز.");
391
+ } else if (status.stage === "error") {
392
+ statusText.innerText = "خطا در پردازش!";
393
+ appendLog("خطا (Error): " + (status.message || "نامشخص"));
394
+ }
395
+ });
396
+
397
+ // گرفتن لاگ‌های زنده که توسط سرور چاپ می‌شود (مثل خطاهای ComfyUI)
398
+ job.on("log", (logData) => {
399
+ appendLog("[سرور]: " + logData.log);
400
+ });
401
+
402
+ // گرفتن خروجی نهایی
403
+ job.on("data", (result) => {
404
+ appendLog("پاسخ نهایی دریافت شد.");
405
+ const data = result.data;
406
+ if (data && data[0] && data[0].url) {
407
+ videoRes.src = data[0].url;
408
+ videoRes.style.display = "block";
409
+ statusText.innerText = data[1] || "تولید با موفقیت به پایان رسید!";
410
+ seedRes.innerText = "Seed استفاده شده: " + data[2];
411
+ appendLog("ویدیو با موفقیت بارگذاری شد.");
412
+ } else {
413
+ statusText.innerText = "خطا: ویدیو در پاسخ یافت نشد.";
414
+ appendLog("خطای منطقی: فایل ویدیو در خروجی Gradio قرار نگرفت.");
415
+ }
416
+ });
417
+
418
+ // در صورت بروز خطا در ارسال درخواست
419
+ job.on("error", (error) => {
420
+ console.error(error);
421
+ statusText.innerText = "خطای کلاینت: " + error.message;
422
+ appendLog("استثنا/خطا: " + error.message);
423
+ });
424
 
425
  } catch (error) {
426
  console.error(error);
427
+ statusText.innerText = "خطا در برقراری ارتباط اولیه: " + error.message;
428
+ appendLog("استثنای اتصال: " + error.message);
429
  } finally {
430
+ // از آنجایی که job ناهمگام (Event-based) است، دکمه را در زمان پایان (داخل status) یا همینجا با تایمر موقت آزاد می‌کنیم.
431
+ // به صورت اصولی دکمه باید وقتی stage === complete شد فعال شود، اما برای اطمینان این تنظیمات را اینجا می‌گذاریم.
432
+ setTimeout(() => {
433
+ btn.disabled = false;
434
+ loader.style.display = "none";
435
+ }, 3000); // فقط برای بازگرداندن وضعیت دکمه
436
  }
437
  });
438
  </script>