Elias207 commited on
Commit
08d89e1
·
verified ·
1 Parent(s): ae22409

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +276 -414
index.html CHANGED
@@ -20,6 +20,8 @@
20
  --accent-primary-hover: #3553D6;
21
  --accent-primary-glow: rgba(74, 108, 250, 0.25);
22
  --accent-secondary: #0FD4A8;
 
 
23
  --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
24
  --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
25
  --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
@@ -94,7 +96,7 @@
94
  }
95
  .subtitle { font-size: 1.1rem; color: var(--text-secondary); margin-top: 0; max-width: 650px; margin-left: auto; margin-right: auto; line-height: 1.8; }
96
 
97
- main {
98
  padding: 3rem;
99
  background-color: var(--panel-bg);
100
  border-radius: var(--radius-card);
@@ -102,6 +104,10 @@
102
  border: 1px solid var(--panel-border);
103
  animation: fadeIn 0.8s 0.3s ease-out backwards;
104
  }
 
 
 
 
105
 
106
  .form-group { margin-bottom: 2.5rem; }
107
  .form-group:last-child { margin-bottom: 0; }
@@ -283,7 +289,8 @@
283
 
284
  #result-image-wrapper { display: none; width: 100%; }
285
  #result-container.has-content #result-image-wrapper { display: block; animation: fadeIn 0.5s; text-align: center; }
286
- #result-image-display { max-width: 100%; max-height: 500px; object-fit: contain; border-radius: var(--radius-input); box-shadow: var(--shadow-md); border: 1px solid var(--panel-border); }
 
287
  .error-message { color: #d93025; font-weight: bold; margin-top: 10px; background: #ffebee; padding: 1rem; border-radius: var(--radius-input); border: 1px solid #e57373; }
288
 
289
  .output-details { display: none !important; }
@@ -315,12 +322,10 @@
315
  gap: 1rem;
316
  padding: 10px;
317
  }
318
-
319
- /* <<< START: روش جدید و سازگار برای ساخت مربع >>> */
320
  .lora-card {
321
- position: relative; /* ایجاد موقعیت برای عکس داخلی */
322
  height: 0;
323
- padding-bottom: 100%; /* این باعث می‌شود ارتفاع کارت برابر با عرض آن (مربع) شود */
324
  border-radius: var(--radius-input);
325
  overflow: hidden;
326
  cursor: pointer;
@@ -328,32 +333,26 @@
328
  background-color: var(--input-bg);
329
  border: 1px solid transparent;
330
  box-shadow: var(--shadow-sm);
331
- -webkit-user-select: none; /* Safari */
332
- -ms-user-select: none; /* IE 10+ */
333
- user-select: none; /* Standard syntax */
334
  }
335
-
336
  .lora-card:hover {
337
  transform: translateY(-5px);
338
  box-shadow: var(--shadow-lg);
339
  border-color: var(--accent-primary);
340
  }
341
-
342
  .lora-card img {
343
- position: absolute; /* عکس را داخل کادر مربع قرار می‌دهد */
344
  top: 0;
345
  left: 0;
346
  width: 100%;
347
- height: 100%; /* عکس کل فضای مربع را پر می‌کند */
348
  object-fit: cover;
349
  transition: opacity 0.3s ease-in-out;
350
  opacity: 0;
351
  }
352
- /* <<< END: روش جدید و سازگار برای ساخت مربع >>> */
353
-
354
- .lora-card img.loaded {
355
- opacity: 1;
356
- }
357
 
358
  .ip-reset-guide-container { text-align: right; background: var(--panel-bg); padding: 10px; border-radius: var(--radius-lg-guide); animation: slideInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1) both; max-width: 100%; width: 100%; position: relative; overflow: hidden; box-sizing: border-box; }
359
  .ip-reset-guide-container::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 5px; background: var(--primary-gradient-guide); }
@@ -421,11 +420,121 @@
421
  font-weight: 500;
422
  }
423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
  @media (max-width: 768px) {
425
- main { padding: 1.5rem; } h1 { font-size: 2.2rem; }
 
426
  .aspect-ratio-selector { grid-template-columns: repeat(2, 1fr); }
427
  .style-preview-card { flex-direction: column; text-align: center; gap: 1rem; }
428
  #selected-style-image-large { margin-left: 0; }
 
429
  }
430
  </style>
431
  </head>
@@ -503,6 +612,20 @@
503
 
504
  <div class="output-details" id="output-details"></div>
505
  </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  </div>
507
 
508
  <div class="modal-overlay" id="style-modal">
@@ -514,294 +637,74 @@
514
  <div id="lora-grid"></div>
515
  </div>
516
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
518
  <script type="module">
519
  import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
520
 
