John6666 commited on
Commit
00a9140
·
verified ·
1 Parent(s): f088106

Upload modutils.py

Browse files
Files changed (1) hide show
  1. modutils.py +206 -23
modutils.py CHANGED
@@ -31,6 +31,101 @@ from env import (HF_LORA_PRIVATE_REPOS1, HF_LORA_PRIVATE_REPOS2,
31
  HF_MODEL_USER_EX, HF_MODEL_USER_LIKES, DIFFUSERS_FORMAT_LORAS,
32
  DIRECTORY_LORAS, HF_READ_TOKEN, HF_TOKEN, CIVITAI_API_KEY)
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  MODEL_TYPE_DICT = {
35
  "diffusers:StableDiffusionPipeline": "SD 1.5",
36
  "diffusers:StableDiffusionXLPipeline": "SDXL",
@@ -339,14 +434,20 @@ def get_civitai_active_api_origin(force_refresh: bool = False, api_key: str = ""
339
  if CIVITAI_ACTIVE_API_ORIGIN and not force_refresh:
340
  return CIVITAI_ACTIVE_API_ORIGIN
341
  session = create_retry_session(total=2, backoff_factor=0.5)
342
- for origin in CIVITAI_API_ORIGIN_CANDIDATES:
343
- if probe_civitai_api_origin(session, origin, api_key=api_key):
344
- set_civitai_active_api_origin(origin)
345
- print(f"[civitai] selected api origin: {CIVITAI_ACTIVE_API_ORIGIN}")
346
- return CIVITAI_ACTIVE_API_ORIGIN
347
- set_civitai_active_api_origin(CIVITAI_DEFAULT_ORIGIN)
348
- print(f"[civitai] api probe fallback origin: {CIVITAI_ACTIVE_API_ORIGIN}")
349
- return CIVITAI_ACTIVE_API_ORIGIN
 
 
 
 
 
 
350
 
351
  def get_civitai_active_api_base(force_refresh: bool = False, api_key: str = ""):
352
  if CIVITAI_ACTIVE_API_BASE and not force_refresh:
@@ -622,6 +723,7 @@ def resolve_civitai_model_page_to_download_url(url: str, api_key: str = ""):
622
  headers = get_civitai_headers(api_key if parts.netloc.lower().endswith("civitai.com") else "")
623
  headers['Referer'] = f"{parts.scheme or 'https'}://{parts.netloc}/"
624
  session = create_retry_session(total=CIVITAI_RESOLVE_RETRY_TOTAL, backoff_factor=CIVITAI_RESOLVE_RETRY_BACKOFF)
 
625
  try:
626
  r = session.get(raw, headers=headers, timeout=CIVITAI_RESOLVE_TIMEOUT)
627
  if not r.ok:
@@ -638,6 +740,16 @@ def resolve_civitai_model_page_to_download_url(url: str, api_key: str = ""):
638
  except Exception as e:
639
  print(f"Failed to resolve Civitai model page URL: {sanitize_url_for_log(raw)} {type(e).__name__}: {sanitize_sensitive_log_text(e)}")
640
  return raw
 
 
 
 
 
 
 
 
 
 
641
 
642
  def normalize_civitai_input_url(url: str, api_key: str = ""):
643
  raw = str(url or "").strip()
@@ -679,8 +791,9 @@ def resolve_civitai_download_url(url: str, civitai_api_key: str = "", max_tries:
679
  last_error = None
680
  for attempt in range(1, max_tries + 1):
681
  response = None
 
682
  try:
683
- response = create_retry_session(total=3, backoff_factor=1.0).get(
684
  dl_url,
685
  headers=headers,
686
  allow_redirects=False,
@@ -712,6 +825,10 @@ def resolve_civitai_download_url(url: str, civitai_api_key: str = "", max_tries:
712
  response.close()
713
  except Exception:
714
  pass
 
 
 
 
715
  if attempt < max_tries:
716
  time.sleep(min(3.0, 0.8 * attempt))
717
  if last_error is not None:
@@ -819,6 +936,7 @@ def request_json_data(url, api_key: str = ""):
819
  if attempt > 1:
820
  headers["Connection"] = "close"
821
  endpoint_url = ""
 
822
  try:
823
  json_data, endpoint_url, result = request_civitai_api_json(
824
  endpoint_path,
@@ -849,6 +967,11 @@ def request_json_data(url, api_key: str = ""):
849
  f"error={type(e).__name__}: {sanitize_sensitive_log_text(e)}"
850
  )
851
  finally:
 
 
 
 
 
852
  try:
853
  session.close()
854
  except Exception:
@@ -874,6 +997,7 @@ class ModelInformation:
874
  self.description = ""
875
  self.model_name = json_data.get("model", {}).get("name", "")
876
  self.model_type = json_data.get("model", {}).get("type", "")
 
877
  self.nsfw = json_data.get("model", {}).get("nsfw", False)
878
  self.poi = json_data.get("model", {}).get("poi", False)
879
  self.images = [img.get("url", "") for img in json_data.get("images", [])]
@@ -1145,6 +1269,13 @@ def download_things(directory, url, hf_token="", civitai_api_key="", romanize=Fa
1145
  print(f"Civitai download URL normalized: {sanitize_url_for_log(url)} -> {sanitize_url_for_log(normalized_url)}")
1146
  model_profile = retrieve_model_info(normalized_url, api_key=civitai_api_key)
1147
  selected_file = model_profile.selected_file if model_profile else {}
 
 
 
 
 
 
 
1148
  if model_profile and model_profile.download_url:
1149
  url = model_profile.download_url
1150
  filename = model_profile.filename_url or ""
@@ -1333,15 +1464,20 @@ def save_gallery_images(images, model_name="", progress=gr.Progress(track_tqdm=T
1333
  output_images = []
1334
  output_paths = []
1335
  for i, image in enumerate(images):
 
1336
  filename = f"{basename}{str(i + 1)}.png"
 
1337
  oldpath = Path(image[0])
1338
  newpath = oldpath.resolve() if oldpath.exists() else oldpath
1339
  try:
1340
  if oldpath.exists():
1341
  source_path = oldpath.resolve()
1342
- target_path = Path(filename).resolve()
1343
  if source_path != target_path:
1344
- shutil.copy2(str(source_path), str(target_path))
 
 
 
 
1345
  newpath = target_path
1346
  else:
1347
  newpath = source_path
@@ -1351,6 +1487,7 @@ def save_gallery_images(images, model_name="", progress=gr.Progress(track_tqdm=T
1351
  finally:
1352
  output_paths.append(str(newpath))
1353
  output_images.append((str(newpath), str(filename)))
 
1354
  progress(1, desc="Gallery updated.")
1355
  return gr.update(value=output_images), gr.update(value=output_paths, visible=True)
1356
 
@@ -1360,6 +1497,10 @@ def save_gallery_history(images, files, history_gallery, history_files, progress
1360
  if not history_files: history_files = []
1361
  output_gallery = images + history_gallery
1362
  output_files = files + history_files
 
 
 
 
1363
  return gr.update(value=output_gallery), gr.update(value=output_files, visible=True)
1364
 
1365
  def save_image_history(image, gallery, files, model_name: str, progress=gr.Progress(track_tqdm=True)):
@@ -1369,7 +1510,9 @@ def save_image_history(image, gallery, files, model_name: str, progress=gr.Progr
1369
  try:
1370
  basename = f"{model_name.split('/')[-1]}_{datetime.now(FILENAME_TIMEZONE).strftime('%Y%m%d_%H%M%S')}"
1371
  if image is None or not isinstance(image, (str, Image.Image, np.ndarray, tuple)): return gr.update(), gr.update()
 
1372
  filename = f"{basename}.png"
 
1373
  if isinstance(image, tuple): image = image[0]
1374
  if isinstance(image, str):
1375
  oldpath = image
@@ -1384,10 +1527,21 @@ def save_image_history(image, gallery, files, model_name: str, progress=gr.Progr
1384
  oldpath = Path(oldpath)
1385
  newpath = oldpath
1386
  if oldpath.exists():
1387
- shutil.copy2(str(oldpath.resolve()), str(Path(filename).resolve()))
1388
- newpath = Path(filename).resolve()
 
 
 
 
 
 
1389
  files.insert(0, str(newpath))
1390
  gallery.insert(0, (str(newpath), str(filename)))
 
 
 
 
 
1391
  except Exception as e:
1392
  log_error(e)
1393
  finally:
@@ -1670,6 +1824,8 @@ def finalize_downloaded_lora_path(file_path: str, source_url: str = ""):
1670
  if normalized_url:
1671
  loras_url_to_path_dict[normalized_url] = final_path
1672
  update_lora_dict(final_path)
 
 
1673
  return final_path
1674
 
1675
  def download_lora(dl_urls: str):
@@ -2027,6 +2183,7 @@ def get_civitai_info(path):
2027
  headers = get_civitai_headers(CIVITAI_API_KEY)
2028
  endpoint_path = '/model-versions/by-hash/'
2029
  session = create_retry_session()
 
2030
 
2031
  import hashlib
2032
  sha256_hash = hashlib.sha256()
@@ -2047,6 +2204,16 @@ def get_civitai_info(path):
2047
  except Exception as e:
2048
  print(f"Civitai by-hash lookup failed: {path} {type(e).__name__}: {e}")
2049
  return default
 
 
 
 
 
 
 
 
 
 
2050
  if not r.ok:
2051
  print(f"Civitai by-hash lookup status={r.status_code}: {path}")
2052
  if r.status_code == 404:
@@ -2115,7 +2282,7 @@ def build_civitai_choice_name(item: dict) -> str:
2115
  base_model_name = "Pony🐴" if item.get('base_model') == "Pony" else item.get('base_model', '')
2116
  return f"{item.get('name', '')} (for {base_model_name} / By: {item.get('creator', '')} / Tags: {', '.join(item.get('tags', []))})"
2117
 
2118
- def search_lora_on_civitai(query: str, allow_model: list[str] = ["Pony", "SDXL 1.0"], limit: int = 100,
2119
  sort: str = "Highest Rated", period: str = "AllTime", tag: str = "", user: str = "", page: int = 1):
2120
  headers = get_civitai_headers(CIVITAI_API_KEY)
2121
  endpoint_path = '/models'
@@ -2127,6 +2294,7 @@ def search_lora_on_civitai(query: str, allow_model: list[str] = ["Pony", "SDXL 1
2127
  if user:
2128
  params["username"] = user
2129
  session = create_retry_session()
 
2130
  try:
2131
  json, _, r = request_civitai_api_json(
2132
  endpoint_path,
@@ -2140,6 +2308,16 @@ def search_lora_on_civitai(query: str, allow_model: list[str] = ["Pony", "SDXL 1
2140
  except Exception as e:
2141
  print(f"Civitai search failed: query={query!r} page={page} {type(e).__name__}: {e}")
2142
  return None
 
 
 
 
 
 
 
 
 
 
2143
  if not r.ok or not json:
2144
  print(f"Civitai search status={r.status_code}: query={query!r} page={page}")
2145
  return None
@@ -2147,7 +2325,7 @@ def search_lora_on_civitai(query: str, allow_model: list[str] = ["Pony", "SDXL 1
2147
  print(f"Civitai search returned no items key: query={query!r} page={page}")
2148
  return None
2149
  items = []
2150
- allowed_models = set(allow_model)
2151
  for j in json['items']:
2152
  model_versions = j.get('modelVersions') if isinstance(j, dict) else []
2153
  if not isinstance(model_versions, list):
@@ -2163,13 +2341,7 @@ def search_lora_on_civitai(query: str, allow_model: list[str] = ["Pony", "SDXL 1
2163
 
2164
  CIVITAI_SORT = ["Highest Rated", "Most Downloaded", "Most Liked", "Most Discussed", "Most Collected", "Most Buzz", "Newest"]
2165
  CIVITAI_PERIOD = ["AllTime", "Year", "Month", "Week", "Day"]
2166
- CIVITAI_BASEMODEL_DEFAULT = ["Chroma", "Flux.1 D", "Flux.1 S", "Flux.1 Kontext", "HiDream", "Hunyuan Video",
2167
- "Illustrious", "NoobAI", "Other", "Pony", "SD 1.4", "SD 1.5", "SD 1.5 Hyper",
2168
- "SD 1.5 LCM", "SD 2.0", "SD 2.1", "SD 2.1 768", "SDXL 0.9", "SDXL 1.0", "SDXL Hyper",
2169
- "SDXL Lightning", "Wan Video", "Anima", "Flux.1 Krea", "Flux.2 D", "Flux.2 Klein 4B-base",
2170
- "Flux.2 Klein 9B", "Flux.2 Klein 9B-base", "Grok", "LTXV 2.3", "LTXV2", "Qwen", "SDXL 1.0 LCM",
2171
- "Wan Video 1.3B t2v", "Wan Video 14B i2v 480p", "Wan Video 14B i2v 720p", "Wan Video 14B t2v",
2172
- "Wan Video 2.2 I2V-A14B", "Wan Video 2.2 T2V-A14B", "Wan Video 2.2 TI2V-5B", "ZImageBase", "ZImageTurbo"]
2173
  CIVITAI_BASEMODEL = CIVITAI_BASEMODEL_DEFAULT.copy()
2174
 
2175
 
@@ -2247,6 +2419,7 @@ def get_civitai_tag():
2247
  headers = {'User-Agent': user_agent, 'content-type': 'application/json'}
2248
  params = {'limit': 200}
2249
  session = create_retry_session()
 
2250
  try:
2251
  json_data, _, r = request_civitai_api_json(
2252
  '/tags',
@@ -2271,6 +2444,16 @@ def get_civitai_tag():
2271
  except Exception as e:
2272
  log_warning(e)
2273
  return default
 
 
 
 
 
 
 
 
 
 
2274
 
2275
  LORA_BASE_MODEL_DICT = {
2276
  "diffusers:StableDiffusionPipeline": ["SD 1.5"],
@@ -2706,9 +2889,9 @@ def read_safetensors_key(path: str):
2706
  except Exception as e:
2707
  log_error(e)
2708
  finally:
 
2709
  if torch.cuda.is_available():
2710
  torch.cuda.empty_cache()
2711
- gc.collect()
2712
  return keys
2713
 
2714
  def get_model_type_from_key(path: str):
 
31
  HF_MODEL_USER_EX, HF_MODEL_USER_LIKES, DIFFUSERS_FORMAT_LORAS,
32
  DIRECTORY_LORAS, HF_READ_TOKEN, HF_TOKEN, CIVITAI_API_KEY)
33
 
34
+ OUTPUT_CACHE_DIR = Path(os.getenv("OUTPUT_CACHE_DIR", "outputs"))
35
+ OUTPUT_CACHE_MAX_FILES = max(16, int(os.getenv("OUTPUT_CACHE_MAX_FILES", "256")))
36
+ OUTPUT_CACHE_MAX_BYTES = max(512 * 1024**2, int(float(os.getenv("OUTPUT_CACHE_MAX_GB", "4")) * 1024**3))
37
+ CIVITAI_LORA_CACHE_MAX_FILES = max(16, int(os.getenv("CIVITAI_LORA_CACHE_MAX_FILES", "128")))
38
+ CIVITAI_LORA_CACHE_MAX_BYTES = max(4 * 1024**3, int(float(os.getenv("CIVITAI_LORA_CACHE_MAX_GB", "32")) * 1024**3))
39
+ CIVITAI_LORA_CACHE_INDEX = Path(DIRECTORY_LORAS) / ".civitai_lora_lru.json"
40
+ CIVITAI_ALLOWED_LORA_BASE_MODELS = ['Flux.1 D', 'Flux.1 S', 'Flux.1 Kontext', 'Flux.1 Krea', 'Flux.2 D', 'Flux.2 Klein 4B-base', 'Flux.2 Klein 9B', 'Flux.2 Klein 9B-base', 'SD 1.5', 'SD 1.5 Hyper', 'SD 1.5 LCM', 'SDXL 0.9', 'SDXL 1.0', 'SDXL Hyper', 'SDXL Lightning', 'SDXL 1.0 LCM', 'Pony', 'Illustrious', 'NoobAI']
41
+
42
+
43
+ def _prune_paths_lru(paths, max_files: int, max_bytes: int, protect=None):
44
+ protect = {str(Path(p).resolve()) for p in (protect or []) if p}
45
+ entries = []
46
+ for raw in paths:
47
+ try:
48
+ p = Path(raw)
49
+ if not p.is_file():
50
+ continue
51
+ st = p.stat()
52
+ entries.append((p, int(st.st_size), float(st.st_mtime)))
53
+ except Exception:
54
+ continue
55
+ total = sum(size for _, size, _ in entries)
56
+ entries.sort(key=lambda item: item[2])
57
+ while entries and (len(entries) > max_files or total > max_bytes):
58
+ victim, size, _ = entries.pop(0)
59
+ if str(victim.resolve()) in protect:
60
+ entries.append((victim, size, float("inf")))
61
+ entries.sort(key=lambda item: item[2])
62
+ if all(str(p.resolve()) in protect for p, _, _ in entries):
63
+ break
64
+ continue
65
+ try:
66
+ victim.unlink()
67
+ total -= size
68
+ print(f"[cache] pruned {victim}")
69
+ except Exception as e:
70
+ print(f"[cache] prune failed {victim} {type(e).__name__}: {e}")
71
+ return total
72
+
73
+
74
+ def _prune_generated_outputs(protect=None):
75
+ OUTPUT_CACHE_DIR.mkdir(parents=True, exist_ok=True)
76
+ _prune_paths_lru(OUTPUT_CACHE_DIR.glob("*.png"), OUTPUT_CACHE_MAX_FILES, OUTPUT_CACHE_MAX_BYTES, protect=protect)
77
+ try:
78
+ return {str(path.resolve()) for path in OUTPUT_CACHE_DIR.glob("*.png") if path.is_file()}
79
+ except Exception:
80
+ return set()
81
+
82
+
83
+ def _load_civitai_lora_index():
84
+ try:
85
+ data = json.loads(CIVITAI_LORA_CACHE_INDEX.read_text(encoding="utf-8"))
86
+ return data if isinstance(data, dict) else {}
87
+ except Exception:
88
+ return {}
89
+
90
+
91
+ def _save_civitai_lora_index(data):
92
+ try:
93
+ CIVITAI_LORA_CACHE_INDEX.parent.mkdir(parents=True, exist_ok=True)
94
+ temp = CIVITAI_LORA_CACHE_INDEX.with_suffix(".tmp")
95
+ temp.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
96
+ os.replace(temp, CIVITAI_LORA_CACHE_INDEX)
97
+ except Exception as e:
98
+ print(f"[civitai] lora cache index save failed: {type(e).__name__}: {e}")
99
+
100
+
101
+ def _record_civitai_lora_cache(path: str):
102
+ try:
103
+ current = Path(path).resolve()
104
+ lora_root = Path(DIRECTORY_LORAS).resolve()
105
+ if not current.is_file() or current.parent != lora_root:
106
+ return
107
+ data = _load_civitai_lora_index()
108
+ normalized = {}
109
+ for raw, touched in data.items():
110
+ try:
111
+ p = Path(raw).resolve()
112
+ if p.is_file() and p.parent == lora_root:
113
+ normalized[str(p)] = float(touched)
114
+ except Exception:
115
+ pass
116
+ normalized[str(current)] = time.time()
117
+ paths = list(normalized.keys())
118
+ _prune_paths_lru(paths, CIVITAI_LORA_CACHE_MAX_FILES, CIVITAI_LORA_CACHE_MAX_BYTES, protect=[str(current)])
119
+ normalized = {raw: ts for raw, ts in normalized.items() if Path(raw).is_file()}
120
+ _save_civitai_lora_index(normalized)
121
+ except Exception as e:
122
+ print(f"[civitai] lora cache accounting failed: {type(e).__name__}: {e}")
123
+
124
+
125
+ def is_allowed_civitai_lora_base_model(base_model: str) -> bool:
126
+ value = str(base_model or "").strip()
127
+ return value in CIVITAI_ALLOWED_LORA_BASE_MODELS
128
+
129
  MODEL_TYPE_DICT = {
130
  "diffusers:StableDiffusionPipeline": "SD 1.5",
131
  "diffusers:StableDiffusionXLPipeline": "SDXL",
 
434
  if CIVITAI_ACTIVE_API_ORIGIN and not force_refresh:
435
  return CIVITAI_ACTIVE_API_ORIGIN
436
  session = create_retry_session(total=2, backoff_factor=0.5)
437
+ try:
438
+ for origin in CIVITAI_API_ORIGIN_CANDIDATES:
439
+ if probe_civitai_api_origin(session, origin, api_key=api_key):
440
+ set_civitai_active_api_origin(origin)
441
+ print(f"[civitai] selected api origin: {CIVITAI_ACTIVE_API_ORIGIN}")
442
+ return CIVITAI_ACTIVE_API_ORIGIN
443
+ set_civitai_active_api_origin(CIVITAI_DEFAULT_ORIGIN)
444
+ print(f"[civitai] api probe fallback origin: {CIVITAI_ACTIVE_API_ORIGIN}")
445
+ return CIVITAI_ACTIVE_API_ORIGIN
446
+ finally:
447
+ try:
448
+ session.close()
449
+ except Exception:
450
+ pass
451
 
452
  def get_civitai_active_api_base(force_refresh: bool = False, api_key: str = ""):
453
  if CIVITAI_ACTIVE_API_BASE and not force_refresh:
 
723
  headers = get_civitai_headers(api_key if parts.netloc.lower().endswith("civitai.com") else "")
724
  headers['Referer'] = f"{parts.scheme or 'https'}://{parts.netloc}/"
725
  session = create_retry_session(total=CIVITAI_RESOLVE_RETRY_TOTAL, backoff_factor=CIVITAI_RESOLVE_RETRY_BACKOFF)
726
+ r = None
727
  try:
728
  r = session.get(raw, headers=headers, timeout=CIVITAI_RESOLVE_TIMEOUT)
729
  if not r.ok:
 
740
  except Exception as e:
741
  print(f"Failed to resolve Civitai model page URL: {sanitize_url_for_log(raw)} {type(e).__name__}: {sanitize_sensitive_log_text(e)}")
742
  return raw
743
+ finally:
744
+ try:
745
+ if r is not None:
746
+ r.close()
747
+ except Exception:
748
+ pass
749
+ try:
750
+ session.close()
751
+ except Exception:
752
+ pass
753
 
754
  def normalize_civitai_input_url(url: str, api_key: str = ""):
755
  raw = str(url or "").strip()
 
791
  last_error = None
792
  for attempt in range(1, max_tries + 1):
793
  response = None
794
+ session = create_retry_session(total=3, backoff_factor=1.0)
795
  try:
796
+ response = session.get(
797
  dl_url,
798
  headers=headers,
799
  allow_redirects=False,
 
825
  response.close()
826
  except Exception:
827
  pass
828
+ try:
829
+ session.close()
830
+ except Exception:
831
+ pass
832
  if attempt < max_tries:
833
  time.sleep(min(3.0, 0.8 * attempt))
834
  if last_error is not None:
 
936
  if attempt > 1:
937
  headers["Connection"] = "close"
938
  endpoint_url = ""
939
+ result = None
940
  try:
941
  json_data, endpoint_url, result = request_civitai_api_json(
942
  endpoint_path,
 
967
  f"error={type(e).__name__}: {sanitize_sensitive_log_text(e)}"
968
  )
969
  finally:
970
+ try:
971
+ if result is not None:
972
+ result.close()
973
+ except Exception:
974
+ pass
975
  try:
976
  session.close()
977
  except Exception:
 
997
  self.description = ""
998
  self.model_name = json_data.get("model", {}).get("name", "")
999
  self.model_type = json_data.get("model", {}).get("type", "")
1000
+ self.base_model = json_data.get("baseModel", "")
1001
  self.nsfw = json_data.get("model", {}).get("nsfw", False)
1002
  self.poi = json_data.get("model", {}).get("poi", False)
1003
  self.images = [img.get("url", "") for img in json_data.get("images", [])]
 
1269
  print(f"Civitai download URL normalized: {sanitize_url_for_log(url)} -> {sanitize_url_for_log(normalized_url)}")
1270
  model_profile = retrieve_model_info(normalized_url, api_key=civitai_api_key)
1271
  selected_file = model_profile.selected_file if model_profile else {}
1272
+ if Path(directory).name == Path(DIRECTORY_LORAS).name and model_profile:
1273
+ if str(model_profile.model_type or "").upper() not in {"LORA", "LOCON", "DORA"}:
1274
+ print(f"[civitai] rejected non-LoRA model type={model_profile.model_type!r}")
1275
+ return None
1276
+ if not is_allowed_civitai_lora_base_model(model_profile.base_model):
1277
+ print(f"[civitai] rejected LoRA base model={model_profile.base_model!r}")
1278
+ return None
1279
  if model_profile and model_profile.download_url:
1280
  url = model_profile.download_url
1281
  filename = model_profile.filename_url or ""
 
1464
  output_images = []
1465
  output_paths = []
1466
  for i, image in enumerate(images):
1467
+ OUTPUT_CACHE_DIR.mkdir(parents=True, exist_ok=True)
1468
  filename = f"{basename}{str(i + 1)}.png"
1469
+ target_path = (OUTPUT_CACHE_DIR / filename).resolve()
1470
  oldpath = Path(image[0])
1471
  newpath = oldpath.resolve() if oldpath.exists() else oldpath
1472
  try:
1473
  if oldpath.exists():
1474
  source_path = oldpath.resolve()
 
1475
  if source_path != target_path:
1476
+ owned_temp = source_path.parent == Path(tempfile.gettempdir()).resolve() and source_path.name.startswith("modimg_")
1477
+ if owned_temp:
1478
+ shutil.move(str(source_path), str(target_path))
1479
+ else:
1480
+ shutil.copy2(str(source_path), str(target_path))
1481
  newpath = target_path
1482
  else:
1483
  newpath = source_path
 
1487
  finally:
1488
  output_paths.append(str(newpath))
1489
  output_images.append((str(newpath), str(filename)))
1490
+ _prune_generated_outputs(protect=output_paths)
1491
  progress(1, desc="Gallery updated.")
1492
  return gr.update(value=output_images), gr.update(value=output_paths, visible=True)
1493
 
 
1497
  if not history_files: history_files = []
1498
  output_gallery = images + history_gallery
1499
  output_files = files + history_files
1500
+ survivors = _prune_generated_outputs(protect=files)
1501
+ if survivors:
1502
+ output_files = [path for path in output_files if str(Path(path).resolve()) in survivors]
1503
+ output_gallery = [item for item in output_gallery if item and str(Path(item[0]).resolve()) in survivors]
1504
  return gr.update(value=output_gallery), gr.update(value=output_files, visible=True)
1505
 
1506
  def save_image_history(image, gallery, files, model_name: str, progress=gr.Progress(track_tqdm=True)):
 
1510
  try:
1511
  basename = f"{model_name.split('/')[-1]}_{datetime.now(FILENAME_TIMEZONE).strftime('%Y%m%d_%H%M%S')}"
1512
  if image is None or not isinstance(image, (str, Image.Image, np.ndarray, tuple)): return gr.update(), gr.update()
1513
+ OUTPUT_CACHE_DIR.mkdir(parents=True, exist_ok=True)
1514
  filename = f"{basename}.png"
1515
+ target_path = (OUTPUT_CACHE_DIR / filename).resolve()
1516
  if isinstance(image, tuple): image = image[0]
1517
  if isinstance(image, str):
1518
  oldpath = image
 
1527
  oldpath = Path(oldpath)
1528
  newpath = oldpath
1529
  if oldpath.exists():
1530
+ source_path = oldpath.resolve()
1531
+ owned_temp = source_path.parent == Path(tempfile.gettempdir()).resolve() and source_path.name.startswith(("modimg_", "history_"))
1532
+ if source_path != target_path:
1533
+ if owned_temp:
1534
+ shutil.move(str(source_path), str(target_path))
1535
+ else:
1536
+ shutil.copy2(str(source_path), str(target_path))
1537
+ newpath = target_path
1538
  files.insert(0, str(newpath))
1539
  gallery.insert(0, (str(newpath), str(filename)))
1540
+ survivors = _prune_generated_outputs(protect=[str(newpath)])
1541
+ if survivors:
1542
+ kept = [(g, f) for g, f in zip(gallery, files) if str(Path(f).resolve()) in survivors]
1543
+ gallery = [g for g, _ in kept]
1544
+ files = [f for _, f in kept]
1545
  except Exception as e:
1546
  log_error(e)
1547
  finally:
 
1824
  if normalized_url:
1825
  loras_url_to_path_dict[normalized_url] = final_path
1826
  update_lora_dict(final_path)
1827
+ if source_url and is_civitai_url(source_url):
1828
+ _record_civitai_lora_cache(final_path)
1829
  return final_path
1830
 
1831
  def download_lora(dl_urls: str):
 
2183
  headers = get_civitai_headers(CIVITAI_API_KEY)
2184
  endpoint_path = '/model-versions/by-hash/'
2185
  session = create_retry_session()
2186
+ r = None
2187
 
2188
  import hashlib
2189
  sha256_hash = hashlib.sha256()
 
2204
  except Exception as e:
2205
  print(f"Civitai by-hash lookup failed: {path} {type(e).__name__}: {e}")
2206
  return default
2207
+ finally:
2208
+ try:
2209
+ if r is not None:
2210
+ r.close()
2211
+ except Exception:
2212
+ pass
2213
+ try:
2214
+ session.close()
2215
+ except Exception:
2216
+ pass
2217
  if not r.ok:
2218
  print(f"Civitai by-hash lookup status={r.status_code}: {path}")
2219
  if r.status_code == 404:
 
2282
  base_model_name = "Pony🐴" if item.get('base_model') == "Pony" else item.get('base_model', '')
2283
  return f"{item.get('name', '')} (for {base_model_name} / By: {item.get('creator', '')} / Tags: {', '.join(item.get('tags', []))})"
2284
 
2285
+ def search_lora_on_civitai(query: str, allow_model: list[str] | None = None, limit: int = 100,
2286
  sort: str = "Highest Rated", period: str = "AllTime", tag: str = "", user: str = "", page: int = 1):
2287
  headers = get_civitai_headers(CIVITAI_API_KEY)
2288
  endpoint_path = '/models'
 
2294
  if user:
2295
  params["username"] = user
2296
  session = create_retry_session()
2297
+ r = None
2298
  try:
2299
  json, _, r = request_civitai_api_json(
2300
  endpoint_path,
 
2308
  except Exception as e:
2309
  print(f"Civitai search failed: query={query!r} page={page} {type(e).__name__}: {e}")
2310
  return None
2311
+ finally:
2312
+ try:
2313
+ if r is not None:
2314
+ r.close()
2315
+ except Exception:
2316
+ pass
2317
+ try:
2318
+ session.close()
2319
+ except Exception:
2320
+ pass
2321
  if not r.ok or not json:
2322
  print(f"Civitai search status={r.status_code}: query={query!r} page={page}")
2323
  return None
 
2325
  print(f"Civitai search returned no items key: query={query!r} page={page}")
2326
  return None
2327
  items = []
2328
+ allowed_models = set(allow_model or CIVITAI_ALLOWED_LORA_BASE_MODELS)
2329
  for j in json['items']:
2330
  model_versions = j.get('modelVersions') if isinstance(j, dict) else []
2331
  if not isinstance(model_versions, list):
 
2341
 
2342
  CIVITAI_SORT = ["Highest Rated", "Most Downloaded", "Most Liked", "Most Discussed", "Most Collected", "Most Buzz", "Newest"]
2343
  CIVITAI_PERIOD = ["AllTime", "Year", "Month", "Week", "Day"]
2344
+ CIVITAI_BASEMODEL_DEFAULT = ['Flux.1 D', 'Flux.1 S', 'Flux.1 Kontext', 'Flux.1 Krea', 'Flux.2 D', 'Flux.2 Klein 4B-base', 'Flux.2 Klein 9B', 'Flux.2 Klein 9B-base', 'SD 1.5', 'SD 1.5 Hyper', 'SD 1.5 LCM', 'SDXL 0.9', 'SDXL 1.0', 'SDXL Hyper', 'SDXL Lightning', 'SDXL 1.0 LCM', 'Pony', 'Illustrious', 'NoobAI']
 
 
 
 
 
 
2345
  CIVITAI_BASEMODEL = CIVITAI_BASEMODEL_DEFAULT.copy()
2346
 
2347
 
 
2419
  headers = {'User-Agent': user_agent, 'content-type': 'application/json'}
2420
  params = {'limit': 200}
2421
  session = create_retry_session()
2422
+ r = None
2423
  try:
2424
  json_data, _, r = request_civitai_api_json(
2425
  '/tags',
 
2444
  except Exception as e:
2445
  log_warning(e)
2446
  return default
2447
+ finally:
2448
+ try:
2449
+ if r is not None:
2450
+ r.close()
2451
+ except Exception:
2452
+ pass
2453
+ try:
2454
+ session.close()
2455
+ except Exception:
2456
+ pass
2457
 
2458
  LORA_BASE_MODEL_DICT = {
2459
  "diffusers:StableDiffusionPipeline": ["SD 1.5"],
 
2889
  except Exception as e:
2890
  log_error(e)
2891
  finally:
2892
+ gc.collect()
2893
  if torch.cuda.is_available():
2894
  torch.cuda.empty_cache()
 
2895
  return keys
2896
 
2897
  def get_model_type_from_key(path: str):