lollms-client 1.7.10__py3-none-any.whl → 1.8.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  from abc import abstractmethod
3
3
  import importlib
4
4
  from pathlib import Path
5
- from typing import Optional, List, Dict, Any, Union
5
+ from typing import Optional, List, Dict, Any, Union, Callable
6
6
  from ascii_colors import trace_exception
7
7
  from lollms_client.lollms_base_binding import LollmsBaseBinding
8
8
 
@@ -31,6 +31,20 @@ class LollmsTTVBinding(LollmsBaseBinding):
31
31
  """
32
32
  pass
33
33
 
34
+ def get_zoo(self) -> List[Dict[str, Any]]:
35
+ """
36
+ Returns a list of models available for download.
37
+ each entry is a dict with:
38
+ name, description, size, type, link
39
+ """
40
+ return []
41
+
42
+ def download_from_zoo(self, index: int, progress_callback: Callable[[dict], None] = None) -> dict:
43
+ """
44
+ Downloads a model from the zoo using its index.
45
+ """
46
+ return {"status": False, "message": "Not implemented"}
47
+
34
48
  class LollmsTTVBindingManager:
35
49
  """Manages TTV binding discovery and instantiation."""
36
50
 
@@ -62,7 +76,7 @@ class LollmsTTVBindingManager:
62
76
  binding_class = self.available_bindings.get(binding_name)
63
77
  if binding_class:
64
78
  try:
65
- return binding_class(binding_name=binding_name, **kwargs)
79
+ return binding_class(**kwargs)
66
80
  except Exception as e:
67
81
  trace_exception(e)
68
82
  print(f"Failed to instantiate TTV binding {binding_name}: {str(e)}")
@@ -26,26 +26,20 @@ except ImportError:
26
26
  from lollms_client.lollms_tti_binding import LollmsTTIBinding
27
27
  from ascii_colors import ASCIIColors
28
28
 
29
- BindingName = "DiffusersBinding"
30
-
31
- class DiffusersBinding(LollmsTTIBinding):
32
- """
33
- Client binding for a dedicated, managed Diffusers server.
34
- This architecture prevents multiple models from being loaded into memory
35
- in a multi-worker environment, solving OOM errors.
36
- """
37
- def __init__(self,
38
- **kwargs):
29
+ BindingName = "DiffusersTTIBinding"
30
+
31
+ class DiffusersTTIBinding(LollmsTTIBinding):
32
+ def __init__(self, **kwargs):
39
33
  # Prioritize 'model_name' but accept 'model' as an alias from config files.
40
34
  if 'model' in kwargs and 'model_name' not in kwargs:
41
35
  kwargs['model_name'] = kwargs.pop('model')
42
36
  super().__init__(binding_name=BindingName, config=kwargs)
43
37
 
44
-
45
-
38
+ self.config = kwargs
46
39
  self.host = kwargs.get("host", "localhost")
47
40
  self.port = kwargs.get("port", 9632)
48
- self.auto_start_server = kwargs.get("auto_start_server", True)
41
+ self.auto_start_server = kwargs.get("auto_start_server", False)
42
+ self.wait_for_server = kwargs.get("wait_for_server", False)
49
43
  self.server_process = None
50
44
  self.base_url = f"http://{self.host}:{self.port}"
51
45
  self.binding_root = Path(__file__).parent
@@ -53,9 +47,11 @@ class DiffusersBinding(LollmsTTIBinding):
53
47
  self.venv_dir = Path("./venv/tti_diffusers_venv")
54
48
  self.models_path = Path(kwargs.get("models_path", "./data/models/diffusers_models")).resolve()
55
49
  self.extra_models_path = kwargs.get("extra_models_path")
50
+ self.hf_token = kwargs.get("hf_token", "") # NEW
56
51
  self.models_path.mkdir(exist_ok=True, parents=True)
57
52
  if self.auto_start_server:
58
- self.ensure_server_is_running()
53
+ self.ensure_server_is_running(self.wait_for_server)
54
+
59
55
 
60
56
  def is_server_running(self) -> bool:
61
57
  """Checks if the server is already running and responsive."""
@@ -68,47 +64,21 @@ class DiffusersBinding(LollmsTTIBinding):
68
64
  return False
69
65
 
70
66
 
71
- def ensure_server_is_running(self):
67
+ def ensure_server_is_running(self, wait= False):
72
68
  """
73
69
  Ensures the Diffusers server is running. If not, it attempts to start it