521
  const loras = [
522
- {
523
- "image": "https://huggingface.co/prithivMLmods/Flux-Dalle-Mix-LoRA/resolve/main/images/D3.png",
524
- "title": "Dalle Mix",
525
- "repo": "prithivMLmods/Flux-Dalle-Mix-LoRA",
526
- "trigger_word": "dalle-mix"
527
- },
528
- {
529
- "image": "https://huggingface.co/alvdansen/flux-koda/resolve/main/images/ComfyUI_00583_%20(1).png",
530
- "title": "Koda",
531
- "repo": "alvdansen/flux-koda",
532
- "trigger_word": "flmft style"
533
- },
534
- {
535
- "image": "https://huggingface.co/strangerzonehf/Flux-Super-Realism-LoRA/resolve/main/images/1.png",
536
- "title": "Super Realism",
537
- "repo": "strangerzonehf/Flux-Super-Realism-LoRA",
538
- "trigger_word": "Super Realism"
539
- },
540
- {
541
- "image": "https://huggingface.co/strangerzonehf/Ghibli-Flux-Cartoon-LoRA/resolve/main/images/3333.png",
542
- "title": "Ghibli Flux",
543
- "repo": "strangerzonehf/Ghibli-Flux-Cartoon-LoRA",
544
- "trigger_word": "Ghibli Art"
545
- },
546
- {
547
- "image": "https://huggingface.co/strangerzonehf/Flux-Sketch-Smudge-LoRA/resolve/main/images/5.png",
548
- "title": "Sketch_Smudge",
549
- "repo": "strangerzonehf/Flux-Sketch-Smudge-LoRA",
550
- "trigger_word": " Sketch Smudge"
551
- },
552
- {
553
- "image": "https://huggingface.co/strangerzonehf/Flux-Animeo-v1-LoRA/resolve/main/images/A4.png",
554
- "title": "Animeo Mix",
555
- "repo": "strangerzonehf/Flux-Animeo-v1-LoRA",
556
- "trigger_word": "Animeo"
557
- },
558
- {
559
- "image": "https://huggingface.co/strangerzonehf/Flux-Animex-v2-LoRA/resolve/main/images/A33.png",
560
- "title": "Animex Mix",
561
- "repo": "strangerzonehf/Flux-Animex-v2-LoRA",
562
- "trigger_word": "Animex"
563
- },
564
- {
565
- "image": "https://huggingface.co/strangerzonehf/Flux-Super-Portrait-LoRA/resolve/main/images/3.png",
566
- "title": "Super Portraits",
567
- "repo": "strangerzonehf/Flux-Super-Portrait-LoRA",
568
- "trigger_word": "Super Portrait"
569
- },
570
- {
571
- "image": "https://huggingface.co/strangerzonehf/Flux-Super-Blend-LoRA/resolve/main/images/SB1.png",
572
- "title": "Super Blend",
573
- "repo": "strangerzonehf/Flux-Super-Blend-LoRA",
574
- "trigger_word": "Super Blend"
575
- },
576
- {
577
- "image": "https://huggingface.co/strangerzonehf/Flux-Midjourney-Mix2-LoRA/resolve/main/images/3.png",
578
- "title": "Midjourney Mix 2",
579
- "repo": "strangerzonehf/Flux-Midjourney-Mix2-LoRA",
580
- "trigger_word": "MJ v6"
581
- },
582
- {
583
- "image": "https://huggingface.co/strangerzonehf/Flux-Ultimate-LoRA-Collection/resolve/main/images/example_aqz3dv60n.jpeg",
584
- "title": "SHOU_XIN",
585
- "repo": "Datou1111/shou_xin",
586
- "trigger_word": "shou_xin, pencil sketch"
587
- },
588
- {
589
- "image": "https://huggingface.co/prithivMLmods/Flux-Long-Toon-LoRA/resolve/main/images/LT5.png",
590
- "title": "Long Toons",
591
- "repo": "prithivMLmods/Flux-Long-Toon-LoRA",
592
- "trigger_word": "Long toons"
593
- },
594
- {
595
- "image": "https://huggingface.co/strangerzonehf/Flux-Cute-3D-Kawaii-LoRA/resolve/main/images/CK3.png",
596
- "title": "Cute 3D Kawaii",
597
- "repo": "strangerzonehf/Flux-Cute-3D-Kawaii-LoRA",
598
- "trigger_word": "Cute 3d Kawaii"
599
- },
600
- {
601
- "image": "https://huggingface.co/strangerzonehf/Flux-Isometric-3D-LoRA/resolve/main/images/ID2.png",
602
- "title": "Isometric 3D",
603
- "repo": "strangerzonehf/Flux-Isometric-3D-LoRA",
604
- "trigger_word": "Isometric 3D"
605
- },
606
- {
607
- "image": "https://huggingface.co/prithivMLmods/Flux-Toonic-2.5D-LoRA/resolve/main/images/T2.png",
608
- "title": "Toon 2.5D",
609
- "repo": "prithivMLmods/Flux-Toonic-2.5D-LoRA",
610
- "trigger_word": "toonic 2.5D"
611
- },
612
- {
613
- "image": "https://huggingface.co/strangerzonehf/Flux-YWL-Realism-LoRA/resolve/main/images/R3.png",
614
- "title": "YWL Realism",
615
- "repo": "strangerzonehf/Flux-YWL-Realism-LoRA",
616
- "trigger_word": "ylw realism"
617
- },
618
- {
619
- "image": "https://huggingface.co/prithivMLmods/Flux-Chill-Guy-Zone/resolve/main/images/8.png",
620
- "title": "Chill Guy",
621
- "repo": "prithivMLmods/Flux-Chill-Guy-Zone",
622
- "trigger_word": "chill guy"
623
- },
624
- {
625
- "image": "https://huggingface.co/p1atdev/flux.1-schnell-pvc-style-lora/resolve/main/images/flux_lora_00221_.png",
626
- "title": "Anime PVC Style",
627
- "repo": "p1atdev/flux.1-schnell-pvc-style-lora",
628
- "trigger_word": "pvc figure, nendoroid, figma"
629
- },
630
- {
631
- "image": "https://huggingface.co/strangerzonehf/Flux-C4C-Design-LoRA/resolve/main/images/4.png",
632
- "title": "Smiley C4C",
633
- "repo": "strangerzonehf/Flux-C4C-Design-LoRA",
634
- "trigger_word": "Smiley C4C"
635
- },
636
- {
637
- "image": "https://huggingface.co/prithivMLmods/Purple-Dreamy-Flux-LoRA/resolve/main/images/PD3.png",
638
- "title": "Purple Dream",
639
- "repo": "prithivMLmods/Purple-Dreamy-Flux-LoRA",
640
- "trigger_word": "Purple Dreamy"
641
- },
642
- {
643
- "image": "https://huggingface.co/prithivMLmods/Canopus-LoRA-Flux-FaceRealism/resolve/main/images/11.png",
644
- "title": "Flux Face Realism",
645
- "repo": "prithivMLmods/Canopus-LoRA-Flux-FaceRealism",
646
- "trigger_word": "Realism"
647
- },
648
- {
649
- "image": "https://huggingface.co/alvdansen/softserve_anime/resolve/main/images/ComfyUI_00134_.png",
650
- "title": "Softserve Anime",
651
- "repo": "alvdansen/softserve_anime",
652
- "trigger_word": "sftsrv style illustration"
653
- },
654
- {
655
- "image": "https://huggingface.co/prithivMLmods/Fashion-Hut-Modeling-LoRA/resolve/main/images/MO1.png",
656
- "title": "Modeling Hut",
657
- "repo": "prithivMLmods/Fashion-Hut-Modeling-LoRA",
658
- "trigger_word": "Modeling of"
659
- },
660
- {
661
- "image": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-LoRA-One-Click-Creative-Template/resolve/main/images/f2cc649985648e57b9b9b14ca7a8744ac8e50d75b3a334ed4df0f368.jpg",
662
- "title": "Creative Template",
663
- "repo": "Shakker-Labs/FLUX.1-dev-LoRA-One-Click-Creative-Template",
664
- "trigger_word": "The background is 4 real photos, and in the middle is a cartoon picture summarizing the real photos."
665
- },
666
- {
667
- "image": "https://huggingface.co/prithivMLmods/Canopus-LoRA-Flux-UltraRealism-2.0/resolve/main/images/XX.png",
668
- "title": "Ultra Realism",
669
- "repo": "prithivMLmods/Canopus-LoRA-Flux-UltraRealism-2.0",
670
- "trigger_word": "Ultra realistic"
671
- },
672
- {
673
- "image": "https://huggingface.co/gokaygokay/Flux-Game-Assets-LoRA-v2/resolve/main/images/example_y2bqpuphc.png",
674
- "title": "Game Assets",
675
- "repo": "gokaygokay/Flux-Game-Assets-LoRA-v2",
676
- "trigger_word": "wbgmsst, white background"
677
- },
678
- {
679
- "image": "https://huggingface.co/alvdansen/softpasty-flux-dev/resolve/main/images/ComfyUI_00814_%20(2).png",
680
- "title": "Softpasty",
681
- "repo": "alvdansen/softpasty-flux-dev",
682
- "trigger_word": "araminta_illus illustration style"
683
- },
684
- {
685
- "image": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-LoRA-add-details/resolve/main/images/0.png",
686
- "title": "Details Add",
687
- "repo": "Shakker-Labs/FLUX.1-dev-LoRA-add-details",
688
- "trigger_word": ""
689
- },
690
- {
691
- "image": "https://huggingface.co/prithivMLmods/Canopus-LoRA-Flux-Anime/resolve/main/assets/4.png",
692
- "title": "Flux Anime",
693
- "repo": "prithivMLmods/Canopus-LoRA-Flux-Anime",
694
- "trigger_word": "Anime"
695
- },
696
- {
697
- "image": "https://huggingface.co/aleksa-codes/flux-ghibsky-illustration/resolve/main/images/example5.jpg",
698
- "title": "Ghibsky Illustration",
699
- "repo": "aleksa-codes/flux-ghibsky-illustration",
700
- "trigger_word": "GHIBSKY style painting"
701
- },
702
- {
703
- "image": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-LoRA-Dark-Fantasy/resolve/main/images/c2215bd73da9f14fcd63cc93350e66e2901bdafa6fb8abaaa2c32a1b.jpg",
704
- "title": "Dark Fantasy",
705
- "repo": "Shakker-Labs/FLUX.1-dev-LoRA-Dark-Fantasy",
706
- "trigger_word": ""
707
- },
708
- {
709
- "image": "https://huggingface.co/Norod78/Flux_1_Dev_LoRA_Paper-Cutout-Style/resolve/main/d13591878d5043f3989dd6eb1c25b710_233c18effb4b491cb467ca31c97e90b5.png",
710
- "title": "Paper Cutout",
711
- "repo": "Norod78/Flux_1_Dev_LoRA_Paper-Cutout-Style",
712
- "trigger_word": "Paper Cutout Style"
713
- },
714
- {
715
- "image": "https://huggingface.co/alvdansen/mooniverse/resolve/main/images/out-0%20(17).webp",
716
- "title": "Mooniverse",
717
- "repo": "alvdansen/mooniverse",
718
- "trigger_word": "surreal style"
719
- },
720
- {
721
- "image": "https://huggingface.co/alvdansen/pola-photo-flux/resolve/main/images/out-0%20-%202024-09-22T130819.351.webp",
722
- "title": "Pola Photo",
723
- "repo": "alvdansen/pola-photo-flux",
724
- "trigger_word": "polaroid style"
725
- },
726
- {
727
- "image": "https://huggingface.co/multimodalart/flux-tarot-v1/resolve/main/images/7e180627edd846e899b6cd307339140d_5b2a09f0842c476b83b6bd2cb9143a52.png",
728
- "title": "Flux Tarot",
729
- "repo": "multimodalart/flux-tarot-v1",
730
- "trigger_word": "in the style of TOK a trtcrd tarot style"
731
- },
732
- {
733
- "image": "https://huggingface.co/prithivMLmods/Flux-Dev-Real-Anime-LoRA/resolve/main/images/111.png",
734
- "title": "Real Anime",
735
- "repo": "prithivMLmods/Flux-Dev-Real-Anime-LoRA",
736
- "trigger_word": "Real Anime"
737
- },
738
- {
739
- "image": "https://huggingface.co/diabolic6045/Flux_Sticker_Lora/resolve/main/images/example_s3pxsewcb.png",
740
- "title": "Stickers",
741
- "repo": "diabolic6045/Flux_Sticker_Lora",
742
- "trigger_word": "5t1cker 5ty1e"
743
- },
744
- {
745
- "image": "https://huggingface.co/VideoAditor/Flux-Lora-Realism/resolve/main/images/feel-the-difference-between-using-flux-with-lora-from-xlab-v0-j0ehybmvxehd1.png",
746
- "title": "Realism",
747
- "repo": "XLabs-AI/flux-RealismLora",
748
- "trigger_word": ""
749
- },
750
- {
751
- "image": "https://huggingface.co/mgwr/Cine-Aesthetic/resolve/main/images/00019-1333633802.png",
752
- "title": "Cine Aesthetic",
753
- "repo": "mgwr/Cine-Aesthetic",
754
- "trigger_word": "mgwr/cine"
755
- },
756
- {
757
- "image": "https://huggingface.co/SebastianBodza/flux_cute3D/resolve/main/images/astronaut.webp",
758
- "title": "Cute 3D",
759
- "repo": "SebastianBodza/flux_cute3D",
760
- "trigger_word": "NEOCUTE3D"
761
- }
762
  ];
763
 
