diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 996160c..b8f4c7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,31 +3,55 @@ name: CI on: push: branches: + - main - dev pull_request: branches: - main jobs: - build: - name: Build Check + frontend: + name: Frontend (lint + build) runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v3 with: - node-version: "20" + version: 10.26.2 - - name: Install pnpm - uses: pnpm/action-setup@v2 + - name: Setup Node.js + uses: actions/setup-node@v4 with: - version: 9 + node-version: "latest" + cache: "pnpm" - name: Install dependencies - run: pnpm install + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm lint || true # tanstack-config strictness; soft-fail until codebase is fully clean - name: Build frontend run: pnpm build + + python: + name: Python (ruff) + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Install dev dependencies + run: uv sync --group dev + + - name: Ruff check + run: uv run ruff check . + + - name: Ruff format check + run: uv run ruff format --check . diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 94bc013..42aafc6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,28 +18,39 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 with: - submodules: true + version: 10.26.2 - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" - - - name: Install pnpm - uses: pnpm/action-setup@v2 - with: - version: 9 + node-version: "latest" + cache: "pnpm" - name: Install dependencies - run: pnpm install + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm lint || true # soft-fail; tanstack-config strictness + + - name: Install uv (for Python ruff check) + uses: astral-sh/setup-uv@v4 + + - name: Ruff check (Python) + run: | + uv sync --group dev + uv run ruff check . + uv run ruff format --check . - name: Build frontend run: pnpm build - name: Create artifact archive run: | - mkdir comfyui-embeddr + mkdir comfyui-embeddr cp -r __init__.py nodes js pyproject.toml README.md LICENSE.md comfyui-embeddr/ zip -r comfyui-embeddr.zip comfyui-embeddr diff --git a/.gitignore b/.gitignore index 80ee846..1aeb5b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,20 @@ -__pycache__ node_modules js -config.json -node.zip -*_cache +.DS_Store + +# Python +__pycache__ +*.py[cod] .venv +.ruff_cache +.pytest_cache + +# Local user state +config.json +.env +*.local + +# Release artifacts +comfyui-embeddr/ +comfyui-embeddr.zip +*.zip diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..13b5038 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,9 @@ +js +node_modules +__pycache__ +.venv +config.json +package-lock.json +pnpm-lock.yaml +uv.lock +.github diff --git a/LICENSE.md b/LICENSE similarity index 100% rename from LICENSE.md rename to LICENSE diff --git a/MANIFEST.in b/MANIFEST.in index 3e7fd2c..f0c5fe8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -include LICENSE.txt +include LICENSE include README.md include pyproject.toml include __init__.py @@ -8,5 +8,3 @@ recursive-include nodes * exclude ui recursive-exclude ui * -exclude dist-artifact -recursive-exclude dist-artifact * diff --git a/README.md b/README.md index db819f5..3ab8cc8 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,8 @@ 4. Run Embeddr-CLI with `embeddr serve` 5. Run ComfyUI - ## Usage - ### Use New Load & Save Image ![Example IO](.github/assets/io_nodes.png) @@ -32,8 +30,7 @@ ![Example Retrieval](.github/assets/retrieval_nodes.png) - -### Access more info on the [WebUI](https://github.com/embeddr-net/embeddr-cli) +### Access more info on the [WebUI](https://github.com/embeddr-net/embeddr-cli) ![Example Lineage](.github/assets/lineage_large.png) diff --git a/__init__.py b/__init__.py index bda3f04..cf12e4b 100644 --- a/__init__.py +++ b/__init__.py @@ -1,24 +1,47 @@ -import os import json +import mimetypes +import os +import re +import tempfile +from pathlib import Path + +import aiohttp from aiohttp import web -from server import PromptServer from comfy_api.latest import ComfyExtension, io +from server import PromptServer -from .nodes.EmbeddrUploadImage import EmbeddrSaveToFolderNode -from .nodes.EmbeddrLoadImage import EmbeddrLoadImageNode -from .nodes.EmbeddrLoadImages import EmbeddrLoadImagesNode -from .nodes.EmbeddrMergeIDs import EmbeddrMergeIDsNode -from .nodes.EmbeddrFindSimilar import EmbeddrFindSimilarNode +try: + import folder_paths +except Exception: + folder_paths = None + +import contextlib + +from .nodes.EmbeddrAction import EmbeddrActionNode +from .nodes.EmbeddrExtractArtifactInfo import EmbeddrExtractArtifactInfoNode +from .nodes.EmbeddrFindCollection import EmbeddrFindCollectionNode +from .nodes.EmbeddrFindSimilarArtifacts import EmbeddrFindSimilarArtifactsNode from .nodes.EmbeddrFindSimilarText import EmbeddrFindSimilarTextNode +from .nodes.EmbeddrFindSimilarToArtifact import EmbeddrFindSimilarToArtifactNode +from .nodes.EmbeddrLoadArtifact import EmbeddrLoadArtifactNode +from .nodes.EmbeddrLoadArtifacts import EmbeddrLoadArtifactsNode +from .nodes.EmbeddrLoadVideo import EmbeddrLoadVideoNode +from .nodes.EmbeddrLoRAStack import EmbeddrLoRAStack +from .nodes.EmbeddrMergeIDs import EmbeddrMergeIDsNode +from .nodes.EmbeddrSplitIDs import EmbeddrSplitIDsNode +from .nodes.EmbeddrUploadArtifact import EmbeddrUploadArtifactNode +from .nodes.EmbeddrUploadOptions import UploadArtifactOptionsNode from .nodes.EmbeddrUploadVideo import EmbeddrUploadVideo CONFIG_PATH = os.path.join(os.path.dirname(__file__), "config.json") +print("[Embeddr Extension] Loading... Proxy routes registered.") + def get_api_key(): if os.path.exists(CONFIG_PATH): try: - with open(CONFIG_PATH, "r") as f: + with open(CONFIG_PATH) as f: config = json.load(f) return config.get("api_key", "") except Exception: @@ -26,6 +49,234 @@ def get_api_key(): return "" +def _load_config() -> dict: + if os.path.exists(CONFIG_PATH): + try: + with open(CONFIG_PATH) as f: + return json.load(f) + except Exception: + return {} + return {} + + +def _normalize_base_url(url: str | None, default: str = "http://localhost:8003") -> str: + if not url: + return default + clean = str(url).strip().rstrip("/") + if clean.endswith("/api/v1"): + clean = clean[:-7] + elif clean.endswith("/api"): + clean = clean[:-4] + return clean.rstrip("/") + + +def _sanitize_filename(value: str | None, fallback: str) -> str: + candidate = Path(str(value or "").strip()).name + cleaned = re.sub(r"[^A-Za-z0-9._-]+", "_", candidate).strip("._") + return cleaned or fallback + + +def _candidate_library_roots(library_kind: str) -> list[Path]: + normalized = str(library_kind or "loras").strip().lower() or "loras" + candidates: list[Path] = [] + + if folder_paths is not None: + getter = getattr(folder_paths, "get_folder_paths", None) + if callable(getter): + try: + values = getter(normalized) + if isinstance(values, (list, tuple, set)): + candidates.extend(Path(str(item)).expanduser() for item in values if item) + elif values: + candidates.append(Path(str(values)).expanduser()) + except Exception: + pass + + mapping = getattr(folder_paths, "folder_names_and_paths", None) + if isinstance(mapping, dict): + entry = mapping.get(normalized) + roots = None + if isinstance(entry, tuple) and entry: + roots = entry[0] + elif isinstance(entry, (list, tuple, set)): + roots = entry + if isinstance(roots, (list, tuple, set)): + candidates.extend(Path(str(item)).expanduser() for item in roots if item) + elif roots: + candidates.append(Path(str(roots)).expanduser()) + + models_dir = getattr(folder_paths, "models_dir", None) + if models_dir: + candidates.append(Path(str(models_dir)).expanduser() / normalized) + + deduped: list[Path] = [] + seen = set() + for candidate in candidates: + try: + resolved = candidate.expanduser().resolve(strict=False) + except Exception: + resolved = candidate.expanduser() + key = str(resolved) + if not key or key in seen: + continue + seen.add(key) + deduped.append(resolved) + return deduped + + +def _guess_install_filename( + artifact_payload: dict, + *, + artifact_id: str, + file_name_override: str | None = None, +) -> str: + metadata = artifact_payload.get("metadata_json") if isinstance(artifact_payload, dict) else {} + metadata = metadata if isinstance(metadata, dict) else {} + comfy_meta = metadata.get("comfyui") if isinstance(metadata.get("comfyui"), dict) else {} + civitai_meta = metadata.get("civitai") if isinstance(metadata.get("civitai"), dict) else {} + + fallback_name = f"{artifact_id}.bin" + candidates = [ + file_name_override, + comfy_meta.get("suggested_filename"), + metadata.get("filename"), + Path(str(civitai_meta.get("local_path") or "")).name + if civitai_meta.get("local_path") + else None, + metadata.get("name"), + ] + + selected = next((value for value in candidates if str(value or "").strip()), None) + file_name = _sanitize_filename(selected, fallback_name) + if Path(file_name).suffix: + return file_name + + format_hint = str(metadata.get("format") or "").strip().lower() + ext_map = { + "safetensor": ".safetensors", + "safetensors": ".safetensors", + "ckpt": ".ckpt", + "pt": ".pt", + "pth": ".pth", + "bin": ".bin", + "onnx": ".onnx", + } + suffix = ext_map.get(format_hint) + if not suffix: + guessed = mimetypes.guess_extension( + str(artifact_payload.get("content_type") or "").split(";", 1)[0].strip() + ) + suffix = guessed or ".bin" + return f"{file_name}{suffix}" + + +def _build_sidecar_metadata(artifact_payload: dict, target_path: Path) -> dict: + metadata = artifact_payload.get("metadata_json") if isinstance(artifact_payload, dict) else {} + metadata = metadata if isinstance(metadata, dict) else {} + civitai_meta = metadata.get("civitai") if isinstance(metadata.get("civitai"), dict) else {} + size_bytes = metadata.get("size_bytes") + size_kb = None + try: + size_kb = float(size_bytes) / 1024 if size_bytes is not None else None + except Exception: + size_kb = None + + installed_at = None + try: + installed_at = target_path.stat().st_mtime + except Exception: + installed_at = None + + file_name = target_path.name + sidecar = { + "file_path": str(target_path), + "model_name": metadata.get("name") or civitai_meta.get("model_name"), + "base_model": metadata.get("base_model_name"), + "from_civitai": metadata.get("source") == "civitai", + "metadata_source": "embeddr-sync", + "size": size_bytes, + "modified": installed_at, + "downloadUrl": civitai_meta.get("download_url"), + "civitai": { + "name": metadata.get("name"), + "baseModel": metadata.get("base_model_name"), + "downloadUrl": civitai_meta.get("download_url"), + "model": { + "name": civitai_meta.get("model_name") or metadata.get("name"), + "baseModel": metadata.get("base_model_name"), + "type": civitai_meta.get("model_type"), + }, + "files": [ + { + "name": file_name, + "type": metadata.get("format"), + "sizeKB": size_kb, + "downloadUrl": civitai_meta.get("download_url"), + } + ], + "images": [], + "stats": {}, + "creator": {}, + }, + } + return sidecar + + +@PromptServer.instance.routes.get("/embeddr/proxy") +@PromptServer.instance.routes.post("/embeddr/proxy") +@PromptServer.instance.routes.put("/embeddr/proxy") +@PromptServer.instance.routes.delete("/embeddr/proxy") +async def proxy_request(request): + url = request.rel_url.query.get("url") + if not url: + return web.Response(status=400, text="Missing url param") + + # Load config to get API key + config = {} + if os.path.exists(CONFIG_PATH): + try: + with open(CONFIG_PATH) as f: + config = json.load(f) + except Exception: + pass + + api_key = config.get("api_key", "") + + headers = {} + if api_key: + headers["X-API-Key"] = api_key + + # Forward Content-Type if present + if "Content-Type" in request.headers: + headers["Content-Type"] = request.headers["Content-Type"] + + method = request.method + data = None + if request.can_read_body: + data = await request.read() + + async with aiohttp.ClientSession() as session: + try: + async with session.request(method, url, headers=headers, data=data) as resp: + # Create response with status code from upstream + response = web.StreamResponse(status=resp.status, reason=resp.reason) + + # Forward relevant headers + for h in ["Content-Type", "Content-Length", "Content-Disposition"]: + if h in resp.headers: + response.headers[h] = resp.headers[h] + + await response.prepare(request) + + async for chunk in resp.content.iter_chunked(1024 * 64): + await response.write(chunk) + + return response + except Exception as e: + print(f"[Embeddr Proxy Error] {e}") + return web.Response(status=500, text=str(e)) + + @PromptServer.instance.routes.post("/embeddr/config") async def save_config(request): try: @@ -39,9 +290,9 @@ async def save_config(request): config = {} if os.path.exists(CONFIG_PATH): try: - with open(CONFIG_PATH, "r") as f: + with open(CONFIG_PATH) as f: config = json.load(f) - except: + except Exception: pass if api_key is not None: @@ -63,36 +314,257 @@ async def save_config(request): @PromptServer.instance.routes.get("/embeddr/config") async def get_config(request): - config = {} - if os.path.exists(CONFIG_PATH): - try: - with open(CONFIG_PATH, "r") as f: - config = json.load(f) - except: - pass + config = _load_config() endpoint = config.get("endpoint", "http://localhost:8003") mode = config.get("mode", "local") grid_preview_contain = config.get("grid_preview_contain", False) + api_key = config.get("api_key", "") + + auth_salt = config.get("auth_salt", os.environ.get("EMBEDDR_AUTH_SALT", "")) + + # Return key for UI so frontend can make authorized requests + return web.json_response( + { + "endpoint": endpoint, + "mode": mode, + "grid_preview_contain": grid_preview_contain, + "api_key": api_key, + "auth_salt": auth_salt, + } + ) + + +@PromptServer.instance.routes.get("/embeddr/health") +async def embeddr_health(request): + config = _load_config() + endpoint = config.get("endpoint") or "http://localhost:8003" + api_key = config.get("api_key") + base_url = _normalize_base_url(endpoint) + + if not api_key: + return web.json_response( + { + "ok": False, + "status": "missing_key", + "endpoint": base_url, + "note": "ComfyUI API key is not configured.", + }, + status=400, + ) + + url = f"{base_url}/api/v1/plugins/embeddr-comfyui/check" + headers = {"X-API-Key": api_key} + + async with aiohttp.ClientSession() as session: + try: + async with session.get(url, headers=headers, timeout=5) as resp: + payload = None + try: + payload = await resp.json() + except Exception: + payload = None + + if resp.status == 403: + return web.json_response( + { + "ok": False, + "status": "unauthorized", + "endpoint": base_url, + "note": "Embeddr rejected the API key.", + "embeddr": payload, + }, + status=403, + ) + + return web.json_response( + { + "ok": resp.status < 400, + "status": resp.status, + "endpoint": base_url, + "embeddr": payload, + }, + status=200 if resp.status < 400 else resp.status, + ) + except Exception as e: + return web.json_response( + { + "ok": False, + "status": "error", + "endpoint": base_url, + "note": str(e), + }, + status=500, + ) + + +@PromptServer.instance.routes.post("/embeddr/install-artifact") +async def install_artifact(request): + try: + payload = await request.json() + if not isinstance(payload, dict): + payload = {} + except Exception as exc: + return web.json_response({"ok": False, "error": str(exc)}, status=400) + + artifact_id = str(payload.get("artifact_id") or "").strip() + if not artifact_id: + return web.json_response( + {"ok": False, "error": "artifact_id_required"}, + status=400, + ) + + library_kind = str(payload.get("library_kind") or "loras").strip().lower() or "loras" + overwrite = bool(payload.get("overwrite")) + file_name_override = payload.get("file_name") + + roots = _candidate_library_roots(library_kind) + if not roots: + return web.json_response( + { + "ok": False, + "error": "library_root_not_found", + "message": f"Could not resolve a ComfyUI library root for '{library_kind}'.", + }, + status=500, + ) + + target_root = roots[0] + target_root.mkdir(parents=True, exist_ok=True) + + config = _load_config() + base_url = _normalize_base_url(config.get("endpoint") or os.environ.get("EMBEDDR_BACKEND_URL")) + api_key = config.get("api_key", "") + headers = {"X-API-Key": api_key} if api_key else {} + + metadata_url = f"{base_url}/api/v1/artifacts/{artifact_id}" + content_url = f"{base_url}/api/v1/artifacts/{artifact_id}/content?proxy=1" + + timeout = aiohttp.ClientTimeout(total=None, connect=15, sock_read=300) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.get(metadata_url, headers=headers) as response: + if response.status >= 400: + detail = await response.text() + return web.json_response( + { + "ok": False, + "error": "artifact_lookup_failed", + "status": response.status, + "detail": detail, + }, + status=response.status, + ) + try: + artifact_payload = await response.json() + except Exception: + artifact_payload = {} + + file_name = _guess_install_filename( + artifact_payload, + artifact_id=artifact_id, + file_name_override=file_name_override, + ) + target_path = target_root / file_name + sidecar_path = target_path.with_name(f"{target_path.stem}.metadata.json") + + if target_path.exists() and not overwrite: + return web.json_response( + { + "ok": True, + "cached": True, + "artifact_id": artifact_id, + "library_kind": library_kind, + "file_name": target_path.name, + "local_path": str(target_path), + "sidecar_path": str(sidecar_path) if sidecar_path.exists() else None, + "message": "File already present on this instance.", + } + ) + + tmp_fd, tmp_name = tempfile.mkstemp( + prefix="embeddr-sync-", + suffix=target_path.suffix or ".bin", + dir=str(target_root), + ) + os.close(tmp_fd) + tmp_path = Path(tmp_name) + + try: + async with session.get(content_url, headers=headers) as response: + if response.status >= 400: + detail = await response.text() + return web.json_response( + { + "ok": False, + "error": "artifact_download_failed", + "status": response.status, + "detail": detail, + }, + status=response.status, + ) + + bytes_written = 0 + with tmp_path.open("wb") as handle: + async for chunk in response.content.iter_chunked(1024 * 256): + if not chunk: + continue + handle.write(chunk) + bytes_written += len(chunk) + + target_path.parent.mkdir(parents=True, exist_ok=True) + os.replace(tmp_path, target_path) + finally: + if tmp_path.exists(): + with contextlib.suppress(Exception): + tmp_path.unlink() + + sidecar_payload = _build_sidecar_metadata(artifact_payload, target_path) + try: + with sidecar_path.open("w", encoding="utf-8") as handle: + json.dump(sidecar_payload, handle, indent=2) + except Exception as exc: + return web.json_response( + { + "ok": False, + "error": "sidecar_write_failed", + "detail": str(exc), + "local_path": str(target_path), + }, + status=500, + ) - # Return masked key for UI - return web.json_response({ - "endpoint": endpoint, - "mode": mode, - "grid_preview_contain": grid_preview_contain - }) + return web.json_response( + { + "ok": True, + "cached": False, + "artifact_id": artifact_id, + "library_kind": library_kind, + "file_name": target_path.name, + "local_path": str(target_path), + "sidecar_path": str(sidecar_path), + "message": f"Installed {target_path.name} into {library_kind}.", + } + ) class EmbeddrComfyUIExtension(ComfyExtension): async def get_node_list(self) -> list[type[io.ComfyNode]]: return [ - EmbeddrFindSimilarNode, + EmbeddrFindSimilarArtifactsNode, + EmbeddrFindSimilarToArtifactNode, EmbeddrFindSimilarTextNode, - EmbeddrLoadImageNode, - EmbeddrLoadImagesNode, + EmbeddrLoadArtifactNode, + EmbeddrLoadArtifactsNode, EmbeddrMergeIDsNode, - EmbeddrSaveToFolderNode, + EmbeddrSplitIDsNode, + EmbeddrExtractArtifactInfoNode, + EmbeddrUploadArtifactNode, + UploadArtifactOptionsNode, EmbeddrUploadVideo, + EmbeddrLoadVideoNode, + EmbeddrLoRAStack, + EmbeddrFindCollectionNode, + EmbeddrActionNode, ] @@ -103,4 +575,4 @@ async def comfy_entrypoint() -> ComfyExtension: WEB_DIRECTORY = "./js" -__all__ = ["EmbeddrComfyUIExtension", "comfy_entrypoint", "WEB_DIRECTORY"] +__all__ = ["WEB_DIRECTORY", "EmbeddrComfyUIExtension", "comfy_entrypoint"] diff --git a/components.json b/components.json index 4b1c517..b1fa1ae 100644 --- a/components.json +++ b/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "", - "css": "src/globals.css", + "css": "ui/globals.css", "baseColor": "neutral", "cssVariables": true, "prefix": "" diff --git a/eslint.config.js b/eslint.config.js index e02d867..fc73694 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,5 +1,31 @@ -// @ts-check - +// @ts-check import { tanstackConfig } from "@tanstack/eslint-config"; -export default [...tanstackConfig]; +export default [ + ...tanstackConfig, + { + ignores: [ + "js/**", + "build/**", + ".vite/**", + ".venv/**", + "coverage/**", + "*.config.{js,ts,mjs,cjs,mts}", + ], + }, + { + files: ["ui/**/*.{ts,tsx}", "nodes/**/*.{ts,tsx}"], + rules: { + // pending eslint-plugin-react-hooks v7 + tanstack-config compatibility + "react-hooks/exhaustive-deps": "off", + // Too aggressive for code that handles localStorage / untyped JSON + // (e.g. version-checking parsed values where TS narrows to a literal). + "@typescript-eslint/no-unnecessary-condition": "off", + // ComfyUI integration files extensively use @ts-ignore to bridge + // ComfyUI globals (`app`, the LiteGraph types, etc) that lack + // proper TypeScript declarations. Requiring a description on each + // would just generate noise like "@ts-ignore — ComfyUI lacks types". + "@typescript-eslint/ban-ts-comment": "off", + }, + }, +]; diff --git a/nodes/EmbeddrAction.py b/nodes/EmbeddrAction.py new file mode 100644 index 0000000..bcdcc5e --- /dev/null +++ b/nodes/EmbeddrAction.py @@ -0,0 +1,508 @@ +""" +EmbeddrAction - Dynamic Lotus Action node for ComfyUI. + +Introspects Lotus capabilities (kind=action) at execution time and +dispatches the selected action via the Embeddr REST API. + +The frontend extension (ui/nodes/EmbeddrAction.ts) handles the +dynamic input/widget creation based on the selected action's schema. +""" + +import contextlib +import json + +import requests +from comfy_api.latest import io + +from .types import EmbeddrArtifactID, EmbeddrArtifactIDObject +from .utils.config import get_auth_headers, get_embeddr_base_url + + +def _log(msg: str): + print(f"[Embeddr Action] {msg}") + + +_DYN_PREFIX = "dyn_" + + +def _fetch_actions() -> list[dict]: + """Fetch all exposed Lotus actions from the backend.""" + try: + base_url = get_embeddr_base_url() + resp = requests.get( + f"{base_url}/api/v1/lotus/list", + params={"kind": "action", "limit": 500}, + headers=get_auth_headers(), + timeout=5, + ) + if resp.status_code == 200: + data = resp.json() + items = data.get("items", []) if isinstance(data, dict) else data + # Only return actions that are exposed via lotus API + exposed = [] + for cap in items: + action = cap.get("action") or {} + expose = action.get("expose") or {} + # Include if exposed, or if no expose policy (lenient) + if expose.get("lotus", True): + exposed.append(cap) + return exposed + except Exception as e: + _log(f"Failed to fetch actions: {e}") + return [] + + +# ── Cache for capability schema lookup ── +_cap_cache: dict[str, dict] = {} +_cap_cache_ts: float = 0.0 +_CAP_CACHE_TTL = 30.0 + + +def _get_cap_schema(cap_id: str) -> dict | None: + """Get the capability dict for a given cap_id, using a short-lived cache.""" + import time + + global _cap_cache, _cap_cache_ts + + now = time.time() + if now - _cap_cache_ts > _CAP_CACHE_TTL: + actions = _fetch_actions() + _cap_cache = {a["id"]: a for a in actions if "id" in a} + _cap_cache_ts = now + + return _cap_cache.get(cap_id) + + +def _get_input_schema_props(cap: dict) -> dict[str, dict]: + """Extract the JSON Schema 'properties' from a capability's input schema.""" + action = cap.get("action") or cap.get("data", {}) + inp = action.get("input") or {} + schema = inp.get("schema") or {} + return schema.get("properties", {}) + + +def _get_output_schema_props(cap: dict) -> dict[str, dict]: + """Extract JSON Schema 'properties' for action output.""" + action = cap.get("action") or cap.get("data", {}) + out = action.get("output") or {} + schema = out.get("schema") or {} + return schema.get("properties", {}) + + +def _pick_first_value(source: dict, keys: list[str]): + for key in keys: + if key in source and source.get(key) is not None: + return source.get(key) + return None + + +def _pick_first_value_deep(source, keys: list[str]): + """Depth-first search through nested dict/list values for preferred keys.""" + if not isinstance(source, (dict, list)): + return None + + stack = [source] + seen_ids: set[int] = set() + while stack: + cur = stack.pop() + cur_id = id(cur) + if cur_id in seen_ids: + continue + seen_ids.add(cur_id) + + if isinstance(cur, dict): + val = _pick_first_value(cur, keys) + if val is not None: + return val + for v in cur.values(): + if isinstance(v, (dict, list)): + stack.append(v) + elif isinstance(cur, list): + for item in cur: + if isinstance(item, (dict, list)): + stack.append(item) + + return None + + +def _build_action_choices() -> list[str]: + """Build a list of action IDs for the combo dropdown.""" + actions = _fetch_actions() + choices = ["(select action)"] + for cap in actions: + cap_id = cap.get("id", "") + title = cap.get("title", cap_id) + plugin = cap.get("plugin", "") + label = f"{cap_id}" + if title and title != cap_id: + label = f"{cap_id} [{title}]" + if plugin: + label = f"{plugin}/{label}" + choices.append(label) + return choices + + +def _extract_cap_id(choice: str) -> str: + """Extract the raw capability ID from a combo label. + + Labels look like: + - "plugin/cap.id [Title]" + - "plugin/cap.id" + - "cap.id [Title]" + - "cap.id" + """ + if not choice or choice == "(select action)": + return "" + + # Strip plugin prefix if present + if "/" in choice: + choice = choice.split("/", 1)[1] + + # Strip title suffix + if " [" in choice: + choice = choice.split(" [", 1)[0] + + return choice.strip() + + +class EmbeddrActionNode(io.ComfyNode): + """ + Executes any Lotus action dynamically. + + The node exposes a combo dropdown populated with available actions + and a JSON payload input. The frontend extension adds typed widgets + for each input declared by the selected action's schema; those values + are merged into the payload at execution time. + """ + + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="embeddr.Action", + display_name="Embeddr Action", + description=( + "Execute any Lotus action. Select an action from the " + "dropdown — input widgets are created automatically." + ), + category="Embeddr/Lotus", + inputs=[ + # String input — the frontend extension converts this into + # a combo widget populated with live Lotus actions. + # We intentionally avoid io.Combo.Input because ComfyUI + # validates combo values against the static options list + # defined at schema time, which cannot include actions + # discovered dynamically from the backend. + io.String.Input( + "action_id", + default="", + tooltip="Select a Lotus action to execute (populated by frontend)", + ), + io.String.Input( + "payload_json", + default="{}", + multiline=True, + tooltip=( + "Additional JSON payload merged with widget inputs. " + "Widget values take precedence." + ), + ), + # Optional artifact input for actions that operate on artifacts + EmbeddrArtifactID.Input( + "artifact_id", + optional=True, + tooltip="Artifact ID input (passed as 'artifact_id' in payload)", + ), + EmbeddrArtifactID.Input( + "artifact_ids", + optional=True, + tooltip="Multiple artifact IDs (passed as 'artifact_ids' in payload)", + ), + ], + outputs=[ + io.String.Output( + "result_json", + tooltip="Full JSON response from the action", + ), + io.String.Output( + "status", + tooltip="'ok', 'error', or 'pending'", + ), + EmbeddrArtifactID.Output( + "output_artifact_ids", + tooltip="Artifact IDs extracted from the result (if any)", + ), + io.String.Output( + "text", + tooltip="Primary text content (caption, message, value, etc.)", + ), + io.String.Output( + "error", + tooltip="Error message (empty on success)", + ), + ], + ) + + @classmethod + def execute( + cls, + action_id: str, + payload_json: str = "{}", + artifact_id: EmbeddrArtifactIDObject | None = None, + artifact_ids: EmbeddrArtifactIDObject | None = None, + **kwargs, + ): + cap_id = _extract_cap_id(action_id) + if not cap_id: + _log("No action selected") + return io.NodeOutput( + json.dumps({"error": "No action selected"}), + "error", + "", + "", + "No action selected", + ) + + # ── Build payload from payload_json ── + # The frontend packs all dyn_* widget values into payload_json + # before serialization (since ComfyUI only passes schema-declared + # inputs to execute). + try: + payload = json.loads(payload_json) if payload_json.strip() else {} + except json.JSONDecodeError as e: + _log(f"Invalid payload JSON: {e}") + payload = {} + + # Merge connected dynamic inputs (dyn_*) so graph-linked values + # can drive action payload fields. These override payload_json. + for key, val in (kwargs or {}).items(): + if not isinstance(key, str) or not key.startswith(_DYN_PREFIX): + continue + + payload_key = key[len(_DYN_PREFIX) :] + if not payload_key: + continue + + runtime_val = val + if isinstance(runtime_val, list) and len(runtime_val) == 1: + runtime_val = runtime_val[0] + + if isinstance(runtime_val, EmbeddrArtifactIDObject): + runtime_val = runtime_val.artifact_id + + if isinstance(runtime_val, str): + trimmed = runtime_val.strip() + if trimmed.startswith("{") or trimmed.startswith("["): + with contextlib.suppress(Exception): + runtime_val = json.loads(trimmed) + + if runtime_val is None: + continue + if isinstance(runtime_val, str) and runtime_val == "": + continue + + payload[payload_key] = runtime_val + + # ── Schema-aware artifact_id mapping ── + cap = _get_cap_schema(cap_id) + schema_props = _get_input_schema_props(cap) if cap else {} + has_resource_field = "resource" in schema_props + + # Extract connected artifact IDs + raw_aid = None + if artifact_id: + raw_aid = ( + artifact_id.artifact_id + if isinstance(artifact_id, EmbeddrArtifactIDObject) + else artifact_id + ) + + raw_aids = None + if artifact_ids: + raw_aids = ( + artifact_ids.artifact_id + if isinstance(artifact_ids, EmbeddrArtifactIDObject) + else artifact_ids + ) + if raw_aids and not isinstance(raw_aids, list): + raw_aids = [raw_aids] + + if raw_aid or raw_aids: + # Deduplicate + seen: set[str] = set() + unique_ids: list[str] = [] + for aid in ( + [raw_aid] + if isinstance(raw_aid, str) and raw_aid + else (raw_aid if isinstance(raw_aid, list) else []) + ): + if aid and aid not in seen: + seen.add(aid) + unique_ids.append(aid) + for aid in raw_aids or []: + if aid and aid not in seen: + seen.add(aid) + unique_ids.append(aid) + + if unique_ids: + if has_resource_field and "resource" not in payload: + payload["resource"] = unique_ids[0] if len(unique_ids) == 1 else unique_ids + else: + if "artifact_id" not in payload: + payload["artifact_id"] = unique_ids[0] + if len(unique_ids) > 1 and "artifact_ids" not in payload: + payload["artifact_ids"] = unique_ids + + # Dispatch the action + base_url = get_embeddr_base_url() + url = f"{base_url}/api/v1/lotus/{cap_id}" + + _log(f"Invoking action '{cap_id}' with payload: {json.dumps(payload, default=str)[:500]}") + + try: + resp = requests.post( + url, + json=payload, + headers={ + **get_auth_headers(), + "Content-Type": "application/json", + }, + timeout=120, + ) + + _log(f"Response: {resp.status_code}") + + if resp.status_code >= 400: + error_text = resp.text[:500] + _log(f"Action error: {error_text}") + return io.NodeOutput( + json.dumps({"error": error_text, "status_code": resp.status_code}), + "error", + "", + "", + error_text, + ) + + result = ( + resp.json() + if resp.headers.get("content-type", "").startswith("application/json") + else {"raw": resp.text} + ) + + # ── Extract typed outputs from result ── + output_ids = "" + text_output = "" + error_output = "" + status_str = "ok" + + if isinstance(result, dict): + outputs_obj = ( + result.get("outputs") if isinstance(result.get("outputs"), dict) else {} + ) + output_schema_props = _get_output_schema_props(cap) if cap else {} + + # Status + if "status" in result: + status_str = str(result["status"]) + elif result.get("ok") is False: + status_str = "error" + + # Artifact IDs + artifact_keys = [ + key + for key in ( + "artifact_id", + "id", + "artifact_ids", + "ids", + "output_artifact_id", + ) + if (not output_schema_props) or key in output_schema_props + ] or ["artifact_id", "id", "artifact_ids", "ids", "output_artifact_id"] + artifact_val = _pick_first_value(outputs_obj, artifact_keys) + if artifact_val is None: + artifact_val = _pick_first_value(result, artifact_keys) + if artifact_val: + output_ids = ( + artifact_val if isinstance(artifact_val, str) else json.dumps(artifact_val) + ) + + # Primary text content (captions, messages, values, etc.) + _TEXT_KEYS = ( + "response_text", + "caption_text", + "caption", + "value", + "text", + "message", + "content", + "description", + "summary", + "output", + "answer", + "response", + ) + text_keys = [ + key + for key in _TEXT_KEYS + if (not output_schema_props) or key in output_schema_props + ] or list(_TEXT_KEYS) + text_val = _pick_first_value(outputs_obj, text_keys) + if text_val is None: + text_val = _pick_first_value(result, text_keys) + if text_val is None: + text_val = _pick_first_value_deep(outputs_obj, text_keys) + if text_val is None: + text_val = _pick_first_value_deep(result, text_keys) + if isinstance(text_val, str) and text_val.strip().lower() in { + "completed", + "queued", + "pending", + "ok", + "success", + }: + better_val = _pick_first_value_deep( + outputs_obj, + [k for k in text_keys if k != "message"], + ) + if better_val is None: + better_val = _pick_first_value_deep( + result, + [k for k in text_keys if k != "message"], + ) + if better_val is not None: + text_val = better_val + if text_val is not None: + text_output = str(text_val) if not isinstance(text_val, str) else text_val + + # Error + if result.get("error"): + error_output = str(result["error"]) + elif isinstance(outputs_obj, dict) and outputs_obj.get("error"): + error_output = str(outputs_obj["error"]) + + return io.NodeOutput( + json.dumps(result, default=str), + status_str, + output_ids, + text_output, + error_output, + ) + + except requests.Timeout: + _log(f"Action '{cap_id}' timed out") + return io.NodeOutput( + json.dumps({"error": "Request timed out (120s)"}), + "error", + "", + "", + "Request timed out (120s)", + ) + except Exception as e: + _log(f"Action '{cap_id}' failed: {e}") + return io.NodeOutput( + json.dumps({"error": str(e)}), + "error", + "", + "", + str(e), + ) diff --git a/nodes/EmbeddrExtractArtifactInfo.py b/nodes/EmbeddrExtractArtifactInfo.py new file mode 100644 index 0000000..3832613 --- /dev/null +++ b/nodes/EmbeddrExtractArtifactInfo.py @@ -0,0 +1,91 @@ +from comfy_api.latest import io + +from .types import EmbeddrArtifactID, EmbeddrArtifactIDObject, EmbeddrArtifactInfo + + +class EmbeddrExtractArtifactInfoNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="embeddr.ExtractArtifactInfo", + display_name="Embeddr Extract Artifact Info (V2)", + description="Extracts specific metadata fields from an Artifact Info object.", + category="Embeddr", + inputs=[ + EmbeddrArtifactInfo.Input( + "artifact_info", tooltip="Artifact Info object from Load Artifact" + ), + ], + outputs=[ + EmbeddrArtifactID.Output("parent_ids", tooltip="List of parent Artifact IDs"), + # EmbeddrArtifactID.Output("collection_ids", tooltip="List of collection IDs"), + io.String.Output("tags", tooltip="Comma-separated tags"), + io.String.Output("all_json", tooltip="Full JSON dump of the artifact metadata"), + ], + ) + + @classmethod + def execute(cls, artifact_info): + if not artifact_info: + return io.NodeOutput(EmbeddrArtifactIDObject(artifact_id=""), "", "{}") + + data = artifact_info.data + + # Parse Parents (from relations or metadata) + # Note: API usually returns `parents` relation list in `relations` key or similar depending on implementation + # For now, let's assume standard Artifact response structure. + # If the API doesn't return relations inline, we might need to rely on metadata_json['parent_ids'] + + parents = [] + + # Helper to safely dig into dicts + def get_parents_from_dict(d): + if not isinstance(d, dict): + return [] + res = [] + # Check direct key + if "parent_ids" in d: + val = d["parent_ids"] + if isinstance(val, list): + res.extend([str(v) for v in val]) + elif isinstance(val, str): + res.extend([v.strip() for v in val.split(",")]) + + # Check comfy_meta subkey + if "comfy_meta" in d: + res.extend(get_parents_from_dict(d["comfy_meta"])) + + return res + + # 1. Try Top Level + parents.extend(get_parents_from_dict(data)) + + # 2. Try metadata_json (if distinct from data root) + meta = data.get("metadata_json") + if meta and isinstance(meta, dict): + parents.extend(get_parents_from_dict(meta)) + + # Dedup + parents = list(set(parents)) + parent_str = ",".join(parents) + + # Tags (often in relations or separate 'tags' key) + tags = [] + if "tags" in data: + # If tags are objects + if isinstance(data["tags"], list): + for t in data["tags"]: + if isinstance(t, dict): + tags.append(t.get("label", "")) + else: + tags.append(str(t)) + elif "tags" in meta: + tags = meta["tags"] + + tags_str = ",".join(tags) + + import json + + json_str = json.dumps(data, indent=2) + + return io.NodeOutput(EmbeddrArtifactIDObject(artifact_id=parent_str), tags_str, json_str) diff --git a/nodes/EmbeddrFindCollection.py b/nodes/EmbeddrFindCollection.py new file mode 100644 index 0000000..930a3d9 --- /dev/null +++ b/nodes/EmbeddrFindCollection.py @@ -0,0 +1,108 @@ +import requests +from comfy_api.latest import io + +from .utils.config import get_auth_headers, get_config + + +def Embeddr_Log(message: str): + print(f"[Embeddr] {message}") + + +class EmbeddrFindCollectionNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="embeddr.FindCollection", + display_name="Embeddr Find Collection", + category="Embeddr", + inputs=[ + io.String.Input( + "collection_name", + default="", + optional=True, + tooltip="Name to find (or create if missing)", + ), + io.String.Input( + "collection_id", default="", tooltip="Direct Collection ID (overrides Name)" + ), + io.Boolean.Input( + "create_if_missing", + default=True, + tooltip="Create collection if it doesn't exist (Only applies to Name)", + ), + ], + outputs=[ + io.String.Output("collection_id"), + ], + ) + + @classmethod + def execute(cls, collection_name, create_if_missing, collection_id=""): + Embeddr_Log( + f"EXECUTE FindCollection: name='{collection_name}', id='{collection_id}', create={create_if_missing}" + ) + config = get_config() + base_url = config.get("embeddr_url") or config.get("endpoint") or "http://localhost:8003" + base_url = base_url.rstrip("/") + + # 1. Direct ID Priority + if collection_id and len(str(collection_id).strip()) > 10: + Embeddr_Log(f"Using Direct Collection ID: {collection_id}") + # Assume valid UUID if present + return io.NodeOutput(collection_id) + + try: + # 2. List Collections to Find by Name + # Note: Removed limit=1000 to avoid potential 422 if API doesn't support it + resp = requests.get(f"{base_url}/api/v1/collections", headers=get_auth_headers()) + # If 404, maybe endpoint is different. + if resp.status_code == 404: + # Fallback to V1? Or just fail. + pass + + collections = [] + if resp.status_code == 200: + data = resp.json() + if isinstance(data, list): + collections = data + elif isinstance(data, dict) and "items" in data: + collections = data["items"] + + found = None + if collection_name: + for c in collections: + # Case insensitive match? user might prefer exact. + # API returns 'label' usually, but maybe 'name' in some versions + label = c.get("label") or c.get("name") + if label and label.lower() == collection_name.lower(): + found = c + break + + if found: + Embeddr_Log( + f"Found Collection: {found.get('label', 'Unnamed')} ({found.get('id')})" + ) + return io.NodeOutput(str(found.get("id"))) + + if collection_name and create_if_missing: + # Create + payload = { + "label": collection_name, + "type_name": "collection:mix", + "uri": f"embeddr:///collections/{collection_name.lower().replace(' ', '_')}", + } + resp = requests.post( + f"{base_url}/api/v1/collections", json=payload, headers=get_auth_headers() + ) + resp.raise_for_status() + new_col = resp.json() + Embeddr_Log(f"Created Collection: {new_col.get('label')} ({new_col.get('id')})") + return io.NodeOutput(str(new_col.get("id"))) + + Embeddr_Log(f"Collection '{collection_name}' not found and creation disabled.") + # Fallback to empty string + return io.NodeOutput("") + + except Exception as e: + Embeddr_Log(f"FindCollection error: {e}") + return io.NodeOutput("") diff --git a/nodes/EmbeddrFindSimilar.py b/nodes/EmbeddrFindSimilar.py deleted file mode 100644 index 9abd896..0000000 --- a/nodes/EmbeddrFindSimilar.py +++ /dev/null @@ -1,111 +0,0 @@ -import requests -import torch -import numpy as np -from PIL import Image -import io as pyio -from comfy_api.latest import io, ui -from .utils import get_config -from .utils.api import get_libraries, get_collections - - -class EmbeddrFindSimilarNode(io.ComfyNode): - @classmethod - def define_schema(cls) -> io.Schema: - # Fetch dynamic options - libraries = ["All"] + get_libraries() - collections = ["All"] + get_collections() - - return io.Schema( - node_id="embeddr.FindSimilar", - display_name="Embeddr Find Similar", - description="Finds similar images in your Embeddr library using an input image.", - category="Embeddr", - inputs=[ - io.Image.Input("image"), - io.Combo.Input("library", options=libraries, default="All"), - io.Combo.Input( - "collection", options=collections, default="All"), - io.Int.Input("limit", default=5, min=1, max=50), - io.Float.Input("threshold", default=0.0, min=0.0, - max=1.0, step=0.01, display_name="Min Score"), - ], - outputs=[ - io.Image.Output("images", is_output_list=True), - io.String.Output("embeddr_ids", is_output_list=True), - ], - ) - - @classmethod - def execute(cls, image, library="All", collection="All", limit=5, threshold=0.0): - config = get_config() - endpoint = config.get("endpoint", "http://localhost:8003") - api_url = endpoint.rstrip("/") + "/api/v1/images/search/image" - - # Prepare image (take first of batch) - img_array = (image[0].cpu().numpy() * 255).astype(np.uint8) - img = Image.fromarray(np.clip(img_array, 0, 255)) - - buf = pyio.BytesIO() - img.save(buf, format="PNG") - buf.seek(0) - - files = {"file": ("image.png", buf, "image/png")} - data = { - "limit": limit, - } - - # Parse IDs from "ID: Name" format - if library != "All": - try: - lib_id = int(library.split(":")[0]) - data["library_id"] = lib_id - except: - pass - - if collection != "All": - try: - col_id = int(collection.split(":")[0]) - data["collection_id"] = col_id - except: - pass - - try: - response = requests.post(api_url, files=files, data=data) - response.raise_for_status() - results = response.json() - items = results.get("items", []) - - if not items: - # Return empty - empty = torch.zeros( - (1, 64, 64, 3), dtype=torch.float32, device="cpu") - return io.NodeOutput(empty, "[]") - - # Load images - output_images = [] - output_ids = [] - - for item in items: - # Fetch image file - img_url = endpoint.rstrip( - "/") + f"/api/v1/images/{item['id']}/file" - img_resp = requests.get(img_url) - if img_resp.status_code == 200: - i = Image.open(pyio.BytesIO(img_resp.content)) - i = i.convert("RGB") - i = np.array(i).astype(np.float32) / 255.0 - output_images.append(torch.from_numpy(i)) - output_ids.append(str(item['id'])) - - if not output_images: - return io.NodeOutput([], []) - - # Return list of images (unsqueeze to add batch dimension 1) - final_images = [img.unsqueeze(0) for img in output_images] - return io.NodeOutput(final_images, output_ids) - - except Exception as e: - print(f"[Embeddr] Search failed: {e}") - empty = torch.zeros( - (1, 64, 64, 3), dtype=torch.float32, device="cpu") - return io.NodeOutput(empty, "[]") diff --git a/nodes/EmbeddrFindSimilarArtifacts.py b/nodes/EmbeddrFindSimilarArtifacts.py new file mode 100644 index 0000000..985432a --- /dev/null +++ b/nodes/EmbeddrFindSimilarArtifacts.py @@ -0,0 +1,91 @@ +import io as pyio + +import numpy as np +import requests +import torch +from comfy_api.latest import io +from PIL import Image + +from .utils import get_config +from .utils.config import get_auth_headers + + +class EmbeddrFindSimilarArtifactsNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="embeddr.FindSimilarArtifacts", + display_name="Embeddr Find Similar Artifacts (V2)", + description="Finds similar artifacts using an input image via V2 API.", + category="Embeddr", + inputs=[ + io.Image.Input("image"), + io.Int.Input("limit", default=5, min=1, max=50), + io.String.Input("model_name", default="lotus"), + ], + outputs=[ + io.Image.Output("images", is_output_list=True), + io.String.Output("artifact_ids", is_output_list=True), + ], + ) + + @classmethod + def execute(cls, image, limit, model_name="lotus"): + config = get_config() + base_url = config.get("embeddr_url") or config.get("endpoint") or "http://localhost:8003" + base_url = base_url.rstrip("/") + + # Endpoint in Plugin + api_url = f"{base_url}/api/v1/plugins/embeddr-comfyui/find_similar" + + # Prepare image (take first of batch for query) + img_array = (image[0].cpu().numpy() * 255).astype(np.uint8) + img = Image.fromarray(np.clip(img_array, 0, 255)) + + buf = pyio.BytesIO() + img.save(buf, format="PNG") + buf.seek(0) + + files = {"file": ("query.png", buf, "image/png")} + data = {"limit": limit, "model_name": model_name} + + try: + # Upload & Search + response = requests.post(api_url, files=files, data=data, headers=get_auth_headers()) + response.raise_for_status() + results = response.json() + items = results.get("items", []) # List of objects {id, uri, ...} + + if not items: + empty = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + return io.NodeOutput([empty], ["-1"]) + + output_images = [] + output_ids = [] + + for item in items: + art_id = item.get("id") + content_url = f"{base_url}/api/v1/plugins/embeddr-comfyui/content/{art_id}" + + try: + img_resp = requests.get(content_url, headers=get_auth_headers()) + if img_resp.status_code == 200: + i = Image.open(pyio.BytesIO(img_resp.content)) + i = i.convert("RGB") + i_np = np.array(i).astype(np.float32) / 255.0 + # Add batch dim [1, H, W, C] + output_images.append(torch.from_numpy(i_np)[None,]) + output_ids.append(str(art_id)) + except Exception as e: + print(f"Failed to fetch content for similar item {art_id}: {e}") + + if not output_images: + empty = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + return io.NodeOutput([empty], ["-1"]) + + return io.NodeOutput(output_images, output_ids) + + except Exception as e: + print(f"[Embeddr] FindSimilar Error: {e}") + empty = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + return io.NodeOutput([empty], ["-1"]) diff --git a/nodes/EmbeddrFindSimilarText.py b/nodes/EmbeddrFindSimilarText.py index 0769fae..06258d1 100644 --- a/nodes/EmbeddrFindSimilarText.py +++ b/nodes/EmbeddrFindSimilarText.py @@ -1,19 +1,22 @@ +import io as pyio + +import numpy as np import requests import torch -import numpy as np +from comfy_api.latest import io from PIL import Image, ImageOps -import io as pyio -from comfy_api.latest import io, ui + from .utils import get_config -from .utils.api import get_libraries, get_collections +from .utils.api import get_collections, get_libraries +from .utils.config import get_auth_headers class EmbeddrFindSimilarTextNode(io.ComfyNode): @classmethod def define_schema(cls) -> io.Schema: # Fetch dynamic options - libraries = ["All"] + get_libraries() - collections = ["All"] + get_collections() + libraries = ["All", *get_libraries()] + collections = ["All", *get_collections()] return io.Schema( node_id="embeddr.FindSimilarText", @@ -23,8 +26,7 @@ def define_schema(cls) -> io.Schema: inputs=[ io.String.Input("prompt", multiline=True), io.Combo.Input("library", options=libraries, default="All"), - io.Combo.Input( - "collection", options=collections, default="All"), + io.Combo.Input("collection", options=collections, default="All"), io.Int.Input("limit", default=5, min=1, max=50), ], outputs=[ @@ -49,40 +51,35 @@ def execute(cls, prompt, library="All", collection="All", limit=5): try: lib_id = int(library.split(":")[0]) params["library_id"] = lib_id - except: + except Exception: pass if collection != "All": try: col_id = int(collection.split(":")[0]) params["collection_id"] = col_id - except: + except Exception: pass try: - response = requests.get(api_url, params=params) + response = requests.get(api_url, params=params, headers=get_auth_headers()) response.raise_for_status() results = response.json() items = results.get("items", []) if not items: # Return empty - empty = torch.zeros( - (1, 64, 64, 3), dtype=torch.float32, device="cpu") + empty = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") return io.NodeOutput(empty, "") # Load images output_images = [] output_ids = [] - first_width = 0 - first_height = 0 - for item in items: # Fetch image file - img_url = endpoint.rstrip( - "/") + f"/api/v1/images/{item['id']}/file" - img_resp = requests.get(img_url) + img_url = endpoint.rstrip("/") + f"/api/v1/images/{item['id']}/file" + img_resp = requests.get(img_url, headers=get_auth_headers()) if img_resp.status_code == 200: i = Image.open(pyio.BytesIO(img_resp.content)) i = ImageOps.exif_transpose(i) @@ -91,7 +88,7 @@ def execute(cls, prompt, library="All", collection="All", limit=5): i = np.array(i).astype(np.float32) / 255.0 # Add batch dimension (1, H, W, C) output_images.append(torch.from_numpy(i).unsqueeze(0)) - output_ids.append(str(item['id'])) + output_ids.append(str(item["id"])) if not output_images: return io.NodeOutput([], []) @@ -100,6 +97,5 @@ def execute(cls, prompt, library="All", collection="All", limit=5): except Exception as e: print(f"[Embeddr] Find Similar Text error: {e}") - empty = torch.zeros( - (1, 64, 64, 3), dtype=torch.float32, device="cpu") + empty = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") return io.NodeOutput(empty, "") diff --git a/nodes/EmbeddrFindSimilarToArtifact.py b/nodes/EmbeddrFindSimilarToArtifact.py new file mode 100644 index 0000000..61f5bf0 --- /dev/null +++ b/nodes/EmbeddrFindSimilarToArtifact.py @@ -0,0 +1,89 @@ +import io as pyio + +import numpy as np +import requests +import torch +from comfy_api.latest import io +from PIL import Image + +from .types import EmbeddrArtifactID +from .utils import get_config +from .utils.config import get_auth_headers + + +class EmbeddrFindSimilarToArtifactNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="embeddr.FindSimilarToArtifact", + display_name="Embeddr Find Similar To Artifact ID", + description="Finds similar artifacts using an existing Artifact ID.", + category="Embeddr", + inputs=[ + EmbeddrArtifactID.Input("artifact_id", tooltip="The Source Artifact ID"), + io.Int.Input("limit", default=5, min=1, max=50), + io.String.Input("model_name", default="lotus"), + ], + outputs=[ + io.Image.Output("images", is_output_list=True), + io.String.Output("artifact_ids", is_output_list=True), + ], + ) + + @classmethod + def execute(cls, artifact_id, limit, model_name="lotus"): + config = get_config() + base_url = config.get("embeddr_url") or config.get("endpoint") or "http://localhost:8003" + base_url = base_url.rstrip("/") + + # Endpoint in Plugin + api_url = f"{base_url}/api/v1/plugins/embeddr-comfyui/find_similar_to_id" + + # Resolve ID if object + aid = str(artifact_id) + if hasattr(artifact_id, "artifact_id"): + aid = str(artifact_id.artifact_id) + if isinstance(aid, list): + aid = aid[0] # Take first if list + + data = {"artifact_id": aid, "limit": limit, "model_name": model_name} + + try: + # Search + response = requests.post(api_url, data=data, headers=get_auth_headers()) + response.raise_for_status() + results = response.json() + items = results.get("items", []) + + if not items: + empty = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + return io.NodeOutput([empty], ["-1"]) + + output_images = [] + output_ids = [] + + for item in items: + art_id = item.get("id") + content_url = f"{base_url}/api/v1/plugins/embeddr-comfyui/content/{art_id}" + + try: + img_resp = requests.get(content_url, headers=get_auth_headers()) + if img_resp.status_code == 200: + i = Image.open(pyio.BytesIO(img_resp.content)) + i = i.convert("RGB") + i_np = np.array(i).astype(np.float32) / 255.0 + output_images.append(torch.from_numpy(i_np)[None,]) + output_ids.append(str(art_id)) + except Exception as e: + print(f"Failed to fetch content for similar item {art_id}: {e}") + + if not output_images: + empty = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + return io.NodeOutput([empty], ["-1"]) + + return io.NodeOutput(output_images, output_ids) + + except Exception as e: + print(f"[Embeddr] FindSimilarToID Error: {e}") + empty = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + return io.NodeOutput([empty], ["-1"]) diff --git a/nodes/EmbeddrLoRAStack.py b/nodes/EmbeddrLoRAStack.py new file mode 100644 index 0000000..44dc2c8 --- /dev/null +++ b/nodes/EmbeddrLoRAStack.py @@ -0,0 +1,57 @@ +import folder_paths +from comfy_api.latest import io + + +class EmbeddrLoRAStack(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + loras = folder_paths.get_filename_list("loras") + + inputs = [ + io.Model.Input("model"), + ] + + # Add 1 slot initially, frontend will handle the rest + inputs.append(io.Combo.Input("lora_1", default="None", options=["None", *loras])) + inputs.append(io.Float.Input("strength_1", default=1.0, min=-10.0, max=10.0, step=0.01)) + + return io.Schema( + node_id="embeddr.LoRAStack", + display_name="Embeddr LoRA Stack", + description="Apply multiple LoRAs to a model and clip.", + category="Embeddr", + inputs=inputs, + outputs=[ + io.Model.Output("model"), + ], + ) + + @classmethod + def execute(cls, model, **kwargs): + import comfy.sd + import comfy.utils + + out_model = model + + # Iterate over all provided lora inputs + # We expect keys like lora_1, strength_1, lora_2, strength_2, etc. + + # Find all lora keys + lora_keys = [k for k in kwargs if k.startswith("lora_")] + # Sort them by index + lora_keys.sort(key=lambda x: int(x.split("_")[1])) + + for key in lora_keys: + i = key.split("_")[1] + lora_name = kwargs.get(f"lora_{i}") + strength = kwargs.get(f"strength_{i}", 1.0) + + if lora_name and lora_name != "None": + lora_path = folder_paths.get_full_path("loras", lora_name) + if lora_path: + lora = comfy.utils.load_torch_file(lora_path, safe_load=True) + out_model, _out_clip = comfy.sd.load_lora_for_models( + out_model, None, lora, strength, strength + ) + + return io.NodeOutput(out_model) diff --git a/nodes/EmbeddrLoadArtifact.py b/nodes/EmbeddrLoadArtifact.py new file mode 100644 index 0000000..f103834 --- /dev/null +++ b/nodes/EmbeddrLoadArtifact.py @@ -0,0 +1,205 @@ +import logging +import os +from io import BytesIO +from typing import ClassVar +from urllib.parse import urljoin, urlparse + +import numpy as np +import requests +import torch +from comfy_api.latest import io, ui +from PIL import Image, ImageOps + +from .types import ( + EmbeddrArtifactID, + EmbeddrArtifactIDObject, + EmbeddrArtifactInfo, + EmbeddrArtifactInfoObject, +) +from .utils.config import get_auth_headers, get_embeddr_base_url + + +class EmbeddrLoadArtifactNode(io.ComfyNode): + _cache: ClassVar[dict] = {} + + _logger = logging.getLogger("embeddr.comfyui.load_artifact") + + @classmethod + def _debug(cls, message: str, **fields): + if os.environ.get("EMBEDDR_COMFYUI_DEBUG", "").lower() not in { + "1", + "true", + "yes", + }: + return + try: + cls._logger.info("%s | %s", message, fields) + except Exception: + cls._logger.info("%s", message) + + @classmethod + def _resolve_artifact_url(cls, base_url: str, artifact_id: str, auth_ticket: str = ""): + resolve_url = f"{base_url}/api/v1/artifacts/{artifact_id}/resolve?variant=original&proxy=1" + cls._debug("resolving_artifact", artifact_id=artifact_id, resolve_url=resolve_url) + res = requests.get(resolve_url, headers=get_auth_headers(auth_ticket=auth_ticket)) + res.raise_for_status() + data = res.json() + url = data.get("url") + headers = data.get("headers") or {} + if url and url.startswith("/"): + url = urljoin(base_url, url) + + if url and "/api/v1/artifacts/" in url and "/content" in url and "proxy=" not in url: + url = f"{url}?proxy=1" + + base_netloc = urlparse(base_url).netloc + parsed = urlparse(url) if url else None + url_scheme = parsed.scheme if parsed else "" + url_netloc = parsed.netloc if parsed else "" + needs_proxy = ( + not url + or not url_scheme + or not url_netloc + or (base_netloc and url_netloc != base_netloc) + ) + if needs_proxy: + proxy_url = f"{base_url}/api/v1/artifacts/{artifact_id}/content?proxy=1" + cls._debug( + "forcing_proxy_url", + artifact_id=artifact_id, + resolved_url=url, + proxy_url=proxy_url, + ) + return proxy_url, {} + cls._debug("resolved_artifact", artifact_id=artifact_id, url=url, headers=headers) + return url, headers + + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="embeddr.LoadArtifact", + display_name="Embeddr Load Artifact (V2)", + description="Loads an image/artifact from Embeddr by UUID.", + category="Embeddr", + inputs=[ + io.String.Input( + "manual_artifact_id", + tooltip="Manual UUID string (used if input not connected)", + default="", + ), + EmbeddrArtifactID.Input( + "artifact_id", tooltip="UUID of the artifact to load", optional=True + ), + io.String.Input( + "auth_ticket", + default="", + tooltip="Ephemeral auth ticket for per-user access", + optional=True, + ), + io.Boolean.Input("use_cache", default=True), + ], + outputs=[ + io.Image.Output("image"), + io.Mask.Output("mask"), + EmbeddrArtifactID.Output("artifact_id_out"), + EmbeddrArtifactInfo.Output("artifact_info"), + ], + ) + + @classmethod + def execute(cls, use_cache, artifact_id=None, manual_artifact_id=None, auth_ticket: str = ""): + # Resolve artifact_id from connection or manual input + resolved_id = "" + + if artifact_id is not None: + if isinstance(artifact_id, EmbeddrArtifactIDObject): + resolved_id = artifact_id.artifact_id + else: + resolved_id = str(artifact_id) + + # Fallback to manual input if connection is empty + if not resolved_id and manual_artifact_id: + resolved_id = str(manual_artifact_id).strip() + + if not resolved_id: + # Return empty black image if no ID + empty_image = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + empty_mask = torch.zeros((1, 64, 64), dtype=torch.float32, device="cpu") + return io.NodeOutput( + empty_image, + empty_mask, + EmbeddrArtifactIDObject(artifact_id=""), + EmbeddrArtifactInfoObject(data={}), + ) + + cache_key = (resolved_id, str(auth_ticket or "")) + + if use_cache and cache_key in cls._cache: + image, mask, info = cls._cache[cache_key] + return io.NodeOutput( + image, mask, EmbeddrArtifactIDObject(artifact_id=resolved_id), info + ) + + try: + base_url = get_embeddr_base_url() + + # 1. Fetch JSON metadata first + meta_url = f"{base_url}/api/v1/artifacts/{resolved_id}" + meta_res = requests.get(meta_url, headers=get_auth_headers(auth_ticket=auth_ticket)) + meta_res.raise_for_status() + artifact_data = meta_res.json() + info_obj = EmbeddrArtifactInfoObject(data=artifact_data) + + # 2. Resolve content + endpoint, content_headers = cls._resolve_artifact_url( + base_url, resolved_id, auth_ticket + ) + + cls._debug( + "requesting_artifact_content", + artifact_id=resolved_id, + endpoint=endpoint, + ) + + # Merge auth headers with any resolved headers (e.g. S3 signed headers or similar) + final_headers = get_auth_headers(auth_ticket=auth_ticket) + if content_headers: + final_headers.update(content_headers) + + response = requests.get(endpoint, headers=final_headers) + response.raise_for_status() + cls._debug( + "artifact_content_response", + status=response.status_code, + content_type=response.headers.get("content-type"), + length=response.headers.get("content-length"), + ) + img = Image.open(BytesIO(response.content)) + + img = ImageOps.exif_transpose(img) + image = img.convert("RGB") + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + + if "A" in img.getbands(): + mask = np.array(img.getchannel("A")).astype(np.float32) / 255.0 + mask = 1.0 - torch.from_numpy(mask) + else: + mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu") + + if use_cache: + cls._cache[cache_key] = (image, mask, info_obj) + + return io.NodeOutput( + image, + mask, + EmbeddrArtifactIDObject(artifact_id=resolved_id), + info_obj, + ui=ui.PreviewImage(image), + ) + + except Exception as e: + print(f"[Embeddr] Error loading artifact {resolved_id}: {e}") + cls._debug("artifact_load_failed", artifact_id=resolved_id, error=str(e)) + # Raise error to stop workflow if loading fails + raise e diff --git a/nodes/EmbeddrLoadArtifacts.py b/nodes/EmbeddrLoadArtifacts.py new file mode 100644 index 0000000..3cf6b83 --- /dev/null +++ b/nodes/EmbeddrLoadArtifacts.py @@ -0,0 +1,218 @@ +import logging +import os +from io import BytesIO +from typing import ClassVar +from urllib.parse import urljoin, urlparse + +import numpy as np +import requests +import torch +from comfy_api.latest import io +from PIL import Image, ImageOps + +from .types import EmbeddrArtifactID +from .utils import get_config +from .utils.api import get_collections +from .utils.config import get_auth_headers +from .utils.ids import normalize_ids + + +class EmbeddrLoadArtifactsNode(io.ComfyNode): + _cache: ClassVar[dict] = {} + + _logger = logging.getLogger("embeddr.comfyui.load_artifacts") + + @classmethod + def _debug(cls, message: str, **fields): + if os.environ.get("EMBEDDR_COMFYUI_DEBUG", "").lower() not in { + "1", + "true", + "yes", + }: + return + try: + cls._logger.info("%s | %s", message, fields) + except Exception: + cls._logger.info("%s", message) + + @classmethod + def _resolve_artifact_url(cls, base_url: str, artifact_id: str, auth_ticket: str = ""): + resolve_url = f"{base_url}/api/v1/artifacts/{artifact_id}/resolve?variant=original&proxy=1" + cls._debug("resolving_artifact", artifact_id=artifact_id, resolve_url=resolve_url) + res = requests.get(resolve_url, headers=get_auth_headers(auth_ticket=auth_ticket)) + res.raise_for_status() + data = res.json() + url = data.get("url") + headers = data.get("headers") or {} + if url and url.startswith("/"): + url = urljoin(base_url, url) + + if url and "/api/v1/artifacts/" in url and "/content" in url and "proxy=" not in url: + url = f"{url}?proxy=1" + + base_netloc = urlparse(base_url).netloc + url_netloc = urlparse(url).netloc if url else "" + if url and base_netloc and url_netloc and url_netloc != base_netloc: + proxy_url = f"{base_url}/api/v1/artifacts/{artifact_id}/content?proxy=1" + cls._debug( + "forcing_proxy_url", + artifact_id=artifact_id, + resolved_url=url, + proxy_url=proxy_url, + ) + return proxy_url, {} + cls._debug("resolved_artifact", artifact_id=artifact_id, url=url, headers=headers) + return url, headers + + @classmethod + def define_schema(cls) -> io.Schema: + collections = ["All", *get_collections()] + + return io.Schema( + node_id="embeddr.LoadArtifacts", + display_name="Embeddr Load Artifacts (V2)", + description="Loads generic artifacts (images) from Embeddr using V2 API.", + category="Embeddr", + inputs=[ + EmbeddrArtifactID.Input( + "artifact_ids", + tooltip="Optional list of IDs to load (overrides collection)", + optional=True, + ), + io.String.Input( + "auth_ticket", + default="", + tooltip="Ephemeral auth ticket for per-user access", + optional=True, + ), + io.Combo.Input("collection", options=collections, default="All"), + io.Combo.Input("sort_by", options=["newest", "random"], default="newest"), + io.Int.Input("limit", default=5, min=1, max=50), + io.Int.Input("seed", default=0, display_name="Random Seed"), + ], + outputs=[ + io.Image.Output("images", is_output_list=True), + io.String.Output("artifact_ids", is_output_list=True), + io.Mask.Output("masks", is_output_list=True), + ], + ) + + @classmethod + def execute(cls, collection, sort_by, limit, seed, artifact_ids=None, auth_ticket: str = ""): + # Check for explicit IDs first + manual_ids = normalize_ids(artifact_ids) + + # We cache based on manual_ids if present, else collection params + if manual_ids: + # Sort for stability in cache key + manual_ids.sort() + cache_key = ("ids", tuple(manual_ids), str(auth_ticket or "")) + else: + cache_key = (collection, sort_by, limit, seed, str(auth_ticket or "")) + + if cache_key in cls._cache: + return cls._cache[cache_key] + + try: + config = get_config() + base_url = ( + config.get("embeddr_url") or config.get("endpoint") or "http://localhost:8003" + ) + base_url = base_url.rstrip("/") + + items = [] + + # 1. Load by IDs + if manual_ids: + # We can fetch them one by one or via a wrapper if API supports batch. + # Assuming singular fetch for now to be safe, or check if /api/v1/artifacts/ supports ids=... + # Iterate and construct items list + for aid in manual_ids: + items.append({"id": aid}) + + # 2. Load via Search/Collection + else: + # List Artifacts + api_url = f"{base_url}/api/v1/artifacts/" + params = {"limit": limit, "type_name": "image", "offset": 0} + + if collection != "All": + try: + col_id = collection.split(":")[0].strip() + params["collection_id"] = col_id + except Exception: + pass + + if sort_by == "random": + params["sort"] = "random" + params["seed"] = seed # Pass seed if API supports it + else: + params["sort"] = "new" + + response = requests.get( + api_url, params=params, headers=get_auth_headers(auth_ticket=auth_ticket) + ) + response.raise_for_status() + data = response.json() + items = data.get("items", []) + + if not items: + return cls._return_empty() + + images_list = [] + masks_list = [] + ids_list = [] + + for item in items: + art_id = item.get("id") + + content_url, content_headers = cls._resolve_artifact_url( + base_url, art_id, auth_ticket + ) + try: + final_headers = get_auth_headers(auth_ticket=auth_ticket) + if content_headers: + final_headers.update(content_headers) + + c_resp = requests.get(content_url, headers=final_headers) + c_resp.raise_for_status() + img = Image.open(BytesIO(c_resp.content)) + img = ImageOps.exif_transpose(img) + + i = img.convert("RGB") + i_np = np.array(i).astype(np.float32) / 255.0 + images_list.append(torch.from_numpy(i_np)) + + if "A" in img.getbands(): + m_np = np.array(img.getchannel("A")).astype(np.float32) / 255.0 + masks_list.append(1.0 - torch.from_numpy(m_np)) + else: + masks_list.append( + torch.zeros( + (i_np.shape[0], i_np.shape[1]), dtype=torch.float32, device="cpu" + ) + ) + + ids_list.append(str(art_id)) + except Exception as e: + print(f"Failed to load artifact {art_id}: {e}") + cls._debug("artifact_load_failed", artifact_id=str(art_id), error=str(e)) + + if not images_list: + return cls._return_empty() + + final_images = [img[None,] for img in images_list] + + res = io.NodeOutput(final_images, ids_list, masks_list) + cls._cache[cache_key] = res + return res + + except Exception as e: + print(f"[Embeddr] LoadArtifacts error: {e}") + return cls._return_empty() + + @classmethod + def _return_empty(cls): + empty_image = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + empty_mask = torch.zeros((1, 64, 64), dtype=torch.float32, device="cpu") + return io.NodeOutput([empty_image], ["-1"], [empty_mask]) diff --git a/nodes/EmbeddrLoadImage.py b/nodes/EmbeddrLoadImage.py deleted file mode 100644 index 437b116..0000000 --- a/nodes/EmbeddrLoadImage.py +++ /dev/null @@ -1,75 +0,0 @@ -import requests -import torch -import numpy as np -from PIL import Image, ImageOps -from io import BytesIO -from comfy_api.latest import io, ui -from .utils import get_config - - -class EmbeddrLoadImageNode(io.ComfyNode): - _cache = {} - - @classmethod - def define_schema(cls) -> io.Schema: - return io.Schema( - node_id="embeddr.LoadImage", - display_name="Embeddr Load Image", - description="Loads an image from a Embeddr Image ID.", - category="Embeddr", - inputs=[ - io.String.Input("image_id", default=""), - ], - outputs=[ - io.Image.Output("image"), - io.Mask.Output("mask"), - io.String.Output("embeddr_id"), - ], - ) - - @classmethod - def execute(cls, image_id): - if not image_id: - # Return empty black image if no ID - empty_image = torch.zeros( - (1, 64, 64, 3), dtype=torch.float32, device="cpu") - empty_mask = torch.zeros( - (1, 64, 64), dtype=torch.float32, device="cpu") - return io.NodeOutput(empty_image, empty_mask, "") - - if image_id in cls._cache: - image, mask = cls._cache[image_id] - return io.NodeOutput(image, mask, image_id) - - try: - config = get_config() - endpoint = config.get("endpoint", "http://localhost:8003") - # Ensure endpoint doesn't end with slash - endpoint = endpoint.rstrip("/") - api_url = f"{endpoint}/api/v1/images/{image_id}/file" - - response = requests.get(api_url) - response.raise_for_status() - img = Image.open(BytesIO(response.content)) - - img = ImageOps.exif_transpose(img) - image = img.convert("RGB") - image = np.array(image).astype(np.float32) / 255.0 - image = torch.from_numpy(image)[None,] - - if 'A' in img.getbands(): - mask = np.array(img.getchannel('A')).astype(np.float32) / 255.0 - mask = 1. - torch.from_numpy(mask) - else: - mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu") - - cls._cache[image_id] = (image, mask) - return io.NodeOutput(image, mask, image_id, ui=ui.PreviewImage(image)) - - except Exception as e: - print(f"[Embeddr] Error loading image {image_id}: {e}") - empty_image = torch.zeros( - (1, 64, 64, 3), dtype=torch.float32, device="cpu") - empty_mask = torch.zeros( - (1, 64, 64), dtype=torch.float32, device="cpu") - return io.NodeOutput(empty_image, empty_mask, "") diff --git a/nodes/EmbeddrLoadImages.py b/nodes/EmbeddrLoadImages.py index 555855e..88ab832 100644 --- a/nodes/EmbeddrLoadImages.py +++ b/nodes/EmbeddrLoadImages.py @@ -1,36 +1,92 @@ +import logging +import os +from io import BytesIO +from typing import ClassVar +from urllib.parse import urljoin, urlparse + +import numpy as np import requests import torch -import numpy as np +from comfy_api.latest import io from PIL import Image, ImageOps -from io import BytesIO -import random -from comfy_api.latest import io, ui + from .utils import get_config from .utils.api import get_collections, get_libraries +from .utils.config import get_auth_headers class EmbeddrLoadImagesNode(io.ComfyNode): - _cache = {} + _cache: ClassVar[dict] = {} + + _logger = logging.getLogger("embeddr.comfyui.load_images") + + @classmethod + def _debug(cls, message: str, **fields): + if os.environ.get("EMBEDDR_COMFYUI_DEBUG", "").lower() not in { + "1", + "true", + "yes", + }: + return + try: + cls._logger.info("%s | %s", message, fields) + except Exception: + cls._logger.info("%s", message) + + @classmethod + def _resolve_artifact_url(cls, base_url: str, artifact_id: str, auth_ticket: str = ""): + resolve_url = f"{base_url}/api/v1/artifacts/{artifact_id}/resolve?variant=original&proxy=1" + cls._debug("resolving_artifact", artifact_id=artifact_id, resolve_url=resolve_url) + res = requests.get(resolve_url, headers=get_auth_headers(auth_ticket=auth_ticket)) + res.raise_for_status() + data = res.json() + url = data.get("url") + headers = data.get("headers") or {} + if url and url.startswith("/"): + url = urljoin(base_url, url) + + if url and "/api/v1/artifacts/" in url and "/content" in url and "proxy=" not in url: + url = f"{url}?proxy=1" + + base_netloc = urlparse(base_url).netloc + url_netloc = urlparse(url).netloc if url else "" + if url and base_netloc and url_netloc and url_netloc != base_netloc: + proxy_url = f"{base_url}/api/v1/artifacts/{artifact_id}/content?proxy=1" + cls._debug( + "forcing_proxy_url", + artifact_id=artifact_id, + resolved_url=url, + proxy_url=proxy_url, + ) + return proxy_url, {} + cls._debug("resolved_artifact", artifact_id=artifact_id, url=url, headers=headers) + return url, headers @classmethod def define_schema(cls) -> io.Schema: - collections = ["All"] + get_collections() - libraries = ["All"] + get_libraries() + # Note: Dynamic fetching (get_collections) might need updates to V2 too + # but for now we focus on the execution logic + collections = ["All", *get_collections()] + # Legacy libraries -> Folders/Collections? + libraries = ["All", *get_libraries()] return io.Schema( node_id="embeddr.EmbeddrLoadImages", display_name="Embeddr Load Images", - description="Loads images from Embeddr with filtering and sorting.", + description="Loads images from Embeddr using V2 Artifacts API.", category="Embeddr", inputs=[ io.Combo.Input("library", options=libraries, default="All"), - io.Combo.Input( - "collection", options=collections, default="All"), - io.Combo.Input("sort_by", options=[ - "newest", "random"], default="newest"), + io.Combo.Input("collection", options=collections, default="All"), + io.Combo.Input("sort_by", options=["newest", "random"], default="newest"), io.Int.Input("limit", default=5, min=1, max=100), - io.Int.Input("seed", default=0, - display_name="Seed (Random Sort)"), + io.String.Input( + "auth_ticket", + default="", + tooltip="Ephemeral auth ticket for per-user access", + optional=True, + ), + io.Int.Input("seed", default=0, display_name="Seed (Random Sort)"), ], outputs=[ io.Image.Output("images", is_output_list=True), @@ -40,49 +96,55 @@ def define_schema(cls) -> io.Schema: ) @classmethod - def execute(cls, library, collection, sort_by, limit, seed): + def execute(cls, library, collection, sort_by, limit, auth_ticket: str = "", seed=0): # Cache key based on inputs - cache_key = (library, collection, sort_by, limit, seed) + cache_key = (library, collection, sort_by, limit, seed, str(auth_ticket or "")) if cache_key in cls._cache: return cls._cache[cache_key] try: config = get_config() - endpoint = config.get("endpoint", "http://localhost:8003") - api_base_url = endpoint.rstrip("/") + "/api/v1" + base_url = ( + config.get("embeddr_url") or config.get("endpoint") or "http://localhost:8003" + ) + base_url = base_url.rstrip("/") + + # V2 API: List Artifacts + api_url = f"{base_url}/api/v1/artifacts/" params = { "limit": limit, + "type_name": "image", # Filter for images + "offset": 0, } - # Parse Library ID - if library != "All": + # Parse Collection ID (UUID in V2?) + # Legacy logic assumed "123: Name" + if collection != "All": try: - lib_id = int(library.split(":")[0]) - params["library_id"] = lib_id - except: + col_id = collection.split(":")[0].strip() + params["collection_id"] = col_id + except Exception: pass - # Parse Collection ID - if collection != "All": + # Legacy Libraries mapped to ? + # In V2 we might ignore library or treat as another collection filter + if library != "All": try: - col_id = int(collection.split(":")[0]) - params["collection_id"] = col_id - except: + lib_id = library.split(":")[0].strip() + params["library_id"] = lib_id + except Exception: pass - # Handle Sort if sort_by == "random": params["sort"] = "random" - # Note: Server-side random might not respect seed, but we pass it just in case - # or we could implement client-side shuffle if we fetched more. - # For now, we rely on server-side random for efficiency. + params["seed"] = seed # Pass seed if API supports it else: params["sort"] = "new" - # Fetch images - url = f"{api_base_url}/images" - response = requests.get(url, params=params) + response = requests.get( + api_url, params=params, headers=get_auth_headers(auth_ticket=auth_ticket) + ) response.raise_for_status() data = response.json() items = data.get("items", []) @@ -95,51 +157,94 @@ def execute(cls, library, collection, sort_by, limit, seed): ids_list = [] for item in items: - image_id = item.get("id") - if not image_id: + art_id = item.get("id") + if not art_id: continue - image_url = f"{api_base_url}/images/{image_id}/file" + # Fetch Content via Plugin Endpoint + # Uses the plugin endpoint we defined to get raw content + content_url, content_headers = cls._resolve_artifact_url( + base_url, art_id, auth_ticket + ) + + cls._debug( + "requesting_artifact_content", + artifact_id=str(art_id), + content_url=content_url, + ) try: - img_resp = requests.get(image_url) + final_headers = get_auth_headers(auth_ticket=auth_ticket) + if content_headers: + final_headers.update(content_headers) + + img_resp = requests.get(content_url, headers=final_headers) img_resp.raise_for_status() + cls._debug( + "artifact_content_response", + artifact_id=str(art_id), + status=img_resp.status_code, + content_type=img_resp.headers.get("content-type"), + length=img_resp.headers.get("content-length"), + ) + img = Image.open(BytesIO(img_resp.content)) img = ImageOps.exif_transpose(img) - image = img.convert("RGB") - image_np = np.array(image).astype(np.float32) / 255.0 - # Add batch dimension: [1, H, W, C] - image_tensor = torch.from_numpy(image_np).unsqueeze(0) + # Convert to tensor conformant + i = img.convert("RGB") + i_np = np.array(i).astype(np.float32) / 255.0 + images_list.append(torch.from_numpy(i_np)) - if 'A' in img.getbands(): - mask_np = np.array(img.getchannel( - 'A')).astype(np.float32) / 255.0 - mask_tensor = 1. - torch.from_numpy(mask_np) - mask_tensor = mask_tensor.unsqueeze(0) # [1, H, W] + # Mask + if "A" in img.getbands(): + m_np = np.array(img.getchannel("A")).astype(np.float32) / 255.0 + masks_list.append(1.0 - torch.from_numpy(m_np)) else: - mask_tensor = torch.zeros( - (1, img.height, img.width), dtype=torch.float32, device="cpu") + masks_list.append( + torch.zeros( + (i_np.shape[0], i_np.shape[1]), dtype=torch.float32, device="cpu" + ) + ) + + ids_list.append(str(art_id)) - images_list.append(image_tensor) - masks_list.append(mask_tensor) - ids_list.append(str(image_id)) except Exception as e: - print(f"[Embeddr] Failed to load image {image_id}: {e}") - continue + print(f"Failed to load artifact {art_id}: {e}") + cls._debug( + "artifact_load_failed", + artifact_id=str(art_id), + error=str(e), + ) if not images_list: return cls._return_empty() - # Return lists - result = io.NodeOutput(images_list, ids_list, masks_list) + # Stack images into batch [B, H, W, C]? + # Note: If images have different sizes, stacking might fail. + # ComfyUI usually expects same size for batch. + # If size differs, we might return list or resize. + # Since output is_output_list=True, likely returns list of individual tensors. + + # Correct return for output list + # "images" -> list of [H, W, C] or list of [1, H, W, C]? + # Usually list output means list of whatever single output is. + # Single Image output is [1, H, W, C]. + + # Ensure each is [1, H, W, C] + final_images = [img[None,] for img in images_list] + + # Cache result + result = io.NodeOutput(final_images, ids_list, masks_list) cls._cache[cache_key] = result return result except Exception as e: - print(f"[Embeddr] Error loading images: {e}") + print(f"[Embeddr] LoadImages Error: {e}") return cls._return_empty() - @staticmethod - def _return_empty(): - return io.NodeOutput([], [], []) + @classmethod + def _return_empty(cls): + empty_image = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + empty_mask = torch.zeros((1, 64, 64), dtype=torch.float32, device="cpu") + return io.NodeOutput([empty_image], ["-1"], [empty_mask]) diff --git a/nodes/EmbeddrLoadOptions.py b/nodes/EmbeddrLoadOptions.py new file mode 100644 index 0000000..e69de29 diff --git a/nodes/EmbeddrLoadVideo.py b/nodes/EmbeddrLoadVideo.py new file mode 100644 index 0000000..db6fd14 --- /dev/null +++ b/nodes/EmbeddrLoadVideo.py @@ -0,0 +1,231 @@ +import contextlib +import os +import shutil +import tempfile +from typing import ClassVar + +import cv2 +import numpy as np +import requests +import torch +from comfy_api.latest import io + +from .utils import get_config +from .utils.config import get_auth_headers + + +class EmbeddrLoadVideoNode(io.ComfyNode): + _cache: ClassVar[dict] = {} + + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="embeddr.LoadVideo", + display_name="Embeddr Load Video", + description="Loads a video from Embeddr ID.", + category="Embeddr", + inputs=[ + io.String.Input("image_id", default=""), + io.Int.Input( + "frame_load_cap", + default=0, + min=0, + max=100000, + step=1, + tooltip="Stop loading after this many frames (0=all)", + ), + io.Int.Input( + "skip_first_frames", + default=0, + min=0, + max=10000, + step=1, + tooltip="Skip this many frames at the start", + ), + io.Int.Input( + "select_every_nth", + default=1, + min=1, + max=100, + step=1, + tooltip="Load every Nth frame", + ), + io.Int.Input( + "force_rate", + default=0, + min=0, + max=120, + step=1, + tooltip="Force playback FPS (0=original)", + ), + io.Int.Input( + "custom_width", + default=0, + min=0, + max=4096, + step=8, + tooltip="Resize width (0=original)", + ), + io.Int.Input( + "custom_height", + default=0, + min=0, + max=4096, + step=8, + tooltip="Resize height (0=original)", + ), + ], + outputs=[ + io.Image.Output("images"), + io.Int.Output("frame_count"), + # Removed experimental outputs for standard compatibility + # io.Output("audio"), + # io.Output("video_info"), + ], + ) + + @classmethod + def execute( + cls, + image_id, + frame_load_cap, + skip_first_frames, + select_every_nth, + force_rate, + custom_width, + custom_height, + ): + if not image_id: + # Return empty + empty_image = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + return io.NodeOutput(empty_image, 0) + + # URL construction + try: + config = get_config() + endpoint = config.get("endpoint", "http://localhost:8003") + endpoint = endpoint.rstrip("/") + api_url = f"{endpoint}/api/v1/images/{image_id}/file" + except Exception: + print("[Embeddr] Could not get config for endpoint") + empty_image = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + return io.NodeOutput(empty_image, 0) + + # Download to temp file + temp_file_path = None + try: + # Check cache for path? For simplicity, we just download. + # In production, we should cache the file path if it's the same ID. + + # Stream download + with requests.get(api_url, stream=True, headers=get_auth_headers()) as r: + r.raise_for_status() + # Determine extension + content_type = r.headers.get("content-type", "") + ext = ".mp4" + if "quicktime" in content_type: + ext = ".mov" + if "webm" in content_type: + ext = ".webm" + + with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as f: + shutil.copyfileobj(r.raw, f) + temp_file_path = f.name + + # Open with CV2 + cap = cv2.VideoCapture(temp_file_path) + if not cap.isOpened(): + raise ValueError(f"Could not open video file: {temp_file_path}") + + fps = cap.get(cv2.CAP_PROP_FPS) + width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + duration = total_frames / fps if fps > 0 else 0 + + # Determine target dimensions + target_w, target_h = width, height + if custom_width > 0: + target_w = custom_width + if custom_height == 0: + target_h = int(height * (custom_width / width)) + if custom_height > 0: + target_h = custom_height + if custom_width == 0: + target_w = int(width * (custom_height / height)) + + # Ensure divisible by 2 for some codecs if needed, but for simple tensor it's fine. + # ComfyUI usually expects divisible by 8 for VAEs? + # We won't enforce it unless user sets it. + + frames = [] + + current_frame = 0 + collected = 0 + + while True: + ret, frame = cap.read() + if not ret: + break + + # Skip logic + if current_frame < skip_first_frames: + current_frame += 1 + continue + + if (current_frame - skip_first_frames) % select_every_nth != 0: + current_frame += 1 + continue + + # Process frame + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + + if target_w != width or target_h != height: + frame = cv2.resize(frame, (target_w, target_h), interpolation=cv2.INTER_LINEAR) + + # Normalize to 0-1 + frame = frame.astype(np.float32) / 255.0 + frames.append(frame) + + collected += 1 + if frame_load_cap > 0 and collected >= frame_load_cap: + break + + current_frame += 1 + cap.release() + + if not frames: + empty_image = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + return io.NodeOutput(empty_image, 0) + + video_tensor = torch.from_numpy(np.stack(frames)) + # Shape is (B, H, W, C) + + final_fps = force_rate if force_rate > 0 else fps + + { + "source_fps": fps, + "source_frame_count": total_frames, + "source_duration": duration, + "source_width": width, + "source_height": height, + "loaded_fps": final_fps, + "loaded_frame_count": len(frames), + "loaded_width": target_w, + "loaded_height": target_h, + } + + # Audio - currently returning None/Empty as we don't have audio extraction logic + # To support audio properly we'd need audio libraries unavailable in minimal env. + + return io.NodeOutput(video_tensor, len(frames)) + + except Exception as e: + print(f"[Embeddr] Error loading video: {e}") + empty_image = torch.zeros((1, 64, 64, 3), dtype=torch.float32, device="cpu") + return io.NodeOutput(empty_image, 0) + + finally: + if temp_file_path and os.path.exists(temp_file_path): + with contextlib.suppress(BaseException): + os.remove(temp_file_path) diff --git a/nodes/EmbeddrMergeIDs.py b/nodes/EmbeddrMergeIDs.py index 15a8954..4a9a15c 100644 --- a/nodes/EmbeddrMergeIDs.py +++ b/nodes/EmbeddrMergeIDs.py @@ -1,4 +1,6 @@ -from comfy_api.latest import io, ui +from comfy_api.latest import io + +from .types import EmbeddrArtifactID class EmbeddrMergeIDsNode(io.ComfyNode): @@ -12,7 +14,11 @@ def define_schema(cls) -> io.Schema: category="Embeddr", inputs=[], outputs=[ - io.String.Output("ids"), + EmbeddrArtifactID.Output( + "artifact_ids", + tooltip="List of merged Artifact IDs", + display_name="artifact_ids", + ), ], ) @@ -21,7 +27,7 @@ def execute(cls, **kwargs): ids = [] # Iterate through all possible inputs for key, value in kwargs.items(): - if key.startswith("id") and value: + if key.startswith("artifact_") and value: ids.append(value) # Return as list of strings diff --git a/nodes/EmbeddrSplitIDs.py b/nodes/EmbeddrSplitIDs.py new file mode 100644 index 0000000..d5e4f97 --- /dev/null +++ b/nodes/EmbeddrSplitIDs.py @@ -0,0 +1,35 @@ +from comfy_api.latest import io + +from .types import EmbeddrArtifactID, EmbeddrArtifactIDObject +from .utils.ids import normalize_ids + + +class EmbeddrSplitIDsNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="embeddr.SplitIDs", + display_name="Embeddr Split IDs", + description="Splits a comma-separated list of Artifact IDs into individual items for batch processing.", + category="Embeddr", + inputs=[ + EmbeddrArtifactID.Input( + "artifact_ids", tooltip="Comma-separated IDs or list of IDs" + ), + ], + outputs=[ + EmbeddrArtifactID.Output( + "split_ids", tooltip="Individual IDs (List Execution)", is_output_list=True + ), + ], + ) + + @classmethod + def execute(cls, artifact_ids): + # Normalize to list of ID strings + final_ids = normalize_ids(artifact_ids) + + # Return a LIST of objects. ComfyUI will iterate this list for downstream nodes if they support it. + results = [EmbeddrArtifactIDObject(artifact_id=fid) for fid in final_ids] + + return io.NodeOutput(results) diff --git a/nodes/EmbeddrUploadArtifact.py b/nodes/EmbeddrUploadArtifact.py new file mode 100644 index 0000000..2cb5cb2 --- /dev/null +++ b/nodes/EmbeddrUploadArtifact.py @@ -0,0 +1,191 @@ +import io as pyio +import json + +import numpy as np +import requests +from comfy_api.latest import io, ui +from PIL import Image + +from .EmbeddrUploadOptions import EmbeddrUploadArtifactOptions, EmbeddrUploadArtifactOptionsObject +from .types import EmbeddrArtifactID, EmbeddrArtifactIDObject +from .utils.config import get_auth_headers, get_embeddr_base_url, get_upload_mode +from .utils.ids import normalize_ids + + +def Embeddr_Log(message: str): + print(f"[Embeddr] {message}") + + +def _has_auth_ticket(value) -> bool: + if isinstance(value, (list, tuple)): + value = value[0] if value else None + if isinstance(value, dict): + value = value.get("auth_ticket") or value.get("ticket") + return bool(str(value).strip()) if value is not None else False + + +class EmbeddrUploadArtifactNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="embeddr.UploadArtifact", + display_name="Embeddr Upload (V2)", + category="Embeddr", + is_output_node=True, + inputs=[ + io.Image.Input("image"), + EmbeddrArtifactID.Input( + "parent_ids", optional=True, tooltip="Parent artifact UUIDs" + ), + io.String.Input( + "auth_ticket", + default="", + tooltip="Ephemeral auth ticket for per-user ownership", + optional=True, + ), + EmbeddrUploadArtifactOptions.Input( + "options", + tooltip="Upload Artifact Options", + optional=True, + display_name="Options", + ), + ], + outputs=[ + EmbeddrArtifactID.Output("artifact_ids"), + ], + ) + + @classmethod + def execute( + cls, + image, + parent_ids: EmbeddrArtifactIDObject = None, + auth_ticket: str = "", + options: EmbeddrUploadArtifactOptionsObject = None, + ) -> io.NodeOutput: + base_url = get_embeddr_base_url() + upload_mode = get_upload_mode() + endpoint = f"{base_url}/api/v1/plugins/embeddr-comfyui/upload" + + # parent_ids could be handled by normalize_list directly + normalized_parent_ids = normalize_ids(parent_ids) + + results = [] + + if upload_mode in {"skip", "disabled", "off", "none"}: + Embeddr_Log("Upload disabled (EMBEDDR_UPLOAD_MODE). Skipping Embeddr upload.") + return io.NodeOutput(EmbeddrArtifactIDObject(artifact_id=""), ui=ui.PreviewImage(image)) + + if upload_mode in {"best_effort", "auto"}: + try: + health_url = f"{base_url}/api/v1/system/routes" + requests.get(health_url, timeout=2) + except Exception as e: + Embeddr_Log(f"Embeddr backend unavailable ({e}); skipping upload.") + return io.NodeOutput( + EmbeddrArtifactIDObject(artifact_id=""), ui=ui.PreviewImage(image) + ) + + # 'image' input is a batch tensor [B, H, W, C] + for batch_idx, img_tensor in enumerate(image): + try: + # Convert tensor to PIL + i = 255.0 * img_tensor.cpu().numpy() + img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) + + # Save to buffer + img_byte_arr = pyio.BytesIO() + img.save(img_byte_arr, format="PNG") + img_byte_arr.seek(0) + Embeddr_Log(f""" + Uploading artifact batch {batch_idx}... + Storage Provider: {options.storage_provider if options else "default"} + Storage Path: {options.storage_path if options else "default"} + Tags: {options.tags if options else "none"} + Related Artifacts: {options.related_artifact_ids if options else "none"} + Parent IDs: {normalized_parent_ids} + Auth Ticket: {"present" if _has_auth_ticket(auth_ticket) else "absent"} + """) + + storage_provider = None + storage_path = None + if options: + if isinstance(options, dict): + storage_provider = options.get("storage_provider") + storage_path = options.get("storage_path") + else: + storage_provider = getattr(options, "storage_provider", None) + storage_path = getattr(options, "storage_path", None) + + storage_provider = ( + str(storage_provider).strip().lower() + if storage_provider not in (None, "", "__default__") + else None + ) + storage_path = ( + str(storage_path).strip() + if storage_path not in (None, "", "__default__") + else None + ) + + # Prepare Metadata + meta = { + "parent_ids": normalized_parent_ids, + "collection_ids": normalize_ids(options.related_artifact_ids) + if options + else [], + "tags": normalize_ids(options.tags) if options else [], + "trigger_automation": options.trigger_ingest if options else True, + "compute_embedding": options.trigger_ingest + if options + else True, # Legacy Compat + "batch_index": batch_idx, + "confirm": True, + } + + if storage_provider: + meta["storage_provider"] = storage_provider + meta["storage_backend"] = storage_provider + if storage_path: + meta["storage_path"] = storage_path + + if storage_provider or storage_path: + Embeddr_Log( + f"Upload storage overrides: provider={storage_provider or 'default'} path={storage_path or 'default'}" + ) + + # Prepare multipart upload + files = {"file": (f"image_{batch_idx}.png", img_byte_arr, "image/png")} + data = {"metadata": json.dumps(meta)} + + # Post to Embeddr Core Plugin + response = requests.post( + endpoint, + files=files, + data=data, + headers=get_auth_headers(auth_ticket=auth_ticket), + ) + response.raise_for_status() + res_json = response.json() + + art_id = res_json.get("id") + results.append(str(art_id)) + Embeddr_Log(f"Uploaded Artifact: {art_id}") + + except requests.HTTPError as e: + body = "" + try: + body = e.response.text[:500] if e.response is not None else "" + except Exception: + body = "" + suffix = f" body={body}" if body else "" + Embeddr_Log(f"Upload failed for batch {batch_idx}: {e}{suffix}") + except Exception as e: + Embeddr_Log(f"Upload failed for batch {batch_idx}: {e}") + # We don't crash the whole node, but result might be partial + + result_str = ",".join(results) + result_obj = EmbeddrArtifactIDObject(artifact_id=result_str) + + # Return IDs and UI Preview + return io.NodeOutput(result_obj, ui=ui.PreviewImage(image)) diff --git a/nodes/EmbeddrUploadImage.py b/nodes/EmbeddrUploadImage.py deleted file mode 100644 index d471806..0000000 --- a/nodes/EmbeddrUploadImage.py +++ /dev/null @@ -1,162 +0,0 @@ -import folder_paths -from .utils.api import get_libraries, get_collections -import os -import json -import requests -import numpy as np -from PIL import Image -from PIL.PngImagePlugin import PngInfo -from comfy_api.latest import io, ui -from comfy_api.latest._io import _UIOutput, ComfyNode, FolderType -import io as pyio -import random -from .utils import get_config - - -def Embeddr_Log(message: str): - print(f"[Embeddr] {message}") - - -class EmbeddrImage(ui.PreviewImage): - def __init__(self, image: io.Image.Type, ids=None, animated: bool = False, cls: type[ComfyNode] = None, **kwargs): - super().__init__(image, animated, cls=cls) - self.extra = {} - if ids is not None: - self.extra["embeddr_ids"] = ids - - def as_dict(self): - d = { - "images": self.values, - "animated": (self.animated,) - } - d.update(self.extra) - return d - - -class EmbeddrSaveToFolderNode(io.ComfyNode): - - @classmethod - def define_schema(cls) -> io.Schema: - libraries = ["Default"] + get_libraries() - collections = ["None"] + get_collections() - - return io.Schema( - node_id="embeddr.SaveToFolder", - display_name="Embeddr Upload Image", - category="Embeddr", - is_output_node=True, - inputs=[ - io.Image.Input("image"), - io.String.Input("caption", optional=True), - io.String.Input("parent_ids", optional=True), - io.Combo.Input("library", options=libraries, - default="Default"), - io.Combo.Input( - "collection", options=collections, default="None"), - io.String.Input("tags", optional=True, default=""), - io.Boolean.Input("allow_duplicates", default=False, - display_name="Allow Duplicates"), - io.Boolean.Input("save_backup", default=False, - display_name="Save to Comfy History"), - ], - outputs=[ - # THIS IS KEY: output name must match - io.String.Output("embeddr_id"), - ], - ) - - @classmethod - def VALIDATE_INPUTS(cls, **kwargs): - return True - - @classmethod - def execute(cls, image, caption=None, parent_ids=None, library="Default", collection="None", tags="", allow_duplicates=False, save_backup=False, **kwargs): - """ - image: input tensor(s) from previous node - caption: optional string - parent_ids: optional string (comma separated) or list of IDs - Returns the backend ID in the outputs so Comfy history sees it. - """ - uploaded_ids = [] - - config = get_config() - print("Loaded Config: ", config) - endpoint = config.get("endpoint", "http://localhost:8003") - api_base_url = endpoint.rstrip("/") + "/api/v1" - upload_url = f"{api_base_url}/images/upload" - - # Loop over batch images - for i in range(image.shape[0]): - img_array = (image[i].cpu().numpy() * 255).astype(np.uint8) - img = Image.fromarray(np.clip(img_array, 0, 255)) - - # Save backup if requested - if save_backup: - try: - output_dir = folder_paths.get_output_directory() - filename_prefix = "Embeddr_Backup" - full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path( - filename_prefix, output_dir, img.size[0], img.size[1]) - - metadata = PngInfo() - if caption: - metadata.add_text("parameters", caption) - - file = f"{filename}_{counter:05}_.png" - img.save(os.path.join(full_output_folder, file), - pnginfo=metadata, compress_level=4) - except Exception as e: - print(f"[Embeddr] Failed to save backup: {e}") - - buf = pyio.BytesIO() - img.save(buf, format="PNG") - buf.seek(0) - - files = {"file": ("image.png", buf, "image/png")} - data = {"prompt": caption or ""} - - if allow_duplicates: - data["force"] = "true" - - if tags: - data["tags"] = tags - - if library != "Default": - try: - data["library_id"] = int(library.split(":")[0]) - except: - pass - - if parent_ids: - if isinstance(parent_ids, list): - data["parent_ids"] = ",".join(map(str, parent_ids)) - else: - data["parent_ids"] = str(parent_ids) - - try: - response = requests.post(upload_url, files=files, data=data) - response.raise_for_status() - result = response.json() - uploaded_id = result.get("id") - uploaded_ids.append(str(uploaded_id)) - - # Add to collection if selected - if collection and collection != "None" and uploaded_id: - try: - collection_id = int(collection.split(":")[0]) - requests.post( - f"{api_base_url}/collections/{collection_id}/items", - json={"image_id": uploaded_id} - ) - except Exception as e: - print(f"[Embeddr] Failed to add to collection: {e}") - - except Exception as e: - print(f"[Embeddr] Upload failed: {e}") - uploaded_ids.append("-1") - - # Create preview - preview = EmbeddrImage(image, uploaded_ids, cls=cls) - - # Return IDs as comma-separated string - return io.NodeOutput(",".join(uploaded_ids), ui=preview) diff --git a/nodes/EmbeddrUploadOptions.py b/nodes/EmbeddrUploadOptions.py new file mode 100644 index 0000000..2cb279d --- /dev/null +++ b/nodes/EmbeddrUploadOptions.py @@ -0,0 +1,63 @@ +from comfy_api.latest import io + +from .types import EmbeddrUploadArtifactOptions, EmbeddrUploadArtifactOptionsObject + + +class UploadArtifactOptionsNode(io.ComfyNode): + @classmethod + def define_schema(cls) -> io.Schema: + return io.Schema( + node_id="embeddr.UploadArtifact.Options", + display_name="Upload Artifact Options", + category="Embeddr", + inputs=[ + io.String.Input( + "storage_provider", + tooltip="Override default storage provider for the artifact", + default=None, + ), + io.String.Input( + "storage_path", + tooltip="Override default storage path for the artifact", + default=None, + ), + io.Boolean.Input( + "trigger_ingest", + default=True, + tooltip="Trigger ingest process after upload", + display_name="Trigger Ingest", + ), + io.String.Input( + "tags", + default=[], + tooltip="Tags to associate with the uploaded artifact", + display_name="Tags", + ), + io.String.Input( + "related_artifact_ids", + default=[], + tooltip="IDs of related artifacts", + display_name="Related Artifact IDs", + ), + # io.Combo.Input( + # "scale_mode", options=[e.value for e in ResizeModeEnum], default="contain", + # tooltip="Choose how images are scaled to fit the target size", display_name="Scale Mode"), + ], + outputs=[ + EmbeddrUploadArtifactOptions.Output( + "options", tooltip="Upload Artifact Options Object", display_name="options" + ), + ], + ) + + @classmethod + def execute(cls, storage_provider, storage_path, trigger_ingest, tags, related_artifact_ids): + options = EmbeddrUploadArtifactOptionsObject( + storage_provider=storage_provider, + storage_path=storage_path, + trigger_ingest=trigger_ingest, + tags=tags, + related_artifact_ids=related_artifact_ids, + ) + + return io.NodeOutput(options) diff --git a/nodes/EmbeddrUploadVideo.py b/nodes/EmbeddrUploadVideo.py index ce6d91f..bfd8a53 100644 --- a/nodes/EmbeddrUploadVideo.py +++ b/nodes/EmbeddrUploadVideo.py @@ -1,21 +1,19 @@ -import folder_paths -from .utils.api import get_libraries, get_collections -import os import json -import requests +import os import tempfile -from comfy_api.latest import io, ui -from comfy_api.latest._io import ComfyNode -from .utils import get_config +import requests +from comfy_api.latest import io -class EmbeddrUploadVideo(io.ComfyNode): +from .EmbeddrUploadOptions import EmbeddrUploadArtifactOptions, EmbeddrUploadArtifactOptionsObject +from .types import EmbeddrArtifactID, EmbeddrArtifactIDObject +from .utils.config import get_auth_headers, get_embeddr_base_url, get_upload_mode +from .utils.ids import normalize_ids + +class EmbeddrUploadVideo(io.ComfyNode): @classmethod def define_schema(cls) -> io.Schema: - libraries = ["Default"] + get_libraries() - collections = ["None"] + get_collections() - formats = ["mp4", "mkv", "webm", "mov", "avi"] codecs = ["h264", "h265", "vp9", "vp8", "prores"] @@ -27,21 +25,20 @@ def define_schema(cls) -> io.Schema: inputs=[ io.Video.Input("video", tooltip="The video to save."), io.String.Input("caption", optional=True), - io.String.Input("parent_ids", optional=True), - io.Combo.Input("library", options=libraries, - default="Default"), - io.Combo.Input( - "collection", options=collections, default="None"), - io.String.Input("tags", optional=True, default=""), + EmbeddrArtifactID.Input( + "parent_ids", optional=True, tooltip="Parent artifact UUIDs" + ), + EmbeddrUploadArtifactOptions.Input( + "options", + tooltip="Upload Artifact Options", + optional=True, + display_name="Options", + ), io.Combo.Input("format", options=formats, default="mp4"), io.Combo.Input("codec", options=codecs, default="h264"), - io.Boolean.Input("allow_duplicates", default=False, - display_name="Allow Duplicates"), - io.Boolean.Input("save_backup", default=False, - display_name="Save to Comfy History"), ], outputs=[ - io.String.Output("embeddr_id"), + EmbeddrArtifactID.Output("artifact_ids"), ], ) @@ -50,12 +47,34 @@ def VALIDATE_INPUTS(cls, **kwargs): return True @classmethod - def execute(cls, video, caption=None, parent_ids=None, library="Default", collection="None", tags="", format="mp4", codec="h264", allow_duplicates=False, save_backup=False, **kwargs): + def execute( + cls, + video, + caption=None, + parent_ids: EmbeddrArtifactIDObject = None, + options: EmbeddrUploadArtifactOptionsObject = None, + format="mp4", + codec="h264", + **kwargs, + ): uploaded_ids = [] - config = get_config() - endpoint = config.get("endpoint", "http://localhost:8003") - api_base_url = endpoint.rstrip("/") + "/api/v1" - upload_url = f"{api_base_url}/images/upload" + base_url = get_embeddr_base_url() + upload_mode = get_upload_mode() + upload_url = f"{base_url}/api/v1/plugins/embeddr-comfyui/upload" + + normalized_parent_ids = normalize_ids(parent_ids) + + if upload_mode in {"skip", "disabled", "off", "none"}: + print("[Embeddr] Upload disabled (EMBEDDR_UPLOAD_MODE). Skipping Embeddr upload.") + return io.NodeOutput(EmbeddrArtifactIDObject(artifact_id="")) + + if upload_mode in {"best_effort", "auto"}: + try: + health_url = f"{base_url}/api/v1/system/routes" + requests.get(health_url, timeout=2) + except Exception as e: + print(f"[Embeddr] Embeddr backend unavailable ({e}); skipping upload.") + return io.NodeOutput(EmbeddrArtifactIDObject(artifact_id="")) try: # Create temp file @@ -68,43 +87,62 @@ def execute(cls, video, caption=None, parent_ids=None, library="Default", collec # But often strings work or we can map them if we knew the library. video.save_to(temp_path, format=format, codec=codec) - # Upload + storage_provider = None + storage_path = None + if options: + if isinstance(options, dict): + storage_provider = options.get("storage_provider") + storage_path = options.get("storage_path") + else: + storage_provider = getattr(options, "storage_provider", None) + storage_path = getattr(options, "storage_path", None) + + storage_provider = ( + str(storage_provider).strip().lower() + if storage_provider not in (None, "", "__default__") + else None + ) + storage_path = ( + str(storage_path).strip() if storage_path not in (None, "", "__default__") else None + ) + + meta = { + "parent_ids": normalized_parent_ids, + "collection_ids": normalize_ids(options.related_artifact_ids) if options else [], + "tags": normalize_ids(options.tags) if options else [], + "trigger_automation": options.trigger_ingest if options else True, + "compute_embedding": options.trigger_ingest if options else True, + "caption": caption or "", + "confirm": True, + } + + if storage_provider: + meta["storage_provider"] = storage_provider + meta["storage_backend"] = storage_provider + if storage_path: + meta["storage_path"] = storage_path + with open(temp_path, "rb") as f: files = {"file": (f"video.{format}", f, f"video/{format}")} - data = {"prompt": caption or ""} - if allow_duplicates: - data["force"] = "true" - if tags: - data["tags"] = tags - if library != "Default": - try: - data["library_id"] = int(library.split(":")[0]) - except: - pass - if parent_ids: - data["parent_ids"] = parent_ids - - response = requests.post(upload_url, files=files, data=data) + data = {"metadata": json.dumps(meta)} + + response = requests.post( + upload_url, + files=files, + data=data, + headers=get_auth_headers(), + ) response.raise_for_status() result = response.json() uploaded_id = result.get("id") uploaded_ids.append(str(uploaded_id)) - if collection and collection != "None" and uploaded_id: - try: - collection_id = int(collection.split(":")[0]) - requests.post( - f"{api_base_url}/collections/{collection_id}/items", - json={"image_id": uploaded_id} - ) - except Exception as e: - print(f"[Embeddr] Failed to add to collection: {e}") - except Exception as e: print(f"[Embeddr] Video upload failed: {e}") uploaded_ids.append("-1") finally: - if 'temp_path' in locals() and os.path.exists(temp_path): + if "temp_path" in locals() and os.path.exists(temp_path): os.remove(temp_path) - return io.NodeOutput(",".join(uploaded_ids)) + result_str = ",".join(uploaded_ids) + return io.NodeOutput(EmbeddrArtifactIDObject(artifact_id=result_str)) diff --git a/nodes/types.py b/nodes/types.py new file mode 100644 index 0000000..4d5e34c --- /dev/null +++ b/nodes/types.py @@ -0,0 +1,49 @@ +from comfy_api.latest._io import ComfyTypeIO, comfytype + + +## Base Artifact ID Type## +@comfytype(io_type="EMBEDDR_ARTIFACT_ID") +class EmbeddrArtifactID(ComfyTypeIO): + Type = str | list[str] + + +class EmbeddrArtifactIDObject: + def __init__(self, artifact_id: str | list[str]): + self.artifact_id: str | list[str] = artifact_id + + +## Upload Artifact Options Type ## +@comfytype(io_type="EMBEDDR_UPLOADARTIFACT_OPTS") +class EmbeddrUploadArtifactOptions(ComfyTypeIO): + Type = object + + +class EmbeddrUploadArtifactOptionsObject: + def __init__( + self, + storage_provider=None, + storage_path=None, + trigger_ingest=True, + tags=None, + related_artifact_ids=None, + ): + self.storage_provider: str | None = storage_provider + self.storage_path: str | None = storage_path + self.trigger_ingest: bool = trigger_ingest + self.tags: list[str] | None = tags + self.related_artifact_ids: list[str] | None = related_artifact_ids + + +## Artifact Info Type ## +@comfytype(io_type="EMBEDDR_ARTIFACT_INFO") +class EmbeddrArtifactInfo(ComfyTypeIO): + Type = dict + + +class EmbeddrArtifactInfoObject: + def __init__(self, data: dict): + self.data = data + self.id = data.get("id") + self.uri = data.get("uri") + self.type_name = data.get("type_name") + self.metadata = data.get("metadata_json") or {} diff --git a/nodes/utils/__init__.py b/nodes/utils/__init__.py index dc46902..4159ef7 100644 --- a/nodes/utils/__init__.py +++ b/nodes/utils/__init__.py @@ -1,3 +1,3 @@ -from .config import get_config +from .config import get_config, get_embeddr_base_url, get_upload_mode -__all__ = ["get_config"] +__all__ = ["get_config", "get_embeddr_base_url", "get_upload_mode"] diff --git a/nodes/utils/api.py b/nodes/utils/api.py index 0ee1c1a..52f2d8a 100644 --- a/nodes/utils/api.py +++ b/nodes/utils/api.py @@ -1,34 +1,36 @@ import requests -from .config import get_config +from .config import get_auth_headers, get_embeddr_base_url -def get_libraries(): + +def _fetch_collection_options(category: str | None = None) -> list[str]: try: - config = get_config() - endpoint = config.get("endpoint", "http://localhost:8003") - api_url = endpoint.rstrip("/") + "/api/v1/libraries" - response = requests.get(api_url) - if response.status_code == 200: - data = response.json() - # Return list of names, but we might need IDs. - # ComfyUI Combo inputs usually take a list of strings. - # We can format as "Name (ID)" or just names if unique. - # Let's return a list of strings formatted as "ID: Name" for easy parsing - return [f"{lib['id']}: {lib['name']}" for lib in data] - except Exception as e: - print(f"[Embeddr] Failed to fetch libraries: {e}") - return [] + base_url = get_embeddr_base_url().rstrip("/") + api_url = f"{base_url}/api/v1/collections" + params = {"category": category} if category else None + response = requests.get(api_url, params=params, headers=get_auth_headers()) + response.raise_for_status() + + data = response.json() + items = data if isinstance(data, list) else data.get("items") or [] + out: list[str] = [] + for item in items: + if not isinstance(item, dict): + continue + item_id = str(item.get("id") or "").strip() + label = str(item.get("label") or item.get("name") or "").strip() + if not item_id or not label: + continue + out.append(f"{item_id}: {label}") + return out + except Exception as exc: + print(f"[Embeddr] Failed to fetch collections(category={category}): {exc}") + return [] + + +def get_libraries(): + return _fetch_collection_options("library") def get_collections(): - try: - config = get_config() - endpoint = config.get("endpoint", "http://localhost:8003") - api_url = endpoint.rstrip("/") + "/api/v1/collections" - response = requests.get(api_url) - if response.status_code == 200: - data = response.json() - return [f"{col['id']}: {col['name']}" for col in data] - except Exception as e: - print(f"[Embeddr] Failed to fetch collections: {e}") - return [] + return _fetch_collection_options() diff --git a/nodes/utils/config.py b/nodes/utils/config.py index c349da9..fd4ed73 100644 --- a/nodes/utils/config.py +++ b/nodes/utils/config.py @@ -1,15 +1,84 @@ -import os import json +import os + + +def _normalize_base_url(url: str | None, default: str) -> str: + if not url: + return default + + clean = str(url).strip().rstrip("/") + + # Strip API suffixes to get the root base + if clean.endswith("/api/v1") or clean.endswith("/api/v1"): + clean = clean[:-7] + elif clean.endswith("/api"): + clean = clean[:-4] + + # Add scheme if missing + if "://" not in clean: + clean = f"http://{clean}" + + return clean.rstrip("/") def get_config(): try: # Go up 3 levels: utils -> nodes -> embeddr-comfyui - base_path = os.path.dirname(os.path.dirname( - os.path.dirname(__file__))) + base_path = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) config_path = os.path.join(base_path, "config.json") if os.path.exists(config_path): - return json.load(open(config_path, "r")) + return json.load(open(config_path)) except Exception: pass return {} + + +def get_embeddr_base_url(default: str = "http://localhost:8003") -> str: + cfg = get_config() + env_url = ( + os.environ.get("EMBEDDR_BACKEND_URL") + or os.environ.get("EMBEDDR_URL") + or os.environ.get("EMBEDDR_ENDPOINT") + ) + cfg_url = cfg.get("embeddr_url") or cfg.get("backend_url") or cfg.get("endpoint") + + return _normalize_base_url(cfg_url or env_url, default) + + +def get_api_key() -> str | None: + cfg = get_config() + return ( + os.environ.get("EMBEDDR_API_KEY") + or os.environ.get("EMBEDDR_KEY") + or cfg.get("api_key") + or cfg.get("key") + ) + + +def get_auth_headers(auth_ticket: str | None = None) -> dict[str, str]: + headers: dict[str, str] = {} + + ticket_value = auth_ticket + if isinstance(ticket_value, (list, tuple)): + ticket_value = ticket_value[0] if ticket_value else None + if isinstance(ticket_value, dict): + ticket_value = ticket_value.get("auth_ticket") or ticket_value.get("ticket") + + if ticket_value is not None: + ticket_text = str(ticket_value).strip() + if ticket_text: + headers["X-Embeddr-Ticket"] = ticket_text + + key = get_api_key() + if key: + headers["X-API-Key"] = key + + return headers + + +def get_upload_mode(default: str = "require") -> str: + cfg = get_config() + env_mode = os.environ.get("EMBEDDR_UPLOAD_MODE") + cfg_mode = cfg.get("upload_mode") + mode = (env_mode or cfg_mode or default or "require").strip().lower() + return mode diff --git a/nodes/utils/ids.py b/nodes/utils/ids.py new file mode 100644 index 0000000..45a8357 --- /dev/null +++ b/nodes/utils/ids.py @@ -0,0 +1,46 @@ +def normalize_ids(value) -> list[str]: + """ + Normalizes a variety of input types (single string, list of strings, + comma-separated strings, EmbeddrArtifactIDObjects) into a clean list of string IDs. + """ + if not value: + return [] + + # Helper to extract string from potential ID objects + def extract_val(x): + if hasattr(x, "artifact_id"): + # artifact_id can be str or list + val = x.artifact_id + if isinstance(val, list): + return ",".join(str(v) for v in val) + return str(val) + return str(x) + + # Initial collection of strings + raw_strings = [] + if isinstance(value, str): + raw_strings = [value] + elif isinstance(value, list): + raw_strings = [extract_val(v) for v in value] + elif hasattr(value, "artifact_id"): + raw_strings = [extract_val(value)] + else: + # Fallback for other single types + raw_strings = [str(value)] + + # Flatten by splitting commas and cleaning + out = [] + seen = set() + for s in raw_strings: + parts = s.split(",") + for p in parts: + clean_p = p.strip() + if not clean_p: + continue + if clean_p.lower() in ("none", "null", "undefined"): + continue + + if clean_p not in seen: + seen.add(clean_p) + out.append(clean_p) + return out diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index b321aa7..0000000 --- a/package-lock.json +++ /dev/null @@ -1,3007 +0,0 @@ -{ - "name": "ComfyUI_frontend_vue_basic", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "@comfyorg/comfyui-frontend-types": "^1.22.1", - "@vitejs/plugin-vue": "^5.2.3", - "vite": "^6.3.5", - "vite-plugin-vue-devtools": "^7.7.2" - }, - "peerDependencies": { - "primevue": "^4.2.5", - "vue": "^3.5.13", - "vue-i18n": "^9.14.3" - } - }, - "../../ComfyUI_frontend/dist": { - "name": "@comfyorg/comfyui-frontend-types", - "version": "1.17.0", - "extraneous": true, - "license": "GPL-3.0-only", - "dependencies": { - "@comfyorg/litegraph": "^0.13.3" - }, - "peerDependencies": { - "vue": "^3.5.13", - "zod": "^3.23.8" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@antfu/utils": { - "version": "0.7.10", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", - "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", - "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", - "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.26.8", - "@babel/helper-validator-option": "^7.25.9", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz", - "integrity": "sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/helper-replace-supers": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/traverse": "^7.27.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", - "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", - "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", - "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.25.9", - "@babel/helper-optimise-call-expression": "^7.25.9", - "@babel/traverse": "^7.26.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", - "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz", - "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/plugin-syntax-decorators": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz", - "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", - "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", - "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", - "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.0.tgz", - "integrity": "sha512-fRGGjO2UEGPjvEcyAZXRXAS8AfdaQoq7HnxAbJoAoW10B9xOKesmmndJv+Sym2a+9FHWZ9KbyyLCe9s0Sn5jtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.25.9", - "@babel/helper-create-class-features-plugin": "^7.27.0", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", - "@babel/plugin-syntax-typescript": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@comfyorg/comfyui-frontend-types": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/@comfyorg/comfyui-frontend-types/-/comfyui-frontend-types-1.22.1.tgz", - "integrity": "sha512-/jtTgvUS8K8KrsSxeIdaaTIMbLSgFFJBefnnv+gYZTd0xp+DRttar2L4/ZQGPZpFR1CwEU70dAEQEd30nxvPcw==", - "dev": true, - "license": "GPL-3.0-only", - "dependencies": { - "@comfyorg/litegraph": "^0.15.15" - }, - "peerDependencies": { - "vue": "^3.5.13", - "zod": "^3.23.8" - } - }, - "node_modules/@comfyorg/litegraph": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.15.15.tgz", - "integrity": "sha512-otOKgTxNPV6gEa6PW1fHGMMF8twjnZkP0vWQhGsRISK4vN8tPfX8O9sC9Hnq3nV8axaMv4/Ff49+7mMVcFEKeA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", - "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", - "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", - "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", - "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", - "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", - "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", - "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", - "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", - "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", - "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", - "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", - "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", - "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", - "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", - "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", - "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", - "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", - "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", - "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", - "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", - "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", - "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", - "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", - "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", - "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@intlify/core-base": { - "version": "9.14.4", - "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.4.tgz", - "integrity": "sha512-vtZCt7NqWhKEtHa3SD/322DlgP5uR9MqWxnE0y8Q0tjDs9H5Lxhss+b5wv8rmuXRoHKLESNgw9d+EN9ybBbj9g==", - "license": "MIT", - "peer": true, - "dependencies": { - "@intlify/message-compiler": "9.14.4", - "@intlify/shared": "9.14.4" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@intlify/message-compiler": { - "version": "9.14.4", - "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.4.tgz", - "integrity": "sha512-vcyCLiVRN628U38c3PbahrhbbXrckrM9zpy0KZVlDk2Z0OnGwv8uQNNXP3twwGtfLsCf4gu3ci6FMIZnPaqZsw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@intlify/shared": "9.14.4", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@intlify/shared": { - "version": "9.14.4", - "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.4.tgz", - "integrity": "sha512-P9zv6i1WvMc9qDBWvIgKkymjY2ptIiQ065PjDv7z7fDqH3J/HBRBN5IoiR46r/ujRcU7hCuSIZWvCAFCyuOYZA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, - "license": "MIT" - }, - "node_modules/@primeuix/styled": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.5.1.tgz", - "integrity": "sha512-5Ftw/KSauDPClQ8F2qCyCUF7cIUEY4yLNikf0rKV7Vsb8zGYNK0dahQe7CChaR6M2Kn+NA2DSBSk76ZXqj6Uog==", - "license": "MIT", - "peer": true, - "dependencies": { - "@primeuix/utils": "^0.5.3" - }, - "engines": { - "node": ">=12.11.0" - } - }, - "node_modules/@primeuix/styles": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@primeuix/styles/-/styles-1.0.3.tgz", - "integrity": "sha512-yHj/Q+fosJ1736Ty5lRbpqhKa9piou+xZPPppNHUDshq0+XhrFwDGggvPGmDAJyUIM+ChM/Nj8lPY/AwTNXAkg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@primeuix/styled": "^0.5.1" - } - }, - "node_modules/@primeuix/utils": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@primeuix/utils/-/utils-0.5.3.tgz", - "integrity": "sha512-7SGh7734wcF1/uK6RzO6Z6CBjGQ97GDHfpyl2F1G/c7R0z9hkT/V72ypDo82AWcCS7Ta07oIjDpOCTkSVZuEGQ==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12.11.0" - } - }, - "node_modules/@primevue/core": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@primevue/core/-/core-4.3.3.tgz", - "integrity": "sha512-kSkN5oourG7eueoFPIqiNX3oDT/f0I5IRK3uOY/ytz+VzTZp5yuaCN0Nt42ZQpVXjDxMxDvUhIdaXVrjr58NhQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@primeuix/styled": "^0.5.0", - "@primeuix/utils": "^0.5.1" - }, - "engines": { - "node": ">=12.11.0" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/@primevue/icons": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@primevue/icons/-/icons-4.3.3.tgz", - "integrity": "sha512-ouQaxHyeFB6MSfEGGbjaK5Qv9efS1xZGetZoU5jcPm090MSYLFtroP1CuK3lZZAQals06TZ6T6qcoNukSHpK5w==", - "license": "MIT", - "peer": true, - "dependencies": { - "@primeuix/utils": "^0.5.1", - "@primevue/core": "4.3.3" - }, - "engines": { - "node": ">=12.11.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", - "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.39.0.tgz", - "integrity": "sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.39.0.tgz", - "integrity": "sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.39.0.tgz", - "integrity": "sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.39.0.tgz", - "integrity": "sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.39.0.tgz", - "integrity": "sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.39.0.tgz", - "integrity": "sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.39.0.tgz", - "integrity": "sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.39.0.tgz", - "integrity": "sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.39.0.tgz", - "integrity": "sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.39.0.tgz", - "integrity": "sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.39.0.tgz", - "integrity": "sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.39.0.tgz", - "integrity": "sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.39.0.tgz", - "integrity": "sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.39.0.tgz", - "integrity": "sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.39.0.tgz", - "integrity": "sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.39.0.tgz", - "integrity": "sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.39.0.tgz", - "integrity": "sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.39.0.tgz", - "integrity": "sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.39.0.tgz", - "integrity": "sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.39.0.tgz", - "integrity": "sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sindresorhus/merge-streams": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitejs/plugin-vue": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", - "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", - "vue": "^3.2.25" - } - }, - "node_modules/@vue/babel-helper-vue-transform-on": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.4.0.tgz", - "integrity": "sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vue/babel-plugin-jsx": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.4.0.tgz", - "integrity": "sha512-9zAHmwgMWlaN6qRKdrg1uKsBKHvnUU+Py+MOCTuYZBoZsopa90Di10QRjB+YPnVss0BZbG/H5XFwJY1fTxJWhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.9", - "@babel/types": "^7.26.9", - "@vue/babel-helper-vue-transform-on": "1.4.0", - "@vue/babel-plugin-resolve-type": "1.4.0", - "@vue/shared": "^3.5.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - } - } - }, - "node_modules/@vue/babel-plugin-resolve-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.4.0.tgz", - "integrity": "sha512-4xqDRRbQQEWHQyjlYSgZsWj44KfiF6D+ktCuXyZ8EnVDYV3pztmXJDf1HveAjUAXxAnR8daCQT51RneWWxtTyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/parser": "^7.26.9", - "@vue/compiler-sfc": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", - "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.13", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.0" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", - "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.13", - "@vue/shared": "3.5.13" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", - "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.13", - "@vue/compiler-dom": "3.5.13", - "@vue/compiler-ssr": "3.5.13", - "@vue/shared": "3.5.13", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.11", - "postcss": "^8.4.48", - "source-map-js": "^1.2.0" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", - "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.13", - "@vue/shared": "3.5.13" - } - }, - "node_modules/@vue/devtools-api": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", - "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", - "license": "MIT", - "peer": true - }, - "node_modules/@vue/devtools-core": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.2.tgz", - "integrity": "sha512-lexREWj1lKi91Tblr38ntSsy6CvI8ba7u+jmwh2yruib/ltLUcsIzEjCnrkh1yYGGIKXbAuYV2tOG10fGDB9OQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-kit": "^7.7.2", - "@vue/devtools-shared": "^7.7.2", - "mitt": "^3.0.1", - "nanoid": "^5.0.9", - "pathe": "^2.0.2", - "vite-hot-client": "^0.2.4" - }, - "peerDependencies": { - "vue": "^3.0.0" - } - }, - "node_modules/@vue/devtools-core/node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz", - "integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^7.7.2", - "birpc": "^0.2.19", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.1" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz", - "integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", - "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@vue/shared": "3.5.13" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", - "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@vue/reactivity": "3.5.13", - "@vue/shared": "3.5.13" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", - "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", - "license": "MIT", - "peer": true, - "dependencies": { - "@vue/reactivity": "3.5.13", - "@vue/runtime-core": "3.5.13", - "@vue/shared": "3.5.13", - "csstype": "^3.1.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", - "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", - "license": "MIT", - "peer": true, - "dependencies": { - "@vue/compiler-ssr": "3.5.13", - "@vue/shared": "3.5.13" - }, - "peerDependencies": { - "vue": "3.5.13" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", - "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", - "license": "MIT" - }, - "node_modules/birpc": { - "version": "0.2.19", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", - "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001712", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz", - "integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/copy-anything": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", - "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-what": "^4.1.8" - }, - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT", - "peer": true - }, - "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.135", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.135.tgz", - "integrity": "sha512-8gXUdEmvb+WCaYUhA0Svr08uSeRjM2w3x5uHOc1QbaEVzJXB8rgm5eptieXzyKoVEtinLvW6MtTcurA65PeS1Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-stack-parser-es": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", - "integrity": "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/esbuild": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", - "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.2", - "@esbuild/android-arm": "0.25.2", - "@esbuild/android-arm64": "0.25.2", - "@esbuild/android-x64": "0.25.2", - "@esbuild/darwin-arm64": "0.25.2", - "@esbuild/darwin-x64": "0.25.2", - "@esbuild/freebsd-arm64": "0.25.2", - "@esbuild/freebsd-x64": "0.25.2", - "@esbuild/linux-arm": "0.25.2", - "@esbuild/linux-arm64": "0.25.2", - "@esbuild/linux-ia32": "0.25.2", - "@esbuild/linux-loong64": "0.25.2", - "@esbuild/linux-mips64el": "0.25.2", - "@esbuild/linux-ppc64": "0.25.2", - "@esbuild/linux-riscv64": "0.25.2", - "@esbuild/linux-s390x": "0.25.2", - "@esbuild/linux-x64": "0.25.2", - "@esbuild/netbsd-arm64": "0.25.2", - "@esbuild/netbsd-x64": "0.25.2", - "@esbuild/openbsd-arm64": "0.25.2", - "@esbuild/openbsd-x64": "0.25.2", - "@esbuild/sunos-x64": "0.25.2", - "@esbuild/win32-arm64": "0.25.2", - "@esbuild/win32-ia32": "0.25.2", - "@esbuild/win32-x64": "0.25.2" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/execa": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", - "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^4.0.0", - "cross-spawn": "^7.0.3", - "figures": "^6.1.0", - "get-stream": "^9.0.0", - "human-signals": "^8.0.0", - "is-plain-obj": "^4.1.0", - "is-stream": "^4.0.1", - "npm-run-path": "^6.0.0", - "pretty-ms": "^9.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^4.0.0", - "yoctocolors": "^2.0.0" - }, - "engines": { - "node": "^18.19.0 || >=20.5.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unicode-supported": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-what": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", - "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.13" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/kolorist": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, - "license": "MIT" - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/npm-run-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", - "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.8", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/pretty-ms": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", - "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-ms": "^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/primevue": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/primevue/-/primevue-4.3.3.tgz", - "integrity": "sha512-nooYVoEz5CdP3EhUkD6c3qTdRmpLHZh75fBynkUkl46K8y5rksHTjdSISiDijwTA5STQIOkyqLb+RM+HQ6nC1Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "@primeuix/styled": "^0.5.0", - "@primeuix/styles": "^1.0.0", - "@primeuix/utils": "^0.5.1", - "@primevue/core": "4.3.3", - "@primevue/icons": "4.3.3" - }, - "engines": { - "node": ">=12.11.0" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/rollup": { - "version": "4.39.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.39.0.tgz", - "integrity": "sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.7" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.39.0", - "@rollup/rollup-android-arm64": "4.39.0", - "@rollup/rollup-darwin-arm64": "4.39.0", - "@rollup/rollup-darwin-x64": "4.39.0", - "@rollup/rollup-freebsd-arm64": "4.39.0", - "@rollup/rollup-freebsd-x64": "4.39.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.39.0", - "@rollup/rollup-linux-arm-musleabihf": "4.39.0", - "@rollup/rollup-linux-arm64-gnu": "4.39.0", - "@rollup/rollup-linux-arm64-musl": "4.39.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.39.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.39.0", - "@rollup/rollup-linux-riscv64-gnu": "4.39.0", - "@rollup/rollup-linux-riscv64-musl": "4.39.0", - "@rollup/rollup-linux-s390x-gnu": "4.39.0", - "@rollup/rollup-linux-x64-gnu": "4.39.0", - "@rollup/rollup-linux-x64-musl": "4.39.0", - "@rollup/rollup-win32-arm64-msvc": "4.39.0", - "@rollup/rollup-win32-ia32-msvc": "4.39.0", - "@rollup/rollup-win32-x64-msvc": "4.39.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-applescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sirv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz", - "integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/superjson": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", - "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "copy-anything": "^3.0.2" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite-hot-client": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-0.2.4.tgz", - "integrity": "sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0" - } - }, - "node_modules/vite-plugin-inspect": { - "version": "0.8.9", - "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.9.tgz", - "integrity": "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@antfu/utils": "^0.7.10", - "@rollup/pluginutils": "^5.1.3", - "debug": "^4.3.7", - "error-stack-parser-es": "^0.1.5", - "fs-extra": "^11.2.0", - "open": "^10.1.0", - "perfect-debounce": "^1.0.0", - "picocolors": "^1.1.1", - "sirv": "^3.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1" - }, - "peerDependenciesMeta": { - "@nuxt/kit": { - "optional": true - } - } - }, - "node_modules/vite-plugin-vue-devtools": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.7.2.tgz", - "integrity": "sha512-5V0UijQWiSBj32blkyPEqIbzc6HO9c1bwnBhx+ay2dzU0FakH+qMdNUT8nF9BvDE+i6I1U8CqCuJiO20vKEdQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-core": "^7.7.2", - "@vue/devtools-kit": "^7.7.2", - "@vue/devtools-shared": "^7.7.2", - "execa": "^9.5.1", - "sirv": "^3.0.0", - "vite-plugin-inspect": "0.8.9", - "vite-plugin-vue-inspector": "^5.3.1" - }, - "engines": { - "node": ">=v14.21.3" - }, - "peerDependencies": { - "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" - } - }, - "node_modules/vite-plugin-vue-inspector": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.1.tgz", - "integrity": "sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.23.0", - "@babel/plugin-proposal-decorators": "^7.23.0", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-transform-typescript": "^7.22.15", - "@vue/babel-plugin-jsx": "^1.1.5", - "@vue/compiler-dom": "^3.3.4", - "kolorist": "^1.8.0", - "magic-string": "^0.30.4" - }, - "peerDependencies": { - "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" - } - }, - "node_modules/vue": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", - "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@vue/compiler-dom": "3.5.13", - "@vue/compiler-sfc": "3.5.13", - "@vue/runtime-dom": "3.5.13", - "@vue/server-renderer": "3.5.13", - "@vue/shared": "3.5.13" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-i18n": { - "version": "9.14.4", - "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.4.tgz", - "integrity": "sha512-B934C8yUyWLT0EMud3DySrwSUJI7ZNiWYsEEz2gknTthqKiG4dzWE/WSa8AzCuSQzwBEv4HtG1jZDhgzPfWSKQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@intlify/core-base": "9.14.4", - "@intlify/shared": "9.14.4", - "@vue/devtools-api": "^6.5.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/kazupon" - }, - "peerDependencies": { - "vue": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yoctocolors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", - "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.25.58", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.58.tgz", - "integrity": "sha512-DVLmMQzSZwNYzQoMaM3MQWnxr2eq+AtM9Hx3w1/Yl0pH8sLTSjN4jGP7w6f7uand6Hw44tsnSu1hz1AOA6qI2Q==", - "dev": true, - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/package.json b/package.json index 1e58a2c..832884b 100644 --- a/package.json +++ b/package.json @@ -7,21 +7,31 @@ }, "devDependencies": { "@comfyorg/comfyui-frontend-types": "^1.22.1", - "@vitejs/plugin-vue": "^5.2.3", - "tw-animate-css": "^1.4.0", "@tanstack/eslint-config": "^0.3.4", - "vite": "^6.3.5", - "vite-plugin-vue-devtools": "^7.7.2" + "@types/node": "^22.19.3", + "eslint": "^9.39.2", + "prettier": "^3.6.2", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.3", + "vite": "^6.3.5" }, "scripts": { "build": "vite build", - "dev": "vite build --watch" + "dev": "vite build --watch", + "typecheck": "tsc --noEmit", + "lint": "eslint", + "lint:fix": "eslint --fix", + "format": "prettier . --write", + "format:check": "prettier . --check" }, "dependencies": { "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@embeddr/react-ui": "^0.1.4", + "@embeddr/client-typescript": "^0.2.0-alpha.3", + "@embeddr/react-ui": "^0.2.0-alpha.2", + "@embeddr/zen-shell": "^0.2.0-alpha.3", + "@fontsource-variable/jetbrains-mono": "^5.2.8", "@radix-ui/react-aspect-ratio": "^1.1.8", "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-dialog": "^1.1.15", @@ -33,15 +43,18 @@ "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-switch": "^1.2.6", - "shadcn": "^3.6.2", "@radix-ui/react-tabs": "^1.1.13", "@tailwindcss/vite": "^4.1.17", + "@tanstack/react-query": "^5.59.0", "@vitejs/plugin-react": "^5.1.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.556.0", "react": "^19.2.1", "react-dom": "^19.2.1", + "recharts": "^2.13.0", + "shadcn": "^3.6.2", + "three": "^0.182.0", "sonner": "^2.0.7", "tailwind-merge": "^3.4.0", "tailwindcss": "^4.1.17" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92e098f..e6555be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,18 @@ importers: '@dnd-kit/utilities': specifier: ^3.2.2 version: 3.2.2(react@19.2.1) + '@embeddr/client-typescript': + specifier: ^0.2.0-alpha.3 + version: 0.2.0-alpha.3 '@embeddr/react-ui': - specifier: ^0.1.4 - version: 0.1.4(16c373490915c1118e2ce5b39846f212) + specifier: ^0.2.0-alpha.2 + version: 0.2.0-alpha.2(@types/react@19.2.7)(@types/three@0.182.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@embeddr/zen-shell': + specifier: ^0.2.0-alpha.3 + version: 0.2.0-alpha.3(@types/react@19.2.7)(@types/three@0.182.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)) + '@fontsource-variable/jetbrains-mono': + specifier: ^5.2.8 + version: 5.2.8 '@radix-ui/react-aspect-ratio': specifier: ^1.1.8 version: 1.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -58,10 +67,13 @@ importers: version: 1.1.13(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@tailwindcss/vite': specifier: ^4.1.17 - version: 4.1.17(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2)) + version: 4.1.17(vite@6.4.1(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.30.2)) + '@tanstack/react-query': + specifier: ^5.59.0 + version: 5.100.6(react@19.2.1) '@vitejs/plugin-react': specifier: ^5.1.1 - version: 5.1.1(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2)) + version: 5.1.1(vite@6.4.1(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.30.2)) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -80,9 +92,12 @@ importers: react-dom: specifier: ^19.2.1 version: 19.2.1(react@19.2.1) + recharts: + specifier: ^2.13.0 + version: 2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) shadcn: specifier: ^3.6.2 - version: 3.6.2(hono@4.11.3)(typescript@5.9.3) + version: 3.6.2(@types/node@22.19.17)(hono@4.11.3)(typescript@5.9.3) sonner: specifier: ^2.0.7 version: 2.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -92,6 +107,9 @@ importers: tailwindcss: specifier: ^4.1.17 version: 4.1.17 + three: + specifier: ^0.182.0 + version: 0.182.0 vue: specifier: ^3.5.13 version: 3.5.25(typescript@5.9.3) @@ -105,18 +123,24 @@ importers: '@tanstack/eslint-config': specifier: ^0.3.4 version: 0.3.4(@typescript-eslint/utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@vitejs/plugin-vue': - specifier: ^5.2.3 - version: 5.2.4(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.25(typescript@5.9.3)) + '@types/node': + specifier: ^22.19.3 + version: 22.19.17 + eslint: + specifier: ^9.39.2 + version: 9.39.2(jiti@2.6.1) + prettier: + specifier: ^3.6.2 + version: 3.8.3 tw-animate-css: specifier: ^1.4.0 version: 1.4.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 vite: specifier: ^6.3.5 - version: 6.4.1(jiti@2.6.1)(lightningcss@1.30.2) - vite-plugin-vue-devtools: - specifier: ^7.7.2 - version: 7.7.9(rollup@4.53.3)(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.25(typescript@5.9.3)) + version: 6.4.1(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.30.2) packages: @@ -124,9 +148,6 @@ packages: resolution: {integrity: sha512-9q/yCljni37pkMr4sPrI3G4jqdIk074+iukc5aFJl7kmDCCsiJrbZ6zKxnES1Gwg+i9RcDZwvktl23puGslmvA==} hasBin: true - '@antfu/utils@0.7.10': - resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} - '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -214,29 +235,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-proposal-decorators@7.28.0': - resolution: {integrity: sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-decorators@7.27.1': - resolution: {integrity: sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-attributes@7.27.1': - resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.27.1': resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} engines: {node: '>=6.9.0'} @@ -336,36 +334,20 @@ packages: peerDependencies: '@noble/ciphers': ^1.0.0 - '@embeddr/react-ui@0.1.4': - resolution: {integrity: sha512-asOTRr5wnQybLpw+5u0BPWgzTKdqeHFYy4P61MhmuomjwXDFOQjty3DB2QfCHASP8E/DuoMP32SkkDR3aRLS2Q==} - peerDependencies: - '@radix-ui/react-accordion': ^1.2.12 - '@radix-ui/react-aspect-ratio': ^1.1.8 - '@radix-ui/react-avatar': ^1.1.11 - '@radix-ui/react-checkbox': ^1.3.3 - '@radix-ui/react-dialog': ^1.1.15 - '@radix-ui/react-dropdown-menu': ^2.1.16 - '@radix-ui/react-label': ^2.1.8 - '@radix-ui/react-menubar': ^1.1.16 - '@radix-ui/react-progress': ^1.1.8 - '@radix-ui/react-scroll-area': ^1.2.10 - '@radix-ui/react-select': ^2.2.6 - '@radix-ui/react-separator': ^1.1.8 - '@radix-ui/react-slider': ^1.3.6 - '@radix-ui/react-slot': ^1.2.4 - '@radix-ui/react-switch': ^1.2.6 - '@radix-ui/react-tabs': ^1.1.13 - '@radix-ui/react-tooltip': ^1.2.8 - '@radix-ui/react-visually-hidden': ^1.2.4 - '@react-three/drei': ^10.7.7 - '@react-three/fiber': ^9.4.2 - next-themes: ^0.4.6 - react: ^19.2.3 - react-dom: ^19.2.3 - react-resizable-panels: ^4.0.15 - shadcn: ^3.6.2 - sonner: ^2.0.7 - three: ^0.182.0 + '@embeddr/client-typescript@0.2.0-alpha.3': + resolution: {integrity: sha512-Flm4QoP9T27JtTuWkZjWuCcyNcxu0aG/TbKD2dXHaimFmTjFqPXsQ7NgoL5eGVMyVrRRsXEgH3bnepf8OcTTEQ==} + + '@embeddr/react-ui@0.2.0-alpha.2': + resolution: {integrity: sha512-PKcaruagzZKnx0D+WBPIW4E2BD6AFnU/KM891QO2aAmZpx5IlhKR+lB+uzSnH+Q4BbegXAoxGTk3UfS8Hm9IEQ==} + peerDependencies: + react: ^19.0.0 + react-dom: ^19.0.0 + + '@embeddr/zen-shell@0.2.0-alpha.3': + resolution: {integrity: sha512-YFl5X2fVVH/R6z53vOqSWczuaocwFnzMzRrgmplaci9Ydvd/mLPHvJzO3JgSw/wxp0CgJ8D1At/ZdxTKvf4hgg==} + peerDependencies: + react: ^19.0.0 + react-dom: ^19.0.0 '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} @@ -585,6 +567,9 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@fontsource-variable/jetbrains-mono@5.2.8': + resolution: {integrity: sha512-WBA9elru6Jdp5df2mES55wuOO0WIrn3kpXnI4+W2ek5u3ZgLS9XS4gmIlcQhiZOWEKl95meYdvK7xI+ETLCq/Q==} + '@hono/node-server@1.19.7': resolution: {integrity: sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==} engines: {node: '>=18.14.1'} @@ -678,6 +663,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + '@mediapipe/tasks-vision@0.10.17': resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==} @@ -736,9 +724,6 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@polka/url@1.0.0-next.29': - resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@primeuix/styled@0.7.4': resolution: {integrity: sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ==} engines: {node: '>=12.11.0'} @@ -766,19 +751,6 @@ packages: '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - '@radix-ui/react-accessible-icon@1.1.7': - resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-accordion@1.2.12': resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} peerDependencies: @@ -792,19 +764,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-alert-dialog@1.1.15': - resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-arrow@1.1.7': resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: @@ -818,19 +777,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-aspect-ratio@1.1.7': - resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-aspect-ratio@1.1.8': resolution: {integrity: sha512-5nZrJTF7gH+e0nZS7/QxFz6tJV4VimhQb1avEgtsJxvvIp5JilL+c58HICsKzPxghdwaDt48hEfPM1au4zGy+w==} peerDependencies: @@ -844,19 +790,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-avatar@1.1.10': - resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-avatar@1.1.11': resolution: {integrity: sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==} peerDependencies: @@ -1019,32 +952,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-form@0.1.8': - resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-hover-card@1.1.15': - resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-id@1.1.1': resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: @@ -1054,19 +961,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-label@2.1.7': - resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-label@2.1.8': resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} peerDependencies: @@ -1106,45 +1000,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-navigation-menu@1.2.14': - resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-one-time-password-field@0.1.8': - resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-password-toggle-field@0.1.3': - resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-popover@1.1.15': resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} peerDependencies: @@ -1223,19 +1078,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-progress@1.1.7': - resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-progress@1.1.8': resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==} peerDependencies: @@ -1249,19 +1091,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-radio-group@1.3.8': - resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-roving-focus@1.1.11': resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} peerDependencies: @@ -1301,19 +1130,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-separator@1.1.7': - resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-separator@1.1.8': resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} peerDependencies: @@ -1384,58 +1200,6 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-toast@1.2.15': - resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-toggle-group@1.1.11': - resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-toggle@1.1.10': - resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-toolbar@1.1.11': - resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-tooltip@1.2.8': resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} peerDependencies: @@ -1543,6 +1307,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-visually-hidden@1.2.4': + resolution: {integrity: sha512-kaeiyGCe844dkb9AVF+rb4yTyb1LiLN/e3es3nLiRyN4dC8AduBYPMnnNlDjX2VDOcvDEiPnRNMJeWCfsX0txg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} @@ -1585,15 +1362,6 @@ packages: '@rolldown/pluginutils@1.0.0-beta.47': resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} - '@rollup/pluginutils@5.3.0': - resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - '@rollup/rollup-android-arm-eabi@4.53.3': resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} cpu: [arm] @@ -1628,56 +1396,67 @@ packages: resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.3': resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.3': resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.3': resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.3': resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.53.3': resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.53.3': resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.3': resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.3': resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.3': resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.3': resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.53.3': resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} @@ -1722,8 +1501,8 @@ packages: peerDependencies: react: '>= 16' - '@tabler/icons@3.36.1': - resolution: {integrity: sha512-f4Jg3Fof/Vru5ioix/UO4GX+sdDsF9wQo47FbtvG+utIYYVQ/QVAC0QYgcBbAjQGfbdOh2CCf0BgiFOF9Ixtjw==} + '@tabler/icons@3.41.1': + resolution: {integrity: sha512-OaRnVbRmH2nHtFeg+RmMJ/7m2oBIF9XCJAUD5gQnMrpK9f05ydj8MZrAf3NZQqOXyxGN1UBL0D5IKLLEUfr74Q==} '@tailwindcss/node@4.1.17': resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} @@ -1763,24 +1542,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.17': resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.17': resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.17': resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.17': resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} @@ -1821,6 +1604,14 @@ packages: peerDependencies: eslint: ^8.0.0 || ^9.0.0 + '@tanstack/query-core@5.100.6': + resolution: {integrity: sha512-Os2CPUr98to98RYm+D4qGqGkiffn7MGSyl2547a4MljVkHE30AMJRqTiyCqBfMwzAx/I91vCkAxp5tHSla6Twg==} + + '@tanstack/react-query@5.100.6': + resolution: {integrity: sha512-uVSrps0PV16Cxmcn2rvL+dUhwTpTUtiRW347AEeYxMZXO2pZe9ja7E24PAMGoQ5u2g89DD8u4QhOviBk+RN8RA==} + peerDependencies: + react: ^18 || ^19 + '@ts-morph/common@0.27.0': resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} @@ -1842,15 +1633,63 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/draco3d@1.4.10': resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@22.19.17': + resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==} + '@types/offscreencanvas@2019.7.3': resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} @@ -1871,6 +1710,12 @@ packages: '@types/three@0.182.0': resolution: {integrity: sha512-WByN9V3Sbwbe2OkWuSGyoqQO8Du6yhYaXtXLoA5FkKTUJorZ+yOHBZ35zUUPQXlAKABZmbYp5oAqpA4RBjtJ/Q==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/webxr@0.5.24': resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} @@ -1933,6 +1778,9 @@ packages: resolution: {integrity: sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} cpu: [arm] @@ -1972,41 +1820,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -2042,31 +1898,8 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - '@vitejs/plugin-vue@5.2.4': - resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} - engines: {node: ^18.0.0 || >=20.0.0} - peerDependencies: - vite: ^5.0.0 || ^6.0.0 - vue: ^3.2.25 - - '@vue/babel-helper-vue-transform-on@1.5.0': - resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==} - - '@vue/babel-plugin-jsx@1.5.0': - resolution: {integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - peerDependenciesMeta: - '@babel/core': - optional: true - - '@vue/babel-plugin-resolve-type@1.5.0': - resolution: {integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@vue/compiler-core@3.5.25': - resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==} + '@vue/compiler-core@3.5.25': + resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==} '@vue/compiler-dom@3.5.25': resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==} @@ -2080,17 +1913,6 @@ packages: '@vue/devtools-api@6.6.4': resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} - '@vue/devtools-core@7.7.9': - resolution: {integrity: sha512-48jrBSwG4GVQRvVeeXn9p9+dlx+ISgasM7SxZZKczseohB0cBz+ITKr4YbLWjmJdy45UHL7UMPlR4Y0CWTRcSQ==} - peerDependencies: - vue: ^3.0.0 - - '@vue/devtools-kit@7.7.9': - resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} - - '@vue/devtools-shared@7.7.9': - resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} - '@vue/reactivity@3.5.25': resolution: {integrity: sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==} @@ -2170,6 +1992,13 @@ packages: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2183,9 +2012,6 @@ packages: bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} - birpc@2.8.0: - resolution: {integrity: sha512-Bz2a4qD/5GRhiHSwj30c/8kC8QGj12nNDwz3D4ErQ4Xhy35dsSDvF+RA/tWpjyU0pdGtSDiEk6B5fBGE1qNVhw==} - body-parser@2.2.1: resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==} engines: {node: '>=18'} @@ -2237,6 +2063,9 @@ packages: caniuse-lite@1.0.30001757: resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2245,6 +2074,18 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -2271,6 +2112,9 @@ packages: code-block-writer@13.0.3: resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -2278,6 +2122,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} @@ -2316,10 +2163,6 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} - copy-anything@4.0.5: - resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} - engines: {node: '>=18'} - cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -2350,6 +2193,50 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -2363,6 +2250,12 @@ packages: supports-color: optional: true + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + dedent@1.7.1: resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==} peerDependencies: @@ -2394,6 +2287,10 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-gpu@5.0.70: resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==} @@ -2404,10 +2301,16 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diff@8.0.2: resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} engines: {node: '>=0.3.1'} + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dotenv@17.2.3: resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} engines: {node: '>=12'} @@ -2454,9 +2357,6 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - error-stack-parser-es@0.1.5: - resolution: {integrity: sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -2469,6 +2369,12 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -2485,6 +2391,10 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + eslint-compat-utils@0.5.1: resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} engines: {node: '>=12'} @@ -2568,9 +2478,30 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -2579,6 +2510,9 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + eventsource-parser@3.0.6: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} @@ -2605,9 +2539,16 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-equals@5.4.0: + resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} + engines: {node: '>=6.0.0'} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -2788,6 +2729,15 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + headers-polyfill@4.0.3: resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} @@ -2798,9 +2748,6 @@ packages: resolution: {integrity: sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w==} engines: {node: '>=16.9.0'} - hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -2846,13 +2793,29 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@3.0.0: resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2870,6 +2833,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-in-ssh@1.0.0: resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} engines: {node: '>=20'} @@ -2924,10 +2890,6 @@ packages: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} - is-what@5.5.0: - resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} - engines: {node: '>=18'} - is-wsl@3.1.0: resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} engines: {node: '>=16'} @@ -3000,9 +2962,6 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -3045,24 +3004,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -3090,13 +3053,28 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + log-symbols@6.0.0: resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} engines: {node: '>=18'} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.544.0: + resolution: {integrity: sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lucide-react@0.556.0: resolution: {integrity: sha512-iOb8dRk7kLaYBZhR2VlV1CeJGxChBgUthpSP8wom9jfj79qovgG6qcSdiy6vkoREKPnbUYzJsCn4o4PtG3Iy+A==} peerDependencies: @@ -3116,10 +3094,65 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} @@ -3143,6 +3176,111 @@ packages: meshoptimizer@0.22.0: resolution: {integrity: sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -3177,13 +3315,6 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - mitt@3.0.1: - resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - - mrmime@2.0.1: - resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} - engines: {node: '>=10'} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3206,11 +3337,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.6: - resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} - engines: {node: ^18 || >=20} - hasBin: true - napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -3276,10 +3402,6 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - open@10.2.0: - resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} - engines: {node: '>=18'} - open@11.0.0: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} @@ -3310,6 +3432,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -3343,12 +3468,6 @@ packages: path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - perfect-debounce@1.0.0: - resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -3383,6 +3502,11 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + pretty-ms@9.3.0: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} @@ -3398,6 +3522,12 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -3413,19 +3543,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - radix-ui@1.4.3: - resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -3439,6 +3556,12 @@ packages: peerDependencies: react: ^19.2.1 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-refresh@0.18.0: resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} @@ -3469,6 +3592,12 @@ packages: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 + react-smooth@4.0.4: + resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-style-singleton@2.2.3: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} @@ -3479,6 +3608,12 @@ packages: '@types/react': optional: true + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + react-use-measure@2.1.7: resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==} peerDependencies: @@ -3496,6 +3631,48 @@ packages: resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} engines: {node: '>= 4'} + recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + + recharts@2.15.4: + resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==} + engines: {node: '>=14'} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -3522,9 +3699,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rfdc@1.4.1: - resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rollup@4.53.3: resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3602,10 +3776,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - sirv@3.0.2: - resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} - engines: {node: '>=18'} - sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -3623,9 +3793,12 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - speakingurl@14.0.1: - resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} - engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} stable-hash-x@0.2.0: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} @@ -3659,6 +3832,9 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + stringify-object@5.0.0: resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==} engines: {node: '>=14.16'} @@ -3687,9 +3863,11 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - superjson@2.2.5: - resolution: {integrity: sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==} - engines: {node: '>=16'} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -3753,14 +3931,13 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - tough-cookie@6.0.0: resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} engines: {node: '>=16'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + troika-three-text@0.52.4: resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==} peerDependencies: @@ -3774,6 +3951,9 @@ packages: troika-worker-utils@0.52.0: resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==} + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.3.0: resolution: {integrity: sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==} engines: {node: '>=18.12'} @@ -3825,10 +4005,34 @@ packages: engines: {node: '>=14.17'} hasBin: true + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -3888,31 +4092,14 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite-hot-client@2.1.0: - resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==} - peerDependencies: - vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 - - vite-plugin-inspect@0.8.9: - resolution: {integrity: sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==} - engines: {node: '>=14'} - peerDependencies: - '@nuxt/kit': '*' - vite: ^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1 - peerDependenciesMeta: - '@nuxt/kit': - optional: true + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - vite-plugin-vue-devtools@7.7.9: - resolution: {integrity: sha512-08DvePf663SxqLFJeMVNW537zzVyakp9KIrI2K7lwgaTqA5R/ydN/N2K8dgZO34tg/Qmw0ch84fOKoBtCEdcGg==} - engines: {node: '>=v14.21.3'} - peerDependencies: - vite: ^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-plugin-vue-inspector@5.3.2: - resolution: {integrity: sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==} - peerDependencies: - vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 + victory-vendor@36.9.2: + resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} vite@6.4.1: resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} @@ -4009,10 +4196,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - wsl-utils@0.1.0: - resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} - engines: {node: '>=18'} - wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -4085,6 +4268,9 @@ packages: use-sync-external-store: optional: true + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@antfu/ni@25.0.0': @@ -4094,8 +4280,6 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.0.2 - '@antfu/utils@0.7.10': {} - '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -4219,30 +4403,6 @@ snapshots: dependencies: '@babel/types': 7.28.5 - '@babel/plugin-proposal-decorators@7.28.0(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-decorators': 7.27.1(@babel/core@7.28.5) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-syntax-decorators@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -4323,7 +4483,8 @@ snapshots: vue: 3.5.25(typescript@5.9.3) zod: 3.25.76 - '@dimforge/rapier3d-compat@0.12.0': {} + '@dimforge/rapier3d-compat@0.12.0': + optional: true '@dnd-kit/accessibility@3.1.1(react@19.2.1)': dependencies: @@ -4366,17 +4527,22 @@ snapshots: dependencies: '@noble/ciphers': 1.3.0 - '@embeddr/react-ui@0.1.4(16c373490915c1118e2ce5b39846f212)': + '@embeddr/client-typescript@0.2.0-alpha.3': {} + + '@embeddr/react-ui@0.2.0-alpha.2(@types/react@19.2.7)(@types/three@0.182.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: + '@mdx-js/mdx': 3.1.1 '@radix-ui/react-accordion': 1.2.12(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-aspect-ratio': 1.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-avatar': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-checkbox': 1.3.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-collapsible': 1.1.12(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-context-menu': 2.2.16(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-dialog': 1.1.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-dropdown-menu': 2.1.16(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-label': 2.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-menubar': 1.1.16(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-popover': 1.1.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-progress': 1.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-scroll-area': 1.2.10(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-select': 2.2.6(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -4386,25 +4552,56 @@ snapshots: '@radix-ui/react-switch': 1.2.6(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-tabs': 1.1.13(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-tooltip': 1.2.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@react-three/drei': 10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(three@0.182.0))(@types/react@19.2.7)(@types/three@0.182.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(three@0.182.0) - '@react-three/fiber': 9.5.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(three@0.182.0) + '@radix-ui/react-visually-hidden': 1.2.4(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@tabler/icons-react': 3.36.1(react@19.2.1) class-variance-authority: 0.7.1 clsx: 2.1.1 lucide-react: 0.562.0(react@19.2.1) next-themes: 0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - radix-ui: 1.4.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) react-resizable-panels: 4.2.1(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - shadcn: 3.6.2(hono@4.11.3)(typescript@5.9.3) + recharts: 2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + remark-gfm: 4.0.1 sonner: 2.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) tailwind-merge: 3.4.0 + optionalDependencies: + '@react-three/drei': 10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(three@0.182.0))(@types/react@19.2.7)(@types/three@0.182.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(three@0.182.0) + '@react-three/fiber': 9.5.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(three@0.182.0) three: 0.182.0 transitivePeerDependencies: - '@types/react' - '@types/react-dom' + - '@types/three' + - expo + - expo-asset + - expo-file-system + - expo-gl + - immer + - react-native + - supports-color + + '@embeddr/zen-shell@0.2.0-alpha.3(@types/react@19.2.7)(@types/three@0.182.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1))': + dependencies: + '@embeddr/client-typescript': 0.2.0-alpha.3 + '@embeddr/react-ui': 0.2.0-alpha.2(@types/react@19.2.7)(@types/three@0.182.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@tanstack/react-query': 5.100.6(react@19.2.1) + lucide-react: 0.544.0(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + zustand: 5.0.9(@types/react@19.2.7)(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - '@types/three' + - expo + - expo-asset + - expo-file-system + - expo-gl + - immer + - react-native + - supports-color + - use-sync-external-store '@emnapi/core@1.7.1': dependencies: @@ -4563,6 +4760,8 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@fontsource-variable/jetbrains-mono@5.2.8': {} + '@hono/node-server@1.19.7(hono@4.11.3)': dependencies: hono: 4.11.3 @@ -4580,25 +4779,31 @@ snapshots: '@inquirer/ansi@1.0.2': {} - '@inquirer/confirm@5.1.21': + '@inquirer/confirm@5.1.21(@types/node@22.19.17)': dependencies: - '@inquirer/core': 10.3.2 - '@inquirer/type': 3.0.10 + '@inquirer/core': 10.3.2(@types/node@22.19.17) + '@inquirer/type': 3.0.10(@types/node@22.19.17) + optionalDependencies: + '@types/node': 22.19.17 - '@inquirer/core@10.3.2': + '@inquirer/core@10.3.2(@types/node@22.19.17)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10 + '@inquirer/type': 3.0.10(@types/node@22.19.17) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.19.17 '@inquirer/figures@1.0.15': {} - '@inquirer/type@3.0.10': {} + '@inquirer/type@3.0.10(@types/node@22.19.17)': + optionalDependencies: + '@types/node': 22.19.17 '@intlify/core-base@9.14.5': dependencies: @@ -4637,7 +4842,38 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@mediapipe/tasks-vision@0.10.17': {} + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + acorn: 8.15.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.15.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@mediapipe/tasks-vision@0.10.17': + optional: true '@modelcontextprotocol/sdk@1.25.1(hono@4.11.3)(zod@3.25.76)': dependencies: @@ -4665,6 +4901,7 @@ snapshots: dependencies: promise-worker-transferable: 1.0.4 three: 0.182.0 + optional: true '@mswjs/interceptors@0.40.0': dependencies: @@ -4711,8 +4948,6 @@ snapshots: '@open-draft/until@2.1.0': {} - '@polka/url@1.0.0-next.29': {} - '@primeuix/styled@0.7.4': dependencies: '@primeuix/utils': 0.6.3 @@ -4740,14 +4975,6 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-accessible-icon@1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/react-visually-hidden': 1.2.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - '@radix-ui/react-accordion@1.2.12(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -4764,19 +4991,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - '@radix-ui/react-alert-dialog@1.1.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-dialog': 1.1.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - '@radix-ui/react-arrow@1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -4785,14 +4999,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - '@radix-ui/react-aspect-ratio@1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - '@radix-ui/react-aspect-ratio@1.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/react-primitive': 2.1.4(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -4801,18 +5007,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - '@radix-ui/react-avatar@1.1.10(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - '@radix-ui/react-avatar@1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/react-context': 1.1.3(@types/react@19.2.7)(react@19.2.1) @@ -4966,35 +5160,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - '@radix-ui/react-form@0.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-label': 2.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - - '@radix-ui/react-hover-card@1.1.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-popper': 1.2.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-portal': 1.1.9(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - '@radix-ui/react-id@1.1.1(@types/react@19.2.7)(react@19.2.1)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) @@ -5002,14 +5167,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - '@radix-ui/react-label@2.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - '@radix-ui/react-label@2.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/react-primitive': 2.1.4(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -5060,61 +5217,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - '@radix-ui/react-navigation-menu@1.2.14(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - - '@radix-ui/react-one-time-password-field@0.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - - '@radix-ui/react-password-toggle-field@0.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.7)(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - '@radix-ui/react-popover@1.1.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -5188,15 +5290,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - '@radix-ui/react-progress@1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - '@radix-ui/react-progress@1.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/react-context': 1.1.3(@types/react@19.2.7)(react@19.2.1) @@ -5206,23 +5299,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - '@radix-ui/react-radio-group@1.3.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - '@radix-ui/react-roving-focus@1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -5283,14 +5359,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - '@radix-ui/react-separator@1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - '@radix-ui/react-separator@1.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/react-primitive': 2.1.4(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -5360,63 +5428,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 - '@radix-ui/react-toast@1.2.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-portal': 1.1.9(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - - '@radix-ui/react-toggle-group@1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-toggle': 1.1.10(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - - '@radix-ui/react-toggle@1.1.10(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - - '@radix-ui/react-toolbar@1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-separator': 1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-toggle-group': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - '@radix-ui/react-tooltip@1.2.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -5505,6 +5516,14 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + '@radix-ui/react-visually-hidden@1.2.4(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.7 + '@radix-ui/rect@1.1.1': {} '@react-three/drei@10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(three@0.182.0))(@types/react@19.2.7)(@types/three@0.182.0)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(three@0.182.0)': @@ -5539,6 +5558,7 @@ snapshots: - '@types/react' - '@types/three' - immer + optional: true '@react-three/fiber@9.5.0(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(three@0.182.0)': dependencies: @@ -5559,17 +5579,10 @@ snapshots: transitivePeerDependencies: - '@types/react' - immer + optional: true '@rolldown/pluginutils@1.0.0-beta.47': {} - '@rollup/pluginutils@5.3.0(rollup@4.53.3)': - dependencies: - '@types/estree': 1.0.8 - estree-walker: 2.0.2 - picomatch: 4.0.3 - optionalDependencies: - rollup: 4.53.3 - '@rollup/rollup-android-arm-eabi@4.53.3': optional: true @@ -5652,10 +5665,10 @@ snapshots: '@tabler/icons-react@3.36.1(react@19.2.1)': dependencies: - '@tabler/icons': 3.36.1 + '@tabler/icons': 3.41.1 react: 19.2.1 - '@tabler/icons@3.36.1': {} + '@tabler/icons@3.41.1': {} '@tailwindcss/node@4.1.17': dependencies: @@ -5718,12 +5731,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 - '@tailwindcss/vite@4.1.17(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))': + '@tailwindcss/vite@4.1.17(vite@6.4.1(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: '@tailwindcss/node': 4.1.17 '@tailwindcss/oxide': 4.1.17 tailwindcss: 4.1.17 - vite: 6.4.1(jiti@2.6.1)(lightningcss@1.30.2) + vite: 6.4.1(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.30.2) '@tanstack/eslint-config@0.3.4(@typescript-eslint/utils@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: @@ -5741,13 +5754,21 @@ snapshots: - supports-color - typescript - '@ts-morph/common@0.27.0': + '@tanstack/query-core@5.100.6': {} + + '@tanstack/react-query@5.100.6(react@19.2.1)': + dependencies: + '@tanstack/query-core': 5.100.6 + react: 19.2.1 + + '@ts-morph/common@0.27.0': dependencies: fast-glob: 3.3.3 minimatch: 10.1.1 path-browserify: 1.0.1 - '@tweenjs/tween.js@23.1.3': {} + '@tweenjs/tween.js@23.1.3': + optional: true '@tybys/wasm-util@0.10.1': dependencies: @@ -5775,23 +5796,76 @@ snapshots: dependencies: '@babel/types': 7.28.5 - '@types/draco3d@1.4.10': {} + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + + '@types/draco3d@1.4.10': + optional: true + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 '@types/estree@1.0.8': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/json-schema@7.0.15': {} - '@types/offscreencanvas@2019.7.3': {} + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + + '@types/node@22.19.17': + dependencies: + undici-types: 6.21.0 + + '@types/offscreencanvas@2019.7.3': + optional: true '@types/react-reconciler@0.28.9(@types/react@19.2.7)': dependencies: '@types/react': 19.2.7 + optional: true '@types/react@19.2.7': dependencies: csstype: 3.2.3 + optional: true - '@types/stats.js@0.17.4': {} + '@types/stats.js@0.17.4': + optional: true '@types/statuses@2.0.6': {} @@ -5804,8 +5878,14 @@ snapshots: '@webgpu/types': 0.1.68 fflate: 0.8.2 meshoptimizer: 0.22.0 + optional: true - '@types/webxr@0.5.24': {} + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@types/webxr@0.5.24': + optional: true '@typescript-eslint/eslint-plugin@8.50.1(@typescript-eslint/parser@8.50.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: @@ -5898,6 +5978,8 @@ snapshots: '@typescript-eslint/types': 8.50.1 eslint-visitor-keys: 4.2.1 + '@ungap/structured-clone@1.3.0': {} + '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -5957,14 +6039,16 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@use-gesture/core@10.3.1': {} + '@use-gesture/core@10.3.1': + optional: true '@use-gesture/react@10.3.1(react@19.2.1)': dependencies: '@use-gesture/core': 10.3.1 react: 19.2.1 + optional: true - '@vitejs/plugin-react@5.1.1(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))': + '@vitejs/plugin-react@5.1.1(vite@6.4.1(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.30.2))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -5972,41 +6056,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.47 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 6.4.1(jiti@2.6.1)(lightningcss@1.30.2) - transitivePeerDependencies: - - supports-color - - '@vitejs/plugin-vue@5.2.4(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.25(typescript@5.9.3))': - dependencies: - vite: 6.4.1(jiti@2.6.1)(lightningcss@1.30.2) - vue: 3.5.25(typescript@5.9.3) - - '@vue/babel-helper-vue-transform-on@1.5.0': {} - - '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.28.5)': - dependencies: - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 - '@vue/babel-helper-vue-transform-on': 1.5.0 - '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.28.5) - '@vue/shared': 3.5.25 - optionalDependencies: - '@babel/core': 7.28.5 - transitivePeerDependencies: - - supports-color - - '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.28.5)': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/parser': 7.28.5 - '@vue/compiler-sfc': 3.5.25 + vite: 6.4.1(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.30.2) transitivePeerDependencies: - supports-color @@ -6042,32 +6092,6 @@ snapshots: '@vue/devtools-api@6.6.4': {} - '@vue/devtools-core@7.7.9(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.25(typescript@5.9.3))': - dependencies: - '@vue/devtools-kit': 7.7.9 - '@vue/devtools-shared': 7.7.9 - mitt: 3.0.1 - nanoid: 5.1.6 - pathe: 2.0.3 - vite-hot-client: 2.1.0(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2)) - vue: 3.5.25(typescript@5.9.3) - transitivePeerDependencies: - - vite - - '@vue/devtools-kit@7.7.9': - dependencies: - '@vue/devtools-shared': 7.7.9 - birpc: 2.8.0 - hookable: 5.5.3 - mitt: 3.0.1 - perfect-debounce: 1.0.0 - speakingurl: 14.0.1 - superjson: 2.2.5 - - '@vue/devtools-shared@7.7.9': - dependencies: - rfdc: 1.4.1 - '@vue/reactivity@3.5.25': dependencies: '@vue/shared': 3.5.25 @@ -6092,7 +6116,8 @@ snapshots: '@vue/shared@3.5.25': {} - '@webgpu/types@0.1.68': {} + '@webgpu/types@0.1.68': + optional: true accepts@2.0.0: dependencies: @@ -6145,17 +6170,21 @@ snapshots: dependencies: tslib: 2.8.1 + astring@1.9.0: {} + + bail@2.0.2: {} + balanced-match@1.0.2: {} - base64-js@1.5.1: {} + base64-js@1.5.1: + optional: true baseline-browser-mapping@2.8.31: {} bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 - - birpc@2.8.0: {} + optional: true body-parser@2.2.1: dependencies: @@ -6196,6 +6225,7 @@ snapshots: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + optional: true bundle-name@4.1.0: dependencies: @@ -6218,9 +6248,12 @@ snapshots: camera-controls@3.1.2(three@0.182.0): dependencies: three: 0.182.0 + optional: true caniuse-lite@1.0.30001757: {} + ccount@2.0.1: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -6228,6 +6261,14 @@ snapshots: chalk@5.6.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -6250,12 +6291,16 @@ snapshots: code-block-writer@13.0.3: {} + collapse-white-space@2.1.0: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} + comma-separated-tokens@2.0.3: {} + commander@11.1.0: {} commander@14.0.2: {} @@ -6276,10 +6321,6 @@ snapshots: cookie@1.1.1: {} - copy-anything@4.0.5: - dependencies: - is-what: 5.5.0 - cors@2.8.5: dependencies: object-assign: 4.1.1 @@ -6297,6 +6338,7 @@ snapshots: cross-env@7.0.3: dependencies: cross-spawn: 7.0.6 + optional: true cross-spawn@7.0.6: dependencies: @@ -6308,12 +6350,56 @@ snapshots: csstype@3.2.3: {} + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + data-uri-to-buffer@4.0.1: {} debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js-light@2.5.1: {} + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + dedent@1.7.1: {} deep-is@0.1.4: {} @@ -6331,19 +6417,32 @@ snapshots: depd@2.0.0: {} + dequal@2.0.3: {} + detect-gpu@5.0.70: dependencies: webgl-constants: 1.1.1 + optional: true detect-libc@2.1.2: {} detect-node-es@1.1.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + diff@8.0.2: {} + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.28.4 + csstype: 3.2.3 + dotenv@17.2.3: {} - draco3d@1.5.7: {} + draco3d@1.5.7: + optional: true dunder-proto@1.0.1: dependencies: @@ -6381,8 +6480,6 @@ snapshots: dependencies: is-arrayish: 0.2.1 - error-stack-parser-es@0.1.5: {} - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -6391,6 +6488,20 @@ snapshots: dependencies: es-errors: 1.3.0 + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.15.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -6426,6 +6537,8 @@ snapshots: escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-compat-utils@0.5.1(eslint@9.39.2(jiti@2.6.1)): dependencies: eslint: 9.39.2(jiti@2.6.1) @@ -6545,12 +6658,47 @@ snapshots: estraverse@5.3.0: {} + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + estree-walker@2.0.2: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} etag@1.8.1: {} + eventemitter3@4.0.7: {} + eventsource-parser@3.0.6: {} eventsource@3.0.7: @@ -6621,8 +6769,12 @@ snapshots: transitivePeerDependencies: - supports-color + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} + fast-equals@5.4.0: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6650,9 +6802,11 @@ snapshots: node-domexception: 1.0.0 web-streams-polyfill: 3.3.3 - fflate@0.6.10: {} + fflate@0.6.10: + optional: true - fflate@0.8.2: {} + fflate@0.8.2: + optional: true figures@6.1.0: dependencies: @@ -6767,7 +6921,8 @@ snapshots: globrex@0.1.2: {} - glsl-noise@0.0.0: {} + glsl-noise@0.0.0: + optional: true gopd@1.2.0: {} @@ -6783,14 +6938,58 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + headers-polyfill@4.0.3: {} - hls.js@1.6.15: {} + hls.js@1.6.15: + optional: true hono@4.11.3: {} - hookable@5.5.3: {} - http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -6814,13 +7013,15 @@ snapshots: dependencies: safer-buffer: 2.1.2 - ieee754@1.2.1: {} + ieee754@1.2.1: + optional: true ignore@5.3.2: {} ignore@7.0.5: {} - immediate@3.0.6: {} + immediate@3.0.6: + optional: true import-fresh@3.3.1: dependencies: @@ -6831,10 +7032,23 @@ snapshots: inherits@2.0.4: {} + inline-style-parser@0.2.7: {} + + internmap@2.0.3: {} + ipaddr.js@1.9.1: {} + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-arrayish@0.2.1: {} + is-decimal@2.0.1: {} + is-docker@3.0.0: {} is-extglob@2.1.1: {} @@ -6845,6 +7059,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-in-ssh@1.0.0: {} is-inside-container@1.0.0: @@ -6861,7 +7077,8 @@ snapshots: is-plain-obj@4.1.0: {} - is-promise@2.2.2: {} + is-promise@2.2.2: + optional: true is-promise@4.0.0: {} @@ -6875,8 +7092,6 @@ snapshots: is-unicode-supported@2.1.0: {} - is-what@5.5.0: {} - is-wsl@3.1.0: dependencies: is-inside-container: 1.0.0 @@ -6891,6 +7106,7 @@ snapshots: react: 19.2.1 transitivePeerDependencies: - '@types/react' + optional: true jiti@2.6.1: {} @@ -6932,8 +7148,6 @@ snapshots: kleur@4.1.5: {} - kolorist@1.8.0: {} - levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -6942,6 +7156,7 @@ snapshots: lie@3.3.0: dependencies: immediate: 3.0.6 + optional: true lightningcss-android-arm64@1.30.2: optional: true @@ -7000,15 +7215,27 @@ snapshots: lodash.merge@4.6.2: {} + lodash@4.18.1: {} + log-symbols@6.0.0: dependencies: chalk: 5.6.2 is-unicode-supported: 1.3.0 + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lru-cache@5.1.1: dependencies: yallist: 3.1.1 + lucide-react@0.544.0(react@19.2.1): + dependencies: + react: 19.2.1 + lucide-react@0.556.0(react@19.2.1): dependencies: react: 19.2.1 @@ -7021,13 +7248,181 @@ snapshots: dependencies: '@types/three': 0.182.0 three: 0.182.0 + optional: true magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + media-typer@1.1.0: {} merge-descriptors@2.0.0: {} @@ -7039,8 +7434,274 @@ snapshots: meshline@3.3.1(three@0.182.0): dependencies: three: 0.182.0 + optional: true + + meshoptimizer@0.22.0: + optional: true + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} - meshoptimizer@0.22.0: {} + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color micromatch@4.0.8: dependencies: @@ -7071,15 +7732,11 @@ snapshots: minimist@1.2.8: {} - mitt@3.0.1: {} - - mrmime@2.0.1: {} - ms@2.1.3: {} - msw@2.12.7(typescript@5.9.3): + msw@2.12.7(@types/node@22.19.17)(typescript@5.9.3): dependencies: - '@inquirer/confirm': 5.1.21 + '@inquirer/confirm': 5.1.21(@types/node@22.19.17) '@mswjs/interceptors': 0.40.0 '@open-draft/deferred-promise': 2.2.0 '@types/statuses': 2.0.6 @@ -7106,8 +7763,6 @@ snapshots: nanoid@3.3.11: {} - nanoid@5.1.6: {} - napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -7160,13 +7815,6 @@ snapshots: dependencies: mimic-function: 5.0.1 - open@10.2.0: - dependencies: - default-browser: 5.4.0 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - wsl-utils: 0.1.0 - open@11.0.0: dependencies: default-browser: 5.4.0 @@ -7213,6 +7861,16 @@ snapshots: dependencies: callsites: 3.1.0 + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.27.1 @@ -7236,10 +7894,6 @@ snapshots: path-to-regexp@8.3.0: {} - pathe@2.0.3: {} - - perfect-debounce@1.0.0: {} - picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -7259,12 +7913,15 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - potpack@1.0.2: {} + potpack@1.0.2: + optional: true powershell-utils@0.1.0: {} prelude-ls@1.2.1: {} + prettier@3.8.3: {} + pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 @@ -7283,12 +7940,21 @@ snapshots: dependencies: is-promise: 2.2.2 lie: 3.3.0 + optional: true prompts@2.4.2: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-information@7.1.0: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -7302,68 +7968,6 @@ snapshots: queue-microtask@1.2.3: {} - radix-ui@1.4.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1): - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-accessible-icon': 1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-accordion': 1.2.12(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-alert-dialog': 1.1.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-arrow': 1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-aspect-ratio': 1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-avatar': 1.1.10(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-checkbox': 1.3.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-collapsible': 1.1.12(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-collection': 1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-context-menu': 2.2.16(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-dialog': 1.1.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-dropdown-menu': 2.1.16(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-focus-scope': 1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-form': 0.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-hover-card': 1.1.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-label': 2.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-menu': 2.1.16(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-menubar': 1.1.16(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-navigation-menu': 1.2.14(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-one-time-password-field': 0.1.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-password-toggle-field': 0.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-popover': 1.1.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-popper': 1.2.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-portal': 1.1.9(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-presence': 1.1.5(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-progress': 1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-radio-group': 1.3.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-roving-focus': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-scroll-area': 1.2.10(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-select': 2.2.6(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-separator': 1.1.7(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-slider': 1.3.6(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-switch': 1.2.6(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-tabs': 1.1.13(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-toast': 1.2.15(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-toggle': 1.1.10(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-toggle-group': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-toolbar': 1.1.11(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-tooltip': 1.2.8(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.7)(react@19.2.1) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react@19.2.7)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) - react: 19.2.1 - react-dom: 19.2.1(react@19.2.1) - optionalDependencies: - '@types/react': 19.2.7 - range-parser@1.2.1: {} raw-body@3.0.2: @@ -7378,6 +7982,10 @@ snapshots: react: 19.2.1 scheduler: 0.27.0 + react-is@16.13.1: {} + + react-is@18.3.1: {} + react-refresh@0.18.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.7)(react@19.2.1): @@ -7404,6 +8012,14 @@ snapshots: react: 19.2.1 react-dom: 19.2.1(react@19.2.1) + react-smooth@4.0.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + fast-equals: 5.4.0 + prop-types: 15.8.1 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-transition-group: 4.4.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.2.1): dependencies: get-nonce: 1.0.1 @@ -7412,11 +8028,21 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 + react-transition-group@4.4.5(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + '@babel/runtime': 7.28.4 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-use-measure@2.1.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: react: 19.2.1 optionalDependencies: react-dom: 19.2.1(react@19.2.1) + optional: true react@19.2.1: {} @@ -7428,6 +8054,101 @@ snapshots: tiny-invariant: 1.3.3 tslib: 2.8.1 + recharts-scale@0.4.5: + dependencies: + decimal.js-light: 2.5.1 + + recharts@2.15.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + clsx: 2.1.1 + eventemitter3: 4.0.7 + lodash: 4.18.1 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-is: 18.3.1 + react-smooth: 4.0.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + recharts-scale: 0.4.5 + tiny-invariant: 1.3.3 + victory-vendor: 36.9.2 + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.8 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -7445,8 +8166,6 @@ snapshots: reusify@1.1.0: {} - rfdc@1.4.1: {} - rollup@4.53.3: dependencies: '@types/estree': 1.0.8 @@ -7526,7 +8245,7 @@ snapshots: setprototypeof@1.2.0: {} - shadcn@3.6.2(hono@4.11.3)(typescript@5.9.3): + shadcn@3.6.2(@types/node@22.19.17)(hono@4.11.3)(typescript@5.9.3): dependencies: '@antfu/ni': 25.0.0 '@babel/core': 7.28.5 @@ -7547,7 +8266,7 @@ snapshots: fuzzysort: 3.1.0 https-proxy-agent: 7.0.6 kleur: 4.1.5 - msw: 2.12.7(typescript@5.9.3) + msw: 2.12.7(@types/node@22.19.17)(typescript@5.9.3) node-fetch: 3.3.2 open: 11.0.0 ora: 8.2.0 @@ -7606,12 +8325,6 @@ snapshots: signal-exit@4.1.0: {} - sirv@3.0.2: - dependencies: - '@polka/url': 1.0.0-next.29 - mrmime: 2.0.1 - totalist: 3.0.1 - sisteransi@1.0.5: {} sonner@2.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1): @@ -7623,7 +8336,9 @@ snapshots: source-map@0.6.1: {} - speakingurl@14.0.1: {} + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} stable-hash-x@0.2.0: {} @@ -7631,8 +8346,10 @@ snapshots: dependencies: '@types/three': 0.182.0 three: 0.182.0 + optional: true - stats.js@0.17.0: {} + stats.js@0.17.0: + optional: true statuses@2.0.2: {} @@ -7652,6 +8369,11 @@ snapshots: get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + stringify-object@5.0.0: dependencies: get-own-enumerable-keys: 1.0.0 @@ -7674,9 +8396,13 @@ snapshots: strip-json-comments@3.1.1: {} - superjson@2.2.5: + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: dependencies: - copy-anything: 4.0.5 + inline-style-parser: 0.2.7 supports-color@7.2.0: dependencies: @@ -7685,6 +8411,7 @@ snapshots: suspend-react@0.1.3(react@19.2.1): dependencies: react: 19.2.1 + optional: true tagged-tag@1.0.0: {} @@ -7697,6 +8424,7 @@ snapshots: three-mesh-bvh@0.8.3(three@0.182.0): dependencies: three: 0.182.0 + optional: true three-stdlib@2.36.1(three@0.182.0): dependencies: @@ -7707,6 +8435,7 @@ snapshots: fflate: 0.6.10 potpack: 1.0.2 three: 0.182.0 + optional: true three@0.182.0: {} @@ -7731,12 +8460,12 @@ snapshots: toidentifier@1.0.1: {} - totalist@3.0.1: {} - tough-cookie@6.0.0: dependencies: tldts: 7.0.19 + trim-lines@3.0.1: {} + troika-three-text@0.52.4(three@0.182.0): dependencies: bidi-js: 1.0.3 @@ -7744,12 +8473,17 @@ snapshots: troika-three-utils: 0.52.4(three@0.182.0) troika-worker-utils: 0.52.0 webgl-sdf-generator: 1.1.1 + optional: true troika-three-utils@0.52.4(three@0.182.0): dependencies: three: 0.182.0 + optional: true - troika-worker-utils@0.52.0: {} + troika-worker-utils@0.52.0: + optional: true + + trough@2.2.0: {} ts-api-utils@2.3.0(typescript@5.9.3): dependencies: @@ -7780,6 +8514,7 @@ snapshots: - '@types/react' - immer - react + optional: true tw-animate-css@1.4.0: {} @@ -7810,8 +8545,47 @@ snapshots: typescript@5.9.3: {} + undici-types@6.21.0: {} + unicorn-magic@0.3.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + universalify@2.0.1: {} unpipe@1.0.0: {} @@ -7873,62 +8647,39 @@ snapshots: util-deprecate@1.0.2: {} - utility-types@3.11.0: {} + utility-types@3.11.0: + optional: true vary@1.1.2: {} - vite-hot-client@2.1.0(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2)): + vfile-message@4.0.3: dependencies: - vite: 6.4.1(jiti@2.6.1)(lightningcss@1.30.2) + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 - vite-plugin-inspect@0.8.9(rollup@4.53.3)(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2)): + vfile@6.0.3: dependencies: - '@antfu/utils': 0.7.10 - '@rollup/pluginutils': 5.3.0(rollup@4.53.3) - debug: 4.4.3 - error-stack-parser-es: 0.1.5 - fs-extra: 11.3.2 - open: 10.2.0 - perfect-debounce: 1.0.0 - picocolors: 1.1.1 - sirv: 3.0.2 - vite: 6.4.1(jiti@2.6.1)(lightningcss@1.30.2) - transitivePeerDependencies: - - rollup - - supports-color + '@types/unist': 3.0.3 + vfile-message: 4.0.3 - vite-plugin-vue-devtools@7.7.9(rollup@4.53.3)(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.25(typescript@5.9.3)): + victory-vendor@36.9.2: dependencies: - '@vue/devtools-core': 7.7.9(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.25(typescript@5.9.3)) - '@vue/devtools-kit': 7.7.9 - '@vue/devtools-shared': 7.7.9 - execa: 9.6.0 - sirv: 3.0.2 - vite: 6.4.1(jiti@2.6.1)(lightningcss@1.30.2) - vite-plugin-inspect: 0.8.9(rollup@4.53.3)(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2)) - vite-plugin-vue-inspector: 5.3.2(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2)) - transitivePeerDependencies: - - '@nuxt/kit' - - rollup - - supports-color - - vue + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 - vite-plugin-vue-inspector@5.3.2(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2)): - dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) - '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.5) - '@vue/compiler-dom': 3.5.25 - kolorist: 1.8.0 - magic-string: 0.30.21 - vite: 6.4.1(jiti@2.6.1)(lightningcss@1.30.2) - transitivePeerDependencies: - - supports-color - - vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2): + vite@6.4.1(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.30.2): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -7937,6 +8688,7 @@ snapshots: rollup: 4.53.3 tinyglobby: 0.2.15 optionalDependencies: + '@types/node': 22.19.17 fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.30.2 @@ -7972,9 +8724,11 @@ snapshots: web-streams-polyfill@3.3.3: {} - webgl-constants@1.1.1: {} + webgl-constants@1.1.1: + optional: true - webgl-sdf-generator@1.1.1: {} + webgl-sdf-generator@1.1.1: + optional: true which@2.0.2: dependencies: @@ -8000,10 +8754,6 @@ snapshots: wrappy@1.0.2: {} - wsl-utils@0.1.0: - dependencies: - is-wsl: 3.1.0 - wsl-utils@0.3.1: dependencies: is-wsl: 3.1.0 @@ -8043,9 +8793,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.7 react: 19.2.1 + optional: true zustand@5.0.9(@types/react@19.2.7)(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)): optionalDependencies: '@types/react': 19.2.7 react: 19.2.1 use-sync-external-store: 1.6.0(react@19.2.1) + + zwitch@2.0.4: {} diff --git a/prettier.config.cjs b/prettier.config.cjs new file mode 100644 index 0000000..40f7389 --- /dev/null +++ b/prettier.config.cjs @@ -0,0 +1,8 @@ +/** @type {import('prettier').Config} */ +module.exports = { + semi: true, + singleQuote: false, + trailingComma: "all", + printWidth: 100, + tabWidth: 2, +}; diff --git a/pyproject.toml b/pyproject.toml index 8b2934a..fa95514 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,14 @@ -# pyproject.toml [project] name = "embeddr-extension" description = "Embeddr ComfyUI Extension" version = "1.2.1" license = { file = "LICENSE.md" } -dependencies = [] -requires-python = ">=3.8" - +requires-python = ">=3.10" +dependencies = [ + # Most runtime deps (torch, numpy, PIL, aiohttp) are provided by the + # ComfyUI host environment. Only declare what we use beyond that. + "requests>=2.31.0", +] [project.urls] Repository = "https://github.com/embeddr-net/embeddr-comfyui" @@ -16,3 +18,38 @@ PublisherId = "nynxz" DisplayName = "Embeddr ComfyUI Extension" Icon = "https://avatars.githubusercontent.com/u/251817691" includes = ["js"] + +[dependency-groups] +dev = ["ruff>=0.7.0"] + +[tool.ruff] +target-version = "py310" +line-length = 100 +extend-exclude = ["js", ".venv", "node_modules"] + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear + "UP", # pyupgrade + "SIM", # flake8-simplify + "C4", # flake8-comprehensions + "RUF", # ruff-specific rules +] +ignore = [ + "E501", # line-too-long (formatter handles wrapping) + "B008", # function-call-in-default-argument (common pattern in ComfyUI nodes) + "SIM108", # use-ternary (often less readable) +] + +[tool.ruff.lint.per-file-ignores] +# ComfyUI nodes follow a specific class naming convention; relax rules +# that fight that convention. +"nodes/*.py" = ["N801", "N802", "N803", "N806"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" diff --git a/tsconfig.json b/tsconfig.json index e2eef02..43069ff 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,15 +3,19 @@ "target": "ES2022", "useDefineForClassFields": true, "jsx": "react-jsx", - "module": "ESNext", + "moduleResolution": "bundler", "resolveJsonModule": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "isolatedModules": true, "paths": { "@components/*": ["./ui/components/*"], "@hooks/*": ["./ui/hooks/*"], "@types": ["./ui/types.ts"] - }, - "typeRoots": ["src/types", "node_modules/@types"] + } }, - "exclude": ["node_modules", "dist", "js", "**/*.test.ts", "**/*.test.tsx"] + "include": ["ui"], + "exclude": ["node_modules", "js", "**/*.test.ts", "**/*.test.tsx"] } diff --git a/ui/components/GlobalDialog.tsx b/ui/components/GlobalDialog.tsx index 68fd9fb..302d916 100644 --- a/ui/components/GlobalDialog.tsx +++ b/ui/components/GlobalDialog.tsx @@ -1,30 +1,43 @@ import React, { useEffect, useState } from "react"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@embeddr/react-ui/components/dialog"; -import { ExploreTab } from "./tabs/ExploreTab"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@embeddr/react-ui/components/ui"; +import { useImageDialog } from "@embeddr/react-ui"; import { useEmbeddrApi } from "../hooks/useEmbeddrApi"; -// @ts-ignore import { app } from "../../../scripts/app.js"; +import { ExploreTab } from "./tabs/ExploreTab"; +import { CollectionSelector } from "./selectors/CollectionSelector"; +// @ts-ignore + +type DialogMode = "image" | "collection"; export function GlobalDialog() { const [isOpen, setIsOpen] = useState(false); const [targetNodeId, setTargetNodeId] = useState(null); + const [mode, setMode] = useState("image"); const api = useEmbeddrApi(); + const { setApiKey: setDialogApiKey } = useImageDialog(); + + useEffect(() => { + if (setDialogApiKey && api.apiKey) { + setDialogApiKey(api.apiKey); + } + }, [api.apiKey, setDialogApiKey]); useEffect(() => { const handleOpen = (e: Event) => { const customEvent = e as CustomEvent; setTargetNodeId(customEvent.detail.nodeId); + const requestedMode = customEvent.detail.mode || "image"; + setMode(requestedMode); setIsOpen(true); // Trigger a fetch if needed if (api.configLoaded) { - api.fetchImages(true); + if (requestedMode === "collection") { + api.fetchCollections(); + } else { + api.fetchImages(true); + } } }; @@ -36,7 +49,7 @@ export function GlobalDialog() { useEffect(() => { const applyTheme = () => { const portals = document.querySelectorAll( - "[data-radix-portal], [data-slot='dialog-content'], [data-slot='dialog-overlay'], [data-slot='select-content'], [data-slot='select-viewport'], [data-slot='popover-content'], [data-slot='dropdown-menu-content']" + "[data-radix-portal], [data-slot='dialog-content'], [data-slot='dialog-overlay'], [data-slot='select-content'], [data-slot='select-viewport'], [data-slot='popover-content'], [data-slot='dropdown-menu-content']", ); const isDark = api.theme === "dark"; @@ -45,6 +58,12 @@ export function GlobalDialog() { if (!portal.classList.contains("tailwind")) { portal.classList.add("tailwind"); } + if (!portal.classList.contains("font-sans")) { + portal.classList.add("font-sans"); + } + if (!portal.classList.contains("embeddr-theme-root")) { + portal.classList.add("embeddr-theme-root"); + } if (isDark) { portal.classList.add("dark"); } else { @@ -69,31 +88,99 @@ export function GlobalDialog() { return () => observer.disconnect(); }, [api.theme]); - const handleSelect = (image: any) => { + const handleSelect = (item: any) => { if (targetNodeId !== null) { const node = app.graph.getNodeById(targetNodeId); if (node) { - // Assuming the first widget is the image_id input - // We should check the widget name or type - const idWidget = node.widgets?.find((w: any) => w.name === "image_id"); - if (idWidget) { - idWidget.value = image.id.toString(); - if (idWidget.callback) { - idWidget.callback(idWidget.value); + if (mode === "collection") { + // Handle Collection Selection + + // 1. Try "collection_ids" -> Set ID + const idWidget = node.widgets?.find((w: any) => w.name === "collection_ids"); + if (idWidget) { + idWidget.value = item.id.toString(); + if (idWidget.callback) { + idWidget.callback(idWidget.value); + } + } + + // 2. Try "collection_id" -> Set ID (for FindCollection node V2) + console.log( + "[Embeddr] Debug: Listing node widgets:", + node.widgets?.map((w: any) => w.name), + ); + + const colIdWidget = node.widgets?.find((w: any) => w.name === "collection_id"); + + let idSet = false; + if (colIdWidget && item.id) { + console.log("[Embeddr] Setting collection_id to", item.id); + colIdWidget.value = item.id.toString(); + if (colIdWidget.callback) { + colIdWidget.callback(colIdWidget.value); + } + idSet = true; + } else { + console.warn("[Embeddr] collection_id widget not found on node or invalid item.id!", { + widgetFound: !!colIdWidget, + itemId: item.id, + }); + } + + // 3. Update Info Widget (Friendly display) + const infoWidget = node.widgets?.find((w: any) => w.name === "Info"); + if (infoWidget) { + const count = item.file_count !== undefined ? item.file_count : "?"; + infoWidget.value = `${ + item.label || item.name + } (${count} items) [ID: ${item.id?.substring(0, 8)}...]`; + } + + // 4. Update or Clear Name Widget + // If we successfully set the ID, clear the name widget to avoid confusion + // and ensure the backend uses the ID. + const nameWidget = node.widgets?.find((w: any) => w.name === "collection_name"); + if (nameWidget) { + if (idSet) { + // Clear name to prioritize ID match and avoid ambiguity + console.log("[Embeddr] Clearing collection_name widget to prioritize ID"); + nameWidget.value = ""; + } else { + nameWidget.value = item.label || item.name; + } + + if (nameWidget.callback) { + nameWidget.callback(nameWidget.value); + } + } + + // Force UI update + if (app.graph) { + app.graph.setDirtyCanvas(true, true); } } else { - // Fallback to first widget if name doesn't match - if (node.widgets && node.widgets[0]) { - node.widgets[0].value = image.id.toString(); + // Handle Image Selection + // Check for artifact_id (V2) or image_id (V1) + const idWidget = node.widgets?.find( + (w: any) => w.name === "artifact_id" || w.name === "image_id", + ); + if (idWidget) { + idWidget.value = item.id.toString(); + if (idWidget.callback) { + idWidget.callback(idWidget.value); + } + } else { + // Fallback to first widget if name doesn't match + if (node.widgets && node.widgets[0]) { + node.widgets[0].value = item.id.toString(); + } } - } - // Also update image_url if it exists (for preview/compatibility) - const urlWidget = node.widgets?.find( - (w: any) => w.name === "image_url" - ); - if (urlWidget) { - urlWidget.value = image.image_url; + // Also update image_url if it exists (for preview/compatibility) + const urlWidget = node.widgets?.find((w: any) => w.name === "image_url"); + if (urlWidget) { + urlWidget.value = item.image_url; + } } app.graph.setDirtyCanvas(true, true); @@ -106,14 +193,26 @@ export function GlobalDialog() { - Select Image + {mode === "collection" ? "Select Collection" : "Select Image"}
- + {mode === "collection" ? ( + + ) : ( + + )}
diff --git a/ui/components/ZenShell.tsx b/ui/components/ZenShell.tsx new file mode 100644 index 0000000..286d660 --- /dev/null +++ b/ui/components/ZenShell.tsx @@ -0,0 +1,1891 @@ +import React, { useCallback, useDeferredValue, useEffect, useMemo, useState } from "react"; +import { useEmbeddrAPI } from "@embeddr/react-ui/context"; +import { + CoreUIEventBridge, + DynamicPluginComponent, + EmbeddrProvider, + PluginErrorBoundary, + ZenDraggablePanel, + ZenPanelManagerCore, + ZenWebSocketProvider, + globalEventBus, + loadExternalPlugins, + usePluginRegistry, + useZenWindowStore, +} from "@embeddr/zen-shell"; +import { + AppWindow, + ExternalLink, + LayoutTemplate, + Minimize2, + Play, + RefreshCw, + RotateCcw, + Search, + X, +} from "lucide-react"; +import { Button } from "@embeddr/react-ui/components/ui"; +import { Badge, Input, ScrollArea, Separator, cn } from "@embeddr/react-ui"; +import { useEmbeddrApi } from "../hooks/useEmbeddrApi"; +import type { PluginLoaderAdapter, ZenWindowRendererProps } from "@embeddr/zen-shell"; +import type { EmbeddrAPI } from "@embeddr/react-ui/types"; + +const PANEL_SAFE_AREA = { top: 8, right: 8, bottom: 8, left: 8 }; + +type PersistedZenWindowStore = typeof useZenWindowStore & { + persist?: { + hasHydrated?: () => boolean; + onFinishHydration?: (listener: () => void) => () => void; + }; +}; + +const zenWindowStoreWithPersist = useZenWindowStore; + +type LauncherComponentEntry = { + pluginId: string; + pluginLabel: string; + componentId: string; + title: string; + subtitle: string; + component: any; +}; + +type LauncherWindowEntry = { + id: string; + title: string; + subtitle: string; + componentId: string; + groupHostId?: string; + isPinned: boolean; + tabsCount: number; + orderIndex: number; +}; + +type LauncherSearchResult = + | { + key: string; + kind: "open"; + score: number; + entry: LauncherWindowEntry; + } + | { + key: string; + kind: "minimized"; + score: number; + entry: LauncherWindowEntry; + } + | { + key: string; + kind: "component"; + score: number; + entry: LauncherComponentEntry; + }; + +function normalizeLauncherText(value: string) { + return value.toLowerCase().trim(); +} + +function scoreLauncherMatch(values: Array, query: string) { + const normalizedQuery = normalizeLauncherText(query); + if (!normalizedQuery) return 1; + + const haystacks = values.map((value) => normalizeLauncherText(value || "")); + if (haystacks.some((value) => value === normalizedQuery)) return 100; + if (haystacks.some((value) => value.startsWith(normalizedQuery))) return 80; + if (haystacks.some((value) => value.includes(normalizedQuery))) return 60; + return 0; +} + +// Helper to resolve component ID to plugin and component name +function resolveComponentId(fullId: string, plugins: Record) { + if (!fullId) return null; + + // Try longest prefix match for pluginId + let bestPid: string | null = null; + for (const pid of Object.keys(plugins)) { + // Exact match check (for simple ID cases) + if (fullId === pid) { + if (!bestPid || pid.length > bestPid.length) bestPid = pid; + } + // Prefix match + const prefix = pid + "-"; + if (fullId.startsWith(prefix)) { + if (!bestPid || pid.length > bestPid.length) bestPid = pid; + } + } + + if (!bestPid) return null; + + const localId = fullId.slice(bestPid.length + 1); + const plugin = plugins[bestPid]; + + // If localId is empty, it might be the main/default component + const compDef = plugin.components?.find( + (c: any) => + c.name === localId || + c.component === localId || + c.exportName === localId || + (!localId && c.name === "main"), + ); + + return { + pluginId: bestPid, + componentName: compDef?.exportName || compDef?.component || localId, + def: compDef, + }; +} + +const CustomWindowRenderer = React.memo((props: ZenWindowRendererProps) => { + const { id, windowState, isActive } = props; + const { plugins } = usePluginRegistry(); + const baseApi = useEmbeddrAPI(); + const updateWindow = useZenWindowStore((s) => s.updateWindow); + + const resolved = useMemo( + () => resolveComponentId(windowState.componentId, plugins), + [windowState.componentId, plugins], + ); + const panelProps = useMemo( + () => ({ + ...(resolved?.def?.props ?? {}), + ...(windowState.props ?? {}), + }), + [resolved?.def?.props, windowState.props], + ); + + const handleClose = useCallback(() => { + useZenWindowStore.getState().closeWindow(id); + }, [id]); + + const handleFocus = useCallback(() => { + useZenWindowStore.getState().bringToFront(id); + }, [id]); + + const handleMinimize = useCallback(() => { + useZenWindowStore.getState().minimizeWindow(id); + }, [id]); + + const handlePinChange = useCallback(() => { + useZenWindowStore.getState().togglePin(id); + }, [id]); + + const defaultPosition = windowState.position || panelProps.defaultPosition || { x: 100, y: 100 }; + const defaultSize = windowState.size || panelProps.defaultSize || { width: 500, height: 400 }; + const panelMeta = useMemo( + () => ({ + id, + defaultPosition, + isActive, + }), + [defaultPosition, id, isActive], + ); + const panelClassName = + typeof panelProps.className === "string" ? panelProps.className : undefined; + const hideHeader = Boolean(panelProps.hideHeader); + const transparent = Boolean(panelProps.transparent); + const panelPluginId = resolved?.pluginId || "unknown"; + const panelComponentName = resolved?.componentName || windowState.componentId; + const resolvedPanelClassName = cn( + panelClassName, + "embeddr-panel", + `embeddr-panel-${panelPluginId.replace(/[^a-zA-Z0-9]/g, "-")}`, + `embeddr-component-${panelComponentName.replace(/[^a-zA-Z0-9]/g, "-")}`, + ); + const panelContentId = `panel-content-${panelPluginId.replace(/[^a-zA-Z0-9]/g, "-")}-${panelComponentName.replace(/[^a-zA-Z0-9]/g, "-")}`; + const pluginApi = useMemo( + () => (resolved ? extendApiForPlugin(baseApi, resolved.pluginId) : baseApi), + [baseApi, resolved?.pluginId], + ); + const GlobalEmbeddrProvider = (window as any).EmbeddrUI?.EmbeddrProvider || EmbeddrProvider; + + if (!resolved) { + return ( +
+ updateWindow(id, { position: pos })} + onSizeChange={(next) => updateWindow(id, { size: next })} + zIndex={props.zIndex} + isActive={isActive} + onMouseDown={handleFocus} + hideHeader={hideHeader} + transparent={transparent} + className={resolvedPanelClassName} + openRevision={windowState.openRevision} + resetUiOnOpen={Boolean(panelProps.resetUiOnOpen)} + > +
+
Component Not Found
+
+ ID: {windowState.componentId} +
+
Plugin might not be loaded yet.
+
+
+
+ ); + } + + return ( +
+ + updateWindow(id, { position: pos })} + onSizeChange={(next) => updateWindow(id, { size: next })} + hideHeader={hideHeader} + transparent={transparent} + className={resolvedPanelClassName} + openRevision={windowState.openRevision} + resetUiOnOpen={Boolean(panelProps.resetUiOnOpen)} + > +
+ + + + + +
+
+
+
+ ); +}); + +class WindowErrorBoundary extends React.Component< + { + windowId: string; + title: string; + onClose: () => void; + onMinimize?: () => void; + onPinChange?: () => void; + onFocus?: (event: React.MouseEvent) => void; + position?: { x: number; y: number }; + size?: { width: number; height: number }; + defaultPosition?: { x: number; y: number }; + defaultSize?: { width: number; height: number }; + isActive?: boolean; + zIndex?: number; + pinned?: boolean; + hideHeader?: boolean; + transparent?: boolean; + className?: string; + children: React.ReactNode; + }, + { error?: Error } +> { + state = { error: undefined } as { error?: Error }; + + static getDerivedStateFromError(error: Error) { + return { error }; + } + + componentDidCatch(error: Error) { + console.error("[ZenShell] Window renderer crashed", error); + } + + render() { + if (this.state.error) { + const defaultPosition = this.props.defaultPosition || { x: 100, y: 100 }; + const defaultSize = this.props.defaultSize || { width: 500, height: 400 }; + return ( + +
+
+ {this.state.error.message} +
+
+
+ ); + } + + return this.props.children; + } +} + +type EmbeddrApiAdapterInput = ReturnType; + +function createEmbeddrApiAdapter(input: EmbeddrApiAdapterInput): EmbeddrAPI { + const backendUrl = (input.endpoint || "http://localhost:8003").replace(/\/$/, ""); + const apiBase = `${backendUrl}/api/v1`; + const assetBase = backendUrl.replace(/\/api(?:\/v\d+)?\/?$/, ""); + + const signProtectedUrl = (url: string) => { + const apiKey = String(input.apiKey || "").trim(); + if (!url || !apiKey) return url; + try { + const baseUrl = + assetBase || + backendUrl || + (typeof window !== "undefined" ? window.location.origin : "http://localhost"); + const parsed = new URL(url, baseUrl); + const assetOrigin = new URL(baseUrl).origin; + const windowOrigin = typeof window !== "undefined" ? window.location.origin : assetOrigin; + const isInternal = parsed.origin === assetOrigin || parsed.origin === windowOrigin; + const isProtectedPath = + parsed.pathname.startsWith("/api/") || parsed.pathname.startsWith("/plugins/"); + if (!isInternal || !isProtectedPath) return parsed.toString(); + if (!parsed.searchParams.has("api_key")) { + parsed.searchParams.set("api_key", apiKey); + } + return parsed.toString(); + } catch { + return url; + } + }; + + const normalizePluginLogoUrl = (value: string | null, pluginName?: string) => { + if (!value) return null; + if (value.startsWith("http://") || value.startsWith("https://")) { + return signProtectedUrl(value); + } + if (value.startsWith("//")) { + const protocol = typeof window !== "undefined" ? window.location.protocol : "https:"; + return signProtectedUrl(`${protocol}${value}`); + } + if (value.startsWith("/api/") || value.startsWith("/plugins/")) { + return signProtectedUrl(`${assetBase}${value}`); + } + if (pluginName && value.startsWith(`/${pluginName}/static/`)) { + return signProtectedUrl(`${assetBase}/plugins${value}`); + } + if (value.startsWith("/")) { + return signProtectedUrl(`${assetBase}${value}`); + } + return signProtectedUrl(`${assetBase}/${value}`); + }; + + const jsonRequest = async (path: string, init?: RequestInit) => { + const normalized = path.startsWith("/") ? path : `/${path}`; + const url = path.startsWith("http") ? path : `${apiBase}${normalized}`; + + const key = input.apiKey || ""; + const headers = new Headers(init?.headers || {}); + if (key && !headers.has("X-API-Key")) { + headers.set("X-API-Key", key); + } + const nextInit: RequestInit = { ...init, headers }; + + const addTrailingSlash = (inputUrl: string) => { + const [base, query] = inputUrl.split("?"); + if (base.endsWith("/")) return inputUrl; + return query ? `${base}/?${query}` : `${base}/`; + }; + + const run = async (target: string) => { + const res = await input.apiClient.fetch(target, nextInit); + if (res.ok) return res.json(); + return res; + }; + + const first = await run(url); + if (first instanceof Response) { + if (first.status === 404) { + const fallbackUrl = addTrailingSlash(url); + const second = await run(fallbackUrl); + if (second instanceof Response) { + const txt = await second.text().catch(() => ""); + throw new Error(txt || second.statusText || "Request failed"); + } + return second; + } + const txt = await first.text().catch(() => ""); + throw new Error(txt || first.statusText || "Request failed"); + } + return first; + }; + + const executionStore = { + pipelines: [], + selectedPipeline: null, + runs: [], + isRunning: false, + run: async () => {}, + setPipelineInput: () => {}, + selectPipeline: () => {}, + }; + + const generationStore = { + workflows: executionStore.pipelines, + selectedWorkflow: executionStore.selectedPipeline, + generations: executionStore.runs, + runs: executionStore.runs, + isGenerating: executionStore.isRunning, + generate: executionStore.run, + setWorkflowInput: executionStore.setPipelineInput, + selectWorkflow: executionStore.selectPipeline, + }; + + const modelCatalog = { + list: async (input: { category: string; page?: number; limit?: number }) => ({ + items: [], + total: 0, + page: input.page || 1, + pages: 1, + category: input.category, + }), + listSamplers: async () => ({ samplers: [], schedulers: [] }), + }; + + const api: EmbeddrAPI = { + stores: { + global: { + selectedImage: null, + selectImage: () => {}, + }, + generation: generationStore, + }, + ui: { + activePanelId: null, + isPanelActive: () => false, + }, + workspaces: { + getState: () => ({}), + subscribe: () => () => {}, + list: () => [], + getActiveId: () => null, + ensureDefault: () => {}, + create: () => "default", + save: () => {}, + saveActive: () => {}, + apply: () => {}, + rename: () => {}, + clone: () => null, + remove: () => {}, + setTemplate: () => {}, + }, + settings: { + get: (key: string, defaultValue?: T) => { + const raw = localStorage.getItem(key); + return raw !== null ? (JSON.parse(raw) as T) : defaultValue; + }, + set: (key: string, value: any) => { + localStorage.setItem(key, JSON.stringify(value)); + }, + getPlugin: (pluginId: string, key: string, defaultValue?: T) => { + const raw = localStorage.getItem(`${pluginId}:${key}`); + return raw !== null ? (JSON.parse(raw) as T) : defaultValue; + }, + setPlugin: (pluginId: string, key: string, value: any) => { + localStorage.setItem(`${pluginId}:${key}`, JSON.stringify(value)); + }, + }, + toast: { + success: (message: string) => console.log("[Embeddr]", message), + error: (message: string) => console.error("[Embeddr]", message), + info: (message: string) => console.info("[Embeddr]", message), + }, + utils: { + backendUrl, + getApiKey: () => input.apiKey || null, + uploadImage: async () => { + throw new Error("uploadImage not implemented in ComfyUI shell"); + }, + getPluginUrl: (path: string) => { + const cleanPath = path.startsWith("/") ? path.slice(1) : path; + return `${apiBase}/plugins/${cleanPath}`; + }, + }, + artifacts: { + list: (inputData) => { + const q = new URLSearchParams(); + if (inputData?.limit !== undefined) q.append("limit", String(inputData.limit)); + if (inputData?.offset !== undefined) q.append("offset", String(inputData.offset)); + if (inputData?.type_name) q.append("type_name", inputData.type_name); + if (inputData?.media_type) q.append("media_type", inputData.media_type); + if (inputData?.sort) q.append("sort", inputData.sort); + if (inputData?.ids?.length) q.append("ids", inputData.ids.join(",")); + const qs = q.toString(); + return jsonRequest(`/artifacts${qs ? `?${qs}` : ""}`); + }, + get: (id: string) => jsonRequest(`/artifacts/${id}`), + getContentUrl: (id: string) => `${apiBase}/artifacts/${id}/content`, + resolve: (inputData: any) => jsonRequest(`/artifacts/${inputData.id}`), + getPreviewUrl: (id: string, type: "thumbnail" | "preview" = "thumbnail") => + `${apiBase}/artifacts/${id}/preview?preview_type=${type}`, + getEmbeddings: (id: string) => jsonRequest(`/artifacts/${id}/embeddings`), + getAnnotations: (id: string) => jsonRequest(`/artifacts/${id}/annotations`), + getLineage: (id: string) => jsonRequest(`/artifacts/${id}/lineage`), + getRelations: (id: string) => jsonRequest(`/artifacts/${id}/relations`), + addRelation: (sourceId: string, inputData: any) => + jsonRequest(`/artifacts/${sourceId}/relations`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + target_id: inputData?.target_id, + relation_type: inputData?.relation_type || "contains", + metadata_json: inputData?.metadata_json || {}, + }), + }), + getSubgraph: (id: string, params: any) => { + const q = new URLSearchParams(); + if (params?.maxDepth !== undefined) q.append("max_depth", String(params.maxDepth)); + if (params?.includeLineage !== undefined) + q.append("include_lineage", String(params.includeLineage)); + if (params?.includeRelations !== undefined) + q.append("include_relations", String(params.includeRelations)); + const qs = q.toString(); + return jsonRequest(`/artifacts/${id}/subgraph${qs ? `?${qs}` : ""}`); + }, + create: (inputData: any) => + jsonRequest(`/artifacts`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(inputData), + }), + update: (id: string, inputData: any) => + jsonRequest(`/artifacts/${id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(inputData), + }), + delete: (id: string) => jsonRequest(`/artifacts/${id}`, { method: "DELETE" }), + uploadInit: (inputData: any) => + jsonRequest(`/artifacts/${inputData.artifact_id}/upload/init`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(inputData), + }), + uploadComplete: (inputData: any) => + jsonRequest(`/artifacts/upload/${inputData.upload_id}/complete`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(inputData), + }), + uploadFile: async (inputData: { artifact_id: string; file: File }) => { + const formData = new FormData(); + formData.append("file", inputData.file); + return jsonRequest(`/artifacts/${inputData.artifact_id}/upload`, { + method: "POST", + body: formData as any, + }); + }, + }, + resources: { + resolve: (inputData: any) => + jsonRequest(`/resources/resolve`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(inputData), + }), + }, + collections: input.apiClient.collections, + library: input.apiClient.collections as any, + executions: { + create: (payload) => + jsonRequest(`/executions`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }), + get: (executionId) => jsonRequest(`/executions/${executionId}`), + list: (payload) => { + const q = new URLSearchParams(); + if (payload?.plugin_name) q.append("plugin_name", payload.plugin_name); + if (payload?.status) q.append("status", payload.status); + if (payload?.limit) q.append("limit", String(payload.limit)); + if (payload?.offset) q.append("offset", String(payload.offset)); + return jsonRequest(`/executions?${q.toString()}`); + }, + cancel: (executionId) => + jsonRequest(`/executions/${executionId}/cancel`, { + method: "POST", + }), + nudge: (executionId, input) => { + const payload = typeof input === "string" ? { message: input } : input; + return jsonRequest(`/executions/${executionId}/nudge`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + }, + }, + lotus: { + invoke: (capId: string, payload?: Record) => + jsonRequest(`/lotus/${capId}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload ?? {}), + }), + query: (query: string, limit = 20) => + jsonRequest(`/lotus/query?q=${encodeURIComponent(query)}&limit=${limit}`), + list: (payload?: any) => { + const q = new URLSearchParams(); + if (payload?.kind) q.append("kind", payload.kind); + if (payload?.plugin) q.append("plugin", payload.plugin); + if (payload?.slot) q.append("slot", payload.slot); + if (payload?.limit) q.append("limit", String(payload.limit)); + if (payload?.offset) q.append("offset", String(payload.offset)); + return jsonRequest(`/lotus/list${q.toString() ? `?${q.toString()}` : ""}`); + }, + }, + client: { + plugins: { + call: (pluginId: string, path: string, method = "GET", body?: any) => { + const normalized = path.startsWith("/") ? path : `/${path}`; + return jsonRequest(`/plugins/${pluginId}${normalized}`, { + method, + headers: body ? { "Content-Type": "application/json" } : undefined, + body: body ? JSON.stringify(body) : undefined, + }); + }, + }, + }, + plugin: { + fetch: async (path: string, init?: RequestInit) => { + const url = path.startsWith("http") + ? path + : `${apiBase}/plugins${path.startsWith("/") ? path : `/${path}`}`; + const key = input.apiKey || ""; + const headers = new Headers(init?.headers || {}); + if (key && !headers.has("X-API-Key")) headers.set("X-API-Key", key); + return input.apiClient.fetch(url, { ...init, headers }); + }, + request: async (path: string, init?: RequestInit): Promise => { + const url = path.startsWith("http") + ? path + : `${apiBase}/plugins${path.startsWith("/") ? path : `/${path}`}`; + const key = input.apiKey || ""; + const headers = new Headers(init?.headers || {}); + if (key && !headers.has("X-API-Key")) headers.set("X-API-Key", key); + const res = await input.apiClient.fetch(url, { ...init, headers }); + if (!res.ok) throw new Error(await res.text().catch(() => res.statusText)); + return res.json(); + }, + }, + security: { + overview: () => jsonRequest(`/system/auth/overview`), + operatorProfile: () => jsonRequest(`/system/auth/me`), + login: async (payload: { username: string; password: string }) => { + try { + return await jsonRequest(`/system/auth/login`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + }); + } catch { + return { ok: false, detail: "Login not supported in ComfyUI shell" }; + } + }, + logout: async () => ({ + ok: true, + message: "No session to clear in ComfyUI shell", + }), + }, + events: { + on: (event, listener) => + globalEventBus.on(event as string, listener), + off: (event, listener) => + globalEventBus.off(event as string, listener), + emit: (event, payload) => globalEventBus.emit(event as string, payload), + }, + comfy: modelCatalog as any, + windows: { + open: (id: string, title: string, componentId: string, props?: any) => + useZenWindowStore.getState().openWindow({ + id, + title, + componentId, + props, + }), + spawn: (componentId: string, title: string, props?: any) => + useZenWindowStore.getState().spawnWindow(componentId, title, props), + register: () => {}, + getState: () => useZenWindowStore.getState(), + list: () => Object.values(useZenWindowStore.getState().windows), + }, + plugins: { + list: () => jsonRequest("/plugins"), + listLogos: async () => { + const data = (await jsonRequest("/plugins/logos")) as { + logos?: Record; + }; + const logos = data?.logos || {}; + return Object.fromEntries( + Object.entries(logos).map(([key, value]) => [key, normalizePluginLogoUrl(value, key)]), + ); + }, + getActions: () => [], + getComponents: () => [], + getApi: () => api, + }, + }; + + (api as any).models = modelCatalog; + (api as any).stores.execution = executionStore; + + (api as any).__proxyFetch = input.apiClient.fetch; + return api; +} + +function extendApiForPlugin(api: EmbeddrAPI, pluginId: string): EmbeddrAPI { + if (!api?.utils) return api; + return { + ...api, + utils: { + ...api.utils, + getPluginUrl: (path: string) => { + const cleanPath = path.startsWith("/") ? path.slice(1) : path; + return `${api.utils.backendUrl}/api/v1/plugins/${pluginId}/${cleanPath}`; + }, + }, + plugin: { + fetch: (path: string, init?: RequestInit) => { + const proxyFetch = (api as any).__proxyFetch || fetch; + const key = (api as any).utils?.getApiKey?.() || ""; + const headers = new Headers(init?.headers || {}); + if (key && !headers.has("X-API-Key")) headers.set("X-API-Key", key); + const nextInit: RequestInit = { ...init, headers }; + if (path.startsWith("http")) { + return proxyFetch(path, nextInit); + } + const cleanPath = path.startsWith("/") ? path.slice(1) : path; + const url = `${api.utils.backendUrl}/api/v1/plugins/${pluginId}/${cleanPath}`; + return proxyFetch(url, nextInit); + }, + request: async (path: string, init?: RequestInit) => { + const proxyFetch = (api as any).__proxyFetch || fetch; + const key = (api as any).utils?.getApiKey?.() || ""; + const headers = new Headers(init?.headers || {}); + if (key && !headers.has("X-API-Key")) headers.set("X-API-Key", key); + const nextInit: RequestInit = { ...init, headers }; + const url = path.startsWith("http") + ? path + : `${api.utils.backendUrl}/api/v1/plugins/${pluginId}/${ + path.startsWith("/") ? path.slice(1) : path + }`; + + if (url.includes("/api/v1/lotus/") && (nextInit.method || "GET").toUpperCase() === "POST") { + const capId = url.split("/api/v1/lotus/")[1] || ""; + let payload: any = undefined; + if (typeof nextInit.body === "string") { + try { + payload = JSON.parse(nextInit.body); + } catch { + payload = {}; + } + } else if (nextInit.body && typeof nextInit.body === "object") { + payload = nextInit.body as any; + } + return api.lotus.invoke(capId, payload); + } + const res = await proxyFetch(url, nextInit); + if (!res.ok) { + const errorText = await res.text().catch(() => res.statusText); + throw new Error(errorText || `Request failed: ${res.status}`); + } + return res.json(); + }, + }, + }; +} + +export function ZenShell() { + console.log("[ZenShell] Rendering..."); + const [isOpen, setIsOpen] = useState(false); + const [launcherCollapsed, setLauncherCollapsed] = useState(false); + const [launcherQuery, setLauncherQuery] = useState(""); + const [launcherMode, setLauncherMode] = useState<"workspace" | "catalog">("workspace"); + const [pluginsLoading, setPluginsLoading] = useState(false); + const [pluginLoadError, setPluginLoadError] = useState(null); + const [pluginBootstrapComplete, setPluginBootstrapComplete] = useState(false); + const [windowStoreHydrated, setWindowStoreHydrated] = useState( + () => zenWindowStoreWithPersist.persist?.hasHydrated?.() ?? true, + ); + + let api; + try { + api = useEmbeddrApi(); + } catch (e) { + console.error("[ZenShell] Failed to get API context", e); + return null; + } + + const { plugins, knownPlugins } = usePluginRegistry(); + const spawnWindow = useZenWindowStore((s) => s.spawnWindow); + const updateWindow = useZenWindowStore((s) => s.updateWindow); + const setPanelConstraints = useZenWindowStore((s) => s.setPanelConstraints); + const windows = useZenWindowStore((s) => s.windows); + const panelOrder = useZenWindowStore((s) => s.panelOrder); + const bringToFront = useZenWindowStore((s) => s.bringToFront); + const restoreWindow = useZenWindowStore((s) => s.restoreWindow); + const minimizeWindow = useZenWindowStore((s) => s.minimizeWindow); + const closeWindow = useZenWindowStore((s) => s.closeWindow); + const setActiveTab = useZenWindowStore((s) => s.setActiveTab); + const [pluginReloadTick, setPluginReloadTick] = useState(0); + const deferredLauncherQuery = useDeferredValue(launcherQuery); + const embeddrApi = useMemo( + () => createEmbeddrApiAdapter(api), + [api.endpoint, api.apiKey, api.apiClient], + ); + const wsBackendUrl = useMemo( + () => (api.endpoint || "http://localhost:8003").replace(/\/$/, ""), + [api.endpoint], + ); + const orderIndexByWindowId = useMemo( + () => new Map(panelOrder.map((id, index) => [id, index] as const)), + [panelOrder], + ); + const launcherComponents = useMemo>( + () => + knownPlugins + .flatMap((pluginId) => { + const plugin = plugins[pluginId]; + return (plugin?.components ?? []).map((component: any, componentIndex: number) => ({ + pluginId, + pluginLabel: plugin?.name || pluginId, + componentId: `${pluginId}-${ + component.exportName || + component.component || + component.name || + `comp-${componentIndex}` + }`, + title: + component.label || + component.name || + component.component || + component.exportName || + pluginId, + subtitle: + component.component || component.exportName || component.name || "Plugin panel", + component, + })); + }) + .sort( + (left, right) => + left.pluginLabel.localeCompare(right.pluginLabel) || + left.title.localeCompare(right.title), + ), + [knownPlugins, plugins], + ); + const launcherOpenWindows = useMemo>( + () => + Object.values(windows) + .filter((windowState) => !windowState.isMinimized && !windowState.groupHostId) + .map((windowState) => { + const resolved = resolveComponentId(windowState.componentId, plugins); + return { + id: windowState.id, + title: + windowState.title || resolved?.def?.label || resolved?.componentName || "Untitled", + subtitle: resolved?.pluginId || resolved?.def?.component || windowState.componentId, + componentId: windowState.componentId, + isPinned: Boolean(windowState.isPinned), + tabsCount: windowState.tabs?.length || 0, + orderIndex: orderIndexByWindowId.get(windowState.id) ?? -1, + }; + }) + .sort((left, right) => right.orderIndex - left.orderIndex), + [orderIndexByWindowId, plugins, windows], + ); + const launcherMinimizedWindows = useMemo>( + () => + Object.values(windows) + .filter((windowState) => windowState.isMinimized) + .map((windowState) => { + const resolved = resolveComponentId(windowState.componentId, plugins); + return { + id: windowState.id, + title: + windowState.title || resolved?.def?.label || resolved?.componentName || "Untitled", + subtitle: resolved?.pluginId || resolved?.def?.component || windowState.componentId, + componentId: windowState.componentId, + groupHostId: windowState.groupHostId, + isPinned: Boolean(windowState.isPinned), + tabsCount: windowState.tabs?.length || 0, + orderIndex: orderIndexByWindowId.get(windowState.id) ?? -1, + }; + }) + .sort((left, right) => right.orderIndex - left.orderIndex), + [orderIndexByWindowId, plugins, windows], + ); + const filteredOpenWindows = useMemo( + () => + launcherOpenWindows + .map((entry) => ({ + entry, + score: scoreLauncherMatch( + [entry.title, entry.subtitle, entry.componentId], + deferredLauncherQuery, + ), + })) + .filter(({ score }) => score > 0) + .sort( + (left, right) => + right.score - left.score || right.entry.orderIndex - left.entry.orderIndex, + ) + .map(({ entry }) => entry), + [deferredLauncherQuery, launcherOpenWindows], + ); + const filteredMinimizedWindows = useMemo( + () => + launcherMinimizedWindows + .map((entry) => ({ + entry, + score: scoreLauncherMatch( + [entry.title, entry.subtitle, entry.componentId], + deferredLauncherQuery, + ), + })) + .filter(({ score }) => score > 0) + .sort( + (left, right) => + right.score - left.score || right.entry.orderIndex - left.entry.orderIndex, + ) + .map(({ entry }) => entry), + [deferredLauncherQuery, launcherMinimizedWindows], + ); + const filteredLauncherComponents = useMemo( + () => + launcherComponents + .map((entry) => ({ + entry, + score: scoreLauncherMatch( + [entry.title, entry.subtitle, entry.pluginId, entry.pluginLabel], + deferredLauncherQuery, + ), + })) + .filter(({ score }) => score > 0) + .sort( + (left, right) => + right.score - left.score || + left.entry.pluginLabel.localeCompare(right.entry.pluginLabel) || + left.entry.title.localeCompare(right.entry.title), + ) + .map(({ entry }) => entry), + [deferredLauncherQuery, launcherComponents], + ); + const groupedLauncherComponents = useMemo(() => { + const groups = new Map< + string, + { + pluginId: string; + pluginLabel: string; + entries: Array; + } + >(); + + filteredLauncherComponents.forEach((entry) => { + const current = groups.get(entry.pluginId); + if (current) { + current.entries.push(entry); + return; + } + groups.set(entry.pluginId, { + pluginId: entry.pluginId, + pluginLabel: entry.pluginLabel, + entries: [entry], + }); + }); + + return Array.from(groups.values()).sort((left, right) => + left.pluginLabel.localeCompare(right.pluginLabel), + ); + }, [filteredLauncherComponents]); + const shellReady = api.configLoaded && windowStoreHydrated && pluginBootstrapComplete; + + useEffect(() => { + console.log("[ZenShell] Mounted"); + return () => console.log("[ZenShell] Unmounted"); + }, []); + + useEffect(() => { + setPanelConstraints({ + enabled: true, + safeArea: PANEL_SAFE_AREA, + snapThreshold: 24, + }); + }, [setPanelConstraints]); + + useEffect(() => { + const persistApi = zenWindowStoreWithPersist.persist; + if (!persistApi?.onFinishHydration) { + setWindowStoreHydrated(true); + return; + } + if (persistApi.hasHydrated?.()) { + setWindowStoreHydrated(true); + return; + } + const unsubscribe = persistApi.onFinishHydration(() => { + setWindowStoreHydrated(true); + }); + return unsubscribe; + }, []); + + const adapter = useMemo(() => { + console.log("[ZenShell] Recreating adapter"); + return { + list: async () => { + try { + const baseUrl = api.endpoint || "http://localhost:8003"; + // Reverting to /v2/plugins if that's what was working, or checking both? + // Let's assume /api/v1/plugins is correct based on recent changes, but we'll log it. + const targetUrl = baseUrl.endsWith("/") + ? `${baseUrl}api/v1/plugins` + : `${baseUrl}/api/v1/plugins`; + + console.log("[ZenShell] Fetching plugins from", targetUrl); + const res = await api.apiClient.fetch(targetUrl); + if (!res.ok) { + console.error("[ZenShell] Plugin fetch failed", res.status, res.statusText); + return []; + } + const data = await res.json(); + console.log("[ZenShell] Fetched plugins:", data.length); + return data; + } catch (e) { + console.error("Failed to list plugins via proxy", e); + return []; + } + }, + resolveScriptUrl: (manifest) => { + const baseUrl = (api.endpoint || "http://localhost:8003").replace(/\/$/, ""); + const url = manifest.url; + + if (!url) return ""; + + if (url.startsWith("/")) { + const target = `${baseUrl}${url}`; + return `/embeddr/proxy?url=${encodeURIComponent(target)}`; + } + return url; + }, + resolveCssUrl: (manifest) => { + const baseUrl = (api.endpoint || "http://localhost:8003").replace(/\/$/, ""); + let url = manifest.url; + + if (!url) return null; + + if (url.startsWith("/")) { + if (url.endsWith(".js")) { + url = url.replace(".js", ".css"); + } + const target = `${baseUrl}${url}`; + return `/embeddr/proxy?url=${encodeURIComponent(target)}`; + } + + if (url.endsWith(".js")) return url.replace(".js", ".css"); + return null; + }, + }; + }, [api.endpoint, api.apiClient]); + + useEffect(() => { + const handleToggle = () => { + console.log("[ZenShell] Toggle event received"); + if (!isOpen) { + setIsOpen(true); + setLauncherCollapsed(false); + return; + } + if (launcherCollapsed) { + setLauncherCollapsed(false); + return; + } + setIsOpen(false); + }; + const handleLaunch = (e: CustomEvent) => { + console.log("[ZenShell] Launch event received", e.detail); + setIsOpen(true); + setLauncherCollapsed(false); + if (e.detail && e.detail.componentId) { + const title = e.detail.title || e.detail.componentId; + spawnWindow(e.detail.componentId, title, e.detail.props); + } + }; + + const targets: Array = []; + const addTarget = (target?: Window | null) => { + if (!target) return; + if (!targets.includes(target)) targets.push(target); + }; + + addTarget(window); + try { + addTarget(window.parent); + } catch (e) { + console.warn("[ZenShell] Unable to access window.parent", e); + } + try { + addTarget(window.top); + } catch (e) { + console.warn("[ZenShell] Unable to access window.top", e); + } + + targets.forEach((target) => { + target.addEventListener("embeddr-toggle-shell", handleToggle); + target.addEventListener("embeddr-launch-window", handleLaunch as EventListener); + }); + + return () => { + targets.forEach((target) => { + target.removeEventListener("embeddr-toggle-shell", handleToggle); + target.removeEventListener("embeddr-launch-window", handleLaunch as EventListener); + }); + }; + }, [isOpen, launcherCollapsed, spawnWindow]); + + const handlePluginLoadError = useCallback((error: unknown) => { + const message = error instanceof Error ? error.message : "Failed to load plugins"; + console.error("[ZenShell] Failed to load external plugins", error); + setPluginLoadError(message); + }, []); + + const reloadPlugins = useCallback(async () => { + setPluginsLoading(true); + setPluginLoadError(null); + try { + await loadExternalPlugins({ adapter }); + setPluginReloadTick((prev) => prev + 1); + setPluginBootstrapComplete(true); + } catch (error) { + handlePluginLoadError(error); + setPluginBootstrapComplete(true); + } finally { + setPluginsLoading(false); + } + }, [adapter, handlePluginLoadError]); + + useEffect(() => { + if (!api.configLoaded) { + setPluginBootstrapComplete(false); + setPluginsLoading(false); + setPluginLoadError(null); + return; + } + console.log("[ZenShell] Loading external plugins..."); + let cancelled = false; + setPluginBootstrapComplete(false); + setPluginsLoading(true); + setPluginLoadError(null); + (async () => { + try { + await loadExternalPlugins({ adapter }); + if (cancelled) return; + setPluginReloadTick((prev) => prev + 1); + } catch (error) { + if (cancelled) return; + handlePluginLoadError(error); + } finally { + if (!cancelled) { + setPluginsLoading(false); + setPluginBootstrapComplete(true); + } + } + })(); + return () => { + cancelled = true; + }; + }, [api.configLoaded, adapter, handlePluginLoadError]); + + const handleSpawnComponent = useCallback( + (entry: LauncherComponentEntry) => { + const windowId = spawnWindow(entry.componentId, entry.title, { + ...(entry.component.props ?? {}), + defaultPosition: entry.component.defaultPosition ?? entry.component.props?.defaultPosition, + defaultSize: entry.component.defaultSize ?? entry.component.props?.defaultSize, + }); + + if (entry.component.defaultPosition || entry.component.defaultSize) { + updateWindow(windowId, { + position: entry.component.defaultPosition, + size: entry.component.defaultSize, + }); + } + }, + [spawnWindow, updateWindow], + ); + + const handleFocusWindow = useCallback( + (windowId: string) => { + bringToFront(windowId); + setActiveTab(windowId, windowId); + }, + [bringToFront, setActiveTab], + ); + + const handleRestoreWindow = useCallback( + (windowId: string, hostId?: string) => { + const restoreId = hostId || windowId; + restoreWindow(restoreId); + if (hostId) { + setActiveTab(hostId, windowId); + bringToFront(hostId); + return; + } + bringToFront(windowId); + }, + [bringToFront, restoreWindow, setActiveTab], + ); + + const hasLauncherResults = + filteredOpenWindows.length > 0 || + filteredMinimizedWindows.length > 0 || + groupedLauncherComponents.length > 0; + const trimmedLauncherQuery = deferredLauncherQuery.trim(); + const launcherSearchResults = useMemo>(() => { + if (!trimmedLauncherQuery) return []; + + const openResults = launcherOpenWindows + .map((entry) => ({ + key: `open:${entry.id}`, + kind: "open" as const, + score: + scoreLauncherMatch( + [entry.title, entry.subtitle, entry.componentId], + trimmedLauncherQuery, + ) + 30, + entry, + })) + .filter((result) => result.score > 30); + + const minimizedResults = launcherMinimizedWindows + .map((entry) => ({ + key: `minimized:${entry.id}`, + kind: "minimized" as const, + score: + scoreLauncherMatch( + [entry.title, entry.subtitle, entry.componentId], + trimmedLauncherQuery, + ) + 20, + entry, + })) + .filter((result) => result.score > 20); + + const componentResults = launcherComponents + .map((entry) => ({ + key: `component:${entry.componentId}`, + kind: "component" as const, + score: scoreLauncherMatch( + [entry.title, entry.subtitle, entry.pluginId, entry.pluginLabel], + trimmedLauncherQuery, + ), + entry, + })) + .filter((result) => result.score > 0); + + return [...openResults, ...minimizedResults, ...componentResults] + .sort((left, right) => right.score - left.score) + .slice(0, 40); + }, [launcherComponents, launcherMinimizedWindows, launcherOpenWindows, trimmedLauncherQuery]); + const effectiveLauncherMode = trimmedLauncherQuery ? "search" : launcherMode; + + return ( + +
+ {/* The Window Manager Layer */} +
+ + + {shellReady ? ( + + ) : ( +
+
+ +
Loading workspace panels...
+
+
+ )} +
+
+
+ + {/* The Shell Dock / Launcher */} + {isOpen && ( +
+ {launcherCollapsed ? ( + + ) : ( +
+
+
+
+
+
+ +
+
+
Zen Launcher
+
+ {effectiveLauncherMode === "search" + ? "Fast search across open windows and plugin panels." + : launcherMode === "workspace" + ? "Manage open and minimized panels." + : "Browse and launch plugin panels."} +
+
+
+
+
+ + + +
+
+ +
+
+
+ Open +
+
{launcherOpenWindows.length}
+
+
+
+ Minimized +
+
+ {launcherMinimizedWindows.length} +
+
+
+
+ Panels +
+
{launcherComponents.length}
+
+
+ +
+ + setLauncherQuery(event.target.value)} + placeholder="Search panels, plugins, or open windows..." + className="h-9 border-border/50 bg-background/80 pl-8 text-xs" + /> +
+ +
+ + + {trimmedLauncherQuery && ( + + {launcherSearchResults.length} results + + )} +
+ + {pluginLoadError && ( +
+ {pluginLoadError} +
+ )} +
+ +
+ {effectiveLauncherMode === "search" ? ( +
+
+
+ Search Results +
+ + {launcherSearchResults.length} + +
+ + {launcherSearchResults.length > 0 ? ( + +
+ {launcherSearchResults.map((result) => { + if (result.kind === "component") { + const entry = result.entry; + return ( +
+
+ +
+
+
+ + {entry.title} + + + Panel + +
+
+ {entry.pluginLabel} · {entry.subtitle} +
+
+ +
+ ); + } + + const entry = result.entry; + const isOpenResult = result.kind === "open"; + return ( + + ); + })} +
+
+ ) : ( +
+ No panels match "{trimmedLauncherQuery}". +
+ )} +
+ ) : effectiveLauncherMode === "workspace" ? ( +
+
+
+
+ + Open Panels +
+ + {launcherOpenWindows.length} + +
+ {launcherOpenWindows.length > 0 ? ( + +
+ {launcherOpenWindows.map((entry) => ( + + +
+ + ))} +
+ + ) : ( +
+ No open panels. +
+ )} + + + + +
+
+
+ + Minimized +
+ + {launcherMinimizedWindows.length} + +
+ {launcherMinimizedWindows.length > 0 ? ( + +
+ {launcherMinimizedWindows.map((entry) => ( + + ))} +
+
+ ) : ( +
+ No minimized panels. +
+ )} +
+
+ ) : ( +
+
+
+ Launch Panels +
+ + {launcherComponents.length} + +
+ + {pluginsLoading && launcherComponents.length === 0 && ( +
+ +
Loading plugins...
+
+ )} + + {!pluginsLoading && groupedLauncherComponents.length > 0 && ( + +
+ {groupedLauncherComponents.map((group) => ( +
+
+
+
+ {group.pluginLabel} +
+
+ {group.pluginId} +
+
+ + {group.entries.length} + +
+
+ {group.entries.map((entry) => ( +
+
+
+ {entry.title} +
+
+ {entry.subtitle} +
+
+ +
+ ))} +
+
+ ))} +
+
+ )} + + {!pluginsLoading && !hasLauncherResults && ( +
+ No panels are available yet. +
+ )} +
+ )} +
+
+ )} + + )} +
+ ); +} diff --git a/ui/components/panels/EmbeddrPanel.tsx b/ui/components/panels/EmbeddrPanel.tsx index 06c1433..264280a 100644 --- a/ui/components/panels/EmbeddrPanel.tsx +++ b/ui/components/panels/EmbeddrPanel.tsx @@ -1,17 +1,11 @@ import React, { useEffect, useState } from "react"; -import { cn } from "@embeddr/react-ui"; -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@embeddr/react-ui/components/tabs"; -import { useExternalNav } from "@embeddr/react-ui"; -import { GlobeIcon, MessageCircleIcon, Search, Settings } from "lucide-react"; -import { Button } from "@embeddr/react-ui/components/button"; +import { cn, useExternalNav, useImageDialog } from "@embeddr/react-ui"; +import { Button, Tabs, TabsContent, TabsList, TabsTrigger } from "@embeddr/react-ui/components/ui"; +import { GlobeIcon, LayoutTemplate, MessageCircleIcon, Search, Settings } from "lucide-react"; import { useEmbeddrApi } from "@hooks/useEmbeddrApi"; import { SettingsForm } from "../tabs/SettingsForm"; import { ExploreTab } from "../tabs/ExploreTab"; +import { PromptTab } from "../tabs/PromptTab"; export default function EmbeddrPanel() { const { @@ -34,9 +28,22 @@ export default function EmbeddrPanel() { setSimilarImageId, theme, setTheme, + themePackId, + setThemePackId, + apiBase, + apiClient, + apiKey, + setApiKey, } = useEmbeddrApi(); const { openExternal } = useExternalNav(); + const { setApiKey: setDialogApiKey } = useImageDialog(); + + useEffect(() => { + if (setDialogApiKey && apiKey) { + setDialogApiKey(apiKey); + } + }, [apiKey, setDialogApiKey]); const [activeTab, setActiveTab] = useState("explore"); @@ -47,21 +54,43 @@ export default function EmbeddrPanel() { // Initial fetch and refetch on change useEffect(() => { if (configLoaded) { - const libId = - selectedLibrary === "all" ? null : parseInt(selectedLibrary); + const libId = selectedLibrary === "all" ? null : parseInt(selectedLibrary); fetchImages(true, searchQuery, viewMode, libId, similarImageId); } }, [viewMode, configLoaded, selectedLibrary, mode, similarImageId]); const handleSave = async () => { - await saveSettings(endpoint, mode, gridSize, gridPreviewContain); + await saveSettings(endpoint, mode, gridSize, gridPreviewContain, apiKey); + }; + + const dispatchShellEvent = (name: string, detail?: Record) => { + const targets: Array = []; + console.log("[EmbeddrPanel] Dispatching event", name, detail); + const addTarget = (target?: Window | null) => { + if (!target) return; + if (!targets.includes(target)) targets.push(target); + }; + addTarget(window); + try { + addTarget(window.parent); + } catch (e) { + console.warn("[EmbeddrPanel] Unable to access window.parent", e); + } + try { + addTarget(window.top); + } catch (e) { + console.warn("[EmbeddrPanel] Unable to access window.top", e); + } + + targets.forEach((target) => { + const event = new CustomEvent(name, { detail }); + console.log("[EmbeddrPanel] Sending event to target", target, event); + target.dispatchEvent(event); + }); }; return ( -
+
- + - + + - {/* Right: Actions & Nodes */} -
- {/* Copy Prompt */} - {selectedImage.prompt && ( +
+ {legacyPrompt && ( )} - {/* Target Nodes */} {targetNodes.map((node) => ( ))}
- {selectedImage.prompt && ( -
- {selectedImage.prompt} +
+
+
+
+ {filename} +
+
+ + {artifact?.type_name || "image"} + + + #{selectedImage.id} + + {artifact?.collections?.length ? ( + + {artifact.collections.length} collections + + ) : null} + {signals.features.length > 0 ? ( + + {signals.features.length} features + + ) : null} +
+
+
+ {artifact?.created_at || selectedImage.created_at} +
- )} +
+ + + + + Overview + + + Signals + + + JSON + + + + + {loading ? ( +
+ + +
+ ) : ( + <> +
+
+
+ + Details +
+
+
+
Filename
+
+ {filename} +
+
+
+
Artifact ID
+
+ {artifact?.id || selectedImage.id} +
+
+ {selectedImage.model && ( +
+
Model
+
{selectedImage.model}
+
+ )} +
+
+ +
+
+ + Media +
+
+
+
Dimensions
+
+ {artifact?.metadata_json?.width || selectedImage.width || "?"} x{" "} + {artifact?.metadata_json?.height || selectedImage.height || "?"} +
+
+
+
Type
+
+ {artifact?.metadata_json?.format || artifact?.type_name || "image"} +
+
+ {artifact?.base_type_name && ( +
+
Base Type
+
{artifact.base_type_name}
+
+ )} +
+
+
+ + {artifact?.collections?.length ? ( +
+
+ + Collections +
+
+ {artifact.collections.map((collection) => ( + + {collection.name} + + ))} +
+
+ ) : null} + + {((artifact?.tags && artifact.tags.length > 0) || + (artifact?.metadata_json?.tags && + Array.isArray(artifact.metadata_json.tags))) && ( +
+
+ + Tags +
+
+ {(artifact?.tags?.length + ? artifact.tags + : (artifact?.metadata_json?.tags as Array).map((tag) => ({ + id: tag, + name: tag, + })) + ).map((tag) => ( + + #{tag.name} + + ))} +
+
+ )} + + {legacyPrompt ? ( + <> + +
+
+
+ + Legacy Prompt +
+ +
+
+ {legacyPrompt} +
+
+ + ) : null} + + )} +
+ + +
+
+
+ Text Signals +
+
{derivedTextSignals.length}
+
+
+
+ Features +
+
{signals.features.length}
+
+
+
+ Embeddings +
+
{signals.embeddings.length}
+
+
+ + {signalsError && ( +
+ {signalsError} +
+ )} + + {signalsLoading ? ( +
+ + + +
+ ) : ( + <> +
+
+ + Text Signals +
+ {derivedTextSignals.length > 0 ? ( +
+ {derivedTextSignals.map((signal) => ( +
+
+ + {signal.source} + + + {signal.label} + + {signal.pluginName ? ( + + {signal.pluginName} + + ) : null} + {signal.confidence != null ? ( + + {(signal.confidence * 100).toFixed(0)}% + + ) : null} +
+
+ {signal.text} +
+
+ ))} +
+ ) : ( +
+ No captions, annotations, or generated text are attached to this artifact + yet. +
+ )} +
+ + + +
+
+ + Features +
+ {signals.features.length > 0 ? ( +
+ {signals.features.map((feature) => ( +
+
+ + {feature.feature_type} + + {feature.name} +
+
+ {feature.producer_plugin ? ( + + {feature.producer_plugin} + + ) : null} + {feature.model_name ? ( + + {feature.model_name} + + ) : null} + {feature.space ? ( + + {feature.space} + + ) : null} + {feature.vector_dim ? ( + + {feature.vector_dim}d + + ) : null} +
+
+ ))} +
+ ) : ( +
+ No attached features found. +
+ )} +
+ + + +
+
+ + Embeddings +
+ {signals.embeddings.length > 0 ? ( +
+ {signals.embeddings.map((embedding) => ( +
+
+ + {embedding.space} + + + {embedding.model_name} + +
+
+ + {embedding.vector_dim}d + + {embedding.plugin_name ? ( + + {embedding.plugin_name} + + ) : null} +
+
+ ))} +
+ ) : ( +
+ No embeddings stored for this artifact. +
+ )} +
+ + )} +
+ + +
+
+                  {rawArtifactPayload}
+                
+
+
+
diff --git a/ui/components/selectors/CollectionSelector.tsx b/ui/components/selectors/CollectionSelector.tsx new file mode 100644 index 0000000..a062da3 --- /dev/null +++ b/ui/components/selectors/CollectionSelector.tsx @@ -0,0 +1,125 @@ +import React, { useEffect, useState } from "react"; +import { + Button, + Card, + CardDescription, + CardHeader, + CardTitle, + Input, + ScrollArea, +} from "@embeddr/react-ui/components/ui"; +import { Folder, Plus, Search } from "lucide-react"; +import type { Collection } from "../../hooks/useEmbeddrCollections"; + +interface CollectionSelectorProps { + collections: Array; + loading: boolean; + onSelect: (collection: Collection) => void; + fetchCollections: () => void; + createCollection: (label: string) => Promise; + creating: boolean; +} + +export function CollectionSelector({ + collections, + loading, + onSelect, + fetchCollections, + createCollection, + creating, +}: CollectionSelectorProps) { + const [search, setSearch] = useState(""); + const [showCreate, setShowCreate] = useState(false); + const [newLabel, setNewLabel] = useState(""); + + useEffect(() => { + fetchCollections(); + }, []); + + const handleCreate = async () => { + if (!newLabel.trim()) return; + const success = await createCollection(newLabel); + if (success) { + setNewLabel(""); + setShowCreate(false); + } + }; + + const filtered = collections.filter((c) => + (c.label || "").toLowerCase().includes(search.toLowerCase()), + ); + + return ( +
+
+
+ + setSearch(e.target.value)} + className="pl-8" + /> +
+ +
+ + {showCreate && ( + +

Create New Collection

+
+ setNewLabel(e.target.value)} + /> + +
+
+ )} + + + {loading ? ( +
Scanning collections...
+ ) : filtered.length === 0 ? ( +
+
No collections found
+
Create one above to get started
+
+ ) : ( +
+ {filtered.map((collection) => ( + onSelect(collection)} + > + +
+ +
+
+ + {collection.label} + + + {collection.file_count ?? 0} items + +
+
+
+ ))} +
+ )} +
+
+ ); +} diff --git a/ui/components/tabs/ExploreTab.tsx b/ui/components/tabs/ExploreTab.tsx index 73a1218..dfd95a3 100644 --- a/ui/components/tabs/ExploreTab.tsx +++ b/ui/components/tabs/ExploreTab.tsx @@ -4,18 +4,16 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup, -} from "@embeddr/react-ui/components/resizable"; -import { Search, Grid3X3 } from "lucide-react"; -import { Slider } from "@embeddr/react-ui/components/slider"; + Slider, +} from "@embeddr/react-ui/components/ui"; +import { Grid3X3, Search } from "lucide-react"; import { ImageGrid } from "@components/ui/ImageGrid"; import { useNodeScanner } from "@hooks/useNodeScanner"; import { ImageDetails } from "../panels/ImageDetails"; import { SearchBar } from "../ui/SearchBar"; -import type { - ApiMode, - LibraryPath, - PromptImageRead, -} from "@hooks/useEmbeddrApi"; +import { useEmbeddrCollections } from "../../hooks/useEmbeddrCollections"; +import type { ApiMode, LibraryPath, PromptImageRead } from "@hooks/useEmbeddrApi"; +import type { EmbeddrApiClient } from "@embeddr/client-typescript"; interface ExploreTabProps { images: Array; @@ -26,18 +24,22 @@ interface ExploreTabProps { query?: string, viewMode?: "all" | "mine", libId?: number | null, - similarId?: number | null + similarId?: string | number | null, + collectionId?: string | null, ) => Promise; libraries: Array; - similarImageId: number | null; - setSimilarImageId: (id: number | null) => void; + similarImageId: string | number | null; + setSimilarImageId: (id: string | number | null) => void; mode: ApiMode; + apiBase?: string; // Need apiBase for collections + apiClient?: EmbeddrApiClient; gridSize: number; setGridSize?: (size: number) => void; gridPreviewContain: boolean; configLoaded: boolean; activeTab: string; onImageSelect?: (image: PromptImageRead) => void; + apiKey?: string; } export function ExploreTab({ @@ -49,31 +51,46 @@ export function ExploreTab({ similarImageId, setSimilarImageId, mode, + apiBase = "", + apiClient, gridSize, setGridSize, gridPreviewContain, configLoaded, activeTab, onImageSelect, + apiKey, }: ExploreTabProps) { const { targetNodes, handleLoadIntoNode, handleUseImage } = useNodeScanner(); - const { openImage, closeImage, setGalleryImages, currentGallery } = - useImageDialog(); + const { openImage, closeImage, setGalleryImages, currentGallery } = useImageDialog(); + + const { collections, fetchCollections } = useEmbeddrCollections({ + apiBase, + configLoaded, + apiClient, + }); + + // Load collections on mount/config load + useEffect(() => { + if (configLoaded) { + fetchCollections(); + } + }, [configLoaded, fetchCollections]); const [searchQuery, setSearchQuery] = useState(""); const [viewMode, setViewMode] = useState<"all" | "mine">("all"); - const [selectedLibrary, setSelectedLibrary] = useState("all"); - const [selectedImage, setSelectedImage] = useState( - null - ); + const [selectedCollectionId, setSelectedCollectionId] = useState("all"); + const [selectedImage, setSelectedImage] = useState(null); const scrollRef = useRef(null); // Fetch when dependencies change useEffect(() => { if (!configLoaded) return; - const libId = selectedLibrary === "all" ? null : parseInt(selectedLibrary); - fetchImages(true, searchQuery, viewMode, libId, similarImageId); - }, [viewMode, configLoaded, selectedLibrary, mode, similarImageId]); // Re-fetch when view mode changes or config is loaded + const colId = selectedCollectionId === "all" ? null : selectedCollectionId; + scrollRef.current?.scrollTo({ top: 0 }); + // libraryId is null now as we use collections + fetchImages(true, searchQuery, viewMode, null, similarImageId, colId); + }, [viewMode, configLoaded, selectedCollectionId, mode, similarImageId]); // Re-fetch when view mode changes or config is loaded // Sync images to lightbox when they change useEffect(() => { @@ -90,8 +107,9 @@ export function ExploreTab({ const handleSearch = (e: React.FormEvent) => { e.preventDefault(); - const libId = selectedLibrary === "all" ? null : parseInt(selectedLibrary); - fetchImages(true, searchQuery, viewMode, libId, similarImageId); + const colId = selectedCollectionId === "all" ? null : selectedCollectionId; + scrollRef.current?.scrollTo({ top: 0 }); + fetchImages(true, searchQuery, viewMode, null, similarImageId, colId); }; return ( @@ -112,18 +130,15 @@ export function ExploreTab({ } }} mode={mode} - selectedLibrary={selectedLibrary} - setSelectedLibrary={setSelectedLibrary} - libraries={libraries} + selectedCollectionId={selectedCollectionId} + setSelectedCollectionId={setSelectedCollectionId} + collections={collections} viewMode={viewMode} setViewMode={setViewMode} />
{setGridSize && ( -
+
{ setSelectedImage(e); }} onLoadMore={() => { - const libId = - selectedLibrary === "all" ? null : parseInt(selectedLibrary); - fetchImages( - false, - searchQuery, - viewMode, - libId, - similarImageId - ); + const colId = selectedCollectionId === "all" ? null : selectedCollectionId; + fetchImages(false, searchQuery, viewMode, null, similarImageId, colId); }} - onSimilarSearch={(image) => { - setSimilarImageId(image); + onSimilarSearch={(imageId) => { + setSimilarImageId(imageId); }} onSelect={(image) => { if (!image) return; @@ -177,30 +186,25 @@ export function ExploreTab({ metadata: p, })); const index = images.findIndex((p) => p.id === image.id); - const totalImages = hasMore - ? images.length + 100 - : images.length; + const totalImages = hasMore ? images.length + 100 : images.length; openImage( image.image_url, { id: "virtual-gallery", - name: - activeTab === "explore" ? "Explore" : "Search Results", + name: activeTab === "explore" ? "Explore" : "Search Results", images: galleryImages, totalImages: totalImages, fetchMore: async (_dir: any, _offset: any) => { if (hasMore) { - const libId = - selectedLibrary === "all" - ? null - : parseInt(selectedLibrary); + const colId = selectedCollectionId === "all" ? null : selectedCollectionId; await fetchImages( false, searchQuery, viewMode, - libId, - similarImageId + null, + similarImageId, + colId, ); } }, @@ -212,9 +216,7 @@ export function ExploreTab({ icon: , label: "Search by Image", onClick: (galleryImage) => { - const img = galleryImage?.metadata as - | PromptImageRead - | undefined; + const img = galleryImage?.metadata as PromptImageRead | undefined; if (img) { setSimilarImageId(img.id); } else { @@ -224,7 +226,7 @@ export function ExploreTab({ }, }, ], - image.prompt + image.prompt, ); }} selectedId={selectedImage?.id} @@ -233,16 +235,15 @@ export function ExploreTab({ {selectedImage && ( <> - + diff --git a/ui/components/tabs/PromptTab.tsx b/ui/components/tabs/PromptTab.tsx new file mode 100644 index 0000000..577951a --- /dev/null +++ b/ui/components/tabs/PromptTab.tsx @@ -0,0 +1,415 @@ +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { AlertCircle, Bot, Loader2, Send, Wrench } from "lucide-react"; +import { + Button, + Card, + Input, + Label, + ScrollArea, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + Textarea, +} from "@embeddr/react-ui/components/ui"; + +type ChatMessage = { + id?: string; + role: "user" | "assistant" | "error"; + content: string; +}; + +type LlmProvider = { + id: string; + name?: string; +}; + +type LlmModel = { + id: string; + name?: string; + provider_id?: string; +}; + +interface PromptTabProps { + endpoint: string; + apiKey?: string; + onOpenDocs: () => void; +} + +const POLL_INTERVAL_MS = 1200; +const MAX_POLL_ATTEMPTS = 120; + +const withApiPrefix = (endpoint: string, path: string) => { + const cleanEndpoint = endpoint.replace(/\/+$/, ""); + const cleanPath = path.startsWith("/") ? path : `/${path}`; + if (cleanEndpoint.endsWith("/api/v1")) { + return `${cleanEndpoint}${cleanPath}`; + } + return `${cleanEndpoint}/api/v1${cleanPath}`; +}; + +const getHeaders = (apiKey?: string) => { + const headers: Record = { + "Content-Type": "application/json", + }; + if (apiKey?.trim()) { + headers["X-API-Key"] = apiKey.trim(); + } + return headers; +}; + +async function jsonRequest( + endpoint: string, + path: string, + init: RequestInit = {}, + apiKey?: string, +): Promise { + const url = withApiPrefix(endpoint, path); + const res = await fetch(url, { + ...init, + headers: { + ...getHeaders(apiKey), + ...(init.headers || {}), + }, + }); + if (!res.ok) { + const body = await res.text().catch(() => ""); + throw new Error(body || `Request failed (${res.status})`); + } + return (await res.json()) as T; +} + +export function PromptTab({ endpoint, apiKey, onOpenDocs }: PromptTabProps) { + const [checkingPlugin, setCheckingPlugin] = useState(true); + const [hasLlmPlugin, setHasLlmPlugin] = useState(false); + const [pluginCheckError, setPluginCheckError] = useState(null); + + const [providers, setProviders] = useState>([]); + const [models, setModels] = useState>([]); + const [selectedProviderId, setSelectedProviderId] = useState("auto"); + const [selectedModel, setSelectedModel] = useState("auto"); + + const [systemPrompt, setSystemPrompt] = useState(""); + const [prompt, setPrompt] = useState(""); + const [messages, setMessages] = useState>([]); + const [isSending, setIsSending] = useState(false); + + const selectedProviderModels = useMemo(() => { + if (selectedProviderId === "auto") { + return models; + } + return models.filter((model) => model.provider_id === selectedProviderId); + }, [models, selectedProviderId]); + + const refreshPluginState = useCallback(async () => { + if (!endpoint) { + setCheckingPlugin(false); + setHasLlmPlugin(false); + setPluginCheckError("No API endpoint configured."); + return; + } + setCheckingPlugin(true); + setPluginCheckError(null); + try { + const data = await jsonRequest(endpoint, "/plugins", {}, apiKey); + const list = Array.isArray(data) ? data : Array.isArray(data?.items) ? data.items : []; + const found = list.some((plugin: any) => { + const id = String(plugin?.id || plugin?.plugin_id || ""); + return id === "embeddr-llm"; + }); + setHasLlmPlugin(found); + } catch (e: any) { + setHasLlmPlugin(false); + setPluginCheckError(e?.message || "Failed to check installed plugins."); + } finally { + setCheckingPlugin(false); + } + }, [endpoint, apiKey]); + + const loadProvidersAndModels = useCallback(async () => { + if (!hasLlmPlugin) return; + try { + const [providerRes, modelRes] = await Promise.all([ + jsonRequest(endpoint, "/plugins/embeddr-llm/providers", {}, apiKey), + jsonRequest(endpoint, "/plugins/embeddr-llm/models", {}, apiKey), + ]); + + const providerList = Array.isArray(providerRes?.data) + ? providerRes.data + : Array.isArray(providerRes) + ? providerRes + : []; + const modelList = Array.isArray(modelRes?.data) + ? modelRes.data + : Array.isArray(modelRes) + ? modelRes + : []; + + setProviders(providerList); + setModels(modelList); + } catch { + setProviders([]); + setModels([]); + } + }, [endpoint, apiKey, hasLlmPlugin]); + + useEffect(() => { + refreshPluginState(); + }, [refreshPluginState]); + + useEffect(() => { + loadProvidersAndModels(); + }, [loadProvidersAndModels]); + + const runChat = useCallback(async () => { + const text = prompt.trim(); + if (!text || isSending || !hasLlmPlugin) return; + + const pendingId = `pending-${Date.now()}`; + setPrompt(""); + setIsSending(true); + setMessages((prev) => [ + ...prev, + { role: "user", content: text }, + { id: pendingId, role: "assistant", content: "Thinking..." }, + ]); + + try { + const execution = await jsonRequest( + endpoint, + "/executions", + { + method: "POST", + body: JSON.stringify({ + plugin_name: "embeddr-llm", + job_type: "llm.respond", + inputs: { + prompt: text, + system_prompt: systemPrompt.trim() || undefined, + provider_id: selectedProviderId !== "auto" ? selectedProviderId : undefined, + model: selectedModel !== "auto" ? selectedModel : undefined, + }, + }), + }, + apiKey, + ); + + const executionId = execution?.id; + if (!executionId) { + throw new Error("No execution id returned."); + } + + let assistantText = ""; + let failedText = ""; + + for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt += 1) { + await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)); + const job = await jsonRequest( + endpoint, + `/executions/${executionId}`, + { method: "GET" }, + apiKey, + ); + + if (job?.status === "completed") { + assistantText = + job?.outputs?.response_text || + job?.outputs?.response || + job?.outputs?.text || + JSON.stringify(job?.outputs || {}, null, 2); + break; + } + + if (job?.status === "failed") { + failedText = job?.error || job?.message || "LLM execution failed."; + break; + } + } + + if (assistantText) { + setMessages((prev) => + prev.map((message) => + message.id === pendingId ? { role: "assistant", content: assistantText } : message, + ), + ); + } else if (failedText) { + setMessages((prev) => + prev.map((message) => + message.id === pendingId ? { role: "error", content: failedText } : message, + ), + ); + } else { + setMessages((prev) => + prev.map((message) => + message.id === pendingId + ? { + role: "error", + content: "Timed out while waiting for LLM response.", + } + : message, + ), + ); + } + } catch (e: any) { + setMessages((prev) => + prev.map((message) => + message.id === pendingId + ? { role: "error", content: e?.message || "Failed to send prompt." } + : message, + ), + ); + } finally { + setIsSending(false); + } + }, [ + apiKey, + endpoint, + hasLlmPlugin, + isSending, + prompt, + selectedModel, + selectedProviderId, + systemPrompt, + ]); + + if (checkingPlugin) { + return ( +
+ + Checking LLM plugin... +
+ ); + } + + if (!hasLlmPlugin) { + return ( +
+ +
+ + LLM chat is not available +
+

+ This tab needs the embeddr-llm plugin installed and enabled on your + configured instance. +

+ {pluginCheckError ? ( +
+ + {pluginCheckError} +
+ ) : null} +
+ + +
+
+
+ ); + } + + return ( +
+
+
+ + +
+ +
+ + +
+
+ +
+ +