74
70
  in a process-safe manner using a file lock. This method is designed to
75
71
  prevent race conditions in multi-worker environments.
76
72
  """
77
73
  self.server_dir.mkdir(exist_ok=True)
78
- # Use a lock file in the binding's server directory for consistency across instances
79
- lock_path = self.server_dir / "diffusers_server.lock"
80
- lock = FileLock(lock_path)
81
-
82
74
  ASCIIColors.info("Attempting to start or connect to the Diffusers server...")
83
75
 
84
76
  # First, perform a quick check without the lock to avoid unnecessary waiting.
85
77
  if self.is_server_running():
86
78
  ASCIIColors.green("Diffusers Server is already running and responsive.")
87
79
  return
88
-
89
- try:
90
- # Try to acquire the lock with a timeout. If another process is starting
91
- # the server, this will wait until it's finished.
92
- with lock.acquire(timeout=3):
93
- # After acquiring the lock, we MUST re-check if the server is running.
94
- # Another process might have started it and released the lock while we were waiting.
95
- if not self.is_server_running():
96
- ASCIIColors.yellow("Lock acquired. Starting dedicated Diffusers server...")
97
- self.start_server()
98
- # The process that starts the server is responsible for waiting for it to be ready
99
- # BEFORE releasing the lock. This is the key to preventing race conditions.
100
- self._wait_for_server()
101
- else:
102
- ASCIIColors.green("Server was started by another process while we waited. Connected successfully.")
103
- except Timeout:
104
- # This happens if the process holding the lock takes more than 60 seconds to start the server.
105
- # We don't try to start another one. We just wait for the existing one to be ready.
106
- ASCIIColors.yellow("Could not acquire lock, another process is taking a long time to start the server. Waiting...")
107
- self._wait_for_server(timeout=60) # Give it a longer timeout here just in case.
108
-
109
- # A final verification to ensure we are connected.
110
- if not self.is_server_running():
111
- raise RuntimeError("Failed to start or connect to the Diffusers server after all attempts.")
80
+ else:
81
+ self.start_server(wait)
112
82
 
113
83
  def install_server_dependencies(self):
114
84
  """
@@ -175,43 +145,70 @@ class DiffusersBinding(LollmsTTIBinding):
175
145
 
176
146
  ASCIIColors.green("Server dependencies are satisfied.")
177
147
 
178
- def start_server(self):
148
+ def start_server(self, wait=True, timeout_s=20):
179
149
  """
180
- Installs dependencies and launches the FastAPI server as a background subprocess.
150
+ Launches the FastAPI server in a background thread and returns immediately.
181
151
  This method should only be called from within a file lock.
182
152
  """
183
- server_script = self.server_dir / "main.py"
184
- if not server_script.exists():
185
- # Fallback for old structure
186
- server_script = self.binding_root / "server.py"
187
- if not server_script.exists():
188
- raise FileNotFoundError(f"Server script not found at {server_script}. Make sure it's in a 'server' subdirectory.")
189
- if not self.venv_dir.exists():
190
- self.install_server_dependencies()
191
-
192
- if sys.platform == "win32":
193
- python_executable = self.venv_dir / "Scripts" / "python.exe"
194
- else:
195
- python_executable = self.venv_dir / "bin" / "python"
196
-
197
- command = [
198
- str(python_executable),
199
- str(server_script),
200
- "--host", self.host,
201
- "--port", str(self.port),
202
- "--models-path", str(self.models_path.resolve()) # Pass models_path to server
203
- ]
204
-
205
- if self.extra_models_path:
206
- resolved_extra_path = Path(self.extra_models_path).resolve()
207
- command.extend(["--extra-models-path", str(resolved_extra_path)])
153
+ import threading
154
+
208
155
 