764
  const loraPersianNames = {
765
- "Dalle Mix": "ترکیب دالی",
766
- "Koda": "کُدا",
767
- "Super Realism": "واقع‌گرایی خارق‌العاده",
768
- "Ghibli Flux": "سبک جیبلی",
769
- "Sketch_Smudge": "طراحی و لکه",
770
- "Animeo Mix": "ترکیب انیمه",
771
- "Animex Mix": "ترکیب انیمکس",
772
- "Super Portraits": "پرتره‌های ویژه",
773
- "Super Blend": "ترکیب ویژه",
774
- "Midjourney Mix 2": "ترکیب میدجرنی ۲",
775
- "SHOU_XIN": "شو شین",
776
- "Long Toons": "کارتون‌های بلند",
777
- "Cute 3D Kawaii": "کاوایی سه‌بعدی",
778
- "Isometric 3D": "ایزومتریک سه‌بعدی",
779
- "Toon 2.5D": "کارتون ۲.۵ بعدی",
780
- "YWL Realism": "واقع‌گرایی YWL",
781
- "Chill Guy": "پسر آرام",
782
- "Anime PVC Style": "سبک انیمه PVC",
783
- "Smiley C4C": "لبخند C4C",
784
- "Purple Dream": "رویای بنفش",
785
- "Flux Face Realism": "چهره واقع‌گرایانه",
786
- "Softserve Anime": "انیمه سافت‌سرو",
787
- "Modeling Hut": "کلبه مدلینگ",
788
- "Creative Template": "قالب خلاقانه",
789
- "Ultra Realism": "فرا واقع‌گرایی",
790
- "Game Assets": "آیتم‌های بازی",
791
- "Softpasty": "سافت‌پِیستی",
792
- "Details Add": "افزودن جزئیات",
793
- "Flux Anime": "فلاکس انیمه",
794
- "Ghibsky Illustration": "تصویرسازی گیبسکی",
795
- "Dark Fantasy": "فانتزی تاریک",
796
- "Paper Cutout": "هنر کاغذبری",
797
- "Mooniverse": "دنیای ماه",
798
- "Pola Photo": "عکس پولاروید",
799
- "Flux Tarot": "فال تاروت",
800
- "Real Anime": "انیمه واقعی",
801
- "Stickers": "استیکرها",
802
- "Realism": "واقع‌گرایی",
803
- "Cine Aesthetic": "زیبایی‌شناسی سینمایی",
804
- "Cute 3D": "سه‌بعدی بامزه"
805
  };
806
 
807
  const GPU_QUOTA_ERROR_HTML = `
@@ -843,35 +746,37 @@
843
  const stylePreviewContainer = document.getElementById('style-preview-container');
844
  const selectedStyleImageLarge = document.getElementById('selected-style-image-large');
845
  const selectedStyleNameFa = document.getElementById('selected-style-name-fa');
846
-
847
  const modalTitle = document.querySelector('#style-modal .modal-header h2');
848
  const originalModalTitleHTML = modalTitle.innerHTML;
 
 
 
 
 
 
 
 
 
 
 
 
849
  let longPressTimer = null;
850
  let longPressJustHappened = false;
851
-
852
  let selectedLora = null;
853
  let selectedRatio = '1:1';
854
  let imageObserver = null;
855
  let fluxClient = null;
856
  let countdownInterval = null;
857
 
858
- const loadingPlaceholderHTML = `
859
- <div id="loading-placeholder">
860
- <div class="generator-container">
861
- <div class="text-overlay" id="loading-status-text">در حال آماده‌سازی...</div>
862
- <div class="progress-bar" id="loading-progress-bar"></div>
863
- </div>
864
- </div>`;
865
  const resultWrapperHTML = `<div id="result-image-wrapper"><img id="result-image-display" src="" alt="تصویر خلق شده"></div>`;
866
 
867
  const startCountdown = (durationInSeconds) => {
868
  stopCountdown();
869
  let remainingTime = durationInSeconds;
870
-
871
  countdownInterval = setInterval(() => {
872
  const statusEl = document.getElementById('loading-status-text');
873
  const progressEl = document.getElementById('loading-progress-bar');
874
-
875
  if (remainingTime >= 0) {
876
  const progress = ((durationInSeconds - remainingTime) / durationInSeconds) * 100;
877
  if (statusEl) statusEl.textContent = `در حال ساخت... زمان باقی‌مانده: ${remainingTime} ثانیه`;
@@ -885,18 +790,12 @@
885
  }, 1000);
886
  };
887
 
888
- const stopCountdown = () => {
889
- if (countdownInterval) {
890
- clearInterval(countdownInterval);
891
- countdownInterval = null;
892
- }
893
- };
894
 
895
  const setLoadingState = (isLoading, message = 'در حال پردازش...') => {
896
  const btnSpinner = submitBtn.querySelector('.spinner');
897
  const btnText = document.getElementById('btn-text');
898
  const btnIcon = submitBtn.querySelector('svg');
899
-
900
  if (isLoading) {
901
  clearResult();
902
  resultContainer.classList.add('loading');
@@ -919,24 +818,21 @@
919
  resultContainer.classList.remove('loading', 'has-error-guide');
920
  resultContainer.classList.add('has-content');
921
  resultContainer.innerHTML = resultWrapperHTML;
922
- document.getElementById('result-image-display').src = imageUrl;
 
 
923
  };
924
 
925
- const clearResult = () => {
926
- resultContainer.classList.remove('has-content', 'loading', 'has-error-guide');
927
- resultContainer.innerHTML = `<p>${'برای شروع، ایده، سبک و اندازه دلخواه خود را انتخاب کرده و دکمه "خلق کن" را بزنید.'}</p>`;
928
- };
929
 
930
  const displayError = (friendlyMessage, rawError) => {
931
  const rawErrorString = String(rawError?.message || rawError).toLowerCase();
932
  const gpuErrorKeywords = ["gpu capacity", "unavailable", "exceeded", "quota", "queue is full"];
933
  const isGpuError = gpuErrorKeywords.some(keyword => rawErrorString.includes(keyword));
934
-
935
  if (isGpuError) {
936
  resultContainer.classList.remove('loading');
937
  resultContainer.classList.add('has-error-guide');
938
  resultContainer.innerHTML = GPU_QUOTA_ERROR_HTML;
939
-
940
  resultContainer.querySelector('.back-button').addEventListener('click', clearResult);
941
  resultContainer.querySelector('.retry-button').addEventListener('click', generateImage);
942
  resultContainer.querySelector('#tutorialLinkBtn').addEventListener('click', () => {
@@ -953,22 +849,12 @@
953
  function updateStyleDisplay(lora) {
954
  selectedStyleNameFa.textContent = loraPersianNames[lora.title] || lora.title;
955
  selectedStyleImageLarge.src = `https://wsrv.nl/?url=${encodeURIComponent(lora.image)}&w=200&h=200&fit=cover&q=80&output=webp`;
956
-
957
  const descriptionP = stylePreviewContainer.querySelector('p');
958
- const tickHTML = `
959
- <span class="tick-wrapper">
960
- <svg class="tick-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
961
- <path class="tick-path" d="M5 13l4 4L19 7" />
962
- </svg>
963
- </span>`;
964
-
965
  descriptionP.innerHTML = `سبک ${loraPersianNames[lora.title] || lora.title} انتخاب شد. ${tickHTML}`;
966
-
967
  setTimeout(() => {
968
  const tickWrapper = descriptionP.querySelector('.tick-wrapper');
969
- if (tickWrapper) {
970
- tickWrapper.classList.add('show');
971
- }
972
  }, 50);
973
  }
974
 
@@ -978,9 +864,7 @@
978
  const card = document.createElement('div');
979
  card.className = 'lora-card';
980
  card.dataset.loraTitle = lora.title;
981
-
982
  const thumbnailUrl = `https://wsrv.nl/?url=${encodeURIComponent(lora.image)}&w=300&h=300&fit=cover&q=75&output=webp`;
983
-
984
  card.innerHTML = `<img data-src="${thumbnailUrl}" alt="${loraPersianNames[lora.title] || lora.title}" onerror="this.onerror=null;this.src='https://placehold.co/300x300/e0e0e0/909090?text=Error';">`;
985
  loraGrid.appendChild(card);
986
  });
@@ -999,33 +883,24 @@
999
  }
1000
 
