jkorstad commited on
Commit
8ee370d
·
verified ·
1 Parent(s): c60c4ea

Update forge-texture from local forge-bricks (with vendored common)

Browse files
app.py CHANGED
@@ -14,14 +14,25 @@ import gradio as gr
14
  import spaces
15
  from PIL import Image
16
 
 
 
 
 
 
 
 
 
 
 
17
  try:
18
- from ..common.manifest import create_manifest
19
- from ..common.health import SpaceHealth
20
- except Exception:
21
- import sys
22
- sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
23
  from common.manifest import create_manifest
24
  from common.health import SpaceHealth
 
 
 
 
 
 
25
 
26
  health = SpaceHealth()
27
 
 
14
  import spaces
15
  from PIL import Image
16
 
17
+ # Robust import for local workspace + HF Space deployment (common is vendored on push)
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ HERE = Path(__file__).resolve().parent
22
+ for candidate in (HERE.parent, HERE):
23
+ if (candidate / "common" / "manifest.py").exists():
24
+ sys.path.insert(0, str(candidate))
25
+ break
26
+
27
  try:
 
 
 
 
 
28
  from common.manifest import create_manifest
29
  from common.health import SpaceHealth
30
+ except ImportError as e:
31
+ raise ImportError(
32
+ "Failed to import shared 'common' package (manifest.py / health.py).\n"
33
+ "For local development: run ./install.sh from the forge-bricks/ root.\n"
34
+ "For HF Spaces: re-run scripts/push_to_hf.py so it vendors common/."
35
+ ) from e
36
 
37
  health = SpaceHealth()
38
 