209
- # Use DETACHED_PROCESS on Windows to allow the server to run independently of the parent process.
210
- # On Linux/macOS, the process will be daemonized enough to not be killed with the worker.
211
- creationflags = subprocess.DETACHED_PROCESS if sys.platform == "win32" else 0
156
+ def _start_server_background():
157
+ """Helper method to start the server in a background thread."""
158
+ # Use a lock file in the binding's server directory for consistency across instances
159
+ lock_path = self.server_dir / "diffusers_server.lock"
160
+ lock = FileLock(lock_path)
161
+ with lock.acquire(timeout=0):
162
+ try:
163
+ server_script = self.server_dir / "main.py"
164
+ if not server_script.exists():
165
+ # Fallback for old structure
166
+ server_script = self.binding_root / "server.py"
167
+ if not server_script.exists():
168
+ raise FileNotFoundError(f"Server script not found at {server_script}. Make sure it's in a 'server' subdirectory.")
169
+ if not self.venv_dir.exists():
170
+ self.install_server_dependencies()
171
+
172
+ if sys.platform == "win32":
173
+ python_executable = self.venv_dir / "Scripts" / "python.exe"
174
+ else:
175
+ python_executable = self.venv_dir / "bin" / "python"
176
+
177
+ command = [
178
+ str(python_executable),
179
+ str(server_script),
180
+ "--host", self.host,
181
+ "--port", str(self.port),
182
+ "--models-path", str(self.models_path.resolve())
183
+ ]
184
+
185
+ if self.extra_models_path:
186
+ resolved_extra_path = Path(self.extra_models_path).resolve()
187
+ command.extend(["--extra-models-path", str(resolved_extra_path)])
188
+
189
+ if self.hf_token:
190
+ command.extend(["--hf-token", self.hf_token])
191
+
192
+ if self.extra_models_path:
193
+ resolved_extra_path = Path(self.extra_models_path).resolve()
194
+ command.extend(["--extra-models-path", str(resolved_extra_path)])
195
+
196
+ creationflags = subprocess.DETACHED_PROCESS if sys.platform == "win32" else 0
197
+ self.server_process = subprocess.Popen(command, creationflags=creationflags)
198
+ ASCIIColors.info("Diffusers server process launched in the background.")
199
+ while(not self.is_server_running()):
200
+ time.sleep(1)
201
+
202
+ except Exception as e:
203
+ ASCIIColors.error(f"Failed to start Diffusers server: {e}")
204
+ raise
205
+
206
+ # Start the server in a background thread
207
+ thread = threading.Thread(target=_start_server_background, daemon=True)
208
+ thread.start()
209
+ if wait:
210
+ thread.join()
212
211
 
213
- self.server_process = subprocess.Popen(command, creationflags=creationflags)
214
- ASCIIColors.info("Diffusers server process launched in the background.")
215
212
 
216
213
  def _wait_for_server(self, timeout=30):
217
214
  """Waits for the server to become responsive."""
@@ -288,6 +285,7 @@ class DiffusersBinding(LollmsTTIBinding):
288
285
  pass
289
286
 
290
287
  def generate_image(self, prompt: str, negative_prompt: str = "", **kwargs) -> bytes:
288
+ self.ensure_server_is_running(True)
291
289
  params = kwargs.copy()
292
290
  if "model_name" not in params and self.config.get("model_name"):
293
291
  params["model_name"] = self.config["model_name"]
@@ -300,6 +298,7 @@ class DiffusersBinding(LollmsTTIBinding):
300
298
  return response.content
301
299
 
302
300
  def edit_image(self, images: Union[str, List[str], "Image.Image", List["Image.Image"]], prompt: str, **kwargs) -> bytes:
301
+ self.ensure_server_is_running(True)
303
302
  images_b64 = []
304
303
  if not isinstance(images, list):
305
304
  images = [images]
@@ -342,24 +341,78 @@ class DiffusersBinding(LollmsTTIBinding):
342
341
  response = self._post_json_request("/edit_image", data=json_payload)
343
342
  return response.content
344
343
 
345
- def list_models(self) -> List[Dict[str, Any]]:
346
- return self._get_request("/list_models").json()
344
+ def list_models(self) -> list:
345
+ """
346
+ Lists only models that are available locally on disk.
347
+
348
+ The Diffusers server scans `models_path` and `extra_models_path` for:
349
+ - Diffusers pipeline folders (with model_index.json, etc.)
350
+ - .safetensors checkpoints and associated configs.
351
+
352
+ Returns list of dicts: {"model_name": str, "display_name": str, "description": str}
353
+ """
354
+ self.ensure_server_is_running(True)
355
+ try:
356
+ response = self._get_request("/list_models")
357
+ data = response.json()
358
+ if not isinstance(data, list):
359
+ return []
360
+ return data
361
+ except Exception as e:
362
+ ASCIIColors.warning(f"Failed to list local Diffusers models: {e}")
363
+ return []
364
+
347
365
 
348
366
  def list_local_models(self) -> List[str]:
367
+ self.ensure_server_is_running(True)
349
368
  return self._get_request("/list_local_models").json()
350
369
 
351
370
  def list_available_models(self) -> List[str]:
