File size: 25,222 Bytes
8eb8311
c02dde5
8eb8311
 
 
c02dde5
 
8eb8311
c02dde5
 
 
 
dcb7e8d
c02dde5
a86e06c
8eb8311
 
c02dde5
8eb8311
dcb7e8d
c02dde5
78a7eba
c02dde5
 
 
dcb7e8d
c02dde5
 
8eb8311
c02dde5
 
8eb8311
dcb7e8d
c02dde5
8eb8311
dcb7e8d
 
 
 
8eb8311
dcb7e8d
c02dde5
8eb8311
c02dde5
 
 
13bd7fd
8eb8311
c02dde5
 
 
 
 
 
 
 
8eb8311
 
 
c02dde5
 
8eb8311
 
 
 
a71d0df
dcb7e8d
c02dde5
 
 
 
 
a86e06c
c02dde5
eeecf98
a86e06c
 
dcb7e8d
c02dde5
 
8eb8311
 
c02dde5
 
 
 
 
 
 
 
8e435d6
c02dde5
 
 
 
dcb7e8d
c02dde5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78a7eba
c02dde5
 
 
 
 
 
 
dcb7e8d
c02dde5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8eb8311
c02dde5
 
 
 
 
 
 
8eb8311
c02dde5
8eb8311
c02dde5
 
 
 
 
 
78a7eba
dcb7e8d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78a7eba
dcb7e8d
 
 
 
 
 
 
78a7eba
dcb7e8d
 
 
 
 
 
 
 
 
 
 
 
 
 
c02dde5
 
 
 
 
 
 
a86e06c
c02dde5
 
 
dcb7e8d
c02dde5
 
 
 
 
 
a86e06c
 
78a7eba
c02dde5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bea53ed
dcb7e8d
 
c02dde5
 
 
 
8eb8311
78a7eba
 
c02dde5
 
78a7eba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eeecf98
78a7eba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a86e06c
8794b29
069e8c0
c02dde5
 
a86e06c
 
78a7eba
c02dde5
8794b29
 