1001
  async function handleStyleSelection(event) {
1002
- if (longPressJustHappened) {
1003
- longPressJustHappened = false;
1004
- return;
1005
- }
1006
  const card = event.target.closest('.lora-card');
1007
  if (!card) return;
1008
-
1009
  const cardTitleEn = card.dataset.loraTitle;
1010
  const newLora = loras.find(lora => lora.title === cardTitleEn);
1011
-
1012
  if (!newLora) return;
1013
-
1014
  selectedLora = newLora;
1015
  updateStyleDisplay(selectedLora);
1016
  styleModal.style.display = 'none';
1017
  resetModalTitle();
1018
-
1019
  if (fluxClient) {
1020
  const btnText = document.getElementById('btn-text');
1021
  const btnSpinner = submitBtn.querySelector('.spinner');
1022
  const btnIcon = submitBtn.querySelector('svg');
1023
-
1024
  submitBtn.disabled = true;
1025
  btnText.textContent = `در حال تنظیم سبک...`;
1026
  btnSpinner.style.display = 'inline-block';
1027
  btnIcon.style.display = 'none';
1028
-
1029
  try {
1030
  await fluxClient.predict("/add_custom_lora", { custom_lora: selectedLora.repo });
1031
  } catch (e) {
@@ -1049,11 +924,9 @@
1049
  btnText.textContent = 'در حال اتصال...';
1050
  submitBtn.disabled = true;
1051
  }
1052
-
1053
  try {
1054
  fluxClient = await Client.connect("prithivMLmods/FLUX-LoRA-DLC");
1055
  await fluxClient.predict("/add_custom_lora", { custom_lora: selectedLora.repo });
1056
-
1057
  if (!isCalledFromGenerate) {
1058
  const btnSpinner = submitBtn.querySelector('.spinner');
1059
  const btnText = document.getElementById('btn-text');
@@ -1085,9 +958,7 @@
1085
  if (!selectedLora) { alert('لطفاً ابتدا یک سبک هنری را انتخاب کنید.'); return; }
1086
  const userPrompt = promptInput.value.trim();
1087
  if (!userPrompt) { alert('لطفاً یک متن برای ساخت تصویر بنویسید.'); return; }
1088
-
1089
  setLoadingState(true, 'در حال پردازش...');
1090
-
1091
  try {
1092
  if (!fluxClient) {
1093
  setLoadingState(true, 'در حال اتصال مجدد...');
@@ -1099,7 +970,6 @@
1099
  return;
1100
  }
1101
  }
1102
-
1103
  setLoadingState(true, 'در حال پردازش...');
1104
  const getDimensions = (ratio) => {
1105
  switch (ratio) {
@@ -1110,47 +980,34 @@
1110
  }
1111
  };
1112
  const dimensions = getDimensions(selectedRatio);
1113
-
1114
  const statusEl = document.getElementById('loading-status-text');
1115
  if (statusEl) statusEl.textContent = 'در حال ترجمه متن...';
1116
-
1117
  const translatorClient = await Client.connect("hamed744/translate-tts-aloha");
1118
  const translationResult = await translatorClient.predict(1, [userPrompt, 'انگلیسی (آمریکا) - جنی (زن)', 0, 0, 0]);
1119
  const translatedPrompt = translationResult.data[0];
1120
  if (!translatedPrompt) throw new Error("سرویس ترجمه یک متن خالی برگرداند.");
1121
-
1122
  startCountdown(30);
1123
-
1124
  const finalPrompt = `${selectedLora.trigger_word} ${translatedPrompt}`;
1125
-
1126
  const result = await fluxClient.predict("/run_lora", {
1127
  prompt: finalPrompt, image_input: null, cfg_scale: 3.5, steps: 28,
1128
  randomize_seed: true, seed: 0,
1129
  width: dimensions.width, height: dimensions.height, lora_scale: 0.8,
1130
  });
1131
-
1132
  const imageData = result.data[0];
1133
  if (imageData && imageData.url) {
1134
  displayResult(imageData.url);
 
1135
  } else {
1136
  throw new Error('پاسخ سرور فاقد تصویر بود.');
1137
  }
1138
  setLoadingState(false);
1139
-
1140
  } catch (error) {
1141
  let friendlyMessage = `خطا در ارتباط با سرور. لطفاً اتصال اینترنت خود را بررسی کرده و دوباره تلاش کنید.`;
1142
- if (String(error.message).includes("hamed744/translate-tts-aloha")) {
1143
- friendlyMessage = `خطا در سرویس ترجمه. لطفاً دقایقی دیگر دوباره امتحان کنید.`;
1144
- }
1145
-
1146
  const rawErrorString = String(error?.message || error).toLowerCase();
1147
  const gpuErrorKeywords = ["gpu capacity", "unavailable", "exceeded", "quota", "queue is full"];
1148
  const isGpuError = gpuErrorKeywords.some(keyword => rawErrorString.includes(keyword));
1149
-
1150
- if (!isGpuError) {
1151
- fluxClient = null;
1152
- }
1153
-
1154
  setLoadingState(false);
1155
  displayError(friendlyMessage, error);
1156
  }
@@ -1162,13 +1019,10 @@
1162
  }
1163
 
1164
  const longPressDuration = 500;
1165
-
1166
  function handlePressStart(event) {
1167
  const card = event.target.closest('.lora-card');
1168
  if (!card) return;
1169
-
1170
  longPressJustHappened = false;
1171
-
1172
  longPressTimer = setTimeout(() => {
1173
  longPressJustHappened = true;
1174
  const cardTitleEn = card.dataset.loraTitle;
@@ -1176,49 +1030,61 @@
1176
  if (loraData) {
1177
  const loraNameFa = loraPersianNames[loraData.title] || loraData.title;
1178
  const thumbnailUrl = `https://wsrv.nl/?url=${encodeURIComponent(loraData.image)}&w=50&h=50&fit=cover&q=80&output=webp`;
1179
- const newTitleHTML = `
1180
- <div class="style-info-box">
1181
- <img src="${thumbnailUrl}" class="style-info-image" alt="${loraNameFa}">
1182
- <div class="style-info-text">
1183
- <span class="persian-name">${loraNameFa}</span>
1184
- <span class="english-name">${loraData.title}</span>
1185
- </div>
1186
- </div>`;
1187
- modalTitle.innerHTML = newTitleHTML;
1188
  }
1189
  }, longPressDuration);
1190
  }
 
 
 
 
 
 
 
 
 
 
 
1191
 
1192
- function handlePressEnd(event) {
1193
- clearTimeout(longPressTimer);
1194
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1195
 
1196
  loraGrid.addEventListener('mousedown', handlePressStart);
1197
  loraGrid.addEventListener('touchstart', handlePressStart, { passive: true });
1198
  loraGrid.addEventListener('mouseup', handlePressEnd);
1199
  loraGrid.addEventListener('mouseleave', handlePressEnd);
1200
  loraGrid.addEventListener('touchend', handlePressEnd);
1201
-
1202
- stylePreviewContainer.addEventListener('click', () => {
1203
- styleModal.style.display = 'flex';
1204
- if (loraGrid.children.length === 0) populateGrid();
1205
- });
1206
-
1207
- closeModalBtn.addEventListener('click', () => {
1208
- styleModal.style.display = 'none';
1209
- resetModalTitle();
1210
- });
1211
-
1212
- window.addEventListener('click', (event) => {
1213
- if (event.target === styleModal) {
1214
- styleModal.style.display = 'none';
1215
- resetModalTitle();
1216
- }
1217
- });
1218
-
1219
  loraGrid.addEventListener('click', handleStyleSelection);
1220
  submitBtn.addEventListener('click', generateImage);
1221
-
1222
  ratioSelector.addEventListener('click', (event) => {
1223
  const selectedOption = event.target.closest('.ratio-option');
1224
  if (!selectedOption) return;
@@ -1226,17 +1092,13 @@
1226
  selectedOption.classList.add('active');
1227
  selectedRatio = selectedOption.dataset.ratio;
1228
  });
1229
-
1230
- clearResult();
1231
-
1232
- function setInitialStyle() {
1233
- selectedLora = loras[0];
1234
- updateStyleDisplay(selectedLora);
1235
- }
1236
-
1237
- setInitialStyle();
1238
- initializeClient();
1239
-
1240
  </script>
1241
 
1242
  <script>
 
20
  --accent-primary-hover: #3553D6;
21
  --accent-primary-glow: rgba(74, 108, 250, 0.25);
22
  --accent-secondary: #0FD4A8;
23
+ --danger-color: #e53e3e;
24
+ --danger-color-hover: #c53030;
25
  --shadow-sm: 0 1px 2px 0 rgba(26, 32, 44, 0.03);
26
  --shadow-md: 0 4px 6px -1px rgba(26, 32, 44, 0.05), 0 2px 4px -2px rgba(26, 32, 44, 0.04);
27
  --shadow-lg: 0 10px 15px -3px rgba(26, 32, 44, 0.06), 0 4px 6px -4px rgba(26, 32, 44, 0.05);
 
96
  }
97
  .subtitle { font-size: 1.1rem; color: var(--text-secondary); margin-top: 0; max-width: 650px; margin-left: auto; margin-right: auto; line-height: 1.8; }
98
 
99
+ main, .gallery-section {
100
  padding: 3rem;
101
  background-color: var(--panel-bg);
102
  border-radius: var(--radius-card);
 
104
  border: 1px solid var(--panel-border);
105
  animation: fadeIn 0.8s 0.3s ease-out backwards;
106
  }
107
+ .gallery-section {
108
+ margin-top: 3rem;
109
+ animation-delay: 0.5s;
110
+ }
111
 
112
  .form-group { margin-bottom: 2.5rem; }
113
  .form-group:last-child { margin-bottom: 0; }
 
289
 
290
  #result-image-wrapper { display: none; width: 100%; }
291
  #result-container.has-content #result-image-wrapper { display: block; animation: fadeIn 0.5s; text-align: center; }
292
+ #result-image-display { max-width: 100%; max-height: 500px; object-fit: contain; border-radius: var(--radius-input); box-shadow: var(--shadow-md); border: 1px solid var(--panel-border); cursor: pointer; transition: var(--transition-smooth); }
293
+ #result-image-display:hover { box-shadow: var(--shadow-lg); transform: scale(1.02); }
294
  .error-message { color: #d93025; font-weight: bold; margin-top: 10px; background: #ffebee; padding: 1rem; border-radius: var(--radius-input); border: 1px solid #e57373; }
295
 
296
  .output-details { display: none !important; }
 
322
  gap: 1rem;
323
  padding: 10px;
324
  }
 
 
325
  .lora-card {
326
+ position: relative;
327
  height: 0;
328
+ padding-bottom: 100%;
329
  border-radius: var(--radius-input);
330
  overflow: hidden;
331
  cursor: pointer;
 
333
  background-color: var(--input-bg);
334
  border: 1px solid transparent;
335
  box-shadow: var(--shadow-sm);
336
+ -webkit-user-select: none;
337
+ -ms-user-select: none;
338
+ user-select: none;
339
  }
 
340
  .lora-card:hover {
341
  transform: translateY(-5px);
342
  box-shadow: var(--shadow-lg);
343
  border-color: var(--accent-primary);
344
  }
 
345
  .lora-card img {
346
+ position: absolute;
347
  top: 0;
348
  left: 0;
349
  width: 100%;
350
+ height: 100%;
351
  object-fit: cover;
352
  transition: opacity 0.3s ease-in-out;
353
  opacity: 0;
354
  }
355
+ .lora-card img.loaded { opacity: 1; }
 
 
 
 
356
 
357
  .ip-reset-guide-container { text-align: right; background: var(--panel-bg); padding: 10px; border-radius: var(--radius-lg-guide); animation: slideInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1) both; max-width: 100%; width: 100%; position: relative; overflow: hidden; box-sizing: border-box; }
358
  .ip-reset-guide-container::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 5px; background: var(--primary-gradient-guide); }
 
420
  font-weight: 500;
421
  }
422
 