371
+ self.ensure_server_is_running(True)
352
372
  return self._get_request("/list_available_models").json()
353
373
 
354
374
  def list_services(self, **kwargs) -> List[Dict[str, str]]:
375
+ self.ensure_server_is_running(True)
355
376
  return self._get_request("/list_models").json()
356
377
 
357
378
  def get_settings(self, **kwargs) -> List[Dict[str, Any]]:
379
+ self.ensure_server_is_running(True)
358
380
  # The server holds the state, so we fetch it.
359
381
  return self._get_request("/get_settings").json()
382
+
383
+ def get_zoo(self):
384
+ return [
385
+ {"name": "Stable Diffusion 1.5", "description": "The classic and versatile SD1.5 base model.", "size": "4GB", "type": "checkpoint", "link": "runwayml/stable-diffusion-v1-5"},
386
+ {"name": "Stable Diffusion 2.1", "description": "The 768x768 base model from the SD2.x series.", "size": "5GB", "type": "checkpoint", "link": "stabilityai/stable-diffusion-2-1"},
387
+ {"name": "Stable Diffusion XL 1.0", "description": "Official 1024x1024 text-to-image model from Stability AI.", "size": "7GB", "type": "checkpoint", "link": "stabilityai/stable-diffusion-xl-base-1.0"},
388
+ {"name": "SDXL Turbo", "description": "A fast, real-time text-to-image model based on SDXL.", "size": "7GB", "type": "checkpoint", "link": "stabilityai/sdxl-turbo"},
389
+ {"name": "Kandinsky 3", "description": "A powerful multilingual model with strong prompt understanding.", "size": "Unknown", "type": "checkpoint", "link": "kandinsky-community/kandinsky-3"},
390
+ {"name": "Playground v2.5", "description": "A high-quality model focused on aesthetic outputs.", "size": "Unknown", "type": "checkpoint", "link": "playgroundai/playground-v2.5-1024px-aesthetic"},
391
+ {"name": "epiCRealism", "description": "A popular community model for generating photorealistic images.", "size": "2GB", "type": "checkpoint", "link": "emilianJR/epiCRealism"},
392
+ {"name": "Realistic Vision 5.1", "description": "One of the most popular realistic models, great for portraits.", "size": "2GB", "type": "checkpoint", "link": "SG161222/Realistic_Vision_V5.1_noVAE"},
393
+ {"name": "Photon", "description": "A model known for high-quality, realistic images with good lighting.", "size": "2GB", "type": "checkpoint", "link": "Photon-v1"},
394
+ {"name": "Waifu Diffusion 1.4", "description": "A widely-used model for generating high-quality anime-style images.", "size": "2GB", "type": "checkpoint", "link": "hakurei/waifu-diffusion"},
395
+ {"name": "Counterfeit V3.0", "description": "A strong model for illustrative and 2.5D anime styles.", "size": "2GB", "type": "checkpoint", "link": "gsdf/Counterfeit-V3.0"},
396
+ {"name": "Animagine XL 3.0", "description": "A state-of-the-art anime model on the SDXL architecture.", "size": "7GB", "type": "checkpoint", "link": "cagliostrolab/animagine-xl-3.0"},
397
+ {"name": "DreamShaper 8", "description": "Versatile SD1.5 style model (CivitAI).", "size": "2GB", "type": "checkpoint", "link": "https://civitai.com/api/download/models/128713"},
398
+ {"name": "Juggernaut XL", "description": "Artistic SDXL (CivitAI).", "size": "7GB", "type": "checkpoint", "link": "https://civitai.com/api/download/models/133005"},
399
+ {"name": "Stable Diffusion 3 Medium", "description": "SOTA model with advanced prompt understanding (Gated).", "size": "Unknown", "type": "checkpoint", "link": "stabilityai/stable-diffusion-3-medium-diffusers"},
400
+ {"name": "FLUX.1 Schnell", "description": "Powerful and fast next-gen model (Gated).", "size": "Unknown", "type": "checkpoint", "link": "black-forest-labs/FLUX.1-schnell"},
401
+ {"name": "FLUX.1 Dev", "description": "Larger developer version of FLUX.1 (Gated).", "size": "Unknown", "type": "checkpoint", "link": "black-forest-labs/FLUX.1-dev"},
402
+ ]
403
+
404
+ def download_from_zoo(self, index: int, progress_callback: Callable[[dict], None] = None) -> dict:
405
+ zoo = self.get_zoo()
406
+ if index < 0 or index >= len(zoo):
407
+ msg = "Index out of bounds"
408
+ ASCIIColors.error(msg)
409
+ return {"status": False, "message": msg}
410
+ item = zoo[index]
411
+ return self.pull_model(item["link"], progress_callback=progress_callback)
360
412
 