c02dde5
 
 
8eb8311
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LTX 2.3 10Eros - Image to Video</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.rtl.min.css">
    <style>
        body { font-family: Tahoma, sans-serif; background-color: #f8f9fa; padding: 20px; }
        .card { margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
        .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; }
        @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
        #videoResult { width: 100%; max-width: 100%; border-radius: 8px; margin-top: 15px; display: none; }
        .form-label { font-weight: bold; font-size: 0.9em; margin-top: 10px; }
        #errorBox { display: none; text-align: left; direction: ltr; font-family: monospace; font-size: 0.85em; max-height: 300px; overflow-y: auto; background: #fff3f3; border: 1px solid #f5c2c2; color: #a94442; }
    </style>
</head>
<body>

<div class="container-fluid">
    <h2 class="text-center mb-4 text-primary">LTX 2.3 10Eros - تبدیل تصویر به ویدیو</h2>
    <p class="text-center text-muted">اتصال مستقیم و پایدار (نسخه نهایی با پشتیبانی از ساختار ویدیوی Gradio)</p>

    <div class="row">
        <!-- ستون تنظیمات -->
        <div class="col-lg-7">
            <div class="card p-4">
                <h5 class="mb-3">تنظیمات اصلی و API</h5>
                
                <label class="form-label">Hugging Face Token (الزامی برای جلوگیری از خطا)</label>
                <input type="text" id="hf_token" class="form-control" placeholder="hf_...">

                <label class="form-label mt-3">تصویر اصلی (الزامی) [image_path]</label>
                <input type="file" id="image_path" class="form-control" accept="image/*">

                <label class="form-label mt-3">پرامپت اصلی (الزامی) [prompt]</label>
                <textarea id="prompt" class="form-control" rows="3" placeholder="مفهوم و توضیح ویدیو..."></textarea>
                
                <button id="enhanceBtn" class="btn btn-outline-primary btn-sm mt-2">✨ بهینه‌سازی خودکار پرامپت (Enhance)</button>

                <label class="form-label mt-3">پرامپت منفی [negative_prompt]</label>
                <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>

                <div class="row mt-3">
                    <div class="col-md-6">
                        <label class="form-label">مدت زمان (ثانیه) [seconds]</label>
                        <input type="number" id="seconds" class="form-control" value="4" step="0.5">
                    </div>
                    <div class="col-md-6">
                        <label class="form-label">پریست (الگوی پیش‌فرض) [preset]</label>
                        <select id="preset" class="form-select">
                            <option value="tuned" selected>Tuned</option>
                            <option value="original">Original</option>
                            <option value="tuned #2">Tuned #2</option>
                            <option value="experimental #1">Experimental #1</option>
                        </select>
                    </div>
                </div>

                <!-- تولید فرم به صورت پویا برای سایر تنظیمات -->
                <div class="accordion mt-4" id="settingsAccordion"></div>

            </div>
        </div>

        <!-- ستون خروجی -->
        <div class="col-lg-5">
            <div class="card p-4 text-center sticky-top" style="top: 20px;">
                <h5 class="mb-3">تولید ویدیو</h5>
                <button id="generateBtn" class="btn btn-success w-100 py-3 mb-3 text-white fw-bold">شروع تولید (Generate)</button>
                
                <div class="d-flex justify-content-center"><div id="loader" class="loader"></div></div>
                <div id="statusText" class="text-secondary small fw-bold mb-2">آماده دریافت فرمان...</div>
                
                <!-- باکس نمایش خطای واقعی سرور -->
                <div id="errorBox" class="alert p-3"></div>

                <!-- ویدیوی خروجی -->
                <video id="videoResult" controls></video>
                <div id="seedResult" class="mt-3 text-info small"></div>
            </div>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>

<script type="module">
    import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";

    // فرمت‌بندی تمامی پارامترها
    const formSchema = [
        { group: "رزولوشن و ابعاد", id: "max_width", type: "number", val: 1120 },
        { group: "رزولوشن و ابعاد", id: "max_height", type: "number", val: 1344 },
        { group: "رزولوشن و ابعاد", id: "target_mp", type: "number", val: 1.15, step: 0.05 },
        { group: "رزولوشن و ابعاد", id: "snap_multiple", type: "select", options: ["32", "64"], val: "64", castTo: "number" },
        { group: "رزولوشن و ابعاد", id: "custom_res_enabled", type: "checkbox", val: false },
        
        { group: "تنظیمات چهره و استایل (Targeting)", id: "mode", type: "select", options: ["anchor only", "auto face", "manual bbox"], val: "anchor only" },
        { group: "تنظیمات چهره و استایل (Targeting)", id: "face_bbox", type: "text", val: "" },
        { group: "تنظیمات چهره و استایل (Targeting)", id: "likeness_strength", type: "number", val: 0.9, step: 0.05 },
        { group: "تنظیمات چهره و استایل (Targeting)", id: "likeness_anchor_strength", type: "number", val: 0.15, step: 0.05 },
        { group: "تنظیمات چهره و استایل (Targeting)", id: "latent_anchor_strength", type: "number", val: 0.08, step: 0.01 },
        { group: "تنظیمات چهره و استایل (Targeting)", id: "first_frame_strength", type: "number", val: 0.82, step: 0.01 },
        
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "sulphur_lora_strength", type: "number", val: 0.15, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "sulphur_v1_lora_strength", type: "number", val: 0.15, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "vbvr_lora_strength", type: "number", val: 0.5, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "dreamly_lora_strength", type: "number", val: 0.6, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "synth_lora_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "plora_lora_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "singularity_lora_strength", type: "number", val: 0.3, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "omninft_lora_strength", type: "number", val: 0.8, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "omninft_bf16_lora_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "better_motion_lora_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "physics_v2_lora_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "hardcut_lora_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - ویدیو)", id: "transition_lora_strength", type: "number", val: 0.15, step: 0.05 },

        { group: "وزن مدل‌ها (LoRA - صدا)", id: "sulphur_audio_strength", type: "number", val: 0.15, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "sulphur_v1_audio_strength", type: "number", val: 0.15, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "vbvr_audio_strength", type: "number", val: 0.5, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "dreamly_audio_strength", type: "number", val: 0.6, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "synth_audio_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "plora_audio_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "singularity_audio_strength", type: "number", val: 0.3, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "omninft_audio_strength", type: "number", val: 0.8, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "omninft_bf16_audio_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "better_motion_audio_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "physics_v2_audio_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "hardcut_audio_strength", type: "number", val: 0.0, step: 0.05 },
        { group: "وزن مدل‌ها (LoRA - صدا)", id: "transition_audio_strength", type: "number", val: 0.0, step: 0.05 },

        { group: "تنظیمات پیشرفته هویت (Identity)", id: "cache_at_step", type: "number", val: 0 },
        { group: "تنظیمات پیشرفته هویت (Identity)", id: "cache_warmup", type: "number", val: 400 },
        { group: "تنظیمات پیشرفته هویت (Identity)", id: "energy_threshold", type: "number", val: 0.3, step: 0.05 },
        { group: "تنظیمات پیشرفته هویت (Identity)", id: "anchor_similarity_threshold", type: "number", val: 0.3, step: 0.05 },
        { group: "تنظیمات پیشرفته هویت (Identity)", id: "sigma_string", type: "text", val: "0.4824, 0.2412, 0.0" },
        { group: "تنظیمات پیشرفته هویت (Identity)", id: "skip_refine", type: "checkbox", val: false },

        { group: "مصرف منابع و Seed", id: "seed", type: "number", val: -1 },
        { group: "مصرف منابع و Seed", id: "randomize_seed", type: "checkbox", val: true },
        { group: "مصرف منابع و Seed", id: "gen_budget", type: "number", val: 0 },
        { group: "مصرف منابع و Seed", id: "enhance_budget", type: "number", val: 80 },
        { group: "مصرف منابع و Seed", id: "profile_name", type: "text", val: "" },

        { group: "حالت چند مرجعی (MSR)", id: "input_mode", type: "select", options: ["single image (i2v)", "multi-reference (MSR)", "multi-reference (original)"], val: "single image (i2v)" },
        { group: "حالت چند مرجعی (MSR)", id: "msr_ref2", type: "file", val: null },
        { group: "حالت چند مرجعی (MSR)", id: "msr_ref3", type: "file", val: null },
        { group: "حالت چند مرجعی (MSR)", id: "msr_ref4", type: "file", val: null },
        { group: "حالت چند مرجعی (MSR)", id: "msr_background", type: "file", val: null },
        { group: "حالت چند مرجعی (MSR)", id: "msr_frame_count", type: "select", options: ["17", "25", "33", "41"], val: "41", castTo: "number" },
        { group: "حالت چند مرجعی (MSR)", id: "msr_guide_strength", type: "number", val: 1.0, step: 0.05 },
        { group: "حالت چند مرجعی (MSR)", id: "msr_lora_strength", type: "number", val: 0.7, step: 0.05 },

        { group: "زنجیره صحنه و پرامپت زمانی", id: "prompt_relay_enabled", type: "checkbox", val: false },
        { group: "زنجیره صحنه و پرامپت زمانی", id: "prompt_segments", type: "textarea", val: "" },
        { group: "زنجیره صحنه و پرامپت زمانی", id: "scene_chain_enabled", type: "checkbox", val: false },
        { group: "زنجیره صحنه و پرامپت زمانی", id: "scene_chain_prompt", type: "textarea", val: "" },
        { group: "زنجیره صحنه و پرامپت زمانی", id: "scene_chain_max_scenes", type: "number", val: 2 },
        { group: "زنجیره صحنه و پرامپت زمانی", id: "scene_chain_frame_overlap", type: "number", val: 8 },
        { group: "زنجیره صحنه و پرامپت زمانی", id: "scene_chain_mid_guide", type: "checkbox", val: true },
        { group: "زنجیره صحنه و پرامپت زمانی", id: "scene_chain_mid_guide_strength", type: "number", val: 0.25, step: 0.05 },

        { group: "صدای مرجع و تنظیمات KV", id: "kv_enabled", type: "checkbox", val: false },
        { group: "صدای مرجع و تنظیمات KV", id: "kv_strength", type: "number", val: 1.0, step: 0.05 },
        { group: "صدای مرجع و تنظیمات KV", id: "audio_ref_enabled", type: "checkbox", val: false },
        { group: "صدای مرجع و تنظیمات KV", id: "audio_ref_file", type: "file", val: null },
        { group: "صدای مرجع و تنظیمات KV", id: "audio_ref_guidance_scale", type: "number", val: 3.0, step: 0.5 },
        { group: "صدای مرجع و تنظیمات KV", id: "audio_ref_stem_sep", type: "checkbox", val: false },
        { group: "صدای مرجع و تنظیمات KV", id: "audio_ref_normalize", type: "checkbox", val: true },

        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_strength", type: "number", val: 0.82, step: 0.01 },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_last_image", type: "file", val: null },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_mid_enabled", type: "checkbox", val: false },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_mid_1_image", type: "file", val: null },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_mid_1_pos", type: "number", val: 50 },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_mid_2_image", type: "file", val: null },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_mid_2_pos", type: "number", val: 50 },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_mid_3_image", type: "file", val: null },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_mid_3_pos", type: "number", val: 50 },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_mid_4_image", type: "file", val: null },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_mid_4_pos", type: "number", val: 50 },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_mid_5_image", type: "file", val: null },
        { group: "کی‌فریم‌ها (Keyframes)", id: "kf_mid_5_pos", type: "number", val: 50 }
    ];

    const accordion = document.getElementById("settingsAccordion");
    const groups = {};
    formSchema.forEach(item => {
        if (!groups[item.group]) groups[item.group] = [];
        groups[item.group].push(item);
    });

    let index = 0;
    for (const [groupName, items] of Object.entries(groups)) {
        let html = `<div class="accordion-item">
            <h2 class="accordion-header" id="heading${index}">
                <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse${index}">
                    ${groupName}
                </button>
            </h2>
            <div id="collapse${index}" class="accordion-collapse collapse" data-bs-parent="#settingsAccordion">
                <div class="accordion-body">
                    <div class="row">`;
        
        items.forEach(field => {
            html += `<div class="${field.type === 'textarea' || field.type === 'file' ? 'col-12' : 'col-md-6'} mb-2">`;
            
            if (field.type === "checkbox") {
                html += `
                    <div class="form-check mt-3">
                        <input class="form-check-input" type="checkbox" id="${field.id}" ${field.val ? "checked" : ""}>
                        <label class="form-check-label" for="${field.id}">${field.id}</label>
                    </div>`;
            } else if (field.type === "select") {
                html += `<label class="form-label">${field.id}</label>
                         <select id="${field.id}" class="form-select">`;
                field.options.forEach(opt => {
                    html += `<option value="${opt}" ${opt === field.val ? "selected" : ""}>${opt}</option>`;
                });
                html += `</select>`;
            } else if (field.type === "textarea") {
                html += `<label class="form-label">${field.id}</label>
                         <textarea id="${field.id}" class="form-control" rows="2">${field.val}</textarea>`;
            } else {
                html += `<label class="form-label">${field.id}</label>
                         <input type="${field.type}" id="${field.id}" class="form-control" value="${field.val !== null ? field.val : ''}" ${field.step ? `step="${field.step}"` : ''}>`;
            }
            html += `</div>`;
        });
        
        html += `</div></div></div></div>`;
        accordion.innerHTML += html;
        index++;
    }


    document.getElementById("enhanceBtn").addEventListener("click", async () => {
        const hfToken = document.getElementById("hf_token").value.trim();
        const imgInput = document.getElementById("image_path");
        const promptInput = document.getElementById("prompt");
        const enhanceBtn = document.getElementById("enhanceBtn");
        
        if (!imgInput.files.length) return alert("ابتدا باید یک تصویر اصلی انتخاب کنید!");
        if (!promptInput.value.trim()) return alert("ابتدا یک پرامپت کوتاه برای ایده اولیه بنویسید!");

        enhanceBtn.innerText = "⏳ در حال بهینه‌سازی...";
        enhanceBtn.disabled = true;

        try {
            const client = await Client.connect("Fighterdan/LTX-2.3-10Eros_I2V", { hf_token: hfToken });
            const getFile = (id) => document.getElementById(id) && document.getElementById(id).files.length ? document.getElementById(id).files[0] : null;

            const enhanceParams = {
                image_path: imgInput.files[0],
                prompt: promptInput.value,
                enhance_budget: Number(document.getElementById("enhance_budget")?.value || 80),
                msr_ref2_path: getFile("msr_ref2"),
                msr_ref3_path: getFile("msr_ref3"),
                msr_ref4_path: getFile("msr_ref4"),
                msr_bg_path: getFile("msr_background")
            };

            const result = await client.predict("/enhance_prompt", enhanceParams);
            
            if (result.data && result.data[0]) {
                promptInput.value = result.data[0];
                alert("پرامپت با موفقیت بهینه‌سازی شد!");
            }

        } catch (error) {
            console.error(error);
            alert("خطا در بهینه‌سازی پرامپت: " + error.message);
        } finally {
            enhanceBtn.innerText = "✨ بهینه‌سازی خودکار پرامپت (Enhance)";
            enhanceBtn.disabled = false;
        }
    });

    document.getElementById("generateBtn").addEventListener("click", async () => {
        const hfToken = document.getElementById("hf_token").value.trim();
        const btn = document.getElementById("generateBtn");
        const loader = document.getElementById("loader");
        const statusText = document.getElementById("statusText");
        const videoRes = document.getElementById("videoResult");
        const seedRes = document.getElementById("seedResult");
        const errorBox = document.getElementById("errorBox");

        const imgInput = document.getElementById("image_path");
        const promptInput = document.getElementById("prompt").value;
        
        if (!imgInput.files.length) return alert("لطفاً تصویر اصلی را آپلود کنید.");
        if (!promptInput) return alert("لطفاً پرامپت را وارد کنید.");

        btn.disabled = true;
        loader.style.display = "block";
        videoRes.style.display = "none";
        errorBox.style.display = "none";
        statusText.style.display = "block";
        statusText.innerText = "در حال تولید ویدیو (بسته به شلوغی سرور ممکن است چند دقیقه طول بکشد)...";

        try {
            const params = {
                image_path: imgInput.files[0],
                prompt: promptInput,
                negative_prompt: document.getElementById("negative_prompt").value,
                preset: document.getElementById("preset").value,
                seconds: parseFloat(document.getElementById("seconds").value),
            };

            formSchema.forEach(field => {
                const el = document.getElementById(field.id);
                if (field.type === "checkbox") {
                    params[field.id] = el.checked;
                } else if (field.type === "number") {
                    params[field.id] = parseFloat(el.value);
                } else if (field.type === "file") {
                    if (el.files.length > 0) {
                        params[field.id] = el.files[0];
                    }
                } else if (field.type === "select") {
                    params[field.id] = field.castTo === "number" ? Number(el.value) : el.value;
                } else {
                    params[field.id] = el.value;
                }
            });

            delete params.enhance_budget;

            const client = await Client.connect("Fighterdan/LTX-2.3-10Eros_I2V", { hf_token: hfToken });
            
            const result = await client.predict("/generate", params);
            const data = result.data;
            
            // ========================================================
            // بخش هوشمند استخراج آدرس ویدیو بر اساس ساختار تو در تو
            // ========================================================
            let videoUrl = "";
            if (data && data[0]) {
                // در نسخه‌های جدید Gradio داده ویدیو در key به نام "video" قرار دارد
                let fileData = data[0].video ? data[0].video : data[0];

                if (fileData instanceof Blob) {
                    videoUrl = URL.createObjectURL(fileData);
                } else if (typeof fileData === "string") {
                    videoUrl = fileData;
                } else if (typeof fileData === "object") {
                    // استخراج URL یا ساخته شدن مسیر پایه
                    videoUrl = fileData.url || (fileData.path ? `https://fighterdan-ltx-2-3-10eros-i2v.hf.space/file=${fileData.path}` : "");
                }
            }

            if (videoUrl) {
                // موفقیت کامل
                videoRes.src = videoUrl;
                videoRes.style.display = "block";
                statusText.innerText = "تولید با موفقیت به پایان رسید!";
                // data[1] همان گزارش موفقیت (مثل 1024x1152, 25 frames) است
                seedRes.innerText = "گزارش خروجی: " + (data[1] || "") + " | Seed: " + (data[2] || "");
            } else {
                // اگر ویدیویی یافت نشد، یعنی واقعا در پایتون اکسپشن رخ داده
                statusText.style.display = "none";
                errorBox.style.display = "block";
                if (data && data[1]) {
                    errorBox.innerHTML = `<strong>خطای سرور:</strong><br><br>${data[1].replace(/\n/g, '<br>')}`;
                } else {
                    errorBox.innerText = "خطا: ارتباط موفق بود اما ویدیویی برگشت داده نشد.";
                }
            }

        } catch (error) {
            console.error(error);
            statusText.style.display = "none";
            errorBox.style.display = "block";
            errorBox.innerText = "خطای کلاینت/شبکه: " + error.message;
        } finally {
            btn.disabled = false;
            loader.style.display = "none";
        }
    });
</script>

</body>
</html>