Spaces:
Running on Zero
Running on Zero
fix(models): walk_workflow_for_models reads API format
Browse filesThe walker was iterating workflow.get('nodes', []) which is editor format;
our workflows are API format ({node_id: {class_type, inputs}}), so the
walk returned an empty set, ensure_models was a no-op, and Spaces
crashed at runtime with 'LTX23_audio_vae_bf16.safetensors not found'.
Locally everything worked because the user's models are symlinked in.
Also unstash the Sway Dance Lesson seed video — *.mp4 in .gitignore was
silently excluding it from the deploy, so VHS_LoadVideo warned at submit
time.
models.py
CHANGED
|
@@ -162,44 +162,33 @@ LOADER_NODE_TYPES: tuple[str, ...] = (
|
|
| 162 |
)
|
| 163 |
|
| 164 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
def walk_workflow_for_models(workflow: dict) -> set[str]:
|
| 166 |
-
"""Return the set of model filenames referenced by
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
(
|
| 171 |
-
|
|
|
|
| 172 |
"""
|
| 173 |
needed: set[str] = set()
|
| 174 |
-
for node in workflow.
|
| 175 |
-
if
|
|
|
|
|
|
|
| 176 |
continue
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
or value.endswith(".gguf")
|
| 182 |
-
or value == "tokenizer.model"
|
| 183 |
-
or value.endswith(".json")
|
| 184 |
-
):
|
| 185 |
needed.add(value)
|
| 186 |
return needed
|
| 187 |
|
| 188 |
|
| 189 |
-
def _flatten_widget_values(values):
|
| 190 |
-
"""Walk nested list/dict widget structures, yielding leaf values."""
|
| 191 |
-
if isinstance(values, dict):
|
| 192 |
-
yield from _flatten_widget_values(list(values.values()))
|
| 193 |
-
return
|
| 194 |
-
for v in values:
|
| 195 |
-
if isinstance(v, (list, tuple)):
|
| 196 |
-
yield from _flatten_widget_values(v)
|
| 197 |
-
elif isinstance(v, dict):
|
| 198 |
-
yield from _flatten_widget_values(list(v.values()))
|
| 199 |
-
else:
|
| 200 |
-
yield v
|
| 201 |
-
|
| 202 |
-
|
| 203 |
@dataclass
|
| 204 |
class DownloadEvent:
|
| 205 |
filename: str
|
|
|
|
| 162 |
)
|
| 163 |
|
| 164 |
|
| 165 |
+
_USER_INPUT_LOADERS = {"LoadImage", "VHS_LoadVideo", "VHS_LoadAudioUpload"}
|
| 166 |
+
_MODEL_EXTS = (".safetensors", ".gguf", ".pt", ".bin", ".ckpt")
|
| 167 |
+
|
| 168 |
+
|
| 169 |
def walk_workflow_for_models(workflow: dict) -> set[str]:
|
| 170 |
+
"""Return the set of model filenames referenced by the API-format workflow.
|
| 171 |
|
| 172 |
+
Walks `{node_id: {class_type, inputs}}` looking for any input value that
|
| 173 |
+
ends in a model extension. Skips loaders that read user-supplied files
|
| 174 |
+
(LoadImage, VHS_LoadVideo, VHS_LoadAudioUpload). Unknown filenames are
|
| 175 |
+
harmless — `ensure_models` log-warns and skips anything not in the
|
| 176 |
+
registry, so being inclusive here costs nothing.
|
| 177 |
"""
|
| 178 |
needed: set[str] = set()
|
| 179 |
+
for node in workflow.values():
|
| 180 |
+
if not isinstance(node, dict):
|
| 181 |
+
continue
|
| 182 |
+
if node.get("class_type") in _USER_INPUT_LOADERS:
|
| 183 |
continue
|
| 184 |
+
for value in (node.get("inputs") or {}).values():
|
| 185 |
+
if isinstance(value, str) and value.endswith(_MODEL_EXTS):
|
| 186 |
+
needed.add(value)
|
| 187 |
+
elif isinstance(value, str) and value == "tokenizer.model":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
needed.add(value)
|
| 189 |
return needed
|
| 190 |
|
| 191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
@dataclass
|
| 193 |
class DownloadEvent:
|
| 194 |
filename: str
|