361
413
  def set_settings(self, settings: Union[Dict[str, Any], List[Dict[str, Any]]], **kwargs) -> bool:
362
- # Normalize settings from list of dicts to a single dict if needed
414
+ self.ensure_server_is_running(True)
415
+ # Normalize settings from list of dicts to a single dict if needed
363
416
  parsed_settings = settings if isinstance(settings, dict) else {s["name"]: s["value"] for s in settings if "name" in s and "value" in s}
364
417
  response = self._post_json_request("/set_settings", data=parsed_settings)
365
418
  return response.json().get("success", False)
@@ -448,4 +501,4 @@ class DiffusersBinding(LollmsTTIBinding):
448
501
  def __del__(self):
449
502
  # The client destructor does not stop the server,
450
503
  # as it is a shared resource for all worker processes.
451
- pass
504
+ pass
@@ -835,7 +835,7 @@ async def edit_image(request: EditRequestJSON):
835
835
  def pull_model_endpoint(payload: PullModelRequest):
836
836
  if not payload.hf_id and not payload.safetensors_url:
837
837
  raise HTTPException(status_code=400, detail="Provide either 'hf_id' or 'safetensors_url'.")
838
-
838
+
839
839
  # 1) Pull Hugging Face model into a folder
840
840
  if payload.hf_id:
841
841
  model_id = payload.hf_id.strip()
@@ -902,83 +902,85 @@ def pull_model_endpoint(payload: PullModelRequest):
902
902
  @router.get("/list_local_models")
903
903
  def list_local_models_endpoint():
904
904
  local_models = set()
905
+ models_root = Path(args.models_path)
906
+ extra_root = Path(args.extra_models_path) if args.extra_models_path else None
905
907
 
906
- # 1) Single-file models: top-level *.safetensors only
907
- for f in state.models_path.glob("*.safetensors"):
908
- local_models.add(f.name)
909
-
910
- if state.extra_models_path and state.extra_models_path.exists():
911
- for f in state.extra_models_path.glob("*.safetensors"):
912
- local_models.add(f.name)
913
-
914
- # 2) Folder-based HF/diffusers models: treat folder name as the model
915
- def add_folder_models(base: Path):
916
- if not base or not base.exists():
908
+ def scan_root(root: Path):
909
+ if not root or not root.exists():
917
910
  return
918
- for entry in base.iterdir():
919
- if not entry.is_dir():
911
+
912
+ # 1. Diffusers folders (Recursive)
913
+ for model_index in root.rglob("model_index.json"):
914
+ # For listing just the name, we probably want the folder name or relative path
915
+ # Keeping it simple: folder name.
916
+ local_models.add(model_index.parent.name)
917
+
918
+ # 2. Safetensors files (Recursive)
919
+ for safepath in root.rglob("*.safetensors"):
920
+ if (safepath.parent / "model_index.json").exists():
920
921
  continue
921
- has_index = (entry / "model_index.json").exists()
922
- has_safetensors = any(entry.glob("*.safetensors"))
923
- if has_index or has_safetensors:
924
- local_models.add(entry.name)
922
+ local_models.add(safepath.name)
923
+
924
+ scan_root(models_root)
925
+ scan_root(extra_root)
926
+
927
+ return sorted(list(local_models))
925
928
 
926
- add_folder_models(state.models_path)
927
- add_folder_models(state.extra_models_path)
929
+ @app.get("/list_models")
930
+ def list_models() -> list[dict]:
931
+ models_root = Path(args.models_path)
932
+ extra_root = Path(args.extra_models_path) if args.extra_models_path else None
933
+ result = []
934
+ seen_paths = set()
928
935
 