common/README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # forge-bricks-common
2
+
3
+ Shared utilities used by all Forge Bricks:
4
+
5
+ - `manifest.py` — `AssetManifest` + `create_manifest()` (lineage, provenance, commercial license flags, game-asset friendly metadata)
6
+ - `health.py` — `SpaceHealth` background monitor (adapted from 3d-creator-suite) for robust fallback behavior when calling other HF Spaces
7
+
8
+ This package is installed in editable mode when you run the root `install.sh`.
9
+
10
+ It is a dependency of the individual bricks via their `pyproject.toml` workspace configuration.
common/__init__.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ """Common utilities for Forge Bricks (HF Gradio ZeroGPU Spaces).
2
+
3
+ Reusable: manifest schema, health monitoring (adapted from 3d-creator-suite),
4
+ asset metadata helpers, lineage tracking, local fallback support.
5
+ """
6
+ from .manifest import AssetManifest, create_manifest
7
+ from .health import SpaceHealth
8
+
9
+ __all__ = ["AssetManifest", "create_manifest", "SpaceHealth"]
common/health.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """SpaceHealth monitor (thread-safe periodic checks + cache).
2
+
3
+ Directly adapted from 3d-creator-suite/src/cloud/health.py for robustness.
4
+ Use inside bricks and daggr clients to avoid calling dead/sleeping providers.
5
+ """
6
+ from __future__ import annotations
7
+ import logging
8
+ import threading
9
+ import time
10
+ from typing import Any, Optional
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class SpaceHealth:
16
+ """Thread-safe health status cache for HF Spaces (or any gradio endpoint)."""
17
+
18
+ def __init__(self, check_interval: int = 120):
19
+ self._interval = check_interval
20
+ self._lock = threading.Lock()
21
+ self._status: dict[str, dict[str, Any]] = {}
22
+ self._thread: threading.Thread | None = None
23
+ self._running = False
24
+
25
+ def start(self, space_ids: list[str]) -> None:
26
+ self._running = True
27
+ self._thread = threading.Thread(
28
+ target=self._check_loop, args=(space_ids,), daemon=True
29
+ )
30
+ self._thread.start()
31
+ logger.info(f"Health monitor started for {len(space_ids)} targets")
32
+
33
+ def stop(self) -> None:
34
+ self._running = False
35
+ if self._thread:
36
+ self._thread.join(timeout=5)
37
+
38
+ def is_ok(self, space_id: str) -> Optional[bool]:
39
+ with self._lock:
40
+ return self._status.get(space_id, {}).get("ok")
41
+
42
+ def get_all(self) -> dict[str, dict[str, Any]]:
43
+ with self._lock:
44
+ return dict(self._status)
45
+
46
+ def _check_loop(self, space_ids: list[str]) -> None:
47
+ while self._running:
48
+ for sid in space_ids:
49
+ self._check_one(sid)
50
+ time.sleep(self._interval)
51
+
52
+ def _check_one(self, space_id: str) -> None:
53
+ try:
54
+ from gradio_client import Client
55
+ start = time.monotonic()
56
+ client = Client(space_id, timeout=10)
57
+ latency = time.monotonic() - start
58
+ del client
59
+ with self._lock:
60
+ self._status[space_id] = {
61
+ "ok": True,
62
+ "latency": round(latency, 2),
63
+ "last_check": time.time(),
64
+ "error": None,
65
+ }
66
+ except Exception as e:
67
+ with self._lock:
68
+ self._status[space_id] = {
69
+ "ok": False,
70
+ "latency": None,
71
+ "last_check": time.time(),
72
+ "error": str(e)[:200],
73
+ }
74
+ logger.debug(f"Health check failed for {space_id}: {e}")
common/manifest.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Asset manifest + lineage for Forge Bricks outputs.
2
+
3
+ Based on patterns from daggr-pipelines packaging + aetherium-hub asset_registry + ForgeDNA descriptors.
4
+ Every brick output includes one of these for traceability, editing lineage, license, and editor integration.
5
+ """
6
+ from __future__ import annotations
7
+ import json
8
+ from dataclasses import dataclass, field, asdict
9
+ from datetime import datetime, timezone
10
+ from pathlib import Path
11
+ from typing import Any, Optional
12
+
13
+
14
+ @dataclass
15
+ class AssetManifest:
16
+ """Standard output manifest for any generated/edited game asset brick."""
17
+ id: str
18
+ name: str
19
+ type: str # e.g. "concept_image", "sprite", "model_3d", "texture_pbr", "audio_sfx", "voice_clip", "rig", "anim_clip", "shader"
20
+ version: int = 1
21
+ parent_id: Optional[str] = None # For edits/refines (lineage)
22
+ source_brick: str = ""
23
+ prompt_or_desc: str = ""
24
+ params: dict[str, Any] = field(default_factory=dict) # seed, strength, steps, voice_description, etc.
25
+ files: dict[str, str] = field(default_factory=dict) # role -> path (e.g. "sprite": "xxx.png", "glb": "...")
26
+ metadata: dict[str, Any] = field(default_factory=dict) # tags, lod, commercial_ok, model, timings, etc.
27
+ created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
28
+ license_note: str = ""
29
+ commercial_ok: bool = False
30
+
31
+ def to_dict(self) -> dict:
32
+ return asdict(self)
33
+
34
+ def save(self, path: str | Path) -> None:
35
+ Path(path).write_text(json.dumps(self.to_dict(), indent=2))
36
+
37
+ @classmethod
38
+ def load(cls, path: str | Path) -> "AssetManifest":
39
+ data = json.loads(Path(path).read_text())
40
+ return cls(**data)
41
+
42
+
43
+ def create_manifest(
44
+ *,
45
+ name: str,
46
+ type: str,
47
+ source_brick: str,
48
+ prompt_or_desc: str = "",
49
+ files: dict[str, str] | None = None,
50
+ params: dict[str, Any] | None = None,
51
+ metadata: dict[str, Any] | None = None,
52
+ parent_id: str | None = None,
53
+ license_note: str = "Check brick README for model licenses",
54
+ commercial_ok: bool = False,
55
+ ) -> AssetManifest:
56
+ """Helper to create a properly populated manifest (used by all bricks)."""
57
+ mid = f"{source_brick}_{int(datetime.now(timezone.utc).timestamp() * 1000)}"
58
+ return AssetManifest(
59
+ id=mid,
60
+ name=name,
61
+ type=type,
62
+ source_brick=source_brick,
63
+ prompt_or_desc=prompt_or_desc,
64
+ parent_id=parent_id,
65
+ files=files or {},
66
+ params=params or {},
67
+ metadata=metadata or {},
68
+ license_note=license_note,
69
+ commercial_ok=commercial_ok,
70
+ )
common/pyproject.toml ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "forge-bricks-common"
3
+ version = "0.1.0"
4
+ description = "Shared utilities for Forge Bricks (manifests, health checks, etc.)"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "pillow>=10.0",
9
+ ]
10
+
11
+ [build-system]
12
+ requires = ["hatchling"]
13
+ build-backend = "hatchling.build"
14
+
15
+ [tool.hatch.build.targets.wheel]
16
+ packages = ["."]