423
+ .gallery-header { display: flex; justify-content: space-between; align-items: center; }
424
+ #clear-history-btn { background: none; border: 1px solid var(--panel-border); color: var(--text-secondary); padding: 0.5rem 1rem; border-radius: var(--radius-btn); cursor: pointer; display: none; align-items: center; gap: 0.5rem; font-family: var(--app-font); font-weight: 500; transition: all 0.2s; }
425
+ #clear-history-btn:hover { border-color: var(--danger-color); color: var(--danger-color); }
426
+ #clear-history-btn svg { width: 18px; height: 18px; }
427
+
428
+ #history-grid {
429
+ display: grid;
430
+ grid-template-columns: repeat(4, 1fr);
431
+ gap: 1rem;
432
+ margin-top: 1.5rem;
433
+ }
434
+ #history-grid:empty::before {
435
+ content: 'هنوز تصویری خلق نکرده‌اید. اولین اثر شما اینجا ذخیره خواهد شد. (توجه: تصاویر پس از ۶ ساعت برای حفظ حریم خصوصی حذف می‌شوند)';
436
+ color: var(--text-secondary);
437
+ grid-column: 1 / -1;
438
+ text-align: center;
439
+ padding: 3rem 1rem;
440
+ background-color: var(--input-bg);
441
+ border-radius: var(--radius-card);
442
+ line-height: 1.8;
443
+ }
444
+ .history-item { position: relative; }
445
+ .history-item img {
446
+ width: 100%;
447
+ aspect-ratio: 1 / 1;
448
+ object-fit: cover;
449
+ border-radius: var(--radius-input);
450
+ cursor: pointer;
451
+ transition: var(--transition-smooth);
452
+ border: 1px solid var(--panel-border);
453
+ }
454
+ .history-item:hover img {
455
+ transform: translateY(-5px) scale(1.05);
456
+ box-shadow: var(--shadow-lg);
457
+ z-index: 5;
458
+ }
459
+ .history-delete-btn { position: absolute; top: 0.5rem; right: 0.5rem; background: rgba(26,32,44,0.6); border: none; cursor: pointer; padding: 0.5rem; border-radius: 50%; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; color: white; transition: var(--transition-smooth); z-index: 6; opacity: 0; }
460
+ .history-item:hover .history-delete-btn { opacity: 1; }
461
+ .history-delete-btn:hover { background-color: var(--danger-color); }
462
+ .history-delete-btn svg { width: 18px; height: 18px; }
463
+
464
+ #lightbox {
465
+ position: fixed;
466
+ inset: 0;
467
+ background-color: rgba(18, 24, 38, 0.6);
468
+ backdrop-filter: blur(10px) saturate(150%);
469
+ display: flex;
470
+ align-items: center;
471
+ justify-content: center;
472
+ z-index: 1000;
473
+ opacity: 0;
474
+ transition: opacity 0.3s;
475
+ pointer-events: none;
476
+ }
477
+ #lightbox.visible {
478
+ opacity: 1;
479
+ pointer-events: auto;
480
+ }
481
+ #lightbox-content {
482
+ position: relative;
483
+ animation: fadeIn 0.3s ease;
484
+ display: flex;
485
+ align-items: center;
486
+ justify-content: center;
487
+ }
488
+ #lightbox-img {
489
+ max-width: 85vw;
490
+ max-height: 80vh;
491
+ object-fit: contain;
492
+ border-radius: var(--radius-card);
493
+ box-shadow: var(--shadow-xl);
494
+ }
495
+ .lightbox-btn {
496
+ position: absolute;
497
+ background-color: rgba(255, 255, 255, 0.1);
498
+ color: white;
499
+ border: 1px solid rgba(255,255,255,0.2);
500
+ width: 44px;
501
+ height: 44px;
502
+ border-radius: 50%;
503
+ cursor: pointer;
504
+ display: flex;
505
+ justify-content: center;
506
+ align-items: center;
507
+ transition: all 0.2s;
508
+ text-decoration: none;
509
+ font-size: 24px;
510
+ }
511
+ .lightbox-btn:hover {
512
+ background-color: rgba(255, 255, 255, 0.2);
513
+ transform: scale(1.1);
514
+ }
515
+ #lightbox-close { top: -50px; right: 0; }
516
+ #lightbox-download { top: -50px; left: 0; }
517
+
518
+ #confirmation-modal { position: fixed; inset: 0; background-color: rgba(18, 24, 38, 0.6); backdrop-filter: blur(10px); display: flex; align-items: center; justify-content: center; z-index: 2000; opacity: 0; transition: opacity 0.3s; pointer-events: none; }
519
+ #confirmation-modal.visible { opacity: 1; pointer-events: auto; }
520
+ .modal-dialog { background: var(--panel-bg); color: var(--text-primary); border-radius: var(--radius-card); padding: 2rem; width: 90%; max-width: 400px; text-align: center; box-shadow: var(--shadow-xl); animation: fadeIn 0.3s ease-out backwards; }
521
+ .modal-icon { width: 48px; height: 48px; margin: 0 auto 1.5rem; color: var(--danger-color); }
522
+ .modal-dialog h3 { font-size: 1.5rem; margin: 0 0 0.75rem; font-weight: 700; }
523
+ .modal-dialog p { font-size: 1rem; color: var(--text-secondary); margin: 0 0 2rem; }
524
+ .modal-buttons { display: flex; gap: 1rem; }
525
+ .modal-btn { flex: 1; padding: 0.8rem 1rem; border-radius: var(--radius-btn); border: none; cursor: pointer; font-family: var(--app-font); font-weight: 600; font-size: 1rem; transition: var(--transition-smooth); }
526
+ .modal-btn.confirm-btn { background-color: var(--danger-color); color: white; }
527
+ .modal-btn.confirm-btn:hover { background-color: var(--danger-color-hover); transform: translateY(-2px); }
528
+ .modal-btn.cancel-btn { background-color: var(--input-bg); color: var(--text-secondary); border: 1px solid var(--input-border); }
529
+ .modal-btn.cancel-btn:hover { background-color: var(--panel-border); color: var(--text-primary); }
530
+
531
  @media (max-width: 768px) {
532
+ main, .gallery-section { padding: 1.5rem; }
533
+ h1 { font-size: 2.2rem; }
534
  .aspect-ratio-selector { grid-template-columns: repeat(2, 1fr); }
535
  .style-preview-card { flex-direction: column; text-align: center; gap: 1rem; }
536
  #selected-style-image-large { margin-left: 0; }
537
+ #history-grid { grid-template-columns: repeat(2, 1fr); }
538
  }
539
  </style>
540
  </head>
 
612
 
613
  <div class="output-details" id="output-details"></div>
614
  </main>
615
+
616
+ <section class="gallery-section">
617
+ <div class="gallery-header">
618
+ <div class="form-label">
619
+ <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"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg>
620
+ گالری و تاریخچه شما
621
+ </div>
622
+ <button id="clear-history-btn">
623
+ <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="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
624
+ <span>پاک کردن همه</span>
625
+ </button>
626
+ </div>
627
+ <div id="history-grid"></div>
628
+ </section>
629
  </div>
630
 
631
  <div class="modal-overlay" id="style-modal">
 
637
  <div id="lora-grid"></div>
638
  </div>
639
  </div>
640
+
641
+ <div id="lightbox">
642
+ <div id="lightbox-content">
643
+ <img id="lightbox-img" src="">
644
+ <button id="lightbox-close" class="lightbox-btn" title="بستن">&times;</button>
645
+ <a id="lightbox-download" title="دانلود تصویر" class="lightbox-btn" href="#" download>
646
+ <svg fill="white" viewBox="0 0 24 24"><path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/></svg>
647
+ </a>
648
+ </div>
649
+ </div>
650
+
651
+ <div id="confirmation-modal">
652
+ <div class="modal-dialog">
653
+ <div class="modal-icon"><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.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></div>
654
+ <h3>تایید حذف</h3>
655
+ <p id="modal-message-text">آیا از انجام این عمل مطمئن هستید؟</p>
656
+ <div class="modal-buttons"><button id="modal-cancel-btn" class="modal-btn cancel-btn">انصراف</button><button id="modal-confirm-btn" class="modal-btn confirm-btn">بله، حذف کن</button></div>
657
+ </div>
658
+ </div>
659
 
660
  <script type="module">
661
  import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
662
 