929
- return sorted(list(local_models))
936
+ def scan_root(root: Path):
937
+ if not root or not root.exists():
938
+ return
939
+
940
+ # 1. Diffusers folders (Recursive)
941
+ # We look for model_index.json
942
+ for model_index in root.rglob("model_index.json"):
943
+ folder = model_index.parent
944
+ resolved_path = str(folder.resolve())
945
+ if resolved_path in seen_paths:
946
+ continue
947
+ seen_paths.add(resolved_path)
948
+
949
+ result.append({
950
+ "model_name": resolved_path,
951
+ "display_name": folder.name,
952
+ "description": "Local Diffusers pipeline"
953
+ })
954
+
955
+ # 2. Safetensors files (Recursive)
956
+ for safepath in root.rglob("*.safetensors"):
957
+ # Skip if part of a diffusers folder
958
+ if (safepath.parent / "model_index.json").exists():
959
+ continue
960
+
961
+ resolved_path = str(safepath.resolve())
962
+ if resolved_path in seen_paths:
963
+ continue
964
+ seen_paths.add(resolved_path)
930
965
 
931
- @router.get("/list_models")
932
- def list_models_endpoint():
933
- models = []
934
-
935
- # 1) Local models - ensure dict format
936
- local_files = list_local_models_endpoint()
937
- for model_name in local_files:
938
- models.append({
939
- "model_name": model_name,
940
- "display_name": model_name,
941
- "description": "(Local) Folder model" if not model_name.endswith(".safetensors") else "(Local) Local safetensors file",
942
- "owned_by": "local_user"
943
- })
944
-
945
- # 2) HF Public models - already dicts from HF_PUBLIC_MODELS
946
- for category, hf_models in HF_PUBLIC_MODELS.items():
947
- for model_info in hf_models:
948
- models.append({
949
- "model_name": model_info["model_name"],
950
- "display_name": model_info["display_name"],
951
- "description": f"({category}) {model_info['desc']}",
952
- "owned_by": "huggingface"
966
+ result.append({
967
+ "model_name": resolved_path,
968
+ "display_name": safepath.stem,
969
+ "description": "Local .safetensors checkpoint"
953
970
  })
954
971
 
955
- # 3) Gated models - same
956
- if state.config.get("hf_token"):
957
- for category, gated_models in HF_GATED_MODELS.items():
958
- for model_info in gated_models:
959
- models.append({
960
- "model_name": model_info["model_name"],
961
- "display_name": model_info["display_name"],
962
- "description": f"({category}) {model_info['desc']}",
963
- "owned_by": "huggingface"
964
- })
972
+ scan_root(models_root)
973
+ scan_root(extra_root)
974
+ return result
965
975
 
966
- # 4) Civitai models - ensure dict format
967
- for key, info in CIVITAI_MODELS.items():
968
- models.append({
969
- "model_name": key,
970
- "display_name": info["display_name"],
971
- "description": f"(Civitai) {info['description']}",
972
- "owned_by": info["owned_by"]
973
- })
974
-
975
- return models # Plain list of dicts - JSON serializable
976
976
 
977
977
 
978
978
 
979
979
  @router.get("/list_available_models")
980
980
  def list_available_models_endpoint():
981
- discoverable = [m['model_name'] for m in list_models_endpoint()]
981
+ # Use list_models() to get all available models (dicts) then extract names
982
+ models_dicts = list_models()
983
+ discoverable = [m['model_name'] for m in models_dicts]
982
984
  return sorted(list(set(discoverable)))
983
985
 
984
986
  @router.get("/get_settings")
@@ -1032,12 +1034,21 @@ if __name__ == "__main__":
1032
1034
  parser.add_argument("--port", type=int, default=9630, help="Port to bind to.")
1033
1035
  parser.add_argument("--models-path", type=str, required=True, help="Path to the models directory.")
1034
1036
  parser.add_argument("--extra-models-path", type=str, default=None, help="Path to an extra models directory.")
1037
+ parser.add_argument(
1038
+ "--hf-token",
1039
+ type=str,
1040
+ default=None,
1041
+ help="Optional Hugging Face access token used to download private or gated repos."
1042
+ )
1043
+
1035
1044
  args = parser.parse_args()
1036
1045
 
1037
1046
  MODELS_PATH = Path(args.models_path)
1038
1047
  EXTRA_MODELS_PATH = Path(args.extra_models_path) if args.extra_models_path else None
1039
1048
  state = ServerState(MODELS_PATH, EXTRA_MODELS_PATH)
1040
-
1049
+ if args.hf_token:
1050
+ state.config["hf_token"] = args.hf_token
1051
+ ASCIIColors.info("Hugging Face token received via CLI and stored in server config.")
1041
1052
  ASCIIColors.cyan(f"--- Diffusers TTI Server ---")
1042
1053
  ASCIIColors.green(f"Starting server on http://{args.host}:{args.port}")
1043
1054
  ASCIIColors.green(f"Serving models from: {MODELS_PATH.resolve()}")