663
  const loras = [
664
+ { "image": "https://huggingface.co/prithivMLmods/Flux-Dalle-Mix-LoRA/resolve/main/images/D3.png", "title": "Dalle Mix", "repo": "prithivMLmods/Flux-Dalle-Mix-LoRA", "trigger_word": "dalle-mix" },
665
+ { "image": "https://huggingface.co/alvdansen/flux-koda/resolve/main/images/ComfyUI_00583_%20(1).png", "title": "Koda", "repo": "alvdansen/flux-koda", "trigger_word": "flmft style" },
666
+ { "image": "https://huggingface.co/strangerzonehf/Flux-Super-Realism-LoRA/resolve/main/images/1.png", "title": "Super Realism", "repo": "strangerzonehf/Flux-Super-Realism-LoRA", "trigger_word": "Super Realism" },
667
+ { "image": "https://huggingface.co/strangerzonehf/Ghibli-Flux-Cartoon-LoRA/resolve/main/images/3333.png", "title": "Ghibli Flux", "repo": "strangerzonehf/Ghibli-Flux-Cartoon-LoRA", "trigger_word": "Ghibli Art" },
668
+ { "image": "https://huggingface.co/strangerzonehf/Flux-Sketch-Smudge-LoRA/resolve/main/images/5.png", "title": "Sketch_Smudge", "repo": "strangerzonehf/Flux-Sketch-Smudge-LoRA", "trigger_word": " Sketch Smudge" },
669
+ { "image": "https://huggingface.co/strangerzonehf/Flux-Animeo-v1-LoRA/resolve/main/images/A4.png", "title": "Animeo Mix", "repo": "strangerzonehf/Flux-Animeo-v1-LoRA", "trigger_word": "Animeo" },
670
+ { "image": "https://huggingface.co/strangerzonehf/Flux-Animex-v2-LoRA/resolve/main/images/A33.png", "title": "Animex Mix", "repo": "strangerzonehf/Flux-Animex-v2-LoRA", "trigger_word": "Animex" },
671
+ { "image": "https://huggingface.co/strangerzonehf/Flux-Super-Portrait-LoRA/resolve/main/images/3.png", "title": "Super Portraits", "repo": "strangerzonehf/Flux-Super-Portrait-LoRA", "trigger_word": "Super Portrait" },
672
+ { "image": "https://huggingface.co/strangerzonehf/Flux-Super-Blend-LoRA/resolve/main/images/SB1.png", "title": "Super Blend", "repo": "strangerzonehf/Flux-Super-Blend-LoRA", "trigger_word": "Super Blend" },
673
+ { "image": "https://huggingface.co/strangerzonehf/Flux-Midjourney-Mix2-LoRA/resolve/main/images/3.png", "title": "Midjourney Mix 2", "repo": "strangerzonehf/Flux-Midjourney-Mix2-LoRA", "trigger_word": "MJ v6" },
674
+ { "image": "https://huggingface.co/strangerzonehf/Flux-Ultimate-LoRA-Collection/resolve/main/images/example_aqz3dv60n.jpeg", "title": "SHOU_XIN", "repo": "Datou1111/shou_xin", "trigger_word": "shou_xin, pencil sketch" },
675
+ { "image": "https://huggingface.co/prithivMLmods/Flux-Long-Toon-LoRA/resolve/main/images/LT5.png", "title": "Long Toons", "repo": "prithivMLmods/Flux-Long-Toon-LoRA", "trigger_word": "Long toons" },
676
+ { "image": "https://huggingface.co/strangerzonehf/Flux-Cute-3D-Kawaii-LoRA/resolve/main/images/CK3.png", "title": "Cute 3D Kawaii", "repo": "strangerzonehf/Flux-Cute-3D-Kawaii-LoRA", "trigger_word": "Cute 3d Kawaii" },
677
+ { "image": "https://huggingface.co/strangerzonehf/Flux-Isometric-3D-LoRA/resolve/main/images/ID2.png", "title": "Isometric 3D", "repo": "strangerzonehf/Flux-Isometric-3D-LoRA", "trigger_word": "Isometric 3D" },
678
+ { "image": "https://huggingface.co/prithivMLmods/Flux-Toonic-2.5D-LoRA/resolve/main/images/T2.png", "title": "Toon 2.5D", "repo": "prithivMLmods/Flux-Toonic-2.5D-LoRA", "trigger_word": "toonic 2.5D" },
679
+ { "image": "https://huggingface.co/strangerzonehf/Flux-YWL-Realism-LoRA/resolve/main/images/R3.png", "title": "YWL Realism", "repo": "strangerzonehf/Flux-YWL-Realism-LoRA", "trigger_word": "ylw realism" },
680
+ { "image": "https://huggingface.co/prithivMLmods/Flux-Chill-Guy-Zone/resolve/main/images/8.png", "title": "Chill Guy", "repo": "prithivMLmods/Flux-Chill-Guy-Zone", "trigger_word": "chill guy" },
681
+ { "image": "https://huggingface.co/p1atdev/flux.1-schnell-pvc-style-lora/resolve/main/images/flux_lora_00221_.png", "title": "Anime PVC Style", "repo": "p1atdev/flux.1-schnell-pvc-style-lora", "trigger_word": "pvc figure, nendoroid, figma" },
682
+ { "image": "https://huggingface.co/strangerzonehf/Flux-C4C-Design-LoRA/resolve/main/images/4.png", "title": "Smiley C4C", "repo": "strangerzonehf/Flux-C4C-Design-LoRA", "trigger_word": "Smiley C4C" },
683
+ { "image": "https://huggingface.co/prithivMLmods/Purple-Dreamy-Flux-LoRA/resolve/main/images/PD3.png", "title": "Purple Dream", "repo": "prithivMLmods/Purple-Dreamy-Flux-LoRA", "trigger_word": "Purple Dreamy" },
684
+ { "image": "https://huggingface.co/prithivMLmods/Canopus-LoRA-Flux-FaceRealism/resolve/main/images/11.png", "title": "Flux Face Realism", "repo": "prithivMLmods/Canopus-LoRA-Flux-FaceRealism", "trigger_word": "Realism" },
685
+ { "image": "https://huggingface.co/alvdansen/softserve_anime/resolve/main/images/ComfyUI_00134_.png", "title": "Softserve Anime", "repo": "alvdansen/softserve_anime", "trigger_word": "sftsrv style illustration" },
686
+ { "image": "https://huggingface.co/prithivMLmods/Fashion-Hut-Modeling-LoRA/resolve/main/images/MO1.png", "title": "Modeling Hut", "repo": "prithivMLmods/Fashion-Hut-Modeling-LoRA", "trigger_word": "Modeling of" },
687
+ { "image": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-LoRA-One-Click-Creative-Template/resolve/main/images/f2cc649985648e57b9b9b14ca7a8744ac8e50d75b3a334ed4df0f368.jpg", "title": "Creative Template", "repo": "Shakker-Labs/FLUX.1-dev-LoRA-One-Click-Creative-Template", "trigger_word": "The background is 4 real photos, and in the middle is a cartoon picture summarizing the real photos." },
688
+ { "image": "https://huggingface.co/prithivMLmods/Canopus-LoRA-Flux-UltraRealism-2.0/resolve/main/images/XX.png", "title": "Ultra Realism", "repo": "prithivMLmods/Canopus-LoRA-Flux-UltraRealism-2.0", "trigger_word": "Ultra realistic" },
689
+ { "image": "https://huggingface.co/gokaygokay/Flux-Game-Assets-LoRA-v2/resolve/main/images/example_y2bqpuphc.png", "title": "Game Assets", "repo": "gokaygokay/Flux-Game-Assets-LoRA-v2", "trigger_word": "wbgmsst, white background" },
690
+ { "image": "https://huggingface.co/alvdansen/softpasty-flux-dev/resolve/main/images/ComfyUI_00814_%20(2).png", "title": "Softpasty", "repo": "alvdansen/softpasty-flux-dev", "trigger_word": "araminta_illus illustration style" },
691
+ { "image": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-LoRA-add-details/resolve/main/images/0.png", "title": "Details Add", "repo": "Shakker-Labs/FLUX.1-dev-LoRA-add-details", "trigger_word": "" },
692
+ { "image": "https://huggingface.co/prithivMLmods/Canopus-LoRA-Flux-Anime/resolve/main/assets/4.png", "title": "Flux Anime", "repo": "prithivMLmods/Canopus-LoRA-Flux-Anime", "trigger_word": "Anime" },
693
+ { "image": "https://huggingface.co/aleksa-codes/flux-ghibsky-illustration/resolve/main/images/example5.jpg", "title": "Ghibsky Illustration", "repo": "aleksa-codes/flux-ghibsky-illustration", "trigger_word": "GHIBSKY style painting" },
694
+ { "image": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-LoRA-Dark-Fantasy/resolve/main/images/c2215bd73da9f14fcd63cc93350e66e2901bdafa6fb8abaaa2c32a1b.jpg", "title": "Dark Fantasy", "repo": "Shakker-Labs/FLUX.1-dev-LoRA-Dark-Fantasy", "trigger_word": "" },
695
+ { "image": "https://huggingface.co/Norod78/Flux_1_Dev_LoRA_Paper-Cutout-Style/resolve/main/d13591878d5043f3989dd6eb1c25b710_233c18effb4b491cb467ca31c97e90b5.png", "title": "Paper Cutout", "repo": "Norod78/Flux_1_Dev_LoRA_Paper-Cutout-Style", "trigger_word": "Paper Cutout Style" },
696
+ { "image": "https://huggingface.co/alvdansen/mooniverse/resolve/main/images/out-0%20(17).webp", "title": "Mooniverse", "repo": "alvdansen/mooniverse", "trigger_word": "surreal style" },
697
+ { "image": "https://huggingface.co/alvdansen/pola-photo-flux/resolve/main/images/out-0%20-%202024-09-22T130819.351.webp", "title": "Pola Photo", "repo": "alvdansen/pola-photo-flux", "trigger_word": "polaroid style" },
698
+ { "image": "https://huggingface.co/multimodalart/flux-tarot-v1/resolve/main/images/7e180627edd846e899b6cd307339140d_5b2a09f0842c476b83b6bd2cb9143a52.png", "title": "Flux Tarot", "repo": "multimodalart/flux-tarot-v1", "trigger_word": "in the style of TOK a trtcrd tarot style" },
699
+ { "image": "https://huggingface.co/prithivMLmods/Flux-Dev-Real-Anime-LoRA/resolve/main/images/111.png", "title": "Real Anime", "repo": "prithivMLmods/Flux-Dev-Real-Anime-LoRA", "trigger_word": "Real Anime" },
700
+ { "image": "https://huggingface.co/diabolic6045/Flux_Sticker_Lora/resolve/main/images/example_s3pxsewcb.png", "title": "Stickers", "repo": "diabolic6045/Flux_Sticker_Lora", "trigger_word": "5t1cker 5ty1e" },
701
+ { "image": "https://huggingface.co/VideoAditor/Flux-Lora-Realism/resolve/main/images/feel-the-difference-between-using-flux-with-lora-from-xlab-v0-j0ehybmvxehd1.png", "title": "Realism", "repo": "XLabs-AI/flux-RealismLora", "trigger_word": "" },
702
+ { "image": "https://huggingface.co/mgwr/Cine-Aesthetic/resolve/main/images/00019-1333633802.png", "title": "Cine Aesthetic", "repo": "mgwr/Cine-Aesthetic", "trigger_word": "mgwr/cine" },
703
+ { "image": "https://huggingface.co/SebastianBodza/flux_cute3D/resolve/main/images/astronaut.webp", "title": "Cute 3D", "repo": "SebastianBodza/flux_cute3D", "trigger_word": "NEOCUTE3D" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
704
  ];
705
 
706
  const loraPersianNames = {
707
+ "Dalle Mix": "ترکیب دالی", "Koda": "کُدا", "Super Realism": "واقع‌گرایی خارق‌العاده", "Ghibli Flux": "سبک جیبلی", "Sketch_Smudge": "طراحی و لکه", "Animeo Mix": "ترکیب انیمه", "Animex Mix": "ترکیب انیمکس", "Super Portraits": "پرتره‌های ویژه", "Super Blend": "ترکیب ویژه", "Midjourney Mix 2": "ترکیب میدجرنی ۲", "SHOU_XIN": "شو شین", "Long Toons": "کارتون‌های بلند", "Cute 3D Kawaii": "کاوایی سه‌بعدی", "Isometric 3D": "ایزومتریک سه‌بعدی", "Toon 2.5D": "کارتون ۲.۵ بعدی", "YWL Realism": "واقع‌گرایی YWL", "Chill Guy": "پسر آرام", "Anime PVC Style": "سبک انیمه PVC", "Smiley C4C": "لبخند C4C", "Purple Dream": "رویای بنفش", "Flux Face Realism": "چهره واقع‌گرایانه", "Softserve Anime": "انیمه سافت‌سرو", "Modeling Hut": "کلبه مدلینگ", "Creative Template": "قالب خلاقانه", "Ultra Realism": "فرا واقع‌گرایی", "Game Assets": "آیتم‌های بازی", "Softpasty": "سافت‌پِیستی", "Details Add": "افزودن جزئیات", "Flux Anime": "فلاکس انیمه", "Ghibsky Illustration": "تصویرسازی گیبسکی", "Dark Fantasy": "فانتزی تاریک", "Paper Cutout": "هنر کاغذبری", "Mooniverse": "دنیای ماه", "Pola Photo": "عکس پولاروید", "Flux Tarot": "فال تاروت", "Real Anime": "انیمه واقعی", "Stickers": "استیکرها", "Realism": "واقع‌گرایی", "Cine Aesthetic": "زیبایی‌شناسی سینمایی", "Cute 3D": "سه‌بعدی بامزه"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
708
  };
709
 
710
  const GPU_QUOTA_ERROR_HTML = `
 
746
  const stylePreviewContainer = document.getElementById('style-preview-container');
747
  const selectedStyleImageLarge = document.getElementById('selected-style-image-large');
748
  const selectedStyleNameFa = document.getElementById('selected-style-name-fa');
 
749
  const modalTitle = document.querySelector('#style-modal .modal-header h2');
750
  const originalModalTitleHTML = modalTitle.innerHTML;
751
+
752
+ const historyGrid = document.getElementById('history-grid');
753
+ const clearHistoryBtn = document.getElementById('clear-history-btn');
754
+ const lightbox = document.getElementById('lightbox');
755
+ const lightboxImg = document.getElementById('lightbox-img');
756
+ const lightboxClose = document.getElementById('lightbox-close');
757
+ const lightboxDownload = document.getElementById('lightbox-download');
758
+ const confirmationModal = document.getElementById('confirmation-modal');
759
+ const modalMessageText = document.getElementById('modal-message-text');
760
+ const modalConfirmBtn = document.getElementById('modal-confirm-btn');
761
+ const modalCancelBtn = document.getElementById('modal-cancel-btn');
762
+
763
  let longPressTimer = null;
764
  let longPressJustHappened = false;
 
765
  let selectedLora = null;
766
  let selectedRatio = '1:1';
767
  let imageObserver = null;
768
  let fluxClient = null;
769
  let countdownInterval = null;
770
 
771
+ const loadingPlaceholderHTML = `<div id="loading-placeholder"><div class="generator-container"><div class="text-overlay" id="loading-status-text">در حال آماده‌سازی...</div><div class="progress-bar" id="loading-progress-bar"></div></div></div>`;
 
 
 
 
 
 
772
  const resultWrapperHTML = `<div id="result-image-wrapper"><img id="result-image-display" src="" alt="تصویر خلق شده"></div>`;
773
 
774
  const startCountdown = (durationInSeconds) => {
775
  stopCountdown();
776
  let remainingTime = durationInSeconds;
 
777
  countdownInterval = setInterval(() => {
778
  const statusEl = document.getElementById('loading-status-text');
779
  const progressEl = document.getElementById('loading-progress-bar');
 
780
  if (remainingTime >= 0) {
781
  const progress = ((durationInSeconds - remainingTime) / durationInSeconds) * 100;
782
  if (statusEl) statusEl.textContent = `در حال ساخت... زمان باقی‌مانده: ${remainingTime} ثانیه`;
 
790
  }, 1000);
791
  };
792
 
793
+ const stopCountdown = () => { if (countdownInterval) { clearInterval(countdownInterval); countdownInterval = null; } };
 
 
 
 
 
794
 
795
  const setLoadingState = (isLoading, message = 'در حال پردازش...') => {
796
  const btnSpinner = submitBtn.querySelector('.spinner');
797
  const btnText = document.getElementById('btn-text');
798
  const btnIcon = submitBtn.querySelector('svg');
 
799
  if (isLoading) {
800
  clearResult();
801
  resultContainer.classList.add('loading');
 
818
  resultContainer.classList.remove('loading', 'has-error-guide');
819
  resultContainer.classList.add('has-content');
820
  resultContainer.innerHTML = resultWrapperHTML;
821
+ const resultImage = document.getElementById('result-image-display');
822
+ resultImage.src = imageUrl;
823
+ resultImage.addEventListener('click', () => openLightbox(imageUrl));
824
  };
825
 
826
+ const clearResult = () => { resultContainer.classList.remove('has-content', 'loading', 'has-error-guide'); resultContainer.innerHTML = `<p>${'برای شروع، ایده، سبک و اندازه دلخواه خود را انتخاب کرده و دکمه "خلق کن" را بزنید.'}</p>`; };
 
 
 
827
 
828
  const displayError = (friendlyMessage, rawError) => {
829
  const rawErrorString = String(rawError?.message || rawError).toLowerCase();
830
  const gpuErrorKeywords = ["gpu capacity", "unavailable", "exceeded", "quota", "queue is full"];
831
  const isGpuError = gpuErrorKeywords.some(keyword => rawErrorString.includes(keyword));
 
832
  if (isGpuError) {
833
  resultContainer.classList.remove('loading');
834
  resultContainer.classList.add('has-error-guide');
835
  resultContainer.innerHTML = GPU_QUOTA_ERROR_HTML;
 
836
  resultContainer.querySelector('.back-button').addEventListener('click', clearResult);
837
  resultContainer.querySelector('.retry-button').addEventListener('click', generateImage);
838
  resultContainer.querySelector('#tutorialLinkBtn').addEventListener('click', () => {
 
849
  function updateStyleDisplay(lora) {
850
  selectedStyleNameFa.textContent = loraPersianNames[lora.title] || lora.title;
851
  selectedStyleImageLarge.src = `https://wsrv.nl/?url=${encodeURIComponent(lora.image)}&w=200&h=200&fit=cover&q=80&output=webp`;
 
852
  const descriptionP = stylePreviewContainer.querySelector('p');
853
+ const tickHTML = `<span class="tick-wrapper"><svg class="tick-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path class="tick-path" d="M5 13l4 4L19 7" /></svg></span>`;
 
 
 
 
 
 
854
  descriptionP.innerHTML = `سبک ${loraPersianNames[lora.title] || lora.title} انتخاب شد. ${tickHTML}`;
 
855
  setTimeout(() => {
856
  const tickWrapper = descriptionP.querySelector('.tick-wrapper');
857
+ if (tickWrapper) tickWrapper.classList.add('show');
 
 
858
  }, 50);
859
  }
860
 
 
864
  const card = document.createElement('div');
865
  card.className = 'lora-card';
866
  card.dataset.loraTitle = lora.title;
 
867
  const thumbnailUrl = `https://wsrv.nl/?url=${encodeURIComponent(lora.image)}&w=300&h=300&fit=cover&q=75&output=webp`;
 
868
  card.innerHTML = `<img data-src="${thumbnailUrl}" alt="${loraPersianNames[lora.title] || lora.title}" onerror="this.onerror=null;this.src='https://placehold.co/300x300/e0e0e0/909090?text=Error';">`;
869
  loraGrid.appendChild(card);
870
  });
 
883
  }
884
 
885
  async function handleStyleSelection(event) {
886
+ if (longPressJustHappened) { longPressJustHappened = false; return; }
 
 
 
887
  const card = event.target.closest('.lora-card');
888
  if (!card) return;
 
889
  const cardTitleEn = card.dataset.loraTitle;
890
  const newLora = loras.find(lora => lora.title === cardTitleEn);
 
891
  if (!newLora) return;
 
892
  selectedLora = newLora;
893
  updateStyleDisplay(selectedLora);
894
  styleModal.style.display = 'none';
895
  resetModalTitle();
 
896
  if (fluxClient) {
897
  const btnText = document.getElementById('btn-text');
898
  const btnSpinner = submitBtn.querySelector('.spinner');
899
  const btnIcon = submitBtn.querySelector('svg');
 
900
  submitBtn.disabled = true;
901
  btnText.textContent = `در حال تنظیم سبک...`;
902
  btnSpinner.style.display = 'inline-block';
903
  btnIcon.style.display = 'none';
 
904
  try {
905
  await fluxClient.predict("/add_custom_lora", { custom_lora: selectedLora.repo });
906
  } catch (e) {
 
924
  btnText.textContent = 'در حال اتصال...';
925
  submitBtn.disabled = true;
926
  }
 
927
  try {
928
  fluxClient = await Client.connect("prithivMLmods/FLUX-LoRA-DLC");
929
  await fluxClient.predict("/add_custom_lora", { custom_lora: selectedLora.repo });
 
930
  if (!isCalledFromGenerate) {
931
  const btnSpinner = submitBtn.querySelector('.spinner');
932
  const btnText = document.getElementById('btn-text');
 
958
  if (!selectedLora) { alert('لطفاً ابتدا یک سبک هنری را انتخاب کنید.'); return; }
959
  const userPrompt = promptInput.value.trim();
960
  if (!userPrompt) { alert('لطفاً یک متن برای ساخت تصویر بنویسید.'); return; }
 
961
  setLoadingState(true, 'در حال پردازش...');
 
962
  try {
963
  if (!fluxClient) {
964
  setLoadingState(true, 'در حال اتصال مجدد...');
 
970
  return;
971
  }
972
  }
 
973
  setLoadingState(true, 'در حال پردازش...');
974
  const getDimensions = (ratio) => {
975
  switch (ratio) {
 
980
  }
981
  };
982
  const dimensions = getDimensions(selectedRatio);
 
983
  const statusEl = document.getElementById('loading-status-text');
984
  if (statusEl) statusEl.textContent = 'در حال ترجمه متن...';
 
985
  const translatorClient = await Client.connect("hamed744/translate-tts-aloha");
986
  const translationResult = await translatorClient.predict(1, [userPrompt, 'انگلیسی (آمریکا) - جنی (زن)', 0, 0, 0]);
987
  const translatedPrompt = translationResult.data[0];
988
  if (!translatedPrompt) throw new Error("سرویس ترجمه یک متن خالی برگرداند.");
 
989
  startCountdown(30);
 
990
  const finalPrompt = `${selectedLora.trigger_word} ${translatedPrompt}`;
 
991
  const result = await fluxClient.predict("/run_lora", {
992
  prompt: finalPrompt, image_input: null, cfg_scale: 3.5, steps: 28,
993
  randomize_seed: true, seed: 0,
994
  width: dimensions.width, height: dimensions.height, lora_scale: 0.8,
995
  });
 
996
  const imageData = result.data[0];
997
  if (imageData && imageData.url) {
998
  displayResult(imageData.url);
999
+ addToHistory(imageData.url);
1000
  } else {
1001
  throw new Error('پاسخ سرور فاقد تصویر بود.');
1002
  }
1003
  setLoadingState(false);
 
1004
  } catch (error) {
1005
  let friendlyMessage = `خطا در ارتباط با سرور. لطفاً اتصال اینترنت خود را بررسی کرده و دوباره تلاش کنید.`;
1006
+ if (String(error.message).includes("hamed744/translate-tts-aloha")) { friendlyMessage = `خطا در سرویس ترجمه. لطفاً دقایقی دیگر دوباره امتحان کنید.`; }
 
 
 
1007
  const rawErrorString = String(error?.message || error).toLowerCase();
1008
  const gpuErrorKeywords = ["gpu capacity", "unavailable", "exceeded", "quota", "queue is full"];
1009
  const isGpuError = gpuErrorKeywords.some(keyword => rawErrorString.includes(keyword));
1010
+ if (!isGpuError) { fluxClient = null; }
 
 
 
 
1011
  setLoadingState(false);
1012
  displayError(friendlyMessage, error);
1013
  }
 
1019
  }
1020
 
1021
  const longPressDuration = 500;
 
1022
  function handlePressStart(event) {
1023
  const card = event.target.closest('.lora-card');
1024
  if (!card) return;
 
1025
  longPressJustHappened = false;
 
1026
  longPressTimer = setTimeout(() => {
1027
  longPressJustHappened = true;
1028
  const cardTitleEn = card.dataset.loraTitle;
 
1030
  if (loraData) {
1031
  const loraNameFa = loraPersianNames[loraData.title] || loraData.title;
1032
  const thumbnailUrl = `https://wsrv.nl/?url=${encodeURIComponent(loraData.image)}&w=50&h=50&fit=cover&q=80&output=webp`;
1033
+ modalTitle.innerHTML = `<div class="style-info-box"><img src="${thumbnailUrl}" class="style-info-image" alt="${loraNameFa}"><div class="style-info-text"><span class="persian-name">${loraNameFa}</span><span class="english-name">${loraData.title}</span></div></div>`;
 
 
 
 
 
 
 
 
1034
  }
1035
  }, longPressDuration);
1036
  }
1037
+ function handlePressEnd(event) { clearTimeout(longPressTimer); }
1038
+
1039
+ const openLightbox = (imageUrl) => { lightboxImg.src = imageUrl; lightboxDownload.href = imageUrl; lightbox.classList.add('visible'); };
1040
+ const closeLightbox = () => lightbox.classList.remove('visible');
1041
+ const getHistory = () => JSON.parse(localStorage.getItem('fluxLoraHistory_v2') || '[]');
1042
+ const saveHistory = (history) => { localStorage.setItem('fluxLoraHistory_v2', JSON.stringify(history)); renderHistory(); };
1043
+ const addToHistory = (imageUrl) => { let history = getHistory(); history.unshift({ url: imageUrl, timestamp: Date.now() }); if (history.length > 20) history.pop(); saveHistory(history); };
1044
+ const showConfirmationModal = (message, onConfirm) => { modalMessageText.textContent = message; confirmationModal.classList.add('visible'); modalConfirmBtn.onclick = () => { onConfirm(); hideConfirmationModal(); }; modalCancelBtn.onclick = () => hideConfirmationModal(); };
1045
+ const hideConfirmationModal = () => confirmationModal.classList.remove('visible');
1046
+ const handleClearHistory = () => showConfirmationModal('آیا از پاک کردن تمام تاریخچه مطمئن هستید؟', () => saveHistory([]));
1047
+ const handleDeleteItem = (index) => showConfirmationModal('آیا ��ز حذف این مورد از تاریخچه مطمئن هستید؟', () => { let history = getHistory(); history.splice(index, 1); saveHistory(history); });
1048
 
1049
+ const renderHistory = () => {
1050
+ let history = getHistory();
1051
+ const sixHoursInMs = 6 * 60 * 60 * 1000;
1052
+ const currentTime = Date.now();
1053
+ const validHistory = history.filter(item => (currentTime - item.timestamp) < sixHoursInMs);
1054
+ if (validHistory.length !== history.length) { localStorage.setItem('fluxLoraHistory_v2', JSON.stringify(validHistory)); }
1055
+ historyGrid.innerHTML = '';
1056
+ clearHistoryBtn.style.display = validHistory.length > 0 ? 'flex' : 'none';
1057
+ validHistory.forEach((item, index) => {
1058
+ const card = document.createElement('div');
1059
+ card.className = 'history-item';
1060
+ const img = document.createElement('img');
1061
+ img.src = item.url;
1062
+ img.alt = 'تصویر ساخته شده در تاریخچه';
1063
+ img.addEventListener('click', () => openLightbox(item.url));
1064
+ card.appendChild(img);
1065
+ const deleteBtn = document.createElement('button');
1066
+ deleteBtn.className = 'history-delete-btn';
1067
+ deleteBtn.title = 'حذف این مورد';
1068
+ deleteBtn.innerHTML = `<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="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`;
1069
+ deleteBtn.onclick = (e) => { e.stopPropagation(); handleDeleteItem(index); };
1070
+ card.appendChild(deleteBtn);
1071
+ historyGrid.appendChild(card);
1072
+ });
1073
+ };
1074
 
1075
  loraGrid.addEventListener('mousedown', handlePressStart);
1076
  loraGrid.addEventListener('touchstart', handlePressStart, { passive: true });
1077
  loraGrid.addEventListener('mouseup', handlePressEnd);
1078
  loraGrid.addEventListener('mouseleave', handlePressEnd);
1079
  loraGrid.addEventListener('touchend', handlePressEnd);
1080
+ stylePreviewContainer.addEventListener('click', () => { styleModal.style.display = 'flex'; if (loraGrid.children.length === 0) populateGrid(); });
1081
+ closeModalBtn.addEventListener('click', () => { styleModal.style.display = 'none'; resetModalTitle(); });
1082
+ window.addEventListener('click', (event) => { if (event.target === styleModal) { styleModal.style.display = 'none'; resetModalTitle(); } });
1083
+ lightboxClose.addEventListener('click', closeLightbox);
1084
+ lightbox.addEventListener('click', (e) => { if (e.target === lightbox) closeLightbox(); });
 
 
 
 
 
 
 
 
 
 
 
 
 
1085
  loraGrid.addEventListener('click', handleStyleSelection);
1086
  submitBtn.addEventListener('click', generateImage);
1087
+ clearHistoryBtn.addEventListener('click', handleClearHistory);
1088
  ratioSelector.addEventListener('click', (event) => {
1089
  const selectedOption = event.target.closest('.ratio-option');
1090
  if (!selectedOption) return;
 
1092
  selectedOption.classList.add('active');
1093
  selectedRatio = selectedOption.dataset.ratio;
1094
  });
1095
+ document.addEventListener('DOMContentLoaded', () => {
1096
+ clearResult();
1097
+ renderHistory();
1098
+ setInitialStyle();
1099
+ initializeClient();
1100
+ });
1101
+ function setInitialStyle() { selectedLora = loras[0]; updateStyleDisplay(selectedLora); }
 
 
 
 
1102
  </script>
1103
 
1104
  <script>