diff --git a/.gitattributes b/.gitattributes index 8a0d2d901e..575b174994 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,4 +5,3 @@ # Ignore *ipynb files to detect the language. # This is because GitHub misdetects the repo language when ipynb files are included. *.ipynb filter=lfs diff=lfs merge=lfs -text -uv.lock filter=lfs diff=lfs merge=lfs -text diff --git a/.github/actions/code-quality/pre-commit/action.yaml b/.github/actions/code-quality/pre-commit/action.yaml deleted file mode 100644 index 7f813725ca..0000000000 --- a/.github/actions/code-quality/pre-commit/action.yaml +++ /dev/null @@ -1,116 +0,0 @@ -# Pre-commit Quality Action -# -# This composite action executes pre-commit hooks for code quality checks -# with configurable Python and Node.js environments. -# -# Key Features: -# - Pre-commit hook execution -# - Environment configuration -# - Cache management -# - Multi-language support -# - Dependency handling -# -# Process Stages: -# 1. Environment Setup: -# - Python installation -# - Node.js installation -# - Cache configuration -# -# 2. Dependency Management: -# - Pre-commit installation -# - Hook installation -# - Cache restoration -# -# 3. Quality Checks: -# - Hook execution -# - Error reporting -# - Result caching -# -# Required Inputs: -# - python-version: Python version to use -# - node-version: Node.js version to use (defaults to "20") -# -# Example Usage: -# steps: -# - uses: ./.github/actions/code-quality/pre-commit -# with: -# python-version: "3.11" -# -# Note: Requires configured pre-commit hooks in repository - -name: "Pre-commit Quality Checks" -description: "Runs pre-commit hooks for code quality checks" - -inputs: - python-version: - description: "Python version to use" - required: false - default: "3.10" - node-version: - description: "Node.js version to use" - required: false - default: "20" - skip: - description: "Comma-separated list of hooks to skip" - required: false - default: "" - cache: - description: "Whether to use caching" - required: false - default: "true" - -outputs: - cache-hit: - description: "Whether the cache was hit" - value: ${{ steps.pre-commit-cache.outputs.cache-hit }} - -runs: - using: composite - steps: - # Set up Python environment with caching - - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6 - with: - python-version: ${{ inputs.python-version }} - cache: pip # Enable pip caching - cache-dependency-path: .pre-commit-config.yaml - - # Set up Node.js for JavaScript-related hooks - - name: Set up Node.js - uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 - with: - node-version: ${{ inputs.node-version }} - - # Install pre-commit with latest pip - - name: Install pre-commit - shell: bash - run: | - python -m pip install --upgrade pip - pip install pre-commit - - # Cache pre-commit hooks to speed up subsequent runs - - name: Cache pre-commit hooks - if: inputs.cache == 'true' - id: pre-commit-cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 - with: - path: ~/.cache/pre-commit - # Cache key includes Python and Node versions to ensure correct environment - key: pre-commit-${{ runner.os }}-py${{ inputs.python-version }}-node${{ inputs.node-version }}-${{ hashFiles('.pre-commit-config.yaml') }} - restore-keys: | - pre-commit-${{ runner.os }}-py${{ inputs.python-version }}-node${{ inputs.node-version }}- - pre-commit-${{ runner.os }}-py${{ inputs.python-version }}- - - # Execute pre-commit checks with optional hook skipping - - name: Run pre-commit checks - shell: bash - env: - SKIP: ${{ inputs.skip }} - run: | - if [ -n "$SKIP" ]; then - # Run specific hooks if skip parameter is provided - pre-commit run --all-files --hook-stage="$SKIP" - else - # Run all hooks if no skip parameter - pre-commit run --all-files - fi diff --git a/.github/workflows/_reusable-code-quality.yaml b/.github/workflows/_reusable-code-quality.yaml index 0fd8b92b14..8a64e14ca4 100644 --- a/.github/workflows/_reusable-code-quality.yaml +++ b/.github/workflows/_reusable-code-quality.yaml @@ -1,25 +1,26 @@ # Code Quality Workflow # -# This reusable workflow executes code quality checks using pre-commit hooks -# and other quality assurance tools across multiple languages. +# This reusable workflow executes code quality checks using prek (pre-commit workspace tool) +# with support for monorepo configurations and multiple pre-commit configs. # # Key Features: -# - Pre-commit hook execution -# - Multi-language support +# - Workspace mode support (multiple .pre-commit-config.yaml files) +# - Multi-language support (Python, Node.js, etc.) +# - Automatic environment management # - Dependency caching -# - Configurable environments # - Parallel check execution # # Process Stages: -# 1. Environment Preparation: -# - Python setup -# - Cache configuration +# 1. Checkout: +# - Full repository history +# - LFS file support # -# 2. Quality Checks: +# 2. Quality Checks (via prek): # - Code linting # - Style verification # - Type checking # - Best practices validation +# - Runs appropriate hooks based on file location # # 3. Results Processing: # - Error reporting @@ -42,7 +43,8 @@ # with: # python-version: "3.11" # -# Note: Requires configured pre-commit hooks in repository +# Note: Uses prek-action (https://github.com/j178/prek-action) which supports +# multiple pre-commit configs in workspace mode for monorepo setups name: Reusable Code Quality @@ -58,15 +60,13 @@ permissions: contents: read jobs: - pre-commit: + prek: runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 lfs: true persist-credentials: false - - uses: ./.github/actions/code-quality/pre-commit - with: - python-version: ${{ inputs.python-version }} + - uses: j178/prek-action@ef075ff6f80a73aeb8facb7dd22f66f344b1d17a # v1 diff --git a/.github/workflows/geti-inspect.yaml b/.github/workflows/geti-inspect.yaml new file mode 100644 index 0000000000..2f5ba89696 --- /dev/null +++ b/.github/workflows/geti-inspect.yaml @@ -0,0 +1,294 @@ +name: Geti Inspect UI checks +permissions: {} # No permissions by default on workflow level + +on: + push: + branches: [main] + paths: + - "application/**" + pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review + paths: + - "application/**" + workflow_dispatch: # run on request (no need for PR) + +jobs: + generate-openapi-spec: + name: Generate OpenAPI Spec + runs-on: ubuntu-latest + permissions: + contents: read # to checkout code + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + lfs: true + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.13" + + - name: Install uv + uses: astral-sh/setup-uv@a02a550bdd3185dba2ebb6aa98d77047ce54ad21 # v6.2.1 + with: + version: "0.8.8" + + - name: Install OpenCV dependencies + run: | + sudo apt-get update + sudo apt-get install -y libgl1 libglib2.0-0 + + - name: Prepare venv and install Python dependencies + working-directory: application/backend + run: | + uv lock --check + uv sync --frozen --all-extras + + - name: Get OpenAPI spec + working-directory: application/backend + run: | + export PYTHONPATH=. + uv run src/cli.py gen-api --target-path openapi-spec.json + + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: openapi-spec + path: application/backend/openapi-spec.json + + python-checks: + runs-on: ubuntu-latest + permissions: + contents: read # to checkout code + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + lfs: true + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: "3.13" + + - name: Install uv + uses: astral-sh/setup-uv@a02a550bdd3185dba2ebb6aa98d77047ce54ad21 # v6.2.1 + with: + version: "0.8.8" + + - name: Prepare venv and install Python dependencies + working-directory: application/backend + run: | + uv lock --check + uv sync --frozen --all-extras + + - name: Check formatting with ruff + working-directory: application/backend + run: | + uv run ruff check --output-format=github . + uv run ruff format --check . + + - name: Check source code with mypy + working-directory: application/backend + run: | + uv run --group lint mypy . --config-file=pyproject.toml + + - name: Run unit tests + working-directory: application/backend + run: | + uv run pytest tests/unit + + ui-build: + name: Build UI + needs: + - generate-openapi-spec + runs-on: ubuntu-latest + permissions: + contents: read # to checkout code + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + lfs: true + persist-credentials: false + + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + id: setup-node + with: + node-version-file: application/ui/.nvmrc + + - name: Install dependencies + working-directory: "application/ui" + run: npm ci + + - name: Download OpenAPI spec artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: openapi-spec + path: application/ui/src/api + + - name: Build UI + working-directory: "application/ui" + run: npm run build + + - name: Compress build + working-directory: "application/ui" + run: tar -czf dist.tar.gz dist + + - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: ui-dist + path: "application/ui/dist.tar.gz" + + ui-lint: + name: Eslint checks + needs: + - generate-openapi-spec + runs-on: ubuntu-latest + permissions: + contents: read # to checkout code + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + lfs: true + persist-credentials: false + + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + id: setup-node + with: + node-version-file: application/ui/.nvmrc + + - name: Install dependencies + working-directory: "application/ui" + run: npm ci + + - name: Download OpenAPI spec artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: openapi-spec + path: application/ui/src/api + + - name: Build OpenAPI type definitions + working-directory: "application/ui" + run: npm run build:api:types + + - name: Prettier + working-directory: "application/ui" + run: npm run format:check + + - name: Eslint + working-directory: "application/ui" + run: npm run lint + + - name: Eslint cyclic imports + working-directory: "application/ui" + run: npm run cyclic-deps-check + + - name: Typescript + working-directory: "application/ui" + run: npm run type-check + + ui-unit-tests: + name: Unit tests + needs: + - generate-openapi-spec + runs-on: ubuntu-latest + permissions: + contents: read # to checkout code + steps: + - name: Checkout code + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + lfs: true + persist-credentials: false + + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + id: setup-node + with: + node-version-file: application/ui/.nvmrc + + - name: Install dependencies + working-directory: "application/ui" + run: npm ci + + - name: Download OpenAPI spec artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: openapi-spec + path: application/ui/src/api + + - name: Build OpenAPI type definitions + working-directory: "application/ui" + run: npm run build:api:types + + - name: UI Unit tests + working-directory: "application/ui" + run: npm run test:unit + + ui-playwright-tests: + name: Playwright Tests + needs: + - ui-build + - generate-openapi-spec + permissions: + contents: read # to checkout code + timeout-minutes: 60 + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright:v1.54.0-noble + steps: + - name: Install git lfs + run: apt-get update && apt-get install git-lfs + + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.2 + with: + lfs: true + persist-credentials: false + + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + id: setup-node + with: + node-version-file: application/ui/.nvmrc + + - name: Install dependencies + working-directory: "application/ui" + run: npm ci + + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: ui-dist + path: "application/ui" + + - name: Unpack build + working-directory: "application/ui" + run: tar -xzf dist.tar.gz + + - name: Download OpenAPI spec artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: openapi-spec + path: application/ui/src/api + + - name: Build OpenAPI type definitions + working-directory: "application/ui" + run: npm run build:api:types + + - name: Run Playwright tests + working-directory: "application/ui" + run: npm run test:component -- --project "Component tests" + + - name: Upload blob report to GitHub Actions Artifacts + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: always() + with: + name: playwright-report + path: application/ui/playwright-report/ + retention-days: 30 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 01d96538f8..6c3d307e67 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,9 @@ default_language_version: node: 24.2.0 +# We use a different CI setup for geti-inspect +exclude: ^application/ + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 @@ -11,6 +14,7 @@ repos: exclude: "docs/source/examples" - id: check-yaml - id: check-added-large-files + exclude: '(^|/).*\.lock$' # Exclude lock files (uv.lock, package-lock.json, etc.) - id: debug-statements - id: detect-private-key @@ -72,6 +76,7 @@ repos: rev: v4.0.0-alpha.8 hooks: - id: prettier + exclude: ^app/ui/ - repo: https://github.com/igorshubovych/markdownlint-cli rev: v0.45.0 diff --git a/application/backend/.dockerignore b/application/backend/.dockerignore new file mode 100644 index 0000000000..e3bb447325 --- /dev/null +++ b/application/backend/.dockerignore @@ -0,0 +1,4 @@ +* +!src +!pyproject.toml +!uv.lock diff --git a/application/backend/.gitignore b/application/backend/.gitignore new file mode 100644 index 0000000000..362184b188 --- /dev/null +++ b/application/backend/.gitignore @@ -0,0 +1,22 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv + +# Other files and folders +.gradio +TODO* + +data/ +logs/ +sink/ + +.idea +.DS_Store +.inspect.db \ No newline at end of file diff --git a/application/backend/.pre-commit-config.yaml b/application/backend/.pre-commit-config.yaml new file mode 100644 index 0000000000..0aeab11e56 --- /dev/null +++ b/application/backend/.pre-commit-config.yaml @@ -0,0 +1,36 @@ +# Pre-commit configuration for geti-inspect application (application-backend + UI) +# This config uses Python 3.13+ and Node.js 24.2.0+ with tool versions matching application dependencies +# Install from application/ directory with: pre-commit install --config .pre-commit-config.yaml + +default_language_version: + python: python3.13 + +repos: + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: "v0.14.3" + hooks: + # Run the linter. + - id: ruff + alias: ruff-application-backend + name: ruff (application-backend) + args: ["--fix"] + # Run the formatter + - id: ruff-format + + # application-backend: python static type checking + - repo: https://github.com/pre-commit/mirrors-mypy + rev: "v1.18.2" + hooks: + - id: mypy + alias: mypy-application-backend + name: mypy (application-backend) + additional_dependencies: [types-PyYAML, types-setuptools, types-requests, alembic] + + # Markdown linting + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.45.0 + hooks: + - id: markdownlint + alias: markdownlint-application-backend + name: markdownlint (application-backend) + args: [--config=.markdownlint.yaml] diff --git a/application/backend/pyproject.toml b/application/backend/pyproject.toml new file mode 100644 index 0000000000..c374ce1b4b --- /dev/null +++ b/application/backend/pyproject.toml @@ -0,0 +1,97 @@ +[project] +name = "geti-inspect" +version = "1.0.0" +description = "Geti inspect server" +requires-python = ">=3.13" + +dependencies = [ + "fastapi[standard]~=0.116", + "jsonschema>=4.18.0a10", + "uvicorn~=0.17", + "sqlalchemy[asyncio]~=2.0", + "aiosqlite~=0.21", + "psutil~=7.0.0", + "alembic~=1.16.4", + "aiofiles==24.1.0", + "openvino==2025.2.0", + "pydantic-settings~=2.10.1", + "aiortc~=1.13.0", + "anomalib[full]", + "torchmetrics>=1.8.2", + "opencv-python-headless~=4.12", + "watchdog==6.0.0", + "uvloop==0.21.0", + "loguru==0.7.3", + "trackio~=0.6.0", +] + +[dependency-groups] +dev = [ + "pytest~=8.3", + "pre-commit~=4.1", + "pdbpp~=0.10", + "flaky~=3.8", + "testfixtures~=8.2", + "httpx~=0.24", + "freezegun~=1.5", +] +lint = [ + "ruff~=0.11.2", + "mypy~=1.17", + "types-requests~=2.32.4", + "types-PyYAML~=6.0.12", +] + +[project.optional-dependencies] +mqtt = [ + "paho-mqtt~=2.1.0", +] + +[tool.uv.sources] +anomalib = { path = "../../", editable = true } + +[tool.ruff] +extend = "../../pyproject.toml" +target-version = "py313" +line-length = 120 + +exclude = [".venv*"] + +src = ["src"] + +lint.select = ["ARG", "E", "F", "I", "N", "UP", "YTT", "ASYNC", "S", "COM", "C4", "FA", "PIE", "PYI", "Q", "RSE", "RET", "SIM", + "TID", "TC", "PL", "RUF", "C90", "D103", "ANN001", "ANN201", "ANN205", "FAST"] +lint.ignore = ["N801", "N805","N806","N807", "N818", "COM812", "RET503", "SIM108", "SIM105", "PLR2004", + "RUF010", "TC001", "RUF012", "PLR6301", "RUF029"] +lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.pytest.ini_options] +pythonpath = ["src"] +filterwarnings = [ + "ignore:.*fork.*may lead to deadlocks.*:DeprecationWarning:multiprocessing", + "ignore:Please use.*python_multipart.*:PendingDeprecationWarning:starlette", + "ignore:Importing from timm.models.layers is deprecated.*:FutureWarning:timm", + # Suppress AsyncMock coroutine warnings - these are test artifacts from unittest.mock + # framework internals during garbage collection, not actual code issues + "ignore:coroutine.*was never awaited:RuntimeWarning:unittest.mock", + # Suppress websockets deprecation warnings from uvicorn (third-party dependency issue) + "ignore:.*websockets.legacy is deprecated.*:DeprecationWarning", + "ignore:.*WebSocketServerProtocol is deprecated.*:DeprecationWarning", + # Suppress third-party library deprecation warnings + "ignore:Jupyter is migrating its paths.*:DeprecationWarning:jupyter_client", + "ignore:.*sentry_sdk.Hub.*is deprecated.*:DeprecationWarning", +] + +[tool.ruff.lint.per-file-ignores] +"*test*.py" = ["S", "ANN", "D", "ARG", "PLR"] + +[tool.ruff.lint.isort] +split-on-trailing-comma = false + +[tool.ruff.lint.pylint] +max-args=7 + +[tool.mypy] +python_version = "3.13" +ignore_missing_imports = true +show_error_codes = true diff --git a/application/backend/run.sh b/application/backend/run.sh new file mode 100755 index 0000000000..465b27354d --- /dev/null +++ b/application/backend/run.sh @@ -0,0 +1,40 @@ +#!/bin/bash +set -euo pipefail + +# ----------------------------------------------------------------------------- +# run.sh - Script to run the Geti Tune FastAPI server +# +# Features: +# - Optionally seed the database before starting the server by setting: +# SEED_DB=true +# +# Usage: +# SEED_DB=true ./run.sh # Seed database before launching server (make sure to pre-upload model artifacts) +# ./run.sh # Run server without seeding +# +# Environment variables: +# SEED_DB If set to "true", runs `uv run app/cli seed` before starting the server. +# APP_MODULE Python module to run (default: app/main.py) +# UV_CMD Command to launch Uvicorn (default: "uv run") +# +# Requirements: +# - 'uv' CLI tool (Uvicorn) installed and available in PATH +# - Python modules and dependencies installed correctly +# ----------------------------------------------------------------------------- + +SEED_DB=${SEED_DB:-false} +APP_MODULE=${APP_MODULE:-src/main.py} +UV_CMD=${UV_CMD:-uv run} + +export PYTHONUNBUFFERED=1 +export PYTHONPATH=. + +if [[ "$SEED_DB" == "true" ]]; then + echo "Seeding the database..." + $UV_CMD src/cli.py init-db + $UV_CMD src/cli.py seed --with-model=True +fi + +echo "Starting FastAPI server..." + +exec $UV_CMD "$APP_MODULE" diff --git a/application/backend/src/alembic.ini b/application/backend/src/alembic.ini new file mode 100644 index 0000000000..d10eb029ce --- /dev/null +++ b/application/backend/src/alembic.ini @@ -0,0 +1,148 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = %(here)s/alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. +prepend_sys_path = %(here)s + + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +# Note: The database URL is dynamically set from settings in env.py, so this value is ignored. +sqlalchemy.url = sqlite:///data/geti_inspect.db + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/application/backend/src/alembic/README b/application/backend/src/alembic/README new file mode 100644 index 0000000000..16ff900716 --- /dev/null +++ b/application/backend/src/alembic/README @@ -0,0 +1,16 @@ +Generic single-database configuration. + +# Generate a new migration +``` +uv run alembic -c src/alembic.ini revision --autogenerate -m "migration_name" +``` + +# Upgrade the database to the latest revision +``` +uv run alembic -c src/alembic.ini upgrade head +``` + +# Downgrade the database to a specific revision +``` +uv run alembic -c src/alembic.ini downgrade +``` \ No newline at end of file diff --git a/application/backend/src/alembic/env.py b/application/backend/src/alembic/env.py new file mode 100644 index 0000000000..6e66ece454 --- /dev/null +++ b/application/backend/src/alembic/env.py @@ -0,0 +1,79 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from logging.config import fileConfig + +from sqlalchemy import engine_from_config, pool + +from alembic import context +from db import schema + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +target_metadata = schema.Base.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + context.execute("PRAGMA foreign_keys=ON") # Enable foreign keys for SQLite + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure(connection=connection, target_metadata=target_metadata) + context.execute("PRAGMA foreign_keys=ON") # Enable foreign keys for SQLite + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/application/backend/src/alembic/script.py.mako b/application/backend/src/alembic/script.py.mako new file mode 100644 index 0000000000..11016301e7 --- /dev/null +++ b/application/backend/src/alembic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/application/backend/src/alembic/versions/7a213a27d666_initial_schema.py b/application/backend/src/alembic/versions/7a213a27d666_initial_schema.py new file mode 100644 index 0000000000..a1ea0d39a3 --- /dev/null +++ b/application/backend/src/alembic/versions/7a213a27d666_initial_schema.py @@ -0,0 +1,147 @@ +"""Initial schema + +Revision ID: 7a213a27d666 +Revises: +Create Date: 2025-11-10 16:20:41.902076 + +""" + +from collections.abc import Sequence + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "7a213a27d666" +down_revision: str | Sequence[str] | None = None +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + """Upgrade schema - create all tables matching schema.py.""" + # Create projects table first (referenced by other tables) + op.create_table( + "projects", + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(length=255), nullable=False), + sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + + # Create jobs table (referenced by models) + op.create_table( + "jobs", + sa.Column("id", sa.String(), nullable=False), + sa.Column("project_id", sa.String(), nullable=False), + sa.Column("type", sa.String(length=64), nullable=False), + sa.Column("progress", sa.Integer(), nullable=False), + sa.Column("status", sa.String(length=64), nullable=False), + sa.Column("message", sa.Text(), nullable=False), + sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.Column("start_time", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.Column("end_time", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.Column("payload", sa.JSON(), nullable=False), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.PrimaryKeyConstraint("id"), + ) + + # Create sources table + op.create_table( + "sources", + sa.Column("id", sa.Text(), nullable=False), + sa.Column("project_id", sa.String(), nullable=False), + sa.Column("name", sa.String(length=255), nullable=False), + sa.Column("source_type", sa.String(length=50), nullable=False), + sa.Column("config_data", sa.JSON(), nullable=False), + sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name", "project_id"), + ) + + # Create sinks table + op.create_table( + "sinks", + sa.Column("id", sa.Text(), nullable=False), + sa.Column("project_id", sa.String(), nullable=False), + sa.Column("name", sa.String(length=255), nullable=False), + sa.Column("sink_type", sa.String(length=50), nullable=False), + sa.Column("rate_limit", sa.Float(), nullable=True), + sa.Column("config_data", sa.JSON(), nullable=False), + sa.Column("output_formats", sa.JSON(), nullable=False), + sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name", "project_id"), + ) + + # Create media table + op.create_table( + "media", + sa.Column("id", sa.String(), nullable=False), + sa.Column("project_id", sa.String(), nullable=False), + sa.Column("filename", sa.Text(), nullable=False), + sa.Column("size", sa.Integer(), nullable=False), + sa.Column("width", sa.Integer(), nullable=False), + sa.Column("height", sa.Integer(), nullable=False), + sa.Column("is_anomalous", sa.Boolean(), nullable=False), + sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.PrimaryKeyConstraint("id"), + ) + + # Create models table (depends on jobs) + op.create_table( + "models", + sa.Column("id", sa.Text(), nullable=False), + sa.Column("project_id", sa.String(), nullable=False), + sa.Column("name", sa.String(length=255), nullable=False), + sa.Column("format", sa.String(length=64), nullable=False), + sa.Column("threshold", sa.Float(), nullable=False), + sa.Column("export_path", sa.Text(), nullable=False), + sa.Column("is_ready", sa.Boolean(), nullable=False), + sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.Column("train_job_id", sa.String(), nullable=False), + sa.Column("size", sa.BigInteger(), nullable=True), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"]), + sa.ForeignKeyConstraint(["train_job_id"], ["jobs.id"], ondelete="RESTRICT"), + sa.PrimaryKeyConstraint("id"), + ) + + # Create pipelines table (depends on projects, sources, sinks, models) + op.create_table( + "pipelines", + sa.Column("project_id", sa.Text(), nullable=False), + sa.Column("source_id", sa.Text(), nullable=True), + sa.Column("sink_id", sa.Text(), nullable=True), + sa.Column("model_id", sa.Text(), nullable=True), + sa.Column("inference_device", sa.String(length=64), nullable=True), + sa.Column("is_running", sa.Boolean(), nullable=False), + sa.Column("is_active", sa.Boolean(), nullable=False), + sa.Column("data_collection_policies", sa.JSON(), nullable=False), + sa.Column("created_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.Column("updated_at", sa.DateTime(), server_default=sa.text("(CURRENT_TIMESTAMP)"), nullable=False), + sa.ForeignKeyConstraint(["project_id"], ["projects.id"], ondelete="CASCADE"), + sa.ForeignKeyConstraint(["source_id"], ["sources.id"], ondelete="RESTRICT"), + sa.ForeignKeyConstraint(["sink_id"], ["sinks.id"], ondelete="RESTRICT"), + sa.ForeignKeyConstraint(["model_id"], ["models.id"], ondelete="RESTRICT"), + sa.PrimaryKeyConstraint("project_id"), + ) + + +def downgrade() -> None: + """Downgrade schema - drop all tables in reverse order.""" + # Drop tables in reverse order of dependencies + op.drop_table("pipelines") + op.drop_table("models") + op.drop_table("media") + op.drop_table("sinks") + op.drop_table("sources") + op.drop_table("jobs") + op.drop_table("projects") diff --git a/application/backend/src/api/__init__.py b/application/backend/src/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/application/backend/src/api/dependencies.py b/application/backend/src/api/dependencies.py new file mode 100644 index 0000000000..dc3096de52 --- /dev/null +++ b/application/backend/src/api/dependencies.py @@ -0,0 +1,164 @@ +from functools import lru_cache +from typing import Annotated +from uuid import UUID + +from fastapi import Depends, Form, HTTPException, Request, status + +from core import Scheduler +from services import ( + ActivePipelineService, + ConfigurationService, + JobService, + MediaService, + PipelineService, + ProjectService, +) +from services.metrics_service import MetricsService +from services.model_service import ModelService +from services.pipeline_metrics_service import PipelineMetricsService +from webrtc.manager import WebRTCManager + + +async def get_project_service() -> ProjectService: + """Provides a ProjectService""" + return ProjectService() + + +async def get_job_service() -> JobService: + """Provides a JobService""" + return JobService() + + +async def get_media_service() -> MediaService: + """Provides a MediaService""" + return MediaService() + + +async def get_scheduler(request: Request) -> Scheduler: + """Provides the global Scheduler instance.""" + return request.app.state.scheduler + + +async def get_model_service(scheduler: Annotated[Scheduler, Depends(get_scheduler)]) -> ModelService: + """Provides a ModelService with access to the model reload event""" + return ModelService(mp_model_reload_event=scheduler.mp_model_reload_event) + + +@lru_cache +def get_metrics_service(scheduler: Annotated[Scheduler, Depends(get_scheduler)]) -> MetricsService: + """Provides a MetricsService instance for collecting and retrieving metrics.""" + return MetricsService(scheduler.shm_metrics.name, scheduler.shm_metrics_lock) + + +async def get_active_pipeline_service(scheduler: Annotated[Scheduler, Depends(get_scheduler)]) -> ActivePipelineService: + """ + Provides an ActivePipelineService instance for managing the active pipeline. + + This dependency is designed for use in FastAPI endpoints and creates a service + without the daemon thread. For worker processes, create ActivePipelineService + directly with the config_changed_condition. + """ + # Create service without daemon thread for API endpoints + return await ActivePipelineService.create(scheduler.mp_config_changed_condition) + + +@lru_cache +def get_configuration_service( + active_pipeline_service: Annotated[ActivePipelineService, Depends(get_active_pipeline_service)], + scheduler: Annotated[Scheduler, Depends(get_scheduler)], +) -> ConfigurationService: + """Provides a ConfigurationService instance with the active pipeline service and config changed condition.""" + return ConfigurationService( + active_pipeline_service=active_pipeline_service, + config_changed_condition=scheduler.mp_config_changed_condition, + ) + + +@lru_cache +def get_pipeline_service( + active_pipeline_service: Annotated[ActivePipelineService, Depends(get_active_pipeline_service)], + scheduler: Annotated[Scheduler, Depends(get_scheduler)], + model_service: Annotated[ModelService, Depends(get_model_service)], +) -> PipelineService: + """Provides a PipelineService instance with the active pipeline service and config changed condition.""" + return PipelineService( + active_pipeline_service=active_pipeline_service, + config_changed_condition=scheduler.mp_config_changed_condition, + model_service=model_service, + ) + + +def get_pipeline_metrics_service( + pipeline_service: Annotated[PipelineService, Depends(get_pipeline_service)], + metrics_service: Annotated[MetricsService, Depends(get_metrics_service)], +) -> PipelineMetricsService: + """Provides a PipelineMetricsService instance.""" + return PipelineMetricsService( + pipeline_service=pipeline_service, + metrics_service=metrics_service, + ) + + +def is_valid_uuid(identifier: str) -> bool: + """ + Check if a given string identifier is formatted as a valid UUID + + :param identifier: String to check + :return: True if valid UUID, False otherwise + """ + try: + UUID(identifier) + except ValueError: + return False + return True + + +def get_uuid(identifier: str, name: str) -> UUID: + """Initializes and validates a source ID""" + if not is_valid_uuid(identifier): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid {name} ID") + return UUID(identifier) + + +async def get_source_id(source_id: str) -> UUID: + """Initializes and validates a source ID""" + if not is_valid_uuid(source_id): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid source ID") + return UUID(source_id) + + +async def get_sink_id(sink_id: str) -> UUID: + """Initializes and validates a sink ID""" + if not is_valid_uuid(sink_id): + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid sink ID") + return UUID(sink_id) + + +async def get_project_id(project_id: str) -> UUID: + """Initializes and validates a project ID""" + return get_uuid(project_id, "project") + + +async def get_media_id(media_id: str) -> UUID: + """Initializes and validates a media ID""" + return get_uuid(media_id, "media") + + +async def get_model_id(model_id: str) -> UUID: + """Initializes and validates a media ID""" + return get_uuid(model_id, "model") + + +async def get_job_id(job_id: str) -> UUID: + """Initializes and validates a job ID""" + return get_uuid(job_id, "job") + + +async def get_webrtc_manager(request: Request) -> WebRTCManager: + """Provides the global WebRTCManager instance from FastAPI application's state.""" + return request.app.state.webrtc_manager + + +async def get_device_name(device: Annotated[str | None, Form()] = None) -> str | None: + """Converts the device name to uppercase if provided""" + return device.upper() if device is not None else None diff --git a/application/backend/src/api/endpoints/__init__.py b/application/backend/src/api/endpoints/__init__.py new file mode 100644 index 0000000000..78c2df90e3 --- /dev/null +++ b/application/backend/src/api/endpoints/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +API_PREFIX = "/api" diff --git a/application/backend/src/api/endpoints/active_pipeline_endpoints.py b/application/backend/src/api/endpoints/active_pipeline_endpoints.py new file mode 100644 index 0000000000..31f7066460 --- /dev/null +++ b/application/backend/src/api/endpoints/active_pipeline_endpoints.py @@ -0,0 +1,29 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""Endpoints for managing active pipelines""" + +from typing import Annotated + +from fastapi import APIRouter, Depends, status + +from api.dependencies import get_pipeline_service +from pydantic_models.pipeline import Pipeline +from services import PipelineService + +router = APIRouter(prefix="/api/active-pipeline", tags=["Pipelines"]) + + +@router.get( + "", + responses={ + status.HTTP_200_OK: {"description": "Active pipeline found"}, + status.HTTP_204_NO_CONTENT: {"description": "No active pipeline found"}, + }, + response_model_exclude_none=True, +) +async def get_active_pipeline( + pipeline_service: Annotated[PipelineService, Depends(get_pipeline_service)], +) -> Pipeline | None: + """Get info about the active pipeline""" + return await pipeline_service.get_active_pipeline() diff --git a/application/backend/src/api/endpoints/devices_endpoints.py b/application/backend/src/api/endpoints/devices_endpoints.py new file mode 100644 index 0000000000..13ee1d2fe7 --- /dev/null +++ b/application/backend/src/api/endpoints/devices_endpoints.py @@ -0,0 +1,25 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from fastapi import APIRouter + +from api.endpoints import API_PREFIX +from pydantic_models.devices import DeviceList +from utils.devices import Devices + +device_router = APIRouter( + prefix=API_PREFIX, + tags=["Job"], +) + + +@device_router.get("/inference-devices") +async def get_inference_devices() -> DeviceList: + """Endpoint to get list of supported devices for inference""" + return DeviceList(devices=Devices.inference_devices()) + + +@device_router.get("/training-devices") +async def get_training_devices() -> DeviceList: + """Endpoint to get list of supported devices for training""" + return DeviceList(devices=Devices.training_devices()) diff --git a/application/backend/src/api/endpoints/job_endpoints.py b/application/backend/src/api/endpoints/job_endpoints.py new file mode 100644 index 0000000000..bb01e187fb --- /dev/null +++ b/application/backend/src/api/endpoints/job_endpoints.py @@ -0,0 +1,62 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from typing import Annotated +from uuid import UUID + +from fastapi import APIRouter, Body, Depends, status +from sse_starlette import EventSourceResponse + +from api.dependencies import get_job_id, get_job_service +from api.endpoints import API_PREFIX +from pydantic_models import JobList +from pydantic_models.job import JobCancelled, JobSubmitted, TrainJobPayload +from services import JobService + +job_api_prefix_url = API_PREFIX + "/jobs" +job_router = APIRouter( + prefix=job_api_prefix_url, + tags=["Job"], +) + + +@job_router.get("", response_model_exclude_none=True) +async def get_jobs(job_service: Annotated[JobService, Depends(get_job_service)]) -> JobList: + """Endpoint to get list of all jobs""" + return await job_service.get_job_list() + + +@job_router.post(":train") +async def submit_train_job( + job_service: Annotated[JobService, Depends(get_job_service)], + payload: Annotated[TrainJobPayload, Body()], +) -> JobSubmitted: + """Endpoint to submit a training job""" + return await job_service.submit_train_job(payload=payload) + + +@job_router.get("/{job_id}/logs") +async def get_job_logs( + job_id: Annotated[UUID, Depends(get_job_id)], + job_service: Annotated[JobService, Depends(get_job_service)], +) -> EventSourceResponse: + """Endpoint to get the logs of a job by its ID""" + return EventSourceResponse(job_service.stream_logs(job_id=job_id)) + + +@job_router.get("/{job_id}/progress") +async def get_job_progress( + job_id: Annotated[UUID, Depends(get_job_id)], + job_service: Annotated[JobService, Depends(get_job_service)], +) -> EventSourceResponse: + """Endpoint to get the progress of a job by its ID""" + return EventSourceResponse(job_service.stream_progress(job_id=job_id)) + + +@job_router.post("/{job_id}:cancel", status_code=status.HTTP_202_ACCEPTED) +async def cancel_job( + job_id: Annotated[UUID, Depends(get_job_id)], + job_service: Annotated[JobService, Depends(get_job_service)], +) -> JobCancelled: + """Endpoint to cancel a job by its ID""" + return await job_service.cancel_job(job_id=job_id) diff --git a/application/backend/src/api/endpoints/media_endpoints.py b/application/backend/src/api/endpoints/media_endpoints.py new file mode 100644 index 0000000000..98d4a9d056 --- /dev/null +++ b/application/backend/src/api/endpoints/media_endpoints.py @@ -0,0 +1,131 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from typing import Annotated +from uuid import UUID + +from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, UploadFile, status +from fastapi.responses import FileResponse + +from api.dependencies import get_media_id, get_media_service, get_project_id +from api.endpoints import API_PREFIX +from api.media_rest_validator import MediaRestValidator +from pydantic_models import Media, MediaList +from services.media_service import MediaService + +media_api_prefix_url = API_PREFIX + "/projects/{project_id}" +media_router = APIRouter( + prefix=media_api_prefix_url, + tags=["media"], +) + + +@media_router.get( + "/images", + responses={ + status.HTTP_200_OK: {"description": "List of media items retrieved successfully"}, + status.HTTP_404_NOT_FOUND: {"description": "Project not found"}, + }, +) +async def get_media_list( + media_service: Annotated[MediaService, Depends(get_media_service)], + project_id: Annotated[UUID, Depends(get_project_id)], +) -> MediaList: + """Endpoint to get list of all media""" + return await media_service.get_media_list(project_id=project_id) + + +@media_router.get( + "/images/{media_id}/full", + response_model_exclude_none=True, + responses={ + status.HTTP_200_OK: {"description": "Media file retrieved successfully"}, + status.HTTP_404_NOT_FOUND: {"description": "Media not found"}, + }, +) +async def get_media( + media_service: Annotated[MediaService, Depends(get_media_service)], + project_id: Annotated[UUID, Depends(get_project_id)], + media_id: Annotated[UUID, Depends(get_media_id)], +) -> FileResponse: + """Endpoint to get media metadata by ID""" + try: + return FileResponse(await media_service.get_media_file_path(project_id=project_id, media_id=media_id)) + except FileNotFoundError as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Media with ID {media_id} not found", + ) from e + + +@media_router.get( + "/images/{media_id}/thumbnail", + response_model_exclude_none=True, + responses={ + status.HTTP_200_OK: {"description": "Thumbnail retrieved successfully"}, + status.HTTP_404_NOT_FOUND: {"description": "Media not found"}, + }, +) +async def get_media_thumbnail( + media_service: Annotated[MediaService, Depends(get_media_service)], + project_id: Annotated[UUID, Depends(get_project_id)], + media_id: Annotated[UUID, Depends(get_media_id)], +) -> FileResponse: + """Return a PNG thumbnail for the requested image.""" + try: + return FileResponse(await media_service.get_thumbnail_file_path(project_id=project_id, media_id=media_id)) + except FileNotFoundError as e: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Media with ID {media_id} not found", + ) from e + + +@media_router.delete( + "/images/{media_id}", + status_code=status.HTTP_204_NO_CONTENT, + responses={ + status.HTTP_204_NO_CONTENT: { + "description": "Image successfully deleted", + }, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid image ID"}, + status.HTTP_404_NOT_FOUND: {"description": "Image not found"}, + }, +) +async def delete_media( + media_service: Annotated[MediaService, Depends(get_media_service)], + project_id: Annotated[UUID, Depends(get_project_id)], + media_id: Annotated[UUID, Depends(get_media_id)], +) -> None: + """Remove an image""" + await media_service.delete_media(media_id=media_id, project_id=project_id) + + +@media_router.post( + "/capture", + response_model_exclude_none=True, + status_code=status.HTTP_201_CREATED, + responses={ + status.HTTP_201_CREATED: {"description": "Image captured successfully"}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid image file"}, + }, +) +async def capture_image( + media_service: Annotated[MediaService, Depends(get_media_service)], + project_id: Annotated[UUID, Depends(get_project_id)], + file: Annotated[UploadFile, Depends(MediaRestValidator.validate_image_file)], + background_tasks: BackgroundTasks, +) -> Media: + """Endpoint to capture an image""" + image_bytes = await file.read() + media = await media_service.upload_image( + project_id=project_id, file=file, image_bytes=image_bytes, is_anomalous=False + ) + + background_tasks.add_task( + media_service.generate_thumbnail, + project_id=project_id, + media_id=media.id, + image_bytes=image_bytes, + ) + return media diff --git a/application/backend/src/api/endpoints/model_endpoints.py b/application/backend/src/api/endpoints/model_endpoints.py new file mode 100644 index 0000000000..f5e1c9436d --- /dev/null +++ b/application/backend/src/api/endpoints/model_endpoints.py @@ -0,0 +1,88 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from typing import Annotated +from uuid import UUID + +from fastapi import APIRouter, Depends, HTTPException, Request, UploadFile, status + +from api.dependencies import get_device_name, get_model_id, get_model_service, get_project_id +from api.endpoints.project_endpoints import project_api_prefix_url +from api.media_rest_validator import MediaRestValidator +from exceptions import ResourceNotFoundException +from pydantic_models import Model, ModelList, PredictionResponse +from services import ModelService +from services.exceptions import DeviceNotFoundError + +model_api_prefix_url = project_api_prefix_url + "/{project_id}/models" +model_router = APIRouter( + prefix=model_api_prefix_url, + tags=["Model"], +) + + +@model_router.get("") +async def get_models( + model_service: Annotated[ModelService, Depends(get_model_service)], + project_id: Annotated[UUID, Depends(get_project_id)], +) -> ModelList: + """Endpoint to get list of all models""" + return await model_service.get_model_list(project_id=project_id) + + +@model_router.get("/{model_id}") +async def get_model_info_by_id( + model_service: Annotated[ModelService, Depends(get_model_service)], + project_id: Annotated[UUID, Depends(get_project_id)], + model_id: Annotated[UUID, Depends(get_model_id)], +) -> Model: + """Endpoint to get model metadata by ID""" + model = await model_service.get_model_by_id(project_id=project_id, model_id=model_id) + if model is None: + raise ResourceNotFoundException(resource_id=model_id, resource_name="model") + return model + + +@model_router.post("/{model_id}:predict") +async def predict( + request: Request, + model_service: Annotated[ModelService, Depends(get_model_service)], + project_id: Annotated[UUID, Depends(get_project_id)], + model_id: Annotated[UUID, Depends(get_model_id)], + file: Annotated[UploadFile, Depends(MediaRestValidator.validate_image_file)], + device: Annotated[str | None, Depends(get_device_name)] = None, +) -> PredictionResponse: + """ + Run prediction on an uploaded image using the specified model. + + Returns prediction results including anomaly map, label, and confidence score. + """ + # Get model from database + model = await model_service.get_model_by_id(project_id=project_id, model_id=model_id) + if model is None: + raise ResourceNotFoundException(resource_id=model_id, resource_name="model") + + # Read uploaded image and run prediction with model caching + # Models are cached in request.app.state.active_models for performance + image_bytes = await file.read() + try: + return await model_service.predict_image(model, image_bytes, request.app.state.active_models, device=device) + except DeviceNotFoundError as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) + + +@model_router.delete( + "/{model_id}", + status_code=status.HTTP_204_NO_CONTENT, + responses={ + status.HTTP_204_NO_CONTENT: {"description": "Model and exported artifacts successfully deleted"}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid model ID"}, + status.HTTP_404_NOT_FOUND: {"description": "Model not found"}, + }, +) +async def delete_model( + model_service: Annotated[ModelService, Depends(get_model_service)], + project_id: Annotated[UUID, Depends(get_project_id)], + model_id: Annotated[UUID, Depends(get_model_id)], +) -> None: + """Delete a model and any exported artifacts.""" + await model_service.delete_model(project_id=project_id, model_id=model_id) diff --git a/application/backend/src/api/endpoints/pipeline_endpoints.py b/application/backend/src/api/endpoints/pipeline_endpoints.py new file mode 100644 index 0000000000..f3cdd9fb1c --- /dev/null +++ b/application/backend/src/api/endpoints/pipeline_endpoints.py @@ -0,0 +1,227 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""Endpoints for managing pipelines""" + +from typing import Annotated +from uuid import UUID + +from fastapi import APIRouter, Body, Depends, status +from fastapi.exceptions import HTTPException +from fastapi.openapi.models import Example +from pydantic import ValidationError + +from api.dependencies import get_pipeline_metrics_service, get_pipeline_service, get_project_id +from pydantic_models.metrics import PipelineMetrics +from pydantic_models.pipeline import Pipeline, PipelineStatus +from services import PipelineService +from services.pipeline_metrics_service import PipelineMetricsService + +router = APIRouter(prefix="/api/projects/{project_id}/pipeline", tags=["Pipelines"]) + +UPDATE_PIPELINE_BODY_DESCRIPTION = """ +Partial pipeline configuration update. May contain any subset of fields including 'name', 'source_id', +'sink_id', or 'model_id'. Fields not included in the request will remain unchanged. +""" +UPDATE_PIPELINE_BODY_EXAMPLES = { + "switch_model": Example( + summary="Switch active model", + description="Change the active model for the pipeline", + value={ + "model_id": "c1feaabc-da2b-442e-9b3e-55c11c2c2ff3", + }, + ), + "reconfigure": Example( + summary="Reconfigure pipeline", + description="Change the name, source and sink of the pipeline", + value={ + "name": "Updated Production Pipeline", + "source_id": "e3cbd8d0-17b8-463e-85a2-4aaed031674e", + "sink_id": "c6787c06-964b-4097-8eca-238b8cf79fc9", + }, + ), +} + + +@router.get( + "", + responses={ + status.HTTP_200_OK: {"description": "Pipeline found"}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid project ID"}, + status.HTTP_404_NOT_FOUND: {"description": "Pipeline not found"}, + }, + response_model_exclude_none=True, +) +async def get_pipeline( + project_id: Annotated[UUID, Depends(get_project_id)], + pipeline_service: Annotated[PipelineService, Depends(get_pipeline_service)], +) -> Pipeline: + """Get info about a given pipeline""" + return await pipeline_service.get_pipeline_by_id(project_id) + + +@router.patch( + "", + responses={ + status.HTTP_200_OK: {"description": "Pipeline successfully reconfigured"}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid project ID or request body"}, + status.HTTP_404_NOT_FOUND: {"description": "Pipeline not found"}, + status.HTTP_409_CONFLICT: {"description": "Pipeline cannot be reconfigured"}, + }, + response_model_exclude_none=True, +) +async def update_pipeline( + project_id: Annotated[UUID, Depends(get_project_id)], + pipeline_config: Annotated[ + dict, + Body( + description=UPDATE_PIPELINE_BODY_DESCRIPTION, + openapi_examples=UPDATE_PIPELINE_BODY_EXAMPLES, + ), + ], + pipeline_service: Annotated[PipelineService, Depends(get_pipeline_service)], +) -> Pipeline: + """Reconfigure an existing pipeline""" + if "status" in pipeline_config: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="The 'status' field cannot be changed") + try: + return await pipeline_service.update_pipeline(project_id, pipeline_config) + except ValidationError as e: + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e)) + + +@router.post( + ":run", + status_code=status.HTTP_204_NO_CONTENT, + responses={ + status.HTTP_204_NO_CONTENT: { + "description": "Pipeline successfully set to run (also activates it if not already active)." + }, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid project ID"}, + status.HTTP_404_NOT_FOUND: {"description": "Pipeline not found"}, + status.HTTP_409_CONFLICT: {"description": "Pipeline cannot be run"}, + }, +) +async def run_pipeline( + project_id: Annotated[UUID, Depends(get_project_id)], + pipeline_service: Annotated[PipelineService, Depends(get_pipeline_service)], +) -> None: + """ + Set the pipeline status to RUNNING. + The pipeline will start processing data from the source, run it through the model, and send results to the sink. + If the pipeline is not yet activated, it will be activated as well. + """ + try: + await pipeline_service.activate_pipeline(project_id, set_running=True) + except ValidationError as e: + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e)) + + +@router.post( + ":stop", + status_code=status.HTTP_204_NO_CONTENT, + responses={ + status.HTTP_204_NO_CONTENT: {"description": "Pipeline stopped successfully."}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid project ID"}, + status.HTTP_404_NOT_FOUND: {"description": "Pipeline not found"}, + status.HTTP_409_CONFLICT: {"description": "Pipeline cannot be stopped"}, + }, +) +async def stop_pipeline( + project_id: Annotated[UUID, Depends(get_project_id)], + pipeline_service: Annotated[PipelineService, Depends(get_pipeline_service)], +) -> None: + """ + Stops the running pipeline but does not deactivate it. + This keeps the pipeline activated, switching to source passthrough mode. + """ + active_pipeline = await pipeline_service.get_active_pipeline() + if active_pipeline and active_pipeline.project_id != project_id: + reason = f"another pipeline `{active_pipeline.project_id}` is currently {active_pipeline.status}." + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Cannot stop pipeline `{project_id}`: {reason}", + ) + if not (active_pipeline and active_pipeline.project_id == project_id and active_pipeline.status.is_running): + reason = "the pipeline is not running." + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Cannot stop pipeline `{project_id}`: {reason}", + ) + try: + await pipeline_service.update_pipeline(project_id, {"status": PipelineStatus.ACTIVE}) + except ValidationError as e: + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e)) + + +@router.post( + ":activate", + status_code=status.HTTP_204_NO_CONTENT, + responses={ + status.HTTP_204_NO_CONTENT: { + "description": "Pipeline successfully activated (but not necessarily running yet)." + }, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid project ID"}, + status.HTTP_404_NOT_FOUND: {"description": "Pipeline not found"}, + status.HTTP_409_CONFLICT: {"description": "Pipeline cannot be activated"}, + }, +) +async def activate_pipeline( + project_id: Annotated[UUID, Depends(get_project_id)], + pipeline_service: Annotated[PipelineService, Depends(get_pipeline_service)], +) -> None: + """ + Set the pipeline status to ACTIVE. + The pipeline will be ready to run but will not start processing data until set to RUNNING. + """ + try: + await pipeline_service.activate_pipeline(project_id) + except ValidationError as e: + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e)) + + +@router.post( + ":disable", + status_code=status.HTTP_204_NO_CONTENT, + responses={ + status.HTTP_204_NO_CONTENT: {"description": "Pipeline successfully disabled"}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid project ID"}, + status.HTTP_404_NOT_FOUND: {"description": "Pipeline not found"}, + }, +) +async def disable_pipeline( + project_id: Annotated[UUID, Depends(get_project_id)], + pipeline_service: Annotated[PipelineService, Depends(get_pipeline_service)], +) -> None: + """Stop a pipeline. The pipeline will become idle, and it won't process any data until re-enabled.""" + await pipeline_service.update_pipeline(project_id, {"status": PipelineStatus.IDLE}) + + +@router.get( + "/metrics", + responses={ + status.HTTP_200_OK: {"description": "Pipeline metrics successfully calculated"}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid project ID or duration parameter"}, + status.HTTP_404_NOT_FOUND: {"description": "Pipeline not found"}, + }, +) +async def get_project_metrics( + project_id: Annotated[UUID, Depends(get_project_id)], + pipeline_metrics_service: Annotated[PipelineMetricsService, Depends(get_pipeline_metrics_service)], + time_window: int = 60, +) -> PipelineMetrics: + """ + Calculate model metrics for a pipeline over a specified time window. + + Returns inference latency metrics including average, min, max, 95th percentile, + and latest latency measurements over the specified duration. + """ + if time_window <= 0 or time_window > 3600: # Limit to 1 hour max + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Duration must be between 1 and 3600 seconds" + ) + + try: + return await pipeline_metrics_service.get_pipeline_metrics(project_id, time_window) + except ValueError as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e)) diff --git a/application/backend/src/api/endpoints/project_endpoints.py b/application/backend/src/api/endpoints/project_endpoints.py new file mode 100644 index 0000000000..09896973de --- /dev/null +++ b/application/backend/src/api/endpoints/project_endpoints.py @@ -0,0 +1,45 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from typing import Annotated +from uuid import UUID + +from fastapi import APIRouter, Body, Depends, HTTPException, status + +from api.dependencies import get_project_id, get_project_service +from api.endpoints import API_PREFIX +from pydantic_models import Project, ProjectList +from services import ProjectService + +project_api_prefix_url = API_PREFIX + "/projects" +project_router = APIRouter( + prefix=project_api_prefix_url, + tags=["Project"], +) + + +@project_router.get("") +async def get_projects(project_service: Annotated[ProjectService, Depends(get_project_service)]) -> ProjectList: + """Endpoint to get list of all projects""" + return await project_service.get_project_list() + + +@project_router.post("") +async def create_project( + project_service: Annotated[ProjectService, Depends(get_project_service)], + project: Annotated[Project, Body()], +) -> Project: + """Endpoint to create a new project""" + return await project_service.create_project(project) + + +@project_router.get("/{project_id}") +async def get_project_by_id( + project_service: Annotated[ProjectService, Depends(get_project_service)], + project_id: Annotated[UUID, Depends(get_project_id)], +) -> Project: + """Endpoint to get project metadata by ID""" + project = await project_service.get_project_by_id(project_id) + if project is None: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Project not found") + return project diff --git a/application/backend/src/api/endpoints/sink_endpoints.py b/application/backend/src/api/endpoints/sink_endpoints.py new file mode 100644 index 0000000000..03762697e4 --- /dev/null +++ b/application/backend/src/api/endpoints/sink_endpoints.py @@ -0,0 +1,266 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""Endpoints for managing pipeline sinks""" + +from typing import Annotated +from uuid import UUID + +import yaml +from fastapi import APIRouter, Body, Depends, File, UploadFile, status +from fastapi.exceptions import HTTPException +from fastapi.openapi.models import Example +from fastapi.responses import FileResponse, Response +from sqlalchemy.exc import IntegrityError + +from api.dependencies import get_configuration_service, get_project_id, get_sink_id +from pydantic_models import Sink, SinkType +from pydantic_models.sink import SinkAdapter, SinkCreate, SinkCreateAdapter +from services import ConfigurationService, ResourceAlreadyExistsError, ResourceInUseError, ResourceNotFoundError + +router = APIRouter(prefix="/api/projects/{project_id}/sinks", tags=["Sinks"]) + +CREATE_SINK_BODY_DESCRIPTION = """ +Configuration for the new sink. The exact list of fields that can be configured depends on the sink type. +""" +CREATE_SINK_BODY_EXAMPLES = { + "folder": Example( + summary="Folder sink", + description="Configuration for a local filesystem folder sink", + value={ + "sink_type": "folder", + "name": "My Output Folder", + "folder_path": "/path/to/output", + "output_formats": ["image_with_predictions"], + "rate_limit": 0.2, + }, + ), + "mqtt": Example( + summary="MQTT sink", + description="Configuration for an MQTT message broker sink", + value={ + "sink_type": "mqtt", + "name": "Local MQTT Broker", + "broker_host": "localhost", + "broker_port": 1883, + "topic": "predictions", + "output_formats": ["predictions"], + }, + ), +} + +UPDATE_SINK_BODY_DESCRIPTION = """ +Partial sink configuration update. May contain any subset of fields from the respective sink type +(e.g., 'broker_host' and 'broker_port' for MQTT; 'folder_path' for folder sinks). +Fields not included in the request will remain unchanged. The 'sink_type' field cannot be changed. +""" +UPDATE_SINK_BODY_EXAMPLES = { + "folder": Example( + summary="Update folder sink", + description="Change the output path for a folder sink", + value={ + "folder_path": "/new/output/directory", + }, + ), + "mqtt": Example( + summary="Update MQTT sink", + description="Change the topic for an MQTT sink", + value={ + "topic": "new_predictions_topic", + }, + ), +} + + +@router.post( + "", + status_code=status.HTTP_201_CREATED, + responses={ + status.HTTP_201_CREATED: {"description": "Sink created", "model": Sink}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid sink ID or request body"}, + status.HTTP_409_CONFLICT: {"description": "Sink already exists"}, + }, +) +async def create_sink( + project_id: Annotated[UUID, Depends(get_project_id)], + sink_config: Annotated[ + SinkCreate, Body(description=CREATE_SINK_BODY_DESCRIPTION, openapi_examples=CREATE_SINK_BODY_EXAMPLES) + ], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> Sink: + """Create and configure a new sink""" + # Inject project_id from URL path into the config + sink_config.project_id = project_id + + # Validate the complete config + validated_sink = SinkCreateAdapter.validate_python(sink_config) + + if validated_sink.sink_type == SinkType.DISCONNECTED: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The sink with sink_type=DISCONNECTED cannot be created", + ) + + try: + return await configuration_service.create_sink(validated_sink) + except ResourceAlreadyExistsError as e: + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e)) + except IntegrityError: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Name must be unique within a project") + + +@router.get( + "", + responses={ + status.HTTP_200_OK: {"description": "List of available sink configurations", "model": list[Sink]}, + }, +) +async def list_sinks( + project_id: Annotated[UUID, Depends(get_project_id)], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> list[Sink]: + """List the available sinks""" + return await configuration_service.list_sinks(project_id) + + +@router.get( + "/{sink_id}", + responses={ + status.HTTP_200_OK: {"description": "Sink found", "model": Sink}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid sink ID"}, + status.HTTP_404_NOT_FOUND: {"description": "Sink not found"}, + }, +) +async def get_sink( + project_id: Annotated[UUID, Depends(get_project_id)], + sink_id: Annotated[UUID, Depends(get_sink_id)], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> Sink: + """Get info about a sink""" + try: + return await configuration_service.get_sink_by_id(sink_id, project_id) + except ResourceNotFoundError as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) + + +@router.patch( + "/{sink_id}", + responses={ + status.HTTP_200_OK: {"description": "Sink successfully updated", "model": Sink}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid sink ID or request body"}, + status.HTTP_404_NOT_FOUND: {"description": "Sink not found"}, + }, +) +async def update_sink( + project_id: Annotated[UUID, Depends(get_project_id)], + sink_id: Annotated[UUID, Depends(get_sink_id)], + sink_config: Annotated[ + dict, + Body( + description=UPDATE_SINK_BODY_DESCRIPTION, + openapi_examples=UPDATE_SINK_BODY_EXAMPLES, + ), + ], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> Sink: + """Reconfigure an existing sink""" + if "sink_type" in sink_config: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="The 'sink_type' field cannot be changed") + try: + return await configuration_service.update_sink(sink_id, project_id, sink_config) + except ResourceNotFoundError as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) + + +@router.post( + "/{sink_id}:export", + response_class=FileResponse, + responses={ + status.HTTP_200_OK: { + "description": "Sink configuration exported as a YAML file", + "content": { + "application/x-yaml": {"schema": {"type": "string", "format": "binary"}}, + }, + }, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid sink ID or request body"}, + status.HTTP_404_NOT_FOUND: {"description": "Sink not found"}, + }, +) +async def export_sink( + project_id: Annotated[UUID, Depends(get_project_id)], + sink_id: Annotated[UUID, Depends(get_sink_id)], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> Response: + """Export a sink to file""" + sink = await configuration_service.get_sink_by_id(sink_id, project_id) + if not sink: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"Sink with ID {sink_id} not found", + ) + + yaml_content = yaml.safe_dump(sink.model_dump(mode="json", exclude={"id", "project_id"})) + + return Response( + content=yaml_content.encode("utf-8"), + media_type="application/x-yaml", + headers={"Content-Disposition": f"attachment; filename=sink_{sink_id}.yaml"}, + ) + + +@router.post( + ":import", + status_code=status.HTTP_201_CREATED, + responses={ + status.HTTP_201_CREATED: {"description": "Sink imported successfully", "model": Sink}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid YAML format or sink type is DISCONNECTED"}, + }, +) +async def import_sink( + project_id: Annotated[UUID, Depends(get_project_id)], + yaml_file: Annotated[UploadFile, File(description="YAML file containing the sink configuration")], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> Sink: + """Import a sink from file""" + try: + yaml_content = await yaml_file.read() + sink_data = yaml.safe_load(yaml_content) + + # Inject project_id from URL path + sink_data["project_id"] = str(project_id) + + sink_config = SinkAdapter.validate_python(sink_data) + if sink_config.sink_type == SinkType.DISCONNECTED: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The sink with sink_type=DISCONNECTED cannot be imported", + ) + return await configuration_service.create_sink(sink_config) + except yaml.YAMLError as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid YAML format: {str(e)}") + + +@router.delete( + "/{sink_id}", + status_code=status.HTTP_204_NO_CONTENT, + responses={ + status.HTTP_204_NO_CONTENT: { + "description": "Sink configuration successfully deleted", + }, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid sink ID"}, + status.HTTP_404_NOT_FOUND: {"description": "Sink not found"}, + status.HTTP_409_CONFLICT: {"description": "Sink is used by at least one pipeline"}, + }, +) +async def delete_sink( + project_id: Annotated[UUID, Depends(get_project_id)], + sink_id: Annotated[UUID, Depends(get_sink_id)], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> None: + """Remove a sink""" + try: + await configuration_service.delete_sink_by_id(sink_id, project_id) + except ResourceNotFoundError as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) + except ResourceInUseError as e: + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e)) diff --git a/application/backend/src/api/endpoints/source_endpoints.py b/application/backend/src/api/endpoints/source_endpoints.py new file mode 100644 index 0000000000..d80b866f19 --- /dev/null +++ b/application/backend/src/api/endpoints/source_endpoints.py @@ -0,0 +1,277 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""Endpoints for managing pipeline sources""" + +from typing import Annotated +from uuid import UUID + +import yaml +from fastapi import APIRouter, Body, Depends, File, UploadFile, status +from fastapi.exceptions import HTTPException +from fastapi.openapi.models import Example +from fastapi.responses import FileResponse, Response +from sqlalchemy.exc import IntegrityError + +from api.dependencies import get_configuration_service, get_project_id, get_source_id +from pydantic_models import Source, SourceType +from pydantic_models.source import SourceAdapter, SourceCreate, SourceCreateAdapter +from services import ConfigurationService, ResourceAlreadyExistsError, ResourceInUseError, ResourceNotFoundError + +router = APIRouter(prefix="/api/projects/{project_id}/sources", tags=["Sources"]) + +CREATE_SOURCE_BODY_DESCRIPTION = """ +Configuration for the new source. The exact list of fields that can be configured depends on the source type. +""" +CREATE_SOURCE_BODY_EXAMPLES = { + "webcam": Example( + summary="Webcam source", + description="Configuration for a webcam source", + value={ + "source_type": "webcam", + "name": "My Webcam", + "device_id": 0, + }, + ), + "ip_camera": Example( + summary="IP camera source", + description="Configuration for an IP camera source", + value={ + "source_type": "ip_camera", + "name": "IP Camera 1", + "stream_url": "rtsp://192.168.1.100:554/stream1", + "auth_required": True, + }, + ), + "video_file": Example( + summary="Video file source", + description="Configuration for a video file source", + value={ + "source_type": "video_file", + "name": "Camera recording 123", + "video_path": "/path/to/video.mp4", + }, + ), + "images_folder": Example( + summary="Images folder source", + description="Configuration for a folder containing images source", + value={ + "source_type": "images_folder", + "name": "Production Samples", + "folder_path": "/path/to/images", + "ignore_existing_images": True, + }, + ), +} + +UPDATE_SOURCE_BODY_DESCRIPTION = """ +Partial source configuration update. May contain any subset of fields from the respective source type +(e.g., 'device_id' for webcams; 'video_path' for video files). +Fields not included in the request will remain unchanged. The 'source_type' field cannot be changed. +""" +UPDATE_SOURCE_BODY_EXAMPLES = { + "webcam": Example( + summary="Update webcam source", + description="Rename a webcam source", + value={ + "name": "Updated Webcam Name", + }, + ), + "video_file": Example( + summary="Update video file source", + description="Change the video path for a video file source", + value={ + "video_path": "/new/path/to/video.mp4", + }, + ), +} + + +@router.post( + "", + status_code=status.HTTP_201_CREATED, + responses={ + status.HTTP_201_CREATED: {"description": "Source created", "model": Source}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid source ID or request body"}, + status.HTTP_409_CONFLICT: {"description": "Source already exists"}, + }, +) +async def create_source( + project_id: Annotated[UUID, Depends(get_project_id)], + source_config: Annotated[ + SourceCreate, Body(description=CREATE_SOURCE_BODY_DESCRIPTION, openapi_examples=CREATE_SOURCE_BODY_EXAMPLES) + ], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> Source: + """Create and configure a new source""" + # Inject project_id from URL path into the config + source_config.project_id = project_id + + # Validate the complete config + validated_source = SourceCreateAdapter.validate_python(source_config) + + if validated_source.source_type == SourceType.DISCONNECTED: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="The source with source_type=DISCONNECTED cannot be created" + ) + + try: + return await configuration_service.create_source(validated_source) + except ResourceAlreadyExistsError as e: + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e)) + except IntegrityError: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Name must be unique within a project") + + +@router.get( + "", + responses={ + status.HTTP_200_OK: {"description": "List of available source configurations", "model": list[Source]}, + }, +) +async def list_sources( + project_id: Annotated[UUID, Depends(get_project_id)], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> list[Source]: + """List the available sources""" + return await configuration_service.list_sources(project_id) + + +@router.get( + "/{source_id}", + responses={ + status.HTTP_200_OK: {"description": "Source found", "model": Source}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid source ID"}, + status.HTTP_404_NOT_FOUND: {"description": "Source not found"}, + }, +) +async def get_source( + project_id: Annotated[UUID, Depends(get_project_id)], + source_id: Annotated[UUID, Depends(get_source_id)], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> Source: + """Get info about a source""" + try: + return await configuration_service.get_source_by_id(source_id, project_id) + except ResourceNotFoundError as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) + + +@router.patch( + "/{source_id}", + responses={ + status.HTTP_200_OK: {"description": "Source successfully updated", "model": Source}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid source ID or request body"}, + status.HTTP_404_NOT_FOUND: {"description": "Source not found"}, + }, +) +async def update_source( + project_id: Annotated[UUID, Depends(get_project_id)], + source_id: Annotated[UUID, Depends(get_source_id)], + source_config: Annotated[ + dict, + Body( + description=UPDATE_SOURCE_BODY_DESCRIPTION, + openapi_examples=UPDATE_SOURCE_BODY_EXAMPLES, + ), + ], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> Source: + """Reconfigure an existing source""" + if "source_type" in source_config: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="The 'source_type' field cannot be changed") + try: + return await configuration_service.update_source(source_id, project_id, source_config) + except ResourceNotFoundError as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) + + +@router.post( + "/{source_id}:export", + response_class=FileResponse, + responses={ + status.HTTP_200_OK: { + "description": "Source configuration exported as a YAML file", + "content": { + "application/x-yaml": {"schema": {"type": "string", "format": "binary"}}, + }, + }, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid source ID or request body"}, + status.HTTP_404_NOT_FOUND: {"description": "Source not found"}, + }, +) +async def export_source( + project_id: Annotated[UUID, Depends(get_project_id)], + source_id: Annotated[UUID, Depends(get_source_id)], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> Response: + """Export a source to file""" + source = await configuration_service.get_source_by_id(source_id, project_id) + if not source: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Source with ID {source_id} not found") + + yaml_content = yaml.safe_dump(source.model_dump(mode="json", exclude={"id", "project_id"})) + + return Response( + content=yaml_content.encode("utf8"), + media_type="application/x-yaml", + headers={"Content-Disposition": f"attachment; filename=source_{source_id}.yaml"}, + ) + + +@router.post( + ":import", + status_code=status.HTTP_201_CREATED, + responses={ + status.HTTP_201_CREATED: {"description": "Source imported successfully", "model": Source}, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid YAML format or source type is DISCONNECTED"}, + }, +) +async def import_source( + project_id: Annotated[UUID, Depends(get_project_id)], + yaml_file: Annotated[UploadFile, File(description="YAML file containing the source configuration")], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> Source: + """Import a source from file""" + try: + yaml_content = await yaml_file.read() + source_data = yaml.safe_load(yaml_content) + + # Inject project_id from URL path + source_data["project_id"] = str(project_id) + + source_config = SourceAdapter.validate_python(source_data) + if source_config.source_type == SourceType.DISCONNECTED: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="The source with source_type=DISCONNECTED cannot be imported", + ) + return await configuration_service.create_source(source_config) + except yaml.YAMLError as e: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid YAML format: {str(e)}") + + +@router.delete( + "/{source_id}", + status_code=status.HTTP_204_NO_CONTENT, + responses={ + status.HTTP_204_NO_CONTENT: { + "description": "Source configuration successfully deleted", + }, + status.HTTP_400_BAD_REQUEST: {"description": "Invalid source ID or source is used by at least one pipeline"}, + status.HTTP_404_NOT_FOUND: {"description": "Source not found"}, + status.HTTP_409_CONFLICT: {"description": "Source is used by at least one pipeline"}, + }, +) +async def delete_source( + project_id: Annotated[UUID, Depends(get_project_id)], + source_id: Annotated[UUID, Depends(get_source_id)], + configuration_service: Annotated[ConfigurationService, Depends(get_configuration_service)], +) -> None: + """Remove a source""" + try: + await configuration_service.delete_source_by_id(source_id, project_id) + except ResourceNotFoundError as e: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) + except ResourceInUseError as e: + raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(e)) diff --git a/application/backend/src/api/endpoints/trainable_models_endpoints.py b/application/backend/src/api/endpoints/trainable_models_endpoints.py new file mode 100644 index 0000000000..618edada73 --- /dev/null +++ b/application/backend/src/api/endpoints/trainable_models_endpoints.py @@ -0,0 +1,32 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from functools import lru_cache + +from anomalib.models import list_models +from fastapi import APIRouter + +from api.endpoints import API_PREFIX +from pydantic_models import TrainableModelList + +router = APIRouter(prefix=f"{API_PREFIX}/trainable-models", tags=["Trainable Models"]) + + +@lru_cache +def _get_trainable_models() -> TrainableModelList: # pragma: no cover + """Return list of trainable models with optional descriptions. + + The available models are retrieved from ``anomalib.models.list_models``. Currently, only + the model names are returned. Descriptions can be added manually in the + ``_MODEL_DESCRIPTIONS`` mapping below. + """ + model_names = sorted(list_models(case="snake")) + + return TrainableModelList(trainable_models=model_names) + + +@router.get("", summary="List trainable models") +async def list_trainable_models() -> TrainableModelList: + """GET endpoint returning available trainable model names.""" + + return _get_trainable_models() diff --git a/application/backend/src/api/endpoints/webrtc.py b/application/backend/src/api/endpoints/webrtc.py new file mode 100644 index 0000000000..1ae74c567f --- /dev/null +++ b/application/backend/src/api/endpoints/webrtc.py @@ -0,0 +1,43 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""WebRTC API Endpoints""" + +from typing import Annotated + +from fastapi import APIRouter, Depends, status +from fastapi.exceptions import HTTPException +from loguru import logger + +from api.dependencies import get_webrtc_manager as get_webrtc +from pydantic_models.webrtc import Answer, InputData, Offer +from webrtc.manager import WebRTCManager + +router = APIRouter(prefix="/api/webrtc", tags=["WebRTC"]) + + +@router.post( + "/offer", + responses={ + status.HTTP_200_OK: {"description": "WebRTC Answer"}, + status.HTTP_500_INTERNAL_SERVER_ERROR: {"description": "Internal Server Error"}, + }, +) +async def create_webrtc_offer(offer: Offer, webrtc_manager: Annotated[WebRTCManager, Depends(get_webrtc)]) -> Answer: + """Create a WebRTC offer""" + try: + return await webrtc_manager.handle_offer(offer) + except Exception as e: + logger.error(f"Error processing WebRTC offer: {e}") + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) + + +@router.post( + "/input_hook", + responses={ + status.HTTP_200_OK: {"description": "WebRTC input data updated"}, + }, +) +async def webrtc_input_hook(data: InputData, webrtc_manager: Annotated[WebRTCManager, Depends(get_webrtc)]) -> None: + """Update webrtc input with user data""" + webrtc_manager.set_input(data) diff --git a/application/backend/src/api/media_rest_validator.py b/application/backend/src/api/media_rest_validator.py new file mode 100644 index 0000000000..b2e1576811 --- /dev/null +++ b/application/backend/src/api/media_rest_validator.py @@ -0,0 +1,43 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os + +from fastapi import File, Request, UploadFile + +from exceptions import InvalidMediaException, PayloadTooLargeException +from pydantic_models import ImageExtension + + +class MediaRestValidator: + SUPPORTED_IMAGE_TYPES = [extension.value for extension in ImageExtension] + MAX_BYTES_SIZE = 4.7 * 1024**3 + + @staticmethod + def validate_image_file(request: Request, file: UploadFile = File(None)) -> UploadFile | None: + """ + Validates a request to upload an image, if the file is not present, returns None. + + :param request: FastAPI request that was made to upload the file + :param file: uploaded image file + :raises InvalidMediaException if the file name is empty or the file extension is + not in the supported file extensions + :raises PayloadTooLargeException when the total size of the request exceeds 8GB + """ + if file is None: + return None + + file_size = file.size if file.size else int(request.headers["content-length"]) + if file_size > MediaRestValidator.MAX_BYTES_SIZE: + raise PayloadTooLargeException(MediaRestValidator.MAX_BYTES_SIZE / (1024**2)) + + if file.filename is None: + raise InvalidMediaException("Filename can't be empty.") + + extension = list(os.path.splitext(file.filename)).pop().lower() + if extension not in MediaRestValidator.SUPPORTED_IMAGE_TYPES: + raise InvalidMediaException( + f"Not a valid image format. Received {extension}, but only the following are allowed: " + f"{str(MediaRestValidator.SUPPORTED_IMAGE_TYPES)}" + ) + return file diff --git a/application/backend/src/cli.py b/application/backend/src/cli.py new file mode 100644 index 0000000000..36a89dda2d --- /dev/null +++ b/application/backend/src/cli.py @@ -0,0 +1,155 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""Command line interface for interacting with the Geti Inspect application.""" + +import sys + +import click +from anomalib.deploy import ExportType + +from db import MigrationManager +from db.engine import get_sync_db_session +from db.schema import JobDB, MediaDB, ModelDB, PipelineDB, ProjectDB, SinkDB, SourceDB +from settings import get_settings + +settings = get_settings() +migration_manager = MigrationManager(settings) + + +@click.group() +def cli() -> None: + """Geti Inspect CLI""" + + +@cli.command() +def init_db() -> None: + """Initialize database with migrations""" + click.echo("Initializing database...") + + if migration_manager.initialize_database(): + click.echo("✓ Database initialized successfully!") + sys.exit(0) + else: + click.echo("✗ Database initialization failed!") + sys.exit(1) + + +@cli.command() +def migrate() -> None: + """Run database migrations""" + click.echo("Running database migrations...") + + if migration_manager.run_migrations(): + click.echo("✓ Migrations completed successfully!") + sys.exit(0) + else: + click.echo("✗ Migration failed!") + sys.exit(1) + + +@cli.command() +def check_db() -> None: + """Check database status""" + click.echo("Checking database status...") + + # Check connection + if not migration_manager.check_connection(): + click.echo("✗ Cannot connect to database") + sys.exit(1) + + click.echo("✓ Database connection OK") + + # Check migration status + needs_migration, status = migration_manager.check_migration_status() + click.echo(f"Migration status: {status}") + + if needs_migration: + click.echo("⚠ Database needs migration") + sys.exit(2) + else: + click.echo("✓ Database is up to date") + sys.exit(0) + + +@cli.command() +@click.option("--with-model", default=False) +def seed(with_model: bool, model_name: str) -> None: + """Seed the database with test data.""" + # If the app is running, it needs to be restarted since it doesn't track direct DB changes + # Fixed IDs are used to ensure consistency in tests + click.echo("Seeding database with test data...") + with get_sync_db_session() as db: + project = ProjectDB( + id="6ee0c080-c7d9-4438-a7d2-067fd395eecf", + name="Project", + ) + db.add(project) + if with_model: + model = ModelDB( + id="977eeb18-eaac-449d-bc80-e340fbe052ad", + name=model_name, + format=ExportType.OPENVINO, + ) + db.add(model) + db.flush() + db.commit() + click.echo("✓ Seeding successful!") + + +@cli.command() +def clean_db() -> None: + """Remove all data from the database (clean but don't drop tables).""" + with get_sync_db_session() as db: + # Delete in order respecting foreign key constraints: + # 1. Pipelines (references projects, sources, sinks, models) + db.query(PipelineDB).delete() + # 2. Models (references jobs and projects) + db.query(ModelDB).delete() + # 3. Media (references projects) + db.query(MediaDB).delete() + # 4. Jobs (references projects) + db.query(JobDB).delete() + # 5. Sources (references projects) + db.query(SourceDB).delete() + # 6. Sinks (references projects) + db.query(SinkDB).delete() + # 7. Projects (parent table, can be deleted last) + db.query(ProjectDB).delete() + db.commit() + click.echo("✓ Database cleaned successfully!") + + +@cli.command() +@click.option("-m", "--message", required=True, help="Migration revision message") +@click.option("--empty", is_flag=True, default=False, help="Create an empty revision (no autogenerate)") +def create_db_revision(message: str, empty: bool) -> None: + """Generate a new Alembic migration revision.""" + click.echo("Generating migration...") + try: + migration_manager.make_revision(message=message, autogenerate=not empty) + click.echo("✓ Created new database revision") + sys.exit(0) + except Exception: + click.echo("✗ Failed to create new database revision") + sys.exit(1) + + +@cli.command() +@click.option("--target-path", default="docs/openapi.json") +def gen_api(target_path: str) -> None: + """Generate OpenAPI specification JSON file.""" + # Importing create_openapi imports threading which is slow. Importing here to not slow down other cli commands. + from create_openapi import create_openapi # noqa: PLC0415 + + try: + create_openapi(target_path=target_path) + click.echo("✓ OpenAPI specification generated successfully!") + except Exception as e: + click.echo(f"✗ Failed to generate OpenAPI specification: {e}") + sys.exit(1) + click.echo("Waiting for threading to finish...") + + +if __name__ == "__main__": + cli() diff --git a/application/backend/src/core/__init__.py b/application/backend/src/core/__init__.py new file mode 100644 index 0000000000..3a2a735562 --- /dev/null +++ b/application/backend/src/core/__init__.py @@ -0,0 +1,7 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from core.lifecycle import lifespan +from core.scheduler import Scheduler + +__all__ = ["Scheduler", "lifespan"] diff --git a/application/backend/src/core/lifecycle.py b/application/backend/src/core/lifecycle.py new file mode 100644 index 0000000000..024c54a9a4 --- /dev/null +++ b/application/backend/src/core/lifecycle.py @@ -0,0 +1,51 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""Application lifecycle management""" + +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager + +from fastapi import FastAPI +from loguru import logger + +from core.logging import setup_logging, setup_uvicorn_logging +from core.scheduler import Scheduler +from db import MigrationManager +from settings import get_settings +from webrtc.manager import WebRTCManager + + +@asynccontextmanager +async def lifespan(app: FastAPI) -> AsyncGenerator[None]: + """FastAPI lifespan context manager""" + # Startup + setup_logging() + setup_uvicorn_logging() + + settings = get_settings() + logger.info(f"Starting {settings.app_name} application...") + + # Initialize database with migrations + migration_manager = MigrationManager(settings) + if not migration_manager.initialize_database(): + logger.error("Failed to initialize database. Application cannot start.") + raise RuntimeError("Database initialization failed") + + # Initialize Scheduler + app_scheduler = Scheduler() + app_scheduler.start_workers() + app.state.scheduler = app_scheduler + app.state.active_models = {} + + webrtc_manager = WebRTCManager(app_scheduler.rtc_stream_queue) + app.state.webrtc_manager = webrtc_manager + logger.info("Application startup completed") + + yield + + # Shutdown + logger.info(f"Shutting down {settings.app_name} application...") + await webrtc_manager.cleanup() + app_scheduler.shutdown() + logger.info("Application shutdown completed") diff --git a/application/backend/src/core/logging/__init__.py b/application/backend/src/core/logging/__init__.py new file mode 100644 index 0000000000..fbb719f2a0 --- /dev/null +++ b/application/backend/src/core/logging/__init__.py @@ -0,0 +1,7 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from .log_config import LogConfig +from .setup import global_log_config, setup_logging, setup_uvicorn_logging + +__all__ = ["LogConfig", "global_log_config", "setup_logging", "setup_uvicorn_logging"] diff --git a/application/backend/src/core/logging/handlers.py b/application/backend/src/core/logging/handlers.py new file mode 100644 index 0000000000..29bfd8cad8 --- /dev/null +++ b/application/backend/src/core/logging/handlers.py @@ -0,0 +1,43 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import inspect +import logging + +from loguru import logger + + +class InterceptHandler(logging.Handler): + """ + This handler intercepts standard logging calls and forwards them to loguru + while preserving the original caller information. + """ + + def emit(self, record: logging.LogRecord) -> None: + # Get corresponding Loguru level if it exists. + level: str | int + try: + level = logger.level(record.levelname).name + except ValueError: + level = record.levelno + + # Find caller from where originated the logged message. + frame, depth = inspect.currentframe(), 0 + while frame and (depth == 0 or frame.f_code.co_filename == logging.__file__): + frame = frame.f_back + depth += 1 + + logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage()) + + +class LoggerStdoutWriter: + """Wrapper for redirecting stdout to logger""" + + @staticmethod + def write(msg: str) -> None: + msg = msg.rstrip("\n") + if msg: + logger.info(msg) + + @staticmethod + def flush() -> None: + pass diff --git a/application/backend/src/core/logging/log_config.py b/application/backend/src/core/logging/log_config.py new file mode 100644 index 0000000000..99b6004112 --- /dev/null +++ b/application/backend/src/core/logging/log_config.py @@ -0,0 +1,35 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os +from dataclasses import dataclass +from pathlib import Path +from typing import ClassVar + +from settings import get_settings +from workers import DispatchingWorker, InferenceWorker, StreamLoader, TrainingWorker + +settings = get_settings() +WORKERS_FOLDER = os.path.join(settings.log_dir, "workers") +JOBS_FOLDER = os.path.join(settings.log_dir, "jobs") + + +@dataclass +class LogConfig: + """Configuration for logging behavior.""" + + rotation: str = "10 MB" + retention: str = "10 days" + level: str = "DEBUG" + serialize: bool = True + log_folder: Path = settings.log_dir + # Mapping of worker classes to their dedicated log files + # None key is used for application-level logs that don't belong to any specific worker + worker_log_info: ClassVar[dict[str | None, str]] = { + TrainingWorker.__name__: "training.log", + InferenceWorker.__name__: "inference.log", + DispatchingWorker.__name__: "dispatching.log", + StreamLoader.__name__: "stream_loader.log", + None: "app.log", + } + tensorboard_log_path: str = os.path.join(settings.log_dir, "tensorboard") diff --git a/application/backend/src/core/logging/setup.py b/application/backend/src/core/logging/setup.py new file mode 100644 index 0000000000..5693cbff86 --- /dev/null +++ b/application/backend/src/core/logging/setup.py @@ -0,0 +1,113 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""Logging configuration and utilities for the application. + +Provides centralized logging using loguru with: +- Worker-specific log files (training, inference, dispatching, stream loading) +- Job-specific temporary log files via context managers +- Automatic log rotation (10MB) and retention (10 days) +- JSON serialization and thread-safe async logging +""" + +import logging +import os +from typing import TYPE_CHECKING + +from core.logging.handlers import InterceptHandler +from core.logging.log_config import LogConfig + +if TYPE_CHECKING: + from loguru import Record + +from loguru import logger + +global_log_config = LogConfig() + + +def setup_logging(config: LogConfig | None = None) -> None: + """Configure application-wide logging with worker-specific log files. + + Creates separate log files for each worker type (training, inference, etc.) with + configurable rotation and retention. Logs are filtered by the 'worker' field in + record extras and serialized as JSON. + + Args: + config: Optional LogConfig instance. If None, uses default configuration. + + Important: Must be called in each child process (BaseProcessWorker does this + automatically) and once at main process startup. Loguru sinks don't transfer + to child processes. + + Example: + >>> setup_logging() + # Creates: logs/training.log, logs/inference.log, logs/app.log, etc. + >>> custom_config = LogConfig(rotation="50 MB", level="INFO") + >>> setup_logging(custom_config) + """ + global global_log_config # noqa: PLW0603 + + if config is None: + config = LogConfig() + + # overwrite global log_config + global_log_config = config + + for worker_name, log_file in global_log_config.worker_log_info.items(): + + def worker_log_filter(record: "Record", worker: str | None = worker_name) -> bool: + return record["extra"].get("worker") == worker + + log_path = os.path.join(config.log_folder, log_file) + + try: + os.makedirs(os.path.dirname(log_path), exist_ok=True) + except OSError as e: + logger.warning(f"Failed to create log directory {log_path}: {e}") + continue + + try: + logger.add( + log_path, + rotation=config.rotation, + retention=config.retention, + level=config.level, + filter=worker_log_filter, + serialize=config.serialize, + enqueue=True, + ) + except Exception as e: + logger.error(f"Failed to add log sink for {worker_name}: {e}") + + +def setup_uvicorn_logging() -> None: + """Configure uvicorn logging to be handled by loguru. + + Intercepts all uvicorn log messages (from uvicorn.error, uvicorn.access, etc.) + and redirects them to loguru for unified logging output. This ensures uvicorn + logs follow the same format and routing as application logs. + + The function: + 1. Attaches InterceptHandler to the main uvicorn logger + 2. Sets propagate=False on uvicorn logger to prevent duplicate logs to root + 3. Clears handlers from child loggers (uvicorn.access, uvicorn.error) + 4. Enables propagation on child loggers so they forward to parent uvicorn logger + + Note: This should be called during application startup, typically before + starting the uvicorn server. + + Example: + >>> setup_uvicorn_logging() + # All uvicorn logs now flow through loguru + """ + # Setup uvicorn logs to be handled by loguru + # Configure the main uvicorn logger with InterceptHandler + uvicorn_logger = logging.getLogger("uvicorn") + uvicorn_logger.handlers = [InterceptHandler()] + uvicorn_logger.setLevel(logging.INFO) + uvicorn_logger.propagate = False # Don't propagate to root to avoid duplicate logs + # Clear handlers from child loggers and let them propagate to parent uvicorn logger + for logger_name in ("uvicorn.access", "uvicorn.error"): + child_logger = logging.getLogger(logger_name) + child_logger.handlers.clear() + child_logger.propagate = True # Propagate to parent uvicorn logger diff --git a/application/backend/src/core/logging/utils.py b/application/backend/src/core/logging/utils.py new file mode 100644 index 0000000000..b7d8587abb --- /dev/null +++ b/application/backend/src/core/logging/utils.py @@ -0,0 +1,104 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os +from collections.abc import Generator +from contextlib import contextmanager +from uuid import UUID + +from loguru import logger + +from core.logging.setup import global_log_config + + +def _validate_job_id(job_id: str | UUID) -> str | UUID: + """Validate job_id to prevent path traversal attacks. + + Args: + job_id: The job identifier to validate + + Returns: + Validated job_id + + Raises: + ValueError: If job_id is not a valid UUID + """ + # Only allow alphanumeric, hyphens, underscores + try: + UUID(str(job_id)) + except ValueError as e: + raise ValueError( + f"Invalid job_id '{job_id}'. Only alphanumeric characters, hyphens, and underscores are allowed." + ) from e + return job_id + + +def get_job_logs_path(job_id: str | UUID) -> str: + """Get the path to the logs folder for a specific job. + + Args: + job_id: Unique identifier for the job + + Returns: + str: Path to the job's logs folder (logs/jobs/{job_id}) + + Raises: + ValueError: If job_id contains invalid characters + + Example: + >>> get_job_logs_path(job_id="foo-123") + 'logs/jobs/foo-123' + """ + job_id = _validate_job_id(job_id) + jobs_folder = os.path.join(global_log_config.log_folder, "jobs") + try: + os.makedirs(jobs_folder, exist_ok=True) + except OSError as e: + raise RuntimeError(f"Failed to create jobs log directory: {e}") from e + return os.path.join(jobs_folder, f"{job_id}.log") + + +@contextmanager +def job_logging_ctx(job_id: str | UUID) -> Generator[str]: + """Add a temporary log sink for a specific job. + + Captures all logs emitted during the context to logs/jobs/{job_id}.log. + The sink is automatically removed on exit, but the log file persists. + Logs also continue to go to other configured sinks. + + Args: + job_id: Unique identifier for the job, used as the log filename + + Yields: + str: Path to the created log file (logs/jobs/{job_id}.log) + + Raises: + ValueError: If job_id contains invalid characters + RuntimeError: If log directory creation or sink addition fails + + Example: + >>> with job_logging_ctx(job_id="foo-123"): + ... logger.info("bar") # All logs saved to logs/jobs/train-123.log + """ + job_id = _validate_job_id(job_id) + + log_file = get_job_logs_path(job_id) + + try: + sink_id = logger.add( + log_file, + rotation=global_log_config.rotation, + retention=global_log_config.retention, + level=global_log_config.level, + serialize=global_log_config.serialize, + enqueue=True, + ) + except Exception as e: + raise RuntimeError(f"Failed to add log sink for job {job_id}: {e}") from e + + try: + logger.info(f"Started logging to {log_file}") + yield log_file + finally: + logger.info(f"Stopped logging to {log_file}") + logger.remove(sink_id) diff --git a/application/backend/src/core/scheduler.py b/application/backend/src/core/scheduler.py new file mode 100644 index 0000000000..0268c45c63 --- /dev/null +++ b/application/backend/src/core/scheduler.py @@ -0,0 +1,172 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import atexit +import multiprocessing as mp +import os +import queue +import threading # noqa: TC003 +from multiprocessing.shared_memory import SharedMemory + +import psutil +from loguru import logger + +from services.metrics_service import SIZE +from utils.singleton import Singleton +from workers import DispatchingWorker, InferenceWorker, StreamLoader, TrainingWorker + + +class Scheduler(metaclass=Singleton): + """Manages application processes and threads""" + + FRAME_QUEUE_SIZE = 5 + PREDICTION_QUEUE_SIZE = 5 + + def __init__(self) -> None: + logger.info("Initializing Scheduler...") + # Queue for the frames acquired from the stream source and decoded + self.frame_queue: mp.Queue = mp.Queue(maxsize=self.FRAME_QUEUE_SIZE) + # Queue for the inference results (predictions) + self.pred_queue: mp.Queue = mp.Queue(maxsize=self.PREDICTION_QUEUE_SIZE) + # Queue for pushing predictions to the visualization stream (WebRTC) + self.rtc_stream_queue: queue.Queue = queue.Queue(maxsize=1) + # Event to sync all processes on application shutdown + self.mp_stop_event = mp.Event() + # Event to signal that the model has to be reloaded + self.mp_model_reload_event = mp.Event() + # Condition variable to notify processes about configuration updates + self.mp_config_changed_condition = mp.Condition() + + # Shared memory for metrics collector + self.shm_metrics: SharedMemory = SharedMemory(create=True, size=SIZE) + self.shm_metrics_lock = mp.Lock() + + self.processes: list[mp.Process] = [] + self.threads: list[threading.Thread] = [] + logger.info("Scheduler initialized") + # Ensure we always attempt a graceful shutdown when the main process exits + atexit.register(self.shutdown) + + def start_workers(self) -> None: + """Start all worker processes and threads""" + logger.info("Starting worker processes...") + + # Create and start processes + training_proc = TrainingWorker( + stop_event=self.mp_stop_event, + logger_=logger.bind(worker=TrainingWorker.__name__), + ) + # Training worker is not a daemon so that training script can spawn child processes + training_proc.daemon = False + + # Inference worker consumes frames and produces predictions + + inference_proc = InferenceWorker( + frame_queue=self.frame_queue, + pred_queue=self.pred_queue, + stop_event=self.mp_stop_event, + model_reload_event=self.mp_model_reload_event, + shm_name=self.shm_metrics.name, + shm_lock=self.shm_metrics_lock, + logger_=logger.bind(worker=InferenceWorker.__name__), + ) + inference_proc.daemon = True + + # Dispatching worker consumes predictions and publishes to outputs/WebRTC + dispatching_thread = DispatchingWorker( + pred_queue=self.pred_queue, + rtc_stream_queue=self.rtc_stream_queue, + stop_event=self.mp_stop_event, + active_config_changed_condition=self.mp_config_changed_condition, + ) + + stream_loader_proc = StreamLoader( + frame_queue=self.frame_queue, + stop_event=self.mp_stop_event, + config_changed_condition=self.mp_config_changed_condition, + logger_=logger.bind(worker=StreamLoader.__name__), + ) + stream_loader_proc.daemon = True + + # Start all workers + training_proc.start() + inference_proc.start() + stream_loader_proc.start() + dispatching_thread.daemon = True + dispatching_thread.start() + + # Track processes and threads + self.processes.extend([training_proc, inference_proc, stream_loader_proc]) + self.threads.append(dispatching_thread) + + logger.info("All worker processes started successfully") + + def shutdown(self) -> None: + """Shutdown all processes gracefully""" + logger.info("Initiating graceful shutdown...") + + # Signal all processes to stop + self.mp_stop_event.set() + + # Get current process info for debugging + pid = os.getpid() + cur_process = psutil.Process(pid) + alive_children = [child.pid for child in cur_process.children(recursive=True) if child.is_running()] + logger.debug(f"Alive children of process '{pid}': {alive_children}") + + # Join threads first + for thread in self.threads: + if thread.is_alive(): + logger.debug(f"Joining thread: {thread.name}") + thread.join(timeout=10) + + # Join processes in reverse order so that consumers are terminated before producers. + for process in self.processes[::-1]: + if process.is_alive(): + logger.debug(f"Joining process: {process.name}") + process.join(timeout=10) + if process.is_alive(): + logger.warning(f"Force terminating process: {process.name}") + process.terminate() + process.join(timeout=2) + if process.is_alive(): + logger.error(f"Force killing process {process.name}") + process.kill() + # Explicitly close the process' resources + try: + process.close() + except Exception as e: + logger.warning(f"Error closing process {process.name}: {e}") + + logger.info("All workers shut down gracefully") + + # Clear references + self.processes.clear() + self.threads.clear() + + self._cleanup_queues() + self._cleanup_shared_memory() + + def _cleanup_queues(self) -> None: + """Final queue cleanup""" + for q, name in [(self.frame_queue, "frame_queue"), (self.pred_queue, "pred_queue")]: + if q is not None: + try: + # https://runebook.dev/en/articles/python/library/multiprocessing/multiprocessing.Queue.close + q.close() + q.join_thread() + logger.debug(f"Successfully cleaned up {name}") + except Exception as e: + logger.warning(f"Error cleaning up {name}: {e}") + + def _cleanup_shared_memory(self) -> None: + """Clean up shared memory objects""" + if hasattr(self, "shm_metrics"): + try: + self.shm_metrics.close() + self.shm_metrics.unlink() # Remove the shared memory segment + # Delete the attribute to make shutdown idempotent and prevent accidental reuse + del self.shm_metrics + logger.debug("Successfully cleaned up shared memory") + except Exception as e: + logger.warning(f"Error cleaning up shared memory: {e}") diff --git a/application/backend/src/create_openapi.py b/application/backend/src/create_openapi.py new file mode 100644 index 0000000000..3e767a612b --- /dev/null +++ b/application/backend/src/create_openapi.py @@ -0,0 +1,29 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""This script generates an OpenAPI JSON file for the FastAPI application.""" + +import json +from pathlib import Path + +from fastapi.openapi.utils import get_openapi + +from main import app + + +def create_openapi(target_path: str) -> None: + """Entry point for creating the OpenAPI specification""" + # Ensure target directory exists + output_path = Path(target_path) + output_path.parent.mkdir(parents=True, exist_ok=True) + + with open(output_path, "w", encoding="utf-8") as file: + open_api = get_openapi( + title=app.title, + version=app.version, + summary=app.summary, + description=app.description, + openapi_version=app.openapi_version, + routes=app.routes, + ) + json.dump(obj=open_api, fp=file, indent=2) diff --git a/application/backend/src/db/__init__.py b/application/backend/src/db/__init__.py new file mode 100644 index 0000000000..5542208356 --- /dev/null +++ b/application/backend/src/db/__init__.py @@ -0,0 +1,7 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from db.engine import get_async_db_session, get_async_db_session_ctx, init_models, sync_engine +from db.migration import MigrationManager + +__all__ = ["MigrationManager", "get_async_db_session", "get_async_db_session_ctx", "init_models", "sync_engine"] diff --git a/application/backend/src/db/engine.py b/application/backend/src/db/engine.py new file mode 100644 index 0000000000..6dac0c4368 --- /dev/null +++ b/application/backend/src/db/engine.py @@ -0,0 +1,96 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from collections.abc import AsyncGenerator, Iterator +from contextlib import asynccontextmanager, contextmanager +from sqlite3 import Connection +from typing import Any + +from sqlalchemy.engine import Engine +from sqlalchemy.engine.create import create_engine, event +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine +from sqlalchemy.orm.session import Session, sessionmaker +from sqlalchemy.pool.impl import NullPool + +from db.schema import Base +from settings import get_settings + +settings = get_settings() + +async_engine = create_async_engine( + settings.database_url, + connect_args={"check_same_thread": False, "timeout": 30}, + # Using NullPool to disable connection pooling, which is necessary for SQLite when using multiprocessing + # https://docs.sqlalchemy.org/en/20/core/pooling.html#using-connection-pools-with-multiprocessing-or-os-fork + poolclass=NullPool, + echo=settings.db_echo, +) +async_session = async_sessionmaker(async_engine, expire_on_commit=False) + +sync_engine = create_engine( + settings.sync_database_url, + connect_args={"check_same_thread": False, "timeout": 30}, + # Using NullPool to disable connection pooling, which is necessary for SQLite when using multiprocessing + # https://docs.sqlalchemy.org/en/20/core/pooling.html#using-connection-pools-with-multiprocessing-or-os-fork + poolclass=NullPool, + echo=settings.db_echo, +) +sync_session = sessionmaker(autocommit=False, autoflush=False, bind=sync_engine) + + +@event.listens_for(Engine, "connect") +def set_sqlite_pragma(dbapi_connection: Connection, _: Any) -> None: + """Enable foreign key support for SQLite.""" + # https://docs.sqlalchemy.org/en/20/dialects/sqlite.html#foreign-key-support + # SQLAlchemy's event system does not directly expose async version + # https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html#using-events-with-the-asyncio-extension + cursor = dbapi_connection.cursor() + cursor.execute("PRAGMA foreign_keys=ON") + cursor.close() + + +async def get_async_db_session() -> AsyncGenerator[AsyncSession, Any]: + """Get a database session. + + To be used for dependency injection. + """ + async with async_session() as session: + try: + yield session + except Exception: + await session.rollback() + raise + + +@asynccontextmanager +async def get_async_db_session_ctx() -> AsyncGenerator[AsyncSession, Any]: + """Get a database session for direct async context manager usage.""" + async with async_session() as session: + try: + yield session + except Exception: + await session.rollback() + raise + + +@contextmanager +def get_sync_db_session() -> Iterator[Session]: + """Context manager to get a database session.""" + db = sync_session() + try: + yield db + except Exception: + db.rollback() + raise + finally: + db.close() + + +async def init_models() -> None: + """Create tables if they don't already exist. + + In a real-life example we would use Alembic to manage migrations. + """ + async with async_engine.begin() as conn: + # await conn.run_sync(Base.metadata.drop_all) + await conn.run_sync(Base.metadata.create_all) diff --git a/application/backend/src/db/migration.py b/application/backend/src/db/migration.py new file mode 100644 index 0000000000..8df546cba6 --- /dev/null +++ b/application/backend/src/db/migration.py @@ -0,0 +1,135 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from alembic.config import Config +from alembic.runtime import migration +from alembic.script import ScriptDirectory +from loguru import logger +from sqlalchemy import text + +from alembic import command +from db import sync_engine as db_engine +from settings import Settings + + +class RevisionNotFoundError(Exception): + """Raised when the current revision is not found in Alembic history.""" + + +class MigrationManager: + """Manages database connections and migrations""" + + def __init__(self, settings: Settings) -> None: + self.settings = settings + self.__ensure_data_directory() + + def __ensure_data_directory(self) -> None: + """Ensure the data directory exists""" + self.settings.data_dir.mkdir(parents=True, exist_ok=True) + + @staticmethod + def check_connection() -> bool: + """Check if database connection is working""" + try: + with db_engine.connect() as conn: + conn.execute(text("SELECT 1")) + return True + except Exception as e: + logger.error(f"Database connection check failed: {e}") + return False + + def get_alembic_config(self) -> Config: + """Get Alembic configuration""" + alembic_cfg = Config(self.settings.alembic_config_path) + alembic_cfg.set_main_option("script_location", self.settings.alembic_script_location) + alembic_cfg.set_main_option("sqlalchemy.url", self.settings.sync_database_url) + return alembic_cfg + + def run_migrations(self) -> bool: + """Run database migrations""" + try: + logger.info("Running database migrations...") + alembic_cfg = self.get_alembic_config() + command.upgrade(alembic_cfg, "head") + logger.success("✓ Database migrations completed successfully") + return True + except Exception as e: + logger.error(f"✗ Database migration failed: {e}") + return False + + def check_migration_status(self) -> tuple[bool, str]: + """Check if database needs migration""" + try: + alembic_cfg = self.get_alembic_config() + script = ScriptDirectory.from_config(alembic_cfg) + current_head = script.get_current_head() + + with db_engine.connect() as conn: + context = migration.MigrationContext.configure(conn) + current_rev = context.get_current_revision() + + # Check if current_rev is in Alembic's tracked revisions + if current_rev and current_rev not in script.get_heads() + script.get_bases(): + raise RevisionNotFoundError( + f"Current revision '{current_rev}' not found in Alembic history. Please, recreate the database." + ) + + needs_migration = current_rev != current_head + status = f"Current: {current_rev or 'None'}, Head: {current_head or 'None'}" + + return needs_migration, status + + except RevisionNotFoundError: + raise + except Exception as e: + logger.warning(f"Could not check migration status: {e}") + return True, "Unknown - assuming migration needed" + + def initialize_database(self) -> bool: + """Initialize database with migrations if needed""" + try: + # Ensure data directory exists + self.__ensure_data_directory() + + # Check if we can connect + if not self.check_connection(): + logger.error("Cannot connect to database") + return False + + # Check migration status + needs_migration, status = self.check_migration_status() + logger.info(f"Migration status: {status}") + + if needs_migration: + logger.info("Database needs migration") + return self.run_migrations() + logger.success("Database is up to date") + return True + + except RevisionNotFoundError as e: + logger.error(f"Revision not found: {e}") + return False + except Exception as e: + logger.error(f"Database initialization failed: {e}") + return False + + def make_revision(self, message: str, autogenerate: bool = True) -> None: + """Create a new Alembic revision. + + Returns: path to the new revision file if successful, else None. + """ + try: + alembic_cfg = self.get_alembic_config() + script = command.revision(alembic_cfg, message=message, autogenerate=autogenerate) + + if isinstance(script, list): + for s in [s for s in script if s]: + logger.success(f"✓ Created migration {s.revision}: {s.path}") + elif script is not None: + # script is an alembic.script.revision.Revision object + logger.success(f"✓ Created migration {script.revision}: {script.path}") + else: + raise RuntimeError("Alembic did not return a revision script.") + except Exception as e: + logger.error(f"✗ Failed to create migration: {e}") + raise e diff --git a/application/backend/src/db/schema.py b/application/backend/src/db/schema.py new file mode 100644 index 0000000000..e9d5fa8d54 --- /dev/null +++ b/application/backend/src/db/schema.py @@ -0,0 +1,114 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from datetime import datetime +from uuid import uuid4 + +from sqlalchemy import JSON, BigInteger, Boolean, DateTime, Float, ForeignKey, String, Text +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship +from sqlalchemy.sql import func + + +class Base(DeclarativeBase): + pass + + +class ProjectDB(Base): + __tablename__ = "projects" + + id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid4())) + name: Mapped[str] = mapped_column(String(255), nullable=False) + created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.current_timestamp()) + updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) + + pipeline = relationship("PipelineDB", back_populates="project", uselist=False, lazy="joined") + + +class MediaDB(Base): + __tablename__ = "media" + + id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid4())) + project_id: Mapped[str] = mapped_column(ForeignKey("projects.id")) + filename: Mapped[str] = mapped_column(Text, nullable=False) + size: Mapped[int] = mapped_column(nullable=False) + width: Mapped[int] = mapped_column(nullable=False) + height: Mapped[int] = mapped_column(nullable=False) + is_anomalous: Mapped[bool] = mapped_column(Boolean, nullable=False) + updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) + created_at: Mapped[DateTime] = mapped_column(DateTime, server_default=func.current_timestamp()) + + +class ModelDB(Base): + __tablename__ = "models" + + id: Mapped[str] = mapped_column(Text, primary_key=True, default=lambda: str(uuid4())) + project_id: Mapped[str] = mapped_column(ForeignKey("projects.id")) + name: Mapped[str] = mapped_column(String(255), nullable=False) + format: Mapped[str] = mapped_column(String(64), nullable=False) + threshold: Mapped[float] = mapped_column(Float, nullable=False) + export_path: Mapped[str] = mapped_column(Text, nullable=False) + is_ready: Mapped[bool] = mapped_column(Boolean, nullable=False) + size: Mapped[int | None] = mapped_column(BigInteger, nullable=True) + created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) + train_job_id: Mapped[str] = mapped_column(ForeignKey("jobs.id", ondelete="RESTRICT")) + + +class JobDB(Base): + __tablename__ = "jobs" + + id: Mapped[str] = mapped_column(primary_key=True, default=lambda: str(uuid4())) + project_id: Mapped[str] = mapped_column(ForeignKey("projects.id")) + type: Mapped[str] = mapped_column(String(64), nullable=False) + progress: Mapped[int] = mapped_column(nullable=False) + status: Mapped[str] = mapped_column(String(64), nullable=False) + message: Mapped[str] = mapped_column(Text, nullable=False) + created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) + start_time: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) + end_time: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) + payload: Mapped[str] = mapped_column(JSON, nullable=False) + + +class PipelineDB(Base): + __tablename__ = "pipelines" + + project_id: Mapped[str] = mapped_column(Text, ForeignKey("projects.id", ondelete="CASCADE"), primary_key=True) + source_id: Mapped[str | None] = mapped_column(Text, ForeignKey("sources.id", ondelete="RESTRICT")) + sink_id: Mapped[str | None] = mapped_column(Text, ForeignKey("sinks.id", ondelete="RESTRICT")) + model_id: Mapped[str | None] = mapped_column(Text, ForeignKey("models.id", ondelete="RESTRICT")) + inference_device: Mapped[str | None] = mapped_column(String(64), nullable=True) + is_running: Mapped[bool] = mapped_column(Boolean, default=False) + is_active: Mapped[bool] = mapped_column(Boolean, default=False) + data_collection_policies: Mapped[list] = mapped_column(JSON, nullable=False, default=list) + created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) + updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) + + project = relationship("ProjectDB", back_populates="pipeline", lazy="joined") + sink = relationship("SinkDB", uselist=False, lazy="joined") + source = relationship("SourceDB", uselist=False, lazy="joined") + model = relationship("ModelDB", uselist=False, lazy="joined") + + +class SourceDB(Base): + __tablename__ = "sources" + + id: Mapped[str] = mapped_column(Text, primary_key=True, default=lambda: str(uuid4())) + project_id: Mapped[str] = mapped_column(ForeignKey("projects.id")) + name: Mapped[str] = mapped_column(String(255), nullable=False) + source_type: Mapped[str] = mapped_column(String(50), nullable=False) + config_data: Mapped[dict] = mapped_column(JSON, nullable=False) + created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) + updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) + + +class SinkDB(Base): + __tablename__ = "sinks" + + id: Mapped[str] = mapped_column(Text, primary_key=True, default=lambda: str(uuid4())) + project_id: Mapped[str] = mapped_column(ForeignKey("projects.id")) + name: Mapped[str] = mapped_column(String(255), nullable=False) + sink_type: Mapped[str] = mapped_column(String(50), nullable=False) + rate_limit: Mapped[float | None] = mapped_column(Float, nullable=True) + config_data: Mapped[dict] = mapped_column(JSON, nullable=False) + output_formats: Mapped[list] = mapped_column(JSON, nullable=False, default=list) + created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) + updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.current_timestamp()) diff --git a/application/backend/src/entities/__init__.py b/application/backend/src/entities/__init__.py new file mode 100644 index 0000000000..4057dc0163 --- /dev/null +++ b/application/backend/src/entities/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 diff --git a/application/backend/src/entities/base_opencv_stream.py b/application/backend/src/entities/base_opencv_stream.py new file mode 100644 index 0000000000..f2f0fffa3b --- /dev/null +++ b/application/backend/src/entities/base_opencv_stream.py @@ -0,0 +1,69 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import time +from abc import ABC +from typing import Any + +import cv2 +import numpy as np + +from entities.stream_data import StreamData +from entities.video_stream import VideoStream +from pydantic_models import SourceType + + +class BaseOpenCVStream(VideoStream, ABC): + """Base class for OpenCV-based video streams with common functionality.""" + + def __init__(self, source: str | int, source_type: SourceType, **metadata) -> None: + """Initialize OpenCV stream. + + Args: + source: Video source (device ID, file path, or URL) + source_type: Type of the video source + **metadata: Additional metadata for the stream + """ + self.source = source + self.source_type = source_type + self.metadata = metadata + self.cap: cv2.VideoCapture + self._initialize_capture() + + def _initialize_capture(self) -> None: + """Initialize the OpenCV VideoCapture.""" + self.cap = cv2.VideoCapture(self.source) + if not self.cap.isOpened(): + raise RuntimeError(f"Could not open video source: {self.source}") + + def _read_frame(self) -> np.ndarray: + """Read a frame from the capture device.""" + if self.cap is None: + raise RuntimeError("Video capture not initialized") + + ret, frame = self.cap.read() + if not ret: + return self._handle_read_failure() + return frame + + def _handle_read_failure(self) -> np.ndarray: + """Handle frame read failure. Override in subclasses for specific behavior.""" + raise RuntimeError(f"Failed to capture frame from {self.source_type.value}") + + def _get_source_metadata(self) -> dict[str, Any]: + """Get metadata specific to this source.""" + return {"source_type": self.source_type.value, **self.metadata} + + def get_data(self) -> StreamData: + """Get the latest frame from the video stream.""" + frame = self._read_frame() + return StreamData( + frame_data=frame, + timestamp=time.time(), + source_metadata=self._get_source_metadata(), + ) + + def release(self) -> None: + """Release OpenCV VideoCapture resources.""" + if self.cap is not None: + self.cap.release() diff --git a/application/backend/src/entities/images_folder_stream.py b/application/backend/src/entities/images_folder_stream.py new file mode 100644 index 0000000000..903b4e7679 --- /dev/null +++ b/application/backend/src/entities/images_folder_stream.py @@ -0,0 +1,115 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os +import threading +from collections.abc import Callable + +import cv2 +from loguru import logger +from watchdog.events import DirCreatedEvent, DirDeletedEvent, FileCreatedEvent, FileDeletedEvent, FileSystemEventHandler +from watchdog.observers import Observer + +from entities.stream_data import StreamData +from entities.video_stream import VideoStream +from pydantic_models.source import SourceType + + +class ImagesFolderEventHandler(FileSystemEventHandler): + """ + Images folder FS event handler, watches for files adding or removing + """ + + def __init__(self, on_file_added: Callable[[str], None], on_file_deleted: Callable[[str], None]): + self.on_file_added = on_file_added + self.on_file_deleted = on_file_deleted + + def on_created(self, event: DirCreatedEvent | FileCreatedEvent) -> None: + if isinstance(event, FileCreatedEvent): + path = event.src_path if isinstance(event.src_path, str) else event.src_path.decode("utf-8") + self.on_file_added(path) + + def on_deleted(self, event: DirDeletedEvent | FileDeletedEvent) -> None: + if isinstance(event, FileDeletedEvent): + path = event.src_path if isinstance(event.src_path, str) else event.src_path.decode("utf-8") + self.on_file_deleted(path) + + +class ImagesFolderStream(VideoStream): + """Video stream implementation using images folder.""" + + def __init__(self, folder_path: str, ignore_existing_images: bool) -> None: + """Initialize images folder stream. + Args: + folder_path (str): Path to the folder with images + ignore_existing_images (bool): Flag if images, which already are in the folder at startup moment, + should be ignored + """ + self.folder_path = folder_path + logger.info(f"Using folder_path: {self.folder_path}") + + self.files = [] + if not ignore_existing_images: + self.files = [ + os.path.join(folder_path, f) + for f in os.listdir(folder_path) + if os.path.isfile(os.path.join(folder_path, f)) + ] + self.files.sort(key=os.path.getmtime) + logger.info(f"Folder contains {len(self.files)} files") + + # Lock for thread-safe operations, required by watchdog thread(s) + self.files_lock = threading.Lock() + + self._init_watchdog(folder_path) + + def _init_watchdog(self, folder_path: str) -> None: + """ + Initialize the watchdog to monitor images folder events. + :param folder_path: path to the folder with images + """ + event_handler = ImagesFolderEventHandler( + self.file_added, + self.file_deleted, + ) + + self.observer = Observer() + self.observer.schedule(event_handler, folder_path, recursive=True) + self.observer.start() + + def file_added(self, path: str) -> None: + logger.debug(f"Added file {path}") + if path not in self.files: + with self.files_lock: + self.files.append(path) + + def file_deleted(self, path: str) -> None: + logger.debug(f"Deleted file {path}") + if path in self.files: + with self.files_lock: + self.files.remove(path) + + def get_data(self) -> StreamData | None: + try: + file = self.files.pop(0) + image = cv2.imread(file) + if image is None: + # Image cannot be loaded + return None + return StreamData( + frame_data=image, + timestamp=os.path.getmtime(file), + source_metadata={ + "source_type": SourceType.IMAGES_FOLDER.value, + "folder_path": self.folder_path, + }, + ) + except IndexError: + return None + + def is_real_time(self) -> bool: + return False + + def release(self) -> None: + self.observer.stop() + self.observer.join() diff --git a/application/backend/src/entities/ip_camera_stream.py b/application/backend/src/entities/ip_camera_stream.py new file mode 100644 index 0000000000..76cf0d38ba --- /dev/null +++ b/application/backend/src/entities/ip_camera_stream.py @@ -0,0 +1,51 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import time + +import cv2 +import numpy as np +from loguru import logger + +from entities.base_opencv_stream import BaseOpenCVStream +from pydantic_models.source import IPCameraSourceConfig, SourceType + + +class IPCameraStream(BaseOpenCVStream): + """Video stream implementation using IP camera via OpenCV.""" + + MAX_RETRIES = 3 + BACKOFF_FACTOR = 0.1 + + def __init__(self, config: IPCameraSourceConfig) -> None: + """Initialize IP camera stream.""" + super().__init__( + source=config.get_configured_stream_url(), + source_type=SourceType.IP_CAMERA, + stream_url=config.stream_url, # Original stream URL is kept for metadata + ) + logger.info("IP camera stream initialized") + self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) + + def _handle_read_failure(self) -> np.ndarray: + """Handle IP camera read failure""" + if self.cap is None: + raise RuntimeError("Video capture not initialized") + + for attempt in range(self.MAX_RETRIES): + logger.warning(f"Attempt {attempt + 1}: Failed to capture frame from IP camera, retrying...") + self.release() + self._initialize_capture() + ret, frame = self.cap.read() + if ret: + logger.info("Successfully reconnected to IP camera stream.") + return frame + time.sleep(self.BACKOFF_FACTOR * (attempt + 1)) # Exponential backoff + + raise RuntimeError("Failed to capture frame from IP camera after multiple retries") + + def is_real_time(self) -> bool: + return True + + def __enter__(self) -> "IPCameraStream": + return self diff --git a/application/backend/src/entities/stream_data.py b/application/backend/src/entities/stream_data.py new file mode 100644 index 0000000000..ce8b1d11f8 --- /dev/null +++ b/application/backend/src/entities/stream_data.py @@ -0,0 +1,42 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from dataclasses import dataclass +from typing import Any + +import numpy as np +from anomalib.data import NumpyImageBatch as PredictionResult + + +@dataclass(kw_only=True) +class InferenceData: + """ + InferenceData represents the data that is produced by the inference stage of the pipeline. + """ + + prediction: PredictionResult # prediction result, e.g., bounding boxes, masks, etc. + visualized_prediction: np.ndarray # visualized prediction (e.g., bounding boxes, masks, etc. drawn on the frame) + model_name: str # name of the model that produced the prediction + + +@dataclass(kw_only=True) +class StreamData: + """ + StreamData represents the data that flows through the various stages of the pipeline. + Each stage of the pipeline may set some of the attributes of this class, + making the corresponding information available to the subsequent stages. + """ + + # available after 'stream loading' stage + frame_data: np.ndarray # frame loaded as numpy array + timestamp: float # timestamp of the frame (epoch) + source_metadata: dict[str, Any] # unstructured metadata about the source of the frame (camera ID, video file, etc.) + + # available after 'data monitoring' stage + # TODO add information such as frame quality, anomalies detected, etc. + + # available after 'inference' stage + inference_data: InferenceData | None = None + + # available after 'model monitoring' stage + # TODO add information about drift detection, anomalies, etc. diff --git a/application/backend/src/entities/video_file_stream.py b/application/backend/src/entities/video_file_stream.py new file mode 100644 index 0000000000..254e049128 --- /dev/null +++ b/application/backend/src/entities/video_file_stream.py @@ -0,0 +1,31 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import cv2 +import numpy as np + +from entities.base_opencv_stream import BaseOpenCVStream +from pydantic_models import SourceType + + +class VideoFileStream(BaseOpenCVStream): + """Video stream implementation using video file via OpenCV.""" + + def __init__(self, video_path: str) -> None: + """Initialize video file stream.""" + super().__init__(source=video_path, source_type=SourceType.VIDEO_FILE, video_path=video_path) + + def _handle_read_failure(self) -> np.ndarray: + """Reset video to beginning when it ends and try again.""" + if self.cap is None: + raise RuntimeError("Video capture not initialized") + + # Reset video to beginning when it ends + self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0) + ret, frame = self.cap.read() + if not ret: + raise RuntimeError("Failed to capture frame from video file") + return frame + + def is_real_time(self) -> bool: + return False diff --git a/application/backend/src/entities/video_stream.py b/application/backend/src/entities/video_stream.py new file mode 100644 index 0000000000..82db11b6c1 --- /dev/null +++ b/application/backend/src/entities/video_stream.py @@ -0,0 +1,46 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from abc import ABC, abstractmethod +from collections.abc import Iterator + +from loguru import logger + +from entities.stream_data import StreamData + + +class VideoStream(ABC): + """Abstract base class for video stream implementations.""" + + @abstractmethod + def get_data(self) -> StreamData | None: + """Get the latest frame from the video stream if available. + Returns: + np.ndarray: The latest frame as a numpy array or None if no frame is available + """ + + @abstractmethod + def is_real_time(self) -> bool: + """Check if the video stream is real-time. + Returns: + bool: True if the video stream is real-time, False otherwise + """ + + def __enter__(self) -> "VideoStream": + return self + + def __exit__(self, exc_type, exc_val, exc_tb): # noqa: ANN001 + self.release() + + @abstractmethod + def release(self) -> None: + """Release the video stream resources.""" + + def __iter__(self) -> Iterator[StreamData | None]: + while True: + try: + yield self.get_data() + except RuntimeError as exc: + self.release() + logger.error(f"Stream error: {exc}", exc_info=True) + return diff --git a/application/backend/src/entities/webcam_stream.py b/application/backend/src/entities/webcam_stream.py new file mode 100644 index 0000000000..11a9abb6e3 --- /dev/null +++ b/application/backend/src/entities/webcam_stream.py @@ -0,0 +1,16 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from entities.base_opencv_stream import BaseOpenCVStream +from pydantic_models import SourceType + + +class WebcamStream(BaseOpenCVStream): + """Video stream implementation using webcam via OpenCV.""" + + def __init__(self, device_id: int = 0) -> None: + """Initialize webcam stream.""" + super().__init__(source=device_id, source_type=SourceType.WEBCAM, device_id=device_id) + + def is_real_time(self) -> bool: + return True diff --git a/application/backend/src/exception_handlers.py b/application/backend/src/exception_handlers.py new file mode 100644 index 0000000000..7f67a11d2e --- /dev/null +++ b/application/backend/src/exception_handlers.py @@ -0,0 +1,137 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import http +from collections import defaultdict +from collections.abc import Sequence + +import pydantic +from fastapi import Request, status +from fastapi.encoders import jsonable_encoder +from fastapi.exceptions import RequestValidationError +from loguru import logger +from starlette.responses import JSONResponse, Response + +from exceptions import GetiBaseException +from main import app +from services import ActivePipelineConflictError, ResourceNotFoundError + + +@app.exception_handler(GetiBaseException) +def handle_base_exception(request: Request, e: GetiBaseException) -> Response: + """ + Base exception handler + """ + response = jsonable_encoder({"error_code": e.error_code, "message": e.message, "http_status": e.http_status}) + headers: dict[str, str] | None = None + # 204 skipped as No Content needs to be revalidated + if e.http_status not in {200, 201, 202, 203, 205, 206, 207, 208, 226} and request.method == "GET": + headers = {"Cache-Control": "no-cache"} # always revalidate + if e.http_status in {204, 304} or e.http_status < 200: + return Response(status_code=int(e.http_status), headers=headers) + return JSONResponse(content=response, status_code=int(e.http_status), headers=headers) + + +@app.exception_handler(500) +async def handle_error(request, exception) -> JSONResponse: # noqa: ANN001, ARG001 + """ + Handler for internal server errors + """ + logger.error(f"Internal server error: {exception}") + headers = {"Cache-Control": "no-cache"} # always revalidate + return JSONResponse( + {"internal_server_error": "An internal server error occurred."}, + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + headers=headers, + ) + + +@app.exception_handler(ResourceNotFoundError) +async def handle_resource_not_found(request: Request, exception: ResourceNotFoundError) -> JSONResponse: # noqa: ARG001 + """Handler for resource not found errors""" + logger.error(str(exception)) + return JSONResponse( + {"detail": exception.message}, + status_code=status.HTTP_404_NOT_FOUND, + headers={"Cache-Control": "no-cache"}, + ) + + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse: # noqa: ARG001 + """ + Converts a RequestValidationError to a better readable Bad request exception. + """ + reformatted_message = defaultdict(list) + for pydantic_error in exc.errors(): + # `loc` usually is a list with 2 items describing the location of the error. + # The first item specifies if the error is a body, query or path parameter and + # the second is the parameter name. Here, only the parameter name is used along + # with a message explaining what the problem with the parameter is. + loc, msg = pydantic_error["loc"], pydantic_error["msg"] + filtered_loc = loc[1:] if loc[0] in {"body", "query", "path"} else loc + field_string = ".".join(str(item) for item in filtered_loc) # nested fields with dot-notation + reformatted_message[field_string].append(msg) + + headers = {"Cache-Control": "no-cache"} # always revalidate + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content=jsonable_encoder({ + "error_code": "bad_request", + "message": reformatted_message, + "http_status": http.HTTPStatus.BAD_REQUEST.value, + }), + headers=headers, + ) + + +@app.exception_handler(pydantic.ValidationError) +async def pydantic_validation_exception_handler(request: Request, exc: pydantic.ValidationError) -> JSONResponse: # noqa: ARG001 + """ + Converts a pydantic ValidationError to a better readable Bad request exception. + """ + + def format_location(loc: Sequence[str | int]) -> str: + """ + Format location path with proper dot notation and array indices. + + Example: + format_location(['a', 0, 'b', 1, 'c']) -> 'a[0].b[1].c' + """ + result = "" + for i, item in enumerate(loc): + if isinstance(item, int): + result += f"[{item}]" + else: + result += f".{item}" if i > 0 else str(item) + return result + + errors = [ + {"message": error["msg"], "type": error["type"], "location": format_location(error.get("loc", []))} + for error in exc.errors() + ] + + headers = {"Cache-Control": "no-cache"} # always revalidate + return JSONResponse( + status_code=status.HTTP_400_BAD_REQUEST, + content=jsonable_encoder({ + "error_code": "invalid_payload", + "errors": errors, + "http_status": http.HTTPStatus.BAD_REQUEST.value, + }), + headers=headers, + ) + + +@app.exception_handler(ActivePipelineConflictError) +async def handle_active_pipeline_conflict( + request: Request, # noqa: ARG001 + exception: ActivePipelineConflictError, +) -> JSONResponse: + """Handler for active pipeline conflict errors""" + logger.error(exception.message) + return JSONResponse( + {"detail": exception.message}, + status_code=status.HTTP_409_CONFLICT, + headers={"Cache-Control": "no-cache"}, + ) diff --git a/application/backend/src/exceptions.py b/application/backend/src/exceptions.py new file mode 100644 index 0000000000..51a353de8e --- /dev/null +++ b/application/backend/src/exceptions.py @@ -0,0 +1,80 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import http +from uuid import UUID + + +class GetiBaseException(Exception): + """ + Base class for Geti exceptions with a predefined HTTP error code. + + :param message: str message providing short description of error + :param error_code: str id of error + :param http_status: int default http status code to return to user + """ + + def __init__(self, message: str, error_code: str, http_status: int) -> None: + self.message = message + self.error_code = error_code + self.http_status = http_status + super().__init__(message) + + +class InvalidMediaException(GetiBaseException): + """ + Exception raised when uploaded media file is invalid. + + :param message: str containing a custom message. + """ + + def __init__(self, message: str) -> None: + super().__init__( + message=message, + error_code="invalid_media", + http_status=http.HTTPStatus.UNSUPPORTED_MEDIA_TYPE, + ) + + +class PayloadTooLargeException(GetiBaseException): + """ + Exception raised when the request payload is too large. + + :param max_size: Max size in MB + """ + + def __init__(self, max_size: float) -> None: + super().__init__( + message=f"Request too large: exceeding {max_size} MB is not allowed.", + error_code="payload_too_large", + http_status=http.HTTPStatus.REQUEST_ENTITY_TOO_LARGE, + ) + + +class DuplicateJobException(GetiBaseException): + """ + Exception raised when attempting to submit a duplicate job. + + :param message: str containing a custom message about the duplicate job. + """ + + def __init__(self, message: str = "A job with the same payload is already running or queued") -> None: + super().__init__( + message=message, + error_code="duplicate_job", + http_status=http.HTTPStatus.CONFLICT, + ) + + +class ResourceNotFoundException(GetiBaseException): + """ + Exception raised when a resource could not be found in database. + + :param resource_id: ID of the resource that was not found + """ + + def __init__(self, resource_id: str | UUID, resource_name: str) -> None: + super().__init__( + message=f"The requested {resource_name} could not be found. {resource_name.title()} ID: `{resource_id}`.", + error_code=f"{resource_name}_not_found", + http_status=http.HTTPStatus.NOT_FOUND, + ) diff --git a/application/backend/src/main.py b/application/backend/src/main.py new file mode 100644 index 0000000000..ea1336fb36 --- /dev/null +++ b/application/backend/src/main.py @@ -0,0 +1,64 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os + +import uvicorn +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from api.endpoints.active_pipeline_endpoints import router as active_pipeline_router +from api.endpoints.devices_endpoints import device_router +from api.endpoints.job_endpoints import job_router +from api.endpoints.media_endpoints import media_router +from api.endpoints.model_endpoints import model_router +from api.endpoints.pipeline_endpoints import router as pipeline_router +from api.endpoints.project_endpoints import project_router +from api.endpoints.sink_endpoints import router as sink_router +from api.endpoints.source_endpoints import router as source_router +from api.endpoints.trainable_models_endpoints import router as trainable_model_router +from api.endpoints.webrtc import router as webrtc_router +from core.lifecycle import lifespan +from settings import get_settings + +app = FastAPI( + lifespan=lifespan, + openapi_url="/api/openapi.json", + redoc_url=None, + docs_url=None, +) + +import exception_handlers # noqa: E402 + +_ = exception_handlers # to avoid import being removed by linters + +# TODO: check if middleware is required +# Enable CORS for local test UI +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "http://localhost:3000", + "http://localhost:9000", + "http://127.0.0.1:9000", + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) +app.include_router(project_router) +app.include_router(job_router) +app.include_router(media_router) +app.include_router(model_router) +app.include_router(pipeline_router) +app.include_router(active_pipeline_router) +app.include_router(source_router) +app.include_router(sink_router) +app.include_router(webrtc_router) +app.include_router(trainable_model_router) +app.include_router(device_router) + + +if __name__ == "__main__": + settings = get_settings() + uvicorn_port = int(os.environ.get("HTTP_SERVER_PORT", settings.port)) + uvicorn.run("main:app", loop="uvloop", host=settings.host, port=uvicorn_port, log_config=None) diff --git a/application/backend/src/pydantic_models/__init__.py b/application/backend/src/pydantic_models/__init__.py new file mode 100644 index 0000000000..0335d5a4f5 --- /dev/null +++ b/application/backend/src/pydantic_models/__init__.py @@ -0,0 +1,42 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from .job import Job, JobList, JobStatus, JobType +from .media import ImageExtension, Media, MediaList +from .metrics import InferenceMetrics, LatencyMetrics, PipelineMetrics, TimeWindow +from .model import Model, ModelList, PredictionLabel, PredictionResponse +from .pipeline import Pipeline, PipelineStatus +from .project import Project, ProjectList +from .sink import DisconnectedSinkConfig, OutputFormat, Sink, SinkType +from .source import DisconnectedSourceConfig, Source, SourceType +from .trainable_model import TrainableModelList + +__all__ = [ + "DisconnectedSinkConfig", + "DisconnectedSourceConfig", + "ImageExtension", + "InferenceMetrics", + "Job", + "JobList", + "JobStatus", + "JobType", + "LatencyMetrics", + "Media", + "MediaList", + "Model", + "ModelList", + "OutputFormat", + "Pipeline", + "PipelineMetrics", + "PipelineStatus", + "PredictionLabel", + "PredictionResponse", + "Project", + "ProjectList", + "Sink", + "SinkType", + "Source", + "SourceType", + "TimeWindow", + "TrainableModelList", +] diff --git a/application/backend/src/pydantic_models/base.py b/application/backend/src/pydantic_models/base.py new file mode 100644 index 0000000000..e5f9215694 --- /dev/null +++ b/application/backend/src/pydantic_models/base.py @@ -0,0 +1,42 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from abc import ABC +from typing import Any +from uuid import UUID, uuid4 + +from pydantic import BaseModel, Field, field_serializer +from pydantic.json_schema import SkipJsonSchema + + +class BaseIDModel(ABC, BaseModel): + """Base model with an id field.""" + + id: UUID = Field(default_factory=uuid4) + + @field_serializer("id") + def serialize_id(self, id: UUID, _info: Any) -> str: + return str(id) + + +class BaseIDNameModel(BaseIDModel): + """Base model with id and name fields.""" + + name: str = "Default Name" + + +class NoRequiredIDs(BaseModel): + """ + Mixin class for create models (POST request schemas) that makes id and project_id optional. + + This class is used as a base class for create models (e.g., WebcamSourceConfigCreate, + FolderSinkConfigCreate) to override required id and project_id fields from base classes. + Via Method Resolution Order (MRO), this class's fields take precedence, making these + fields optional and excluded from the JSON schema. + + The id field is auto-generated if not provided, and project_id is typically injected + from the URL path parameter in the endpoint handler. + """ + + project_id: SkipJsonSchema[UUID] = Field(exclude=True, default=UUID("00000000-0000-0000-0000-000000000000")) + id: SkipJsonSchema[UUID] = Field(exclude=True, default_factory=uuid4) diff --git a/application/backend/src/pydantic_models/devices.py b/application/backend/src/pydantic_models/devices.py new file mode 100644 index 0000000000..3bdbecb590 --- /dev/null +++ b/application/backend/src/pydantic_models/devices.py @@ -0,0 +1,15 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from pydantic import BaseModel + + +class DeviceList(BaseModel): + devices: list[str] + + model_config = { + "json_schema_extra": { + "example": { + "devices": ["CPU", "XPU", "NPU"], + } + } + } diff --git a/application/backend/src/pydantic_models/job.py b/application/backend/src/pydantic_models/job.py new file mode 100644 index 0000000000..d701c7900b --- /dev/null +++ b/application/backend/src/pydantic_models/job.py @@ -0,0 +1,60 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from datetime import datetime +from enum import StrEnum +from typing import Any +from uuid import UUID + +from pydantic import BaseModel, Field, computed_field, field_serializer + +from pydantic_models.base import BaseIDModel + + +class JobType(StrEnum): + TRAINING = "training" + OPTIMIZATION = "optimization" + + +class JobStatus(StrEnum): + PENDING = "pending" + RUNNING = "running" + COMPLETED = "completed" + FAILED = "failed" + CANCELED = "canceled" + + +class Job(BaseIDModel): + project_id: UUID + type: JobType = JobType.TRAINING + progress: int = Field(default=0, ge=0, le=100, description="Progress percentage from 0 to 100") + status: JobStatus = JobStatus.PENDING + payload: dict + message: str = "Job created" + start_time: datetime | None = None + end_time: datetime | None = None + + @field_serializer("project_id") + def serialize_project_id(self, project_id: UUID, _info: Any) -> str: + return str(project_id) + + +class JobList(BaseModel): + jobs: list[Job] + + +class JobSubmitted(BaseModel): + job_id: UUID + + +class JobCancelled(BaseModel): + job_id: UUID + + @computed_field + def message(self) -> str: + return f"Job with ID `{self.job_id}` marked as cancelled." + + +class TrainJobPayload(BaseModel): + project_id: UUID = Field(exclude=True) + model_name: str + device: str | None = Field(default=None) diff --git a/application/backend/src/pydantic_models/media.py b/application/backend/src/pydantic_models/media.py new file mode 100644 index 0000000000..13d1f793e3 --- /dev/null +++ b/application/backend/src/pydantic_models/media.py @@ -0,0 +1,39 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from datetime import datetime +from enum import StrEnum +from uuid import UUID + +from pydantic import BaseModel, Field + +from pydantic_models.base import BaseIDModel + + +class Subset(StrEnum): + TRAINING = "training" + TESTING = "testing" + + +class ImageExtension(StrEnum): + JPG = ".jpg" + JPEG = ".jpeg" + PNG = ".png" + BMP = ".bmp" + TIF = ".tif" + TIFF = ".tiff" + JFIF = ".jfif" + WEBP = ".webp" + + +class Media(BaseIDModel): + project_id: UUID + filename: str + size: int = Field(..., ge=0) + is_anomalous: bool + width: int = Field(..., ge=0) + height: int = Field(..., ge=0) + created_at: datetime | None = None + + +class MediaList(BaseModel): + media: list[Media] diff --git a/application/backend/src/pydantic_models/metrics.py b/application/backend/src/pydantic_models/metrics.py new file mode 100644 index 0000000000..00e01e45e8 --- /dev/null +++ b/application/backend/src/pydantic_models/metrics.py @@ -0,0 +1,62 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from datetime import datetime + +from pydantic import BaseModel, Field + + +class LatencyMetrics(BaseModel): + """Latency metrics for inference operations""" + + avg_ms: float | None = Field(..., description="Average latency in milliseconds") + min_ms: float | None = Field(..., description="Minimum latency in milliseconds") + max_ms: float | None = Field(..., description="Maximum latency in milliseconds") + p95_ms: float | None = Field(..., description="95th percentile latency in milliseconds") + latest_ms: float | None = Field(..., description="Latest recorded latency in milliseconds") + + +class ThroughputMetrics(BaseModel): + """Throughput metrics for inference operations""" + + avg_requests_per_second: float | None = Field(..., description="Average requests per second") + total_requests: int | None = Field(..., description="Total number of requests in the time window") + max_requests_per_second: float | None = Field(..., description="Max requests per second") + + +class InferenceMetrics(BaseModel): + """Inference-related metrics""" + + latency: LatencyMetrics + throughput: ThroughputMetrics + + +class TimeWindow(BaseModel): + """Time window for metrics calculation""" + + start: datetime = Field(..., description="Start timestamp of the time window") + end: datetime = Field(..., description="End timestamp of the time window") + time_window: int = Field(..., description="Duration of the time window in seconds") + + +class PipelineMetrics(BaseModel): + """Pipeline metrics response""" + + time_window: TimeWindow + inference: InferenceMetrics + + model_config = { + "json_schema_extra": { + "example": { + "time_window": {"start": "2025-08-25T10:00:00Z", "end": "2025-08-25T10:01:00Z", "time_window": 60}, + "inference": { + "latency": {"avg_ms": 15.1, "min_ms": 12.3, "max_ms": 30.4, "p95_ms": 25.4, "latest_ms": 15.6}, + "throughput": { + "avg_requests_per_second": 66.7, + "total_requests": 4000, + "max_requests_per_second": 85.2, + }, + }, + } + } + } diff --git a/application/backend/src/pydantic_models/model.py b/application/backend/src/pydantic_models/model.py new file mode 100644 index 0000000000..8d705fd489 --- /dev/null +++ b/application/backend/src/pydantic_models/model.py @@ -0,0 +1,87 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import os +from enum import StrEnum +from uuid import UUID + +from anomalib.deploy import ExportType +from pydantic import BaseModel, Field, model_validator + +from pydantic_models.base import BaseIDNameModel + + +class PredictionLabel(StrEnum): + """Enum for prediction labels.""" + + NORMAL = "Normal" + ANOMALOUS = "Anomalous" + + +class Model(BaseIDNameModel): + """ + Base model schema that includes common fields for all models. + This can be extended by other schemas to include additional fields. + """ + + format: ExportType = ExportType.OPENVINO + project_id: UUID + threshold: float = Field(default=0.5, gt=0.0, lt=1.0, description="Confidence threshold for the model") + is_ready: bool = Field(default=False, description="Indicates if the model is ready for use") + export_path: str | None = None + train_job_id: UUID = Field(description="ID of the training job for this model") + size: int | None = Field(default=None, ge=0, description="Total size in bytes of exported model artifacts") + + @property + def weights_path(self) -> str: + if self.export_path is None: + raise ValueError("export_path is required to get weights_path") + return os.path.join(self.export_path, self.format.name.lower()) + + model_config = { + "json_schema_extra": { + "example": { + "id": "76e07d18-196e-4e33-bf98-ac1d35dca4cb", + "project_id": "16e07d18-196e-4e33-bf98-ac1d35dcaaaa", + "name": "PatchCore", + "format": "openvino", + "is_ready": True, + "export_path": ( + "/data/projects/16e07d18-196e-4e33-bf98-ac1d35dcaaaa/models/76e07d18-196e-4e33-bf98-ac1d35dca4cb" + ), + "threshold": 0.5, + "train_job_id": "0db0c16d-0d3c-4e0e-bc5a-ca710579e549", + "size": 12345678, + } + } + } + + +class ModelList(BaseModel): + models: list[Model] + + +class PredictionResponse(BaseModel): + """Response model for model prediction results.""" + + anomaly_map: str = Field(description="Base64-encoded anomaly map image") + label: PredictionLabel = Field(description="Prediction label") + score: float = Field(ge=0.0, le=1.0, description="Confidence score between 0 and 1") + + @model_validator(mode="after") + def validate_score_range(self) -> "PredictionResponse": + """Ensure score is within valid range [0, 1] and handle edge cases.""" + if not (0.0 <= self.score <= 1.0): + raise ValueError(f"Score must be between 0.0 and 1.0, got {self.score}") + return self + + model_config = { + "json_schema_extra": { + "example": { + "anomaly_map": ( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==" + ), + "label": "Normal", + "score": 0.23, + } + } + } diff --git a/application/backend/src/pydantic_models/pipeline.py b/application/backend/src/pydantic_models/pipeline.py new file mode 100644 index 0000000000..bc4efe3050 --- /dev/null +++ b/application/backend/src/pydantic_models/pipeline.py @@ -0,0 +1,97 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from enum import StrEnum +from functools import cached_property +from uuid import UUID + +from pydantic import BaseModel, ConfigDict, Field, model_validator + +from pydantic_models.model import Model +from pydantic_models.sink import Sink +from pydantic_models.source import Source + + +class PipelineStatus(StrEnum): + IDLE = "idle" + ACTIVE = "active" + RUNNING = "running" + + @classmethod + def from_bool(cls, is_running: bool, is_active: bool) -> "PipelineStatus": + if is_running: + return cls.RUNNING + if is_active: + return cls.ACTIVE + return cls.IDLE + + @property + def is_running(self) -> bool: + return self == PipelineStatus.RUNNING + + @property + def is_active(self) -> bool: + return self in {PipelineStatus.RUNNING, PipelineStatus.ACTIVE} + + +class Pipeline(BaseModel): + project_id: UUID # ID of the project this pipeline belongs to + source: Source | None = None # None if disconnected + sink: Sink | None = None # None if disconnected + model: Model | None = None # None if no model is selected + source_id: UUID | None = Field( + default=None, exclude=True + ) # ID of the source, used for DB mapping, not exposed in API + sink_id: UUID | None = Field(default=None, exclude=True) # ID of the sink, used for DB mapping, not exposed in API + model_id: UUID | None = Field( + default=None, exclude=True + ) # ID of the model, used for DB mapping, not exposed in API + status: PipelineStatus = PipelineStatus.IDLE # Current status of the pipeline + inference_device: str | None = Field(default=None) + + # TODO: can be confused with status.is_running / is_active, consider refactoring + is_running: bool | None = Field(default=None, exclude=True) # If set will overwrite status + is_active: bool | None = Field(default=None, exclude=True) # If set will overwrite status + + model_config = ConfigDict( + validate_assignment=True, + json_schema_extra={ + "example": { + "project_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "source": { + "source_type": "video_file", + "name": "Sample Video", + "id": "712750b2-5a82-47ee-8fba-f3dc96cb615d", + "video_path": "/path/to/video.mp4", + }, + "sink": { + "id": "b5787c06-964b-4097-8eca-238b8cf79fc8", + "sink_type": "folder", + "name": "Local Folder", + "folder_path": "/path/to/output", + "output_formats": ["image_original", "image_with_predictions", "predictions"], + "rate_limit": 0.2, + }, + "model": { + "id": "76e07d18-196e-4e33-bf98-ac1d35dca4cb", + "name": "PatchCore", + "format": "openvino", + }, + "status": "running", + } + }, + ) + + @model_validator(mode="after") + def validate_running_status(self) -> "Pipeline": + if self.is_running: + self.status = PipelineStatus.RUNNING + if self.status == PipelineStatus.RUNNING and any( + x is None for x in (self.source_id or self.source, self.sink_id or self.sink, self.model_id or self.model) + ): + raise ValueError("Pipeline cannot be in 'running' status when source, sink, or model is not configured.") + return self + + @cached_property + def id(self) -> UUID: + return self.project_id diff --git a/application/backend/src/pydantic_models/project.py b/application/backend/src/pydantic_models/project.py new file mode 100644 index 0000000000..2344d504d2 --- /dev/null +++ b/application/backend/src/pydantic_models/project.py @@ -0,0 +1,16 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from datetime import datetime + +from pydantic import BaseModel, Field + +from pydantic_models.base import BaseIDNameModel + + +class Project(BaseIDNameModel): + created_at: datetime | None = Field(default=None, description="Project creation timestamp", exclude=True) + + +class ProjectList(BaseModel): + projects: list[Project] diff --git a/application/backend/src/pydantic_models/sink.py b/application/backend/src/pydantic_models/sink.py new file mode 100644 index 0000000000..66b742e7cc --- /dev/null +++ b/application/backend/src/pydantic_models/sink.py @@ -0,0 +1,186 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from enum import StrEnum +from os import getenv +from typing import Annotated, Literal +from uuid import UUID + +from pydantic import Field, TypeAdapter + +from pydantic_models.base import BaseIDNameModel, NoRequiredIDs + +MQTT_USERNAME = "MQTT_USERNAME" +MQTT_PASSWORD = "MQTT_PASSWORD" # noqa: S105 + + +class SinkType(StrEnum): + DISCONNECTED = "disconnected" + FOLDER = "folder" + MQTT = "mqtt" + ROS = "ros" + WEBHOOK = "webhook" + + +class OutputFormat(StrEnum): + IMAGE_ORIGINAL = "image_original" + IMAGE_WITH_PREDICTIONS = "image_with_predictions" + PREDICTIONS = "predictions" + + +class BaseSinkConfig(BaseIDNameModel): + project_id: UUID + output_formats: list[OutputFormat] + rate_limit: float | None = None # Rate limit in Hz, None means no limit + + +class DisconnectedSinkConfig(BaseSinkConfig): + sink_type: Literal[SinkType.DISCONNECTED] = SinkType.DISCONNECTED + project_id: UUID = UUID("00000000-0000-0000-0000-000000000000") + name: str = "No Sink" + output_formats: list[OutputFormat] = [] + + model_config = { + "json_schema_extra": { + "example": { + "sink_type": "disconnected", + "name": "No Sink", + "id": "00000000-0000-0000-0000-000000000000", + } + } + } + + +class FolderSinkConfig(BaseSinkConfig): + sink_type: Literal[SinkType.FOLDER] + folder_path: str + + model_config = { + "json_schema_extra": { + "example": { + "id": "b5787c06-964b-4097-8eca-238b8cf79fc8", + "sink_type": "folder", + "name": "Local Folder", + "folder_path": "/path/to/output", + "output_formats": ["image_original", "image_with_predictions", "predictions"], + "rate_limit": 0.2, + } + } + } + + +class MqttSinkConfig(BaseSinkConfig): + sink_type: Literal[SinkType.MQTT] + broker_host: str + broker_port: int + topic: str + auth_required: bool = False + + model_config = { + "json_schema_extra": { + "example": { + "id": "c1a70159-9c9e-4f02-821a-02576321056c", + "sink_type": "mqtt", + "name": "Local MQTT Broker", + "broker_host": "localhost", + "broker_port": 1883, + "topic": "predictions", + "output_formats": ["predictions"], + "auth_required": True, + } + } + } + + def get_credentials(self) -> tuple[str | None, str | None]: + """Configure stream URL with authentication if required.""" + if not self.auth_required: + return None, None + + username = getenv(MQTT_USERNAME) + password = getenv(MQTT_PASSWORD) + + if not username or not password: + raise RuntimeError("MQTT credentials not provided.") + + return username, password + + +class RosSinkConfig(BaseSinkConfig): + sink_type: Literal[SinkType.ROS] + topic: str + + model_config = { + "json_schema_extra": { + "example": { + "id": "6f1d96ac-db38-42a9-9a11-142d404f493f", + "sink_type": "ros", + "name": "ROS2 Predictions Topic", + "topic": "/predictions", + "output_formats": ["predictions"], + } + } + } + + +HttpMethod = Literal["POST", "PUT", "PATCH"] +HttpHeaders = dict[str, str] + + +class WebhookSinkConfig(BaseSinkConfig): + sink_type: Literal[SinkType.WEBHOOK] + webhook_url: str + http_method: HttpMethod = "POST" + headers: HttpHeaders | None = None + timeout: int = 10 # seconds + + model_config = { + "json_schema_extra": { + "example": { + "id": "39ba53e5-9a03-44fc-b78a-83245cf14676", + "sink_type": "webhook", + "name": "Webhook Endpoint", + "webhook_url": "https://example.com/webhook", + "http_method": "PUT", + "headers": {"Authorization": "Bearer YOUR_TOKEN"}, + "output_formats": ["predictions"], + } + } + } + + +Sink = Annotated[ + FolderSinkConfig | MqttSinkConfig | RosSinkConfig | WebhookSinkConfig | DisconnectedSinkConfig, + Field(discriminator="sink_type"), +] + +SinkAdapter: TypeAdapter[Sink] = TypeAdapter(Sink) + +# --------------------------------- +# Creation Schemas (POST requests) +# --------------------------------- +# These schemas inherit from HasID first to override the required ID field with an auto-generated one (if absent) via +# MRO (Method Resolution Order). + + +class FolderSinkConfigCreate(NoRequiredIDs, FolderSinkConfig): + pass + + +class MqttSinkConfigCreate(NoRequiredIDs, MqttSinkConfig): + pass + + +class RosSinkConfigCreate(NoRequiredIDs, RosSinkConfig): + pass + + +class WebhookSinkConfigCreate(NoRequiredIDs, WebhookSinkConfig): + pass + + +SinkCreate = Annotated[ + FolderSinkConfigCreate | MqttSinkConfigCreate | RosSinkConfigCreate | WebhookSinkConfigCreate, + Field(discriminator="sink_type"), +] + +SinkCreateAdapter: TypeAdapter[SinkCreate] = TypeAdapter(SinkCreate) diff --git a/application/backend/src/pydantic_models/source.py b/application/backend/src/pydantic_models/source.py new file mode 100644 index 0000000000..cbfae7371e --- /dev/null +++ b/application/backend/src/pydantic_models/source.py @@ -0,0 +1,165 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from enum import StrEnum +from os import getenv +from typing import Annotated, Literal +from urllib.parse import urlparse, urlunparse +from uuid import UUID + +from pydantic import Field, TypeAdapter + +from pydantic_models.base import BaseIDNameModel, NoRequiredIDs + +IP_CAMERA_USERNAME = "IP_CAMERA_USERNAME" +IP_CAMERA_PASSWORD = "IP_CAMERA_PASSWORD" # noqa: S105 + + +class SourceType(StrEnum): + # TODO: remove or refactor "DISCONNECTED" into separate enums if needed + DISCONNECTED = "disconnected" + WEBCAM = "webcam" + IP_CAMERA = "ip_camera" + VIDEO_FILE = "video_file" + IMAGES_FOLDER = "images_folder" + + +class BaseSourceConfig(BaseIDNameModel): + project_id: UUID + source_type: str + + +class DisconnectedSourceConfig(BaseSourceConfig): + source_type: Literal[SourceType.DISCONNECTED] = SourceType.DISCONNECTED + project_id: UUID = UUID("00000000-0000-0000-0000-000000000000") + name: str = "No Source" + + +class WebcamSourceConfig(BaseSourceConfig): + source_type: Literal[SourceType.WEBCAM] + device_id: int + + model_config = { + "json_schema_extra": { + "example": { + "source_type": "webcam", + "name": "Webcam 0", + "id": "f9e0ae4f-d96c-4304-baab-2ab845362d03", + "device_id": 0, + } + } + } + + +class IPCameraSourceConfig(BaseSourceConfig): + source_type: Literal[SourceType.IP_CAMERA] + stream_url: str + auth_required: bool = False + + model_config = { + "json_schema_extra": { + "example": { + "source_type": "ip_camera", + "name": "Street Camera 123", + "id": "3d055c8a-2536-46ea-8f3c-832bd6f8bbdc", + "stream_url": "http://example.com/stream", + "auth_required": True, + } + } + } + + def get_configured_stream_url(self) -> str: + """Configure stream URL with authentication if required.""" + if not self.auth_required: + return self.stream_url + + username = getenv(IP_CAMERA_USERNAME) + password = getenv(IP_CAMERA_PASSWORD) + + if not username or not password: + raise RuntimeError("IP camera credentials not provided.") + + # Modify the stream URL to include authentication + uri = urlparse(self.stream_url) + netloc = f"{username}:{password}@{uri.netloc}" + return urlunparse((uri.scheme, netloc, uri.path, uri.params, uri.query, uri.fragment)) + + +class VideoFileSourceConfig(BaseSourceConfig): + source_type: Literal[SourceType.VIDEO_FILE] + video_path: str + + model_config = { + "json_schema_extra": { + "example": { + "source_type": "video_file", + "name": "Sample Video", + "id": "712750b2-5a82-47ee-8fba-f3dc96cb615d", + "video_path": "/path/to/video.mp4", + } + } + } + + +class ImagesFolderSourceConfig(BaseSourceConfig): + source_type: Literal[SourceType.IMAGES_FOLDER] + images_folder_path: str + ignore_existing_images: bool + + model_config = { + "json_schema_extra": { + "example": { + "source_type": "images_folder", + "name": "Best Photos", + "id": "4a580a0e-b841-4c70-bf88-2d68a28f780d", + "images_folder_path": "/path/to/images", + "ignore_existing_images": True, + } + } + } + + +Source = Annotated[ + WebcamSourceConfig + | IPCameraSourceConfig + | VideoFileSourceConfig + | ImagesFolderSourceConfig + | DisconnectedSourceConfig, + Field(discriminator="source_type"), +] + +SourceAdapter: TypeAdapter[Source] = TypeAdapter(Source) + + +# --------------------------------- +# Creation Schemas (POST requests) +# --------------------------------- +# These schemas inherit from HasID first to override the required ID field with an auto-generated one (if absent) via +# MRO (Method Resolution Order). + + +class WebcamSourceConfigCreate(NoRequiredIDs, WebcamSourceConfig): + pass + + +class IPCameraSourceConfigCreate(NoRequiredIDs, IPCameraSourceConfig): + pass + + +class VideoFileSourceConfigCreate(NoRequiredIDs, VideoFileSourceConfig): + pass + + +class ImagesFolderSourceConfigCreate(NoRequiredIDs, ImagesFolderSourceConfig): + pass + + +SourceCreate = Annotated[ + WebcamSourceConfigCreate + | IPCameraSourceConfigCreate + | VideoFileSourceConfigCreate + | ImagesFolderSourceConfigCreate, + Field(discriminator="source_type"), +] + +SourceCreateAdapter: TypeAdapter[SourceCreate] = TypeAdapter(SourceCreate) diff --git a/application/backend/src/pydantic_models/trainable_model.py b/application/backend/src/pydantic_models/trainable_model.py new file mode 100644 index 0000000000..745cf0cdd8 --- /dev/null +++ b/application/backend/src/pydantic_models/trainable_model.py @@ -0,0 +1,10 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from pydantic import BaseModel + + +class TrainableModelList(BaseModel): + """List wrapper for returning multiple trainable models.""" + + trainable_models: list[str] diff --git a/application/backend/src/pydantic_models/webrtc.py b/application/backend/src/pydantic_models/webrtc.py new file mode 100644 index 0000000000..44f6a807c8 --- /dev/null +++ b/application/backend/src/pydantic_models/webrtc.py @@ -0,0 +1,20 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from pydantic import BaseModel, Field + + +class InputData(BaseModel): + webrtc_id: str + conf_threshold: float = Field(ge=0, le=1) + + +class Offer(BaseModel): + webrtc_id: str + sdp: str + type: str + + +class Answer(BaseModel): + sdp: str + type: str diff --git a/application/backend/src/repositories/__init__.py b/application/backend/src/repositories/__init__.py new file mode 100644 index 0000000000..f577fc29d5 --- /dev/null +++ b/application/backend/src/repositories/__init__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from .job_repo import JobRepository +from .media_repo import MediaRepository +from .model_repo import ModelRepository +from .pipeline_repo import PipelineRepository +from .project_repo import ProjectRepository +from .sink_repo import SinkRepository +from .source_repo import SourceRepository + +__all__ = [ + "JobRepository", + "MediaRepository", + "ModelRepository", + "PipelineRepository", + "ProjectRepository", + "SinkRepository", + "SourceRepository", +] diff --git a/application/backend/src/repositories/base.py b/application/backend/src/repositories/base.py new file mode 100644 index 0000000000..33a459fdc3 --- /dev/null +++ b/application/backend/src/repositories/base.py @@ -0,0 +1,128 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import abc +from collections.abc import Callable +from typing import Any, TypeVar +from uuid import UUID + +from sqlalchemy.ext.asyncio.session import AsyncSession +from sqlalchemy.sql import expression +from sqlalchemy.sql.selectable import Select, and_ + +from db.schema import Base +from pydantic_models.base import BaseIDModel + +ModelType = TypeVar("ModelType", bound=BaseIDModel) +SchemaType = TypeVar("SchemaType", bound=Base) + + +class BaseRepository[ModelType, SchemaType](metaclass=abc.ABCMeta): + """Base repository class for database operations.""" + + def __init__(self, db: AsyncSession, schema: type[SchemaType]): + self.db = db + self.schema = schema + + @property + @abc.abstractmethod + def to_schema(self) -> Callable[[ModelType], SchemaType]: + """to_schema mapper callable""" + + @property + @abc.abstractmethod + def from_schema(self) -> Callable[[SchemaType], ModelType]: + """from_schema mapper callable""" + + @property + def base_filters(self) -> dict: + """Base filter expression for the repository""" + return {} + + def _get_filter_query(self, extra_filters: dict | None = None, expressions: list[Any] | None = None) -> Select: + """Build query with filters and expressions combined with AND.""" + query = expression.select(self.schema) + + # Apply keyword filters (column=value) + if extra_filters is None: + extra_filters = {} + combined_filters = extra_filters | self.base_filters + if combined_filters: + query = query.filter_by(**combined_filters) + + # Apply additional expressions with AND + if expressions: + query = query.where(and_(*expressions)) + + return query + + async def get_by_id(self, obj_id: str | UUID) -> ModelType | None: + return await self.get_one(extra_filters={"id": self._id_to_str(obj_id)}) + + async def get_one( + self, + extra_filters: dict | None = None, + expressions: list[Any] | None = None, + order_by: Any | None = None, + ascending: bool = False, + ) -> ModelType | None: + query = self._get_filter_query(extra_filters=extra_filters, expressions=expressions) + if order_by is not None: + query = query.order_by(order_by.asc() if ascending else order_by.desc()) + result = await self.db.execute(query) + first_result = result.scalars().first() + if first_result: + return self.from_schema(first_result) + return None + + async def get_all(self, extra_filters: dict | None = None, expressions: list[Any] | None = None) -> list[ModelType]: + query = self._get_filter_query(extra_filters=extra_filters, expressions=expressions) + results = await self.db.execute(query) + scalars = results.scalars().all() + return [self.from_schema(result) for result in scalars] + + async def save(self, item: ModelType) -> ModelType: + schema_item: SchemaType = self.to_schema(item) + self.db.add(schema_item) + await self.db.commit() + return item + + async def update(self, item: ModelType, partial_update: dict) -> ModelType: + # note: model_copy does not validate the model, so we need to validate explicitly + to_update = item.model_copy(update=partial_update, deep=True) # type: ignore[attr-defined] + item.__class__.model_validate(to_update.model_dump()) # type: ignore[attr-defined] + schema_item: SchemaType = self.to_schema(to_update) + await self.db.merge(schema_item) + await self.db.commit() + updated = await self.get_by_id(item.id) # type: ignore[attr-defined] + if updated is None: + raise ValueError(f"{item.__class__} with ID `{item.id}` doesn't exist") # type: ignore[attr-defined] + return updated + + async def delete_by_id(self, obj_id: str | UUID) -> None: + if not hasattr(self.schema, "id"): + raise AttributeError(f"Delete by ID is not supported by schema: `{self.schema}`") + + obj_id = self._id_to_str(obj_id) + where_expression = [ + self.schema.id == obj_id, # type: ignore[attr-defined] + *[self.schema.__table__.c[k] == v for k, v in self.base_filters.items()], # type: ignore[attr-defined] + ] + query = expression.delete(self.schema).where(*where_expression) + await self.db.execute(query) + await self.db.commit() + + @staticmethod + def _id_to_str(obj_id: str | UUID) -> str: + if isinstance(obj_id, UUID): + return str(obj_id) + return obj_id + + +class ProjectBaseRepository(BaseRepository[ModelType, SchemaType], metaclass=abc.ABCMeta): + def __init__(self, db: AsyncSession, project_id: str | UUID, schema: type[SchemaType]): + super().__init__(db, schema) + self.project_id = self._id_to_str(project_id) + + @property + def base_filters(self) -> dict: + return {"project_id": self.project_id} diff --git a/application/backend/src/repositories/binary_repo.py b/application/backend/src/repositories/binary_repo.py new file mode 100644 index 0000000000..8dca70573a --- /dev/null +++ b/application/backend/src/repositories/binary_repo.py @@ -0,0 +1,146 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import abc +import asyncio +import os +import shutil +from enum import StrEnum +from functools import cached_property +from uuid import UUID + +from anomalib.deploy import ExportType + +STORAGE_ROOT_PATH = "data" + + +class FileType(StrEnum): + IMAGES = "images" + MODELS = "models" + + +class BinaryRepository(metaclass=abc.ABCMeta): + def __init__(self, project_id: str | UUID, file_type: FileType): + self.project_id = str(project_id) + self.file_type = file_type + + async def read_file(self, filename: str) -> bytes: + """ + Read a binary file from the filesystem. + + :param filename: Relative path to the file. + :return: Binary content of the file. + """ + + def stdlib_read(): + full_path = self.get_full_path(filename) + if not os.path.isfile(full_path): + raise FileNotFoundError(f"File not found: {full_path}") + with open(full_path, "rb") as fp: + return fp.read() + + return await asyncio.to_thread(stdlib_read) + + @cached_property + def project_folder_path(self) -> str: + """ + Get the project folder path containing the binary files. + """ + return os.path.join(STORAGE_ROOT_PATH, self.file_type, "projects", self.project_id) + + @abc.abstractmethod + def get_full_path(self, filename: str) -> str: + """ + Get the full path for a given filename within the project folder. + + :param filename: Name of the file. + :return: Full path to the file. + """ + + async def save_file(self, filename: str, content: bytes) -> str: + """ + Save a binary file to the filesystem under the project directory. + + :param filename: Name of the file to save. + :param content: Binary content of the file. + :return: The path where the file was saved. + """ + + def stdlib_write(): + full_path = self.get_full_path(filename) + folder, _ = full_path.split(filename) + os.makedirs(folder, exist_ok=True) + with open(full_path, "wb") as f: + f.write(content) + return full_path + + try: + destination_path = await asyncio.to_thread(stdlib_write) + except Exception as e: + raise OSError(f"Failed to save {self.file_type} file: {filename}") from e + return destination_path + + async def delete_file(self, filename: str) -> None: + """ + Delete a binary file from the filesystem. + + :param filename: Name of the file to delete. + """ + + def stdlib_delete(): + full_path = self.get_full_path(filename) + if os.path.isfile(full_path): + os.remove(full_path) + else: + raise FileNotFoundError(f"File not found: {full_path}") + + await asyncio.to_thread(stdlib_delete) + + +class ImageBinaryRepository(BinaryRepository): + def __init__(self, project_id: str | UUID): + super().__init__(project_id=project_id, file_type=FileType.IMAGES) + + def get_full_path(self, filename: str) -> str: + return os.path.join(self.project_folder_path, filename) + + +class ModelBinaryRepository(BinaryRepository): + def __init__(self, project_id: str | UUID, model_id: str | UUID): + super().__init__(project_id=project_id, file_type=FileType.MODELS) + self._model_id = str(model_id) + + def get_full_path(self, filename: str) -> str: + return os.path.join(self.model_folder_path, filename) + + @cached_property + def model_folder_path(self) -> str: + """ + Get the folder path for models. + + :return: Folder path for models. + """ + return os.path.join(self.project_folder_path, self._model_id) + + async def delete_model_folder(self) -> None: + """ + Delete a model folder from the filesystem. + """ + + def stdlib_delete_folder(): + folder_path = self.model_folder_path + if os.path.exists(folder_path): + shutil.rmtree(folder_path) + else: + raise FileNotFoundError(f"Model folder not found: {folder_path}") + + await asyncio.to_thread(stdlib_delete_folder) + + def get_weights_file_path(self, format: ExportType, name: str) -> str: + """ + Read a weights file from the model folder. + + :param format: Format of the model (e.g., ExportType.OPENVINO). + :param name: Name of the weights to read. + :return: path of the weights file. + """ + return os.path.join(self.model_folder_path, "weights", format, name) diff --git a/application/backend/src/repositories/job_repo.py b/application/backend/src/repositories/job_repo.py new file mode 100644 index 0000000000..724559ac5c --- /dev/null +++ b/application/backend/src/repositories/job_repo.py @@ -0,0 +1,48 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from collections.abc import Callable +from uuid import UUID + +from sqlalchemy.ext.asyncio.session import AsyncSession + +from db.schema import JobDB +from pydantic_models import Job +from pydantic_models.job import JobStatus, JobType, TrainJobPayload +from repositories.base import BaseRepository +from repositories.mappers import JobMapper + + +class JobRepository(BaseRepository): + def __init__(self, db: AsyncSession): + super().__init__(db, schema=JobDB) + + @property + def to_schema(self) -> Callable[[Job], JobDB]: + return JobMapper.to_schema + + @property + def from_schema(self) -> Callable[[JobDB], Job]: + return JobMapper.from_schema + + async def is_job_duplicate(self, project_id: UUID, payload: TrainJobPayload) -> bool: + # Convert payload to dict for comparison + payload_dict = payload.model_dump() + + # Check for jobs with same payload that are not completed + existing_job = await self.get_one( + extra_filters={"project_id": self._id_to_str(project_id), "payload": payload_dict}, + expressions=[ + JobDB.status != JobStatus.COMPLETED, + JobDB.status != JobStatus.FAILED, + JobDB.status != JobStatus.CANCELED, + ], + ) + + return existing_job is not None + + async def get_pending_job_by_type(self, job_type: JobType) -> Job | None: + return await self.get_one( + extra_filters={"type": job_type, "status": JobStatus.PENDING}, + order_by=self.schema.created_at, + ascending=True, + ) diff --git a/application/backend/src/repositories/mappers/__init__.py b/application/backend/src/repositories/mappers/__init__.py new file mode 100644 index 0000000000..6eb0c4ba10 --- /dev/null +++ b/application/backend/src/repositories/mappers/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from .job_mapper import JobMapper +from .media_mapper import MediaMapper +from .model_mapper import ModelMapper +from .project_mapper import ProjectMapper +from .sink_mapper import SinkMapper +from .source_mapper import SourceMapper + +__all__ = [ + "JobMapper", + "MediaMapper", + "ModelMapper", + "ProjectMapper", + "SinkMapper", + "SourceMapper", +] diff --git a/application/backend/src/repositories/mappers/base_mapper_interface.py b/application/backend/src/repositories/mappers/base_mapper_interface.py new file mode 100644 index 0000000000..0054b00fd5 --- /dev/null +++ b/application/backend/src/repositories/mappers/base_mapper_interface.py @@ -0,0 +1,25 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from abc import ABCMeta, abstractmethod +from typing import TypeVar + +from pydantic import BaseModel + +from db.schema import Base + +DBEntity = TypeVar("DBEntity", bound=Base) +ModelEntity = TypeVar("ModelEntity", bound=BaseModel) + + +class IBaseMapper[DBEntity, ModelEntity](metaclass=ABCMeta): + """Mapper for pydantic model entity <-> DB schema conversions.""" + + @staticmethod + @abstractmethod + def to_schema(db_schema: ModelEntity) -> DBEntity: + """Convert ModelEntity to DBEntity.""" + + @staticmethod + @abstractmethod + def from_schema(model: DBEntity) -> ModelEntity: + """Convert DBEntity to ModelEntity.""" diff --git a/application/backend/src/repositories/mappers/job_mapper.py b/application/backend/src/repositories/mappers/job_mapper.py new file mode 100644 index 0000000000..cfabd9b60d --- /dev/null +++ b/application/backend/src/repositories/mappers/job_mapper.py @@ -0,0 +1,16 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from db.schema import JobDB +from pydantic_models import Job +from repositories.mappers.base_mapper_interface import IBaseMapper + + +class JobMapper(IBaseMapper): + @staticmethod + def to_schema(job: Job) -> JobDB: + return JobDB(**job.model_dump()) + + @staticmethod + def from_schema(job_db: JobDB) -> Job: + return Job.model_validate(job_db, from_attributes=True) diff --git a/application/backend/src/repositories/mappers/media_mapper.py b/application/backend/src/repositories/mappers/media_mapper.py new file mode 100644 index 0000000000..fc690fbcc7 --- /dev/null +++ b/application/backend/src/repositories/mappers/media_mapper.py @@ -0,0 +1,16 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from db.schema import MediaDB +from pydantic_models import Media +from repositories.mappers.base_mapper_interface import IBaseMapper + + +class MediaMapper(IBaseMapper): + @staticmethod + def to_schema(media: Media) -> MediaDB: + return MediaDB(**media.model_dump(mode="json")) + + @staticmethod + def from_schema(media_db: MediaDB) -> Media: + return Media.model_validate(media_db, from_attributes=True) diff --git a/application/backend/src/repositories/mappers/model_mapper.py b/application/backend/src/repositories/mappers/model_mapper.py new file mode 100644 index 0000000000..b10b6b5ab1 --- /dev/null +++ b/application/backend/src/repositories/mappers/model_mapper.py @@ -0,0 +1,16 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from db.schema import ModelDB +from pydantic_models import Model +from repositories.mappers.base_mapper_interface import IBaseMapper + + +class ModelMapper(IBaseMapper): + @staticmethod + def to_schema(model: Model) -> ModelDB: + return ModelDB(**model.model_dump(mode="json")) + + @staticmethod + def from_schema(model_db: ModelDB) -> Model: + return Model.model_validate(model_db, from_attributes=True) diff --git a/application/backend/src/repositories/mappers/pipeline_mapper.py b/application/backend/src/repositories/mappers/pipeline_mapper.py new file mode 100644 index 0000000000..c47092427e --- /dev/null +++ b/application/backend/src/repositories/mappers/pipeline_mapper.py @@ -0,0 +1,41 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from uuid import UUID + +from db.schema import PipelineDB +from pydantic_models import Pipeline, PipelineStatus +from repositories.mappers import ModelMapper, SinkMapper, SourceMapper +from repositories.mappers.base_mapper_interface import IBaseMapper + + +class PipelineMapper(IBaseMapper): + """Mapper for Pipeline schema entity <-> DB entity conversions.""" + + @staticmethod + def from_schema(pipeline_db: PipelineDB) -> Pipeline: + """Convert Pipeline db schema to Pipeline pydantic model.""" + return Pipeline( + project_id=UUID(pipeline_db.project_id), + source=SourceMapper.from_schema(pipeline_db.source) if pipeline_db.source else None, + sink=SinkMapper.from_schema(pipeline_db.sink) if pipeline_db.sink else None, + model=ModelMapper.from_schema(pipeline_db.model) if pipeline_db.model else None, + sink_id=UUID(pipeline_db.sink_id) if pipeline_db.sink_id else None, + model_id=UUID(pipeline_db.model_id) if pipeline_db.model_id else None, + source_id=UUID(pipeline_db.source_id) if pipeline_db.source_id else None, + status=PipelineStatus.from_bool(pipeline_db.is_running, pipeline_db.is_active), + inference_device=pipeline_db.inference_device.upper() if pipeline_db.inference_device else None, + ) + + @staticmethod + def to_schema(pipeline: Pipeline) -> PipelineDB: + """Convert Pipeline pydantic model to db schema.""" + return PipelineDB( + project_id=str(pipeline.project_id), + source_id=str(pipeline.source_id) if pipeline.source_id else None, + model_id=str(pipeline.model_id) if pipeline.model_id else None, + sink_id=str(pipeline.sink_id) if pipeline.sink_id else None, + is_running=pipeline.status.is_running, + is_active=pipeline.status.is_active, + inference_device=pipeline.inference_device.upper() if pipeline.inference_device else None, + ) diff --git a/application/backend/src/repositories/mappers/project_mapper.py b/application/backend/src/repositories/mappers/project_mapper.py new file mode 100644 index 0000000000..1be9a113b9 --- /dev/null +++ b/application/backend/src/repositories/mappers/project_mapper.py @@ -0,0 +1,16 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from db.schema import ProjectDB +from pydantic_models import Project +from repositories.mappers.base_mapper_interface import IBaseMapper + + +class ProjectMapper(IBaseMapper): + @staticmethod + def to_schema(project: Project) -> ProjectDB: + return ProjectDB(**project.model_dump(mode="json")) + + @staticmethod + def from_schema(model_db: ProjectDB) -> Project: + return Project.model_validate(model_db, from_attributes=True) diff --git a/application/backend/src/repositories/mappers/sink_mapper.py b/application/backend/src/repositories/mappers/sink_mapper.py new file mode 100644 index 0000000000..530885f8e8 --- /dev/null +++ b/application/backend/src/repositories/mappers/sink_mapper.py @@ -0,0 +1,56 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from db.schema import SinkDB +from pydantic_models.sink import Sink, SinkAdapter, SinkType +from repositories.mappers.base_mapper_interface import IBaseMapper + + +class SinkMapper(IBaseMapper): + """Mapper for Sink model <-> Sink schema conversions.""" + + # Define fields to exclude from config_data (common fields) + _COMMON_FIELDS: set[str] = { + "id", + "project_id", + "name", + "sink_type", + "output_formats", + "rate_limit", + "created_at", + "updated_at", + } + + @staticmethod + def from_schema(sink_db: SinkDB) -> Sink: + """Convert Sink DB schema to Sink pydantic model.""" + + config_data = sink_db.config_data or {} + return SinkAdapter.validate_python({ + "id": sink_db.id, + "project_id": sink_db.project_id, + "name": sink_db.name, + "sink_type": SinkType(sink_db.sink_type), + "output_formats": sink_db.output_formats, + "rate_limit": sink_db.rate_limit, + **config_data, + }) + + @staticmethod + def to_schema(sink: Sink) -> SinkDB: + """Convert Sink pydantic model to Sink DB schema.""" + + if sink is None: + raise ValueError("Sink config cannot be None") + + sink_dict = SinkAdapter.dump_python(sink, exclude=SinkMapper._COMMON_FIELDS, exclude_none=True) + + return SinkDB( + id=str(sink.id), + project_id=str(sink.project_id), + name=sink.name, + sink_type=sink.sink_type, + output_formats=sink.output_formats, + rate_limit=sink.rate_limit, + config_data=sink_dict, + ) diff --git a/application/backend/src/repositories/mappers/source_mapper.py b/application/backend/src/repositories/mappers/source_mapper.py new file mode 100644 index 0000000000..13eb9fb050 --- /dev/null +++ b/application/backend/src/repositories/mappers/source_mapper.py @@ -0,0 +1,42 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from db.schema import SourceDB +from pydantic_models.source import Source, SourceAdapter, SourceType +from repositories.mappers.base_mapper_interface import IBaseMapper + + +class SourceMapper(IBaseMapper): + """Mapper for Source model <-> Source schema conversions.""" + + # Define fields to exclude from config_data (common fields) + _COMMON_FIELDS: set[str] = {"id", "project_id", "name", "source_type", "created_at", "updated_at"} + + @staticmethod + def from_schema(source_db: SourceDB) -> Source: + """Convert Source schema to Source model.""" + + config_data = source_db.config_data or {} + return SourceAdapter.validate_python({ + "id": source_db.id, + "project_id": source_db.project_id, + "name": source_db.name, + "source_type": SourceType(source_db.source_type), + **config_data, + }) + + @staticmethod + def to_schema(source: Source) -> SourceDB: + """Convert Source model to Source schema.""" + if source is None: + raise ValueError("Source config cannot be None") + + source_dict = SourceAdapter.dump_python(source, exclude=SourceMapper._COMMON_FIELDS, exclude_none=True) + + return SourceDB( + id=str(source.id), + project_id=str(source.project_id), + name=source.name, + source_type=source.source_type.value, + config_data=source_dict, + ) diff --git a/application/backend/src/repositories/media_repo.py b/application/backend/src/repositories/media_repo.py new file mode 100644 index 0000000000..0508dd75a0 --- /dev/null +++ b/application/backend/src/repositories/media_repo.py @@ -0,0 +1,24 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from collections.abc import Callable +from uuid import UUID + +from sqlalchemy.ext.asyncio.session import AsyncSession + +from db.schema import MediaDB +from pydantic_models import Media +from repositories.base import ProjectBaseRepository +from repositories.mappers import MediaMapper + + +class MediaRepository(ProjectBaseRepository): + def __init__(self, db: AsyncSession, project_id: str | UUID): + super().__init__(db, schema=MediaDB, project_id=str(project_id)) + + @property + def to_schema(self) -> Callable[[Media], MediaDB]: + return MediaMapper.to_schema + + @property + def from_schema(self) -> Callable[[MediaDB], Media]: + return MediaMapper.from_schema diff --git a/application/backend/src/repositories/model_repo.py b/application/backend/src/repositories/model_repo.py new file mode 100644 index 0000000000..b463e28cde --- /dev/null +++ b/application/backend/src/repositories/model_repo.py @@ -0,0 +1,24 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from collections.abc import Callable +from uuid import UUID + +from sqlalchemy.ext.asyncio.session import AsyncSession + +from db.schema import ModelDB +from pydantic_models import Model +from repositories.base import ProjectBaseRepository +from repositories.mappers import ModelMapper + + +class ModelRepository(ProjectBaseRepository): + def __init__(self, db: AsyncSession, project_id: str | UUID): + super().__init__(db, schema=ModelDB, project_id=str(project_id)) + + @property + def to_schema(self) -> Callable[[Model], ModelDB]: + return ModelMapper.to_schema + + @property + def from_schema(self) -> Callable[[ModelDB], Model]: + return ModelMapper.from_schema diff --git a/application/backend/src/repositories/pipeline_repo.py b/application/backend/src/repositories/pipeline_repo.py new file mode 100644 index 0000000000..6e567e519a --- /dev/null +++ b/application/backend/src/repositories/pipeline_repo.py @@ -0,0 +1,33 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from collections.abc import Callable +from uuid import UUID + +from sqlalchemy.ext.asyncio.session import AsyncSession + +from db.schema import PipelineDB +from pydantic_models import Pipeline +from repositories.base import BaseRepository +from repositories.mappers.pipeline_mapper import PipelineMapper + + +class PipelineRepository(BaseRepository): + """Repository for pipeline-related database operations.""" + + def __init__(self, db: AsyncSession): + super().__init__(db, schema=PipelineDB) + + @property + def to_schema(self) -> Callable[[Pipeline], PipelineDB]: + return PipelineMapper.to_schema + + @property + def from_schema(self) -> Callable[[PipelineDB], Pipeline]: + return PipelineMapper.from_schema + + async def get_by_id(self, project_id: str | UUID) -> Pipeline | None: + return await self.get_one(extra_filters={"project_id": self._id_to_str(project_id)}) + + async def get_active_pipeline(self) -> Pipeline | None: + """Get the active pipeline from database.""" + return await self.get_one(expressions=[PipelineDB.is_active]) diff --git a/application/backend/src/repositories/project_repo.py b/application/backend/src/repositories/project_repo.py new file mode 100644 index 0000000000..5a738fb6c0 --- /dev/null +++ b/application/backend/src/repositories/project_repo.py @@ -0,0 +1,35 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from collections.abc import Callable + +from sqlalchemy.ext.asyncio.session import AsyncSession + +from db.schema import PipelineDB, ProjectDB +from pydantic_models import Project +from repositories.base import BaseRepository +from repositories.mappers import ProjectMapper + + +class ProjectRepository(BaseRepository): + def __init__(self, db: AsyncSession): + super().__init__(db, ProjectDB) + + @property + def to_schema(self) -> Callable[[Project], ProjectDB]: + return ProjectMapper.to_schema + + @property + def from_schema(self) -> Callable[[ProjectDB], Project]: + return ProjectMapper.from_schema + + async def get_by_name(self, name: str) -> list[Project]: + return await self.get_all(extra_filters={"name": name}) + + async def save(self, project: Project) -> Project: + project_schema: ProjectDB = self.to_schema(project) + project_schema.pipeline = PipelineDB( + project_id=project_schema.id, + ) + self.db.add(project_schema) + await self.db.commit() + return project diff --git a/application/backend/src/repositories/sink_repo.py b/application/backend/src/repositories/sink_repo.py new file mode 100644 index 0000000000..bc67a162a8 --- /dev/null +++ b/application/backend/src/repositories/sink_repo.py @@ -0,0 +1,26 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from collections.abc import Callable +from uuid import UUID + +from sqlalchemy.ext.asyncio.session import AsyncSession + +from db.schema import SinkDB +from pydantic_models import Sink +from repositories.base import ProjectBaseRepository +from repositories.mappers import SinkMapper + + +class SinkRepository(ProjectBaseRepository): + """Repository for sink-related database operations.""" + + def __init__(self, db: AsyncSession, project_id: UUID): + super().__init__(db, schema=SinkDB, project_id=project_id) + + @property + def to_schema(self) -> Callable[[Sink], SinkDB]: + return SinkMapper.to_schema + + @property + def from_schema(self) -> Callable[[SinkDB], Sink]: + return SinkMapper.from_schema diff --git a/application/backend/src/repositories/source_repo.py b/application/backend/src/repositories/source_repo.py new file mode 100644 index 0000000000..fd574deb7c --- /dev/null +++ b/application/backend/src/repositories/source_repo.py @@ -0,0 +1,26 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from collections.abc import Callable +from uuid import UUID + +from sqlalchemy.ext.asyncio.session import AsyncSession + +from db.schema import SourceDB +from pydantic_models import Source +from repositories.base import ProjectBaseRepository +from repositories.mappers import SourceMapper + + +class SourceRepository(ProjectBaseRepository): + """Repository for source-related database operations.""" + + def __init__(self, db: AsyncSession, project_id: UUID): + super().__init__(db, schema=SourceDB, project_id=project_id) + + @property + def to_schema(self) -> Callable[[Source], SourceDB]: + return SourceMapper.to_schema + + @property + def from_schema(self) -> Callable[[SourceDB], Source]: + return SourceMapper.from_schema diff --git a/application/backend/src/services/__init__.py b/application/backend/src/services/__init__.py new file mode 100644 index 0000000000..057b9d1944 --- /dev/null +++ b/application/backend/src/services/__init__.py @@ -0,0 +1,36 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from .active_pipeline_service import ActivePipelineService +from .configuration_service import ConfigurationService +from .dispatch_service import DispatchService +from .exceptions import ( + ActivePipelineConflictError, + ResourceAlreadyExistsError, + ResourceInUseError, + ResourceNotFoundError, +) +from .job_service import JobService +from .media_service import MediaService +from .model_service import ModelService +from .pipeline_service import PipelineService +from .project_service import ProjectService +from .training_service import TrainingService +from .video_stream_service import VideoStreamService + +__all__ = [ + "ActivePipelineConflictError", + "ActivePipelineService", + "ConfigurationService", + "DispatchService", + "JobService", + "MediaService", + "ModelService", + "PipelineService", + "ProjectService", + "ResourceAlreadyExistsError", + "ResourceInUseError", + "ResourceNotFoundError", + "TrainingService", + "VideoStreamService", +] diff --git a/application/backend/src/services/active_pipeline_service.py b/application/backend/src/services/active_pipeline_service.py new file mode 100644 index 0000000000..46e2336e55 --- /dev/null +++ b/application/backend/src/services/active_pipeline_service.py @@ -0,0 +1,171 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +import multiprocessing as mp +from multiprocessing.synchronize import Condition as ConditionClass +from threading import Thread + +from loguru import logger + +from db import get_async_db_session_ctx +from pydantic_models import DisconnectedSinkConfig, DisconnectedSourceConfig, Pipeline, Sink, Source +from repositories import PipelineRepository + + +class ActivePipelineService: + """ + A service used in workers for loading pipeline-based application configuration from SQLite database. + + This service handles loading and monitoring configuration changes based on the active pipeline. + The configuration is built from Source -> Pipeline -> Sinks relationships. + + The service must be created using the `create()` class method for proper async initialization. + In child processes, it automatically starts a daemon thread to monitor configuration changes. + + Example: + # Create service in main process + service = await ActivePipelineService.create() + + # Create service in child process with condition monitoring + service = await ActivePipelineService.create(config_changed_condition) + """ + + @classmethod + async def create( + cls, config_changed_condition: ConditionClass | None = None, start_daemon: bool = False + ) -> "ActivePipelineService": + """ + Factory method to create and initialize the service asynchronously. + + Args: + config_changed_condition: Multiprocessing Condition object for getting configuration + updates in child processes. Required for child processes. + start_daemon: whether or not to start daemon thread to monitor configuration changes. + + Returns: + ActivePipelineService: Fully initialized service instance. + + Raises: + ValueError: When config_changed_condition is None in a child process. + """ + instance = cls() + await instance._initialize(config_changed_condition, start_daemon) + return instance + + async def _initialize( + self, config_changed_condition: ConditionClass | None = None, start_daemon: bool = False + ) -> None: + """ + Initialize the service asynchronously. + + Args: + config_changed_condition: Multiprocessing Condition object for getting configuration + updates in child processes. Required for child processes. + start_daemon: whether or not to start daemon thread to monitor configuration changes. + + Raises: + ValueError: When config_changed_condition is None in a child process. + """ + self.config_changed_condition = config_changed_condition + self._source: Source = DisconnectedSourceConfig() + self._sink: Sink = DisconnectedSinkConfig() + self._pipeline: Pipeline | None = None + await self._load_app_config() + + # For child processes with config_changed_condition, start a daemon to monitor configuration changes + if start_daemon is not None and self.config_changed_condition is not None: + # Store the current event loop for the daemon thread to use + self._event_loop = asyncio.get_running_loop() + + self._config_reload_daemon = Thread( + target=self._reload_config_daemon_routine, name="Config reloader", daemon=True + ) + self._config_reload_daemon.start() + elif start_daemon is not None and self.config_changed_condition is None: + # This is a child process but no condition provided - this is likely an API process + # that doesn't need the daemon thread, so we just log and continue + logger.debug("Child process detected but no config_changed_condition provided - skipping daemon thread") + + async def reload(self) -> None: + """ + Reload the application configuration from the database. + + This method must be called from an async context and will await + the configuration reload operation. + """ + await self._load_app_config() + + async def _load_app_config(self) -> None: + """ + Load application configuration from the database. + + This method loads the active pipeline configuration and updates the + internal source and sink configurations accordingly. + """ + logger.info("Loading configuration from database") + async with get_async_db_session_ctx() as db: + repo = PipelineRepository(db) + + # Loads the first active pipeline + self._pipeline = await repo.get_active_pipeline() + if self._pipeline is None: + self._source = DisconnectedSourceConfig() + self._sink = DisconnectedSinkConfig() + return + + logger.info(f"Configuration loaded from database: {self._pipeline}") + + source = self._pipeline.source + if source is not None: + self._source = source + + sink = self._pipeline.sink + if sink is not None: + self._sink = sink + + def _reload_config_daemon_routine(self) -> None: + """ + Daemon thread routine to monitor configuration changes and reload when necessary. + + This method runs in a separate thread and waits for configuration change + notifications. When changes are detected, it schedules the async reload + operation in the main event loop. + + Raises: + RuntimeError: When config_changed_condition is None. + """ + if self.config_changed_condition is None: + raise RuntimeError("daemon thread initialized without config_changed_condition") + while True: + with self.config_changed_condition: + notified = self.config_changed_condition.wait(timeout=3) + if not notified: # awakened before of timeout + continue + logger.debug(f"Configuration changes detected. Process: {mp.current_process().name}") + # Schedule the async reload in the event loop using the stored loop reference + asyncio.run(self.reload()) + + @property + def source_config(self) -> Source: + """ + Get the current source configuration. + + Returns: + Source: The current source configuration. + """ + return self._source + + @property + def sink_config(self) -> Sink: + """ + Get the current sink configuration. + + Returns: + Sink: The current sink configuration. + """ + return self._sink + + @property + def is_running(self) -> bool: + """Check if the active pipeline is marked as running""" + return self._pipeline is not None and self._pipeline.status.is_running diff --git a/application/backend/src/services/configuration_service.py b/application/backend/src/services/configuration_service.py new file mode 100644 index 0000000000..bb41e1ff9f --- /dev/null +++ b/application/backend/src/services/configuration_service.py @@ -0,0 +1,128 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import asyncio +from collections.abc import Callable +from enum import StrEnum +from multiprocessing.synchronize import Condition +from uuid import UUID + +from loguru import logger +from sqlalchemy.ext.asyncio import AsyncSession + +from db import get_async_db_session_ctx +from pydantic_models import Sink, Source +from repositories import PipelineRepository, SinkRepository, SourceRepository +from services import ActivePipelineService +from services.exceptions import ResourceNotFoundError, ResourceType + + +class PipelineField(StrEnum): + """Enumeration for pipeline fields that can trigger configuration reloads.""" + + SOURCE_ID = "source_id" + SINK_ID = "sink_id" + + +class ConfigurationService: + def __init__(self, active_pipeline_service: ActivePipelineService, config_changed_condition: Condition) -> None: + self._active_pipeline_service: ActivePipelineService = active_pipeline_service + self._config_changed_condition: Condition = config_changed_condition + + def _notify_sink_changed(self) -> None: + """Notify that sink configuration has changed by reloading the active pipeline service.""" + try: + # Try to get the current event loop + loop = asyncio.get_running_loop() + task = loop.create_task(self._active_pipeline_service.reload()) + task.add_done_callback(lambda _: logger.debug("Sink changed notified")) + except RuntimeError: + # If no event loop is running, create a new one + asyncio.run(self._active_pipeline_service.reload()) + + def _notify_source_changed(self) -> None: + with self._config_changed_condition: + self._config_changed_condition.notify_all() + + @staticmethod + async def _on_config_changed( + config_id: UUID, field: PipelineField, db: AsyncSession, notify_fn: Callable[[], None] + ) -> None: + """Notify threads or child processes that the configuration has changed. + Notification triggered only when the configuration is used by the active pipeline.""" + pipeline_repo = PipelineRepository(db) + active_pipeline = await pipeline_repo.get_active_pipeline() + if active_pipeline and str(getattr(active_pipeline, field)) == str(config_id): + notify_fn() + + async def list_sources(self, project_id: UUID) -> list[Source]: + async with get_async_db_session_ctx() as db: + source_repo = SourceRepository(db, project_id=project_id) + return await source_repo.get_all() + + async def list_sinks(self, project_id: UUID) -> list[Sink]: + async with get_async_db_session_ctx() as db: + sink_repo = SinkRepository(db, project_id=project_id) + return await sink_repo.get_all() + + async def get_source_by_id(self, source_id: UUID, project_id: UUID, db: AsyncSession | None = None) -> Source: + if db is None: + async with get_async_db_session_ctx() as db_session: + source_repo = SourceRepository(db_session, project_id=project_id) + source = await source_repo.get_by_id(source_id) + else: + source_repo = SourceRepository(db, project_id=project_id) + source = await source_repo.get_by_id(source_id) + if not source: + raise ResourceNotFoundError(ResourceType.SOURCE, str(source_id)) + return source + + async def get_sink_by_id(self, sink_id: UUID, project_id: UUID, db: AsyncSession | None = None) -> Sink: + if db is None: + async with get_async_db_session_ctx() as db_session: + sink_repo = SinkRepository(db_session, project_id=project_id) + sink = await sink_repo.get_by_id(sink_id) + else: + sink_repo = SinkRepository(db, project_id=project_id) + sink = await sink_repo.get_by_id(sink_id) + if not sink: + raise ResourceNotFoundError(ResourceType.SINK, str(sink_id)) + return sink + + async def create_source(self, source: Source) -> Source: + async with get_async_db_session_ctx() as db: + source_repo = SourceRepository(db, project_id=source.project_id) + return await source_repo.save(source) + + async def create_sink(self, sink: Sink) -> Sink: + async with get_async_db_session_ctx() as db: + sink_repo = SinkRepository(db, project_id=sink.project_id) + return await sink_repo.save(sink) + + async def update_source(self, source_id: UUID, project_id: UUID, partial_config: dict) -> Source: + async with get_async_db_session_ctx() as db: + source = await self.get_source_by_id(source_id, project_id, db) + source_repo = SourceRepository(db, project_id=project_id) + updated = await source_repo.update(source, partial_config) + await self._on_config_changed(updated.id, PipelineField.SOURCE_ID, db, self._notify_source_changed) + return updated + + async def update_sink(self, sink_id: UUID, project_id: UUID, partial_config: dict) -> Sink: + async with get_async_db_session_ctx() as db: + sink = await self.get_sink_by_id(sink_id, project_id, db) + sink_repo = SinkRepository(db, project_id=project_id) + updated = await sink_repo.update(sink, partial_config) + await self._on_config_changed(updated.id, PipelineField.SINK_ID, db, self._notify_sink_changed) + return updated + + async def delete_source_by_id(self, source_id: UUID, project_id: UUID) -> None: + async with get_async_db_session_ctx() as db: + source = await self.get_source_by_id(source_id, project_id, db) + source_repo = SourceRepository(db, project_id=project_id) + await source_repo.delete_by_id(source.id) + + async def delete_sink_by_id(self, sink_id: UUID, project_id: UUID) -> None: + async with get_async_db_session_ctx() as db: + sink = await self.get_sink_by_id(sink_id, project_id, db) + sink_repo = SinkRepository(db, project_id=project_id) + await sink_repo.delete_by_id(sink.id) diff --git a/application/backend/src/services/dispatch_service.py b/application/backend/src/services/dispatch_service.py new file mode 100644 index 0000000000..1f34c261e4 --- /dev/null +++ b/application/backend/src/services/dispatch_service.py @@ -0,0 +1,40 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from collections.abc import Callable, Sequence + +from pydantic_models import Sink, SinkType +from services.dispatchers import Dispatcher, FolderDispatcher, MqttDispatcher, WebhookDispatcher + + +class DispatchService: + _dispatcher_registry: dict[SinkType, Callable[[Sink], Dispatcher | None]] = { + SinkType.DISCONNECTED: lambda _: None, + SinkType.FOLDER: lambda config: FolderDispatcher(output_config=config), # type: ignore[union-attr, arg-type] + SinkType.MQTT: lambda config: MqttDispatcher(output_config=config), # type: ignore[union-attr, arg-type] + SinkType.ROS: lambda _: _raise_not_implemented("ROS output is not implemented yet"), + SinkType.WEBHOOK: lambda config: WebhookDispatcher(output_config=config), # type: ignore[union-attr, arg-type] + } + + @classmethod + def _get_destination(cls, output_config: Sink) -> Dispatcher | None: + # TODO handle exceptions: if some output cannot be initialized, exclude it and raise a warning + factory = cls._dispatcher_registry.get(output_config.sink_type) + if factory is None: + raise ValueError(f"Unrecognized sink type: {output_config.sink_type}") + + return factory(output_config) + + @classmethod + def get_destinations(cls, output_configs: Sequence[Sink]) -> list[Dispatcher]: + """ + Get a list of dispatchers based on the provided output configurations. + + Args: + output_configs (Sequence[OutputConfig]): A sequence of output configurations. + """ + return [dispatcher for config in output_configs if (dispatcher := cls._get_destination(config)) is not None] + + +def _raise_not_implemented(message: str) -> None: + raise NotImplementedError(message) diff --git a/application/backend/src/services/dispatchers/__init__.py b/application/backend/src/services/dispatchers/__init__.py new file mode 100644 index 0000000000..1f34a564ec --- /dev/null +++ b/application/backend/src/services/dispatchers/__init__.py @@ -0,0 +1,10 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from .filesystem import FolderDispatcher +from .mqtt import MqttDispatcher +from .webhook import WebhookDispatcher + +Dispatcher = FolderDispatcher | MqttDispatcher | WebhookDispatcher + +__all__ = ["Dispatcher", "FolderDispatcher", "MqttDispatcher", "WebhookDispatcher"] diff --git a/application/backend/src/services/dispatchers/base.py b/application/backend/src/services/dispatchers/base.py new file mode 100644 index 0000000000..c1ec4190c5 --- /dev/null +++ b/application/backend/src/services/dispatchers/base.py @@ -0,0 +1,117 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +import base64 +import time +from abc import ABCMeta, abstractmethod +from datetime import datetime +from typing import Any + +import cv2 +import numpy as np +from anomalib.data import NumpyImageBatch as PredictionResult + +from pydantic_models import OutputFormat, Sink + + +def numpy_to_base64(image: np.ndarray, fmt: str = ".jpg") -> str: + """Convert a numpy array image to a base64 string.""" + success, img_buf = cv2.imencode(fmt, image) + if success: + return base64.b64encode(img_buf).decode("utf-8") + raise ValueError(f"Failed to encode image in format {fmt}") + + +class DispatchError(Exception): + """Exception raised when there is an error in dispatching.""" + + def __init__(self): + super().__init__("Failed to dispatch the output") + + +class BaseDispatcher(metaclass=ABCMeta): + """ + Base class for dispatchers. + This class should be inherited by all dispatcher classes. + """ + + def __init__(self, output_config: Sink) -> None: + """ + Initialize the dispatcher. + Args: + output_config: Configuration for the output destination + """ + self.output_formats = output_config.output_formats + self.rate_limit = output_config.rate_limit + self.min_interval = (1.0 / self.rate_limit) if self.rate_limit else 0.0 + self.last_dispatch_time = 0.0 + + @abstractmethod + def _dispatch( + self, + original_image: np.ndarray, + image_with_visualization: np.ndarray, + predictions: PredictionResult, + ) -> None: + """ + Internal method to dispatch an image with predictions. + + This method should be overridden by subclasses to implement specific dispatch logic. + Args: + original_image (np.ndarray): The original image + image_with_visualization (np.ndarray): The image with overlaid predictions + predictions (PredictionResult): Predictions generated by the model + """ + + def _create_payload( + self, original_image: np.ndarray, image_with_visualization: np.ndarray, predictions: PredictionResult + ) -> dict[str, Any]: + """Create a JSON payload with the requested output formats.""" + PredictionResult: dict[str, Any] = {} + payload = {"timestamp": datetime.now().isoformat(), "PredictionResult": PredictionResult} + + if OutputFormat.IMAGE_ORIGINAL in self.output_formats: + PredictionResult[OutputFormat.IMAGE_ORIGINAL] = { + "data": numpy_to_base64(original_image), + "format": "jpeg", + } + + if OutputFormat.IMAGE_WITH_PREDICTIONS in self.output_formats: + PredictionResult[OutputFormat.IMAGE_WITH_PREDICTIONS] = { + "data": numpy_to_base64(image_with_visualization), + "format": "jpeg", + } + + if OutputFormat.PREDICTIONS in self.output_formats: + PredictionResult[OutputFormat.PREDICTIONS] = str(predictions) + + return payload + + async def dispatch( + self, + original_image: np.ndarray, + image_with_visualization: np.ndarray, + predictions: PredictionResult, + ) -> None: + """ + Dispatch an image with predictions. + Args: + original_image (np.ndarray): The original image + image_with_visualization (np.ndarray): The image with overlaid predictions + predictions (PredictionResult): Predictions generated by the model + + Raises: + DispatchError: If there is an error in dispatching the output + """ + # Apply rate limiting + if self.rate_limit is not None: + current_time = time.time() + time_since_last = current_time - self.last_dispatch_time + if time_since_last < self.min_interval: + return + self.last_dispatch_time = time.time() + + try: + await asyncio.to_thread(self._dispatch, original_image, image_with_visualization, predictions) + except Exception as e: + raise DispatchError from e diff --git a/application/backend/src/services/dispatchers/filesystem.py b/application/backend/src/services/dispatchers/filesystem.py new file mode 100644 index 0000000000..d20be68672 --- /dev/null +++ b/application/backend/src/services/dispatchers/filesystem.py @@ -0,0 +1,62 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import os +from datetime import datetime + +import cv2 +import numpy as np +from anomalib.data import NumpyImageBatch as PredictionResult +from loguru import logger + +from pydantic_models.sink import FolderSinkConfig, OutputFormat +from services.dispatchers.base import BaseDispatcher + + +class FolderDispatcher(BaseDispatcher): + """FolderDispatcher allows outputting to a folder in the local filesystem.""" + + def __init__(self, output_config: FolderSinkConfig) -> None: + """ + Initialize the FolderDispatcher. + Args: + output_config: Configuration for the output destination + """ + super().__init__(output_config=output_config) + self.output_folder = output_config.folder_path + if not os.path.exists(self.output_folder): + os.makedirs(self.output_folder, exist_ok=True) + + @staticmethod + def _write_image_to_file(image: np.ndarray, file_path: str) -> None: + with open(file_path, "wb") as f: + success, img_buf = cv2.imencode(".png", image) + if success: + f.write(img_buf.tobytes()) + else: + logger.error(f"Failed to encode image for {file_path}") + + @staticmethod + def _write_predictions_to_file(predictions: str, file_path: str) -> None: + with open(file_path, "w", encoding="utf-8") as f: + f.write(predictions) + + def _dispatch( + self, + original_image: np.ndarray, + image_with_visualization: np.ndarray, + predictions: PredictionResult, + ) -> None: + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3] # up to milliseconds + image_orig_file = os.path.join(self.output_folder, f"{timestamp}-original.jpg") + image_viz_file = os.path.join(self.output_folder, f"{timestamp}-pred.jpg") + pred_txt_file = os.path.join(self.output_folder, f"{timestamp}-pred.txt") + + logger.trace(f"Saving results to folder for timestamp '{timestamp}' to folder '{self.output_folder}'") + + if OutputFormat.IMAGE_ORIGINAL in self.output_formats: + self._write_image_to_file(original_image, image_orig_file) + if OutputFormat.IMAGE_WITH_PREDICTIONS in self.output_formats: + self._write_image_to_file(image_with_visualization, image_viz_file) + if OutputFormat.PREDICTIONS in self.output_formats: + self._write_predictions_to_file(str(predictions), pred_txt_file) diff --git a/application/backend/src/services/dispatchers/mqtt.py b/application/backend/src/services/dispatchers/mqtt.py new file mode 100644 index 0000000000..e5056003ac --- /dev/null +++ b/application/backend/src/services/dispatchers/mqtt.py @@ -0,0 +1,146 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import json +import threading +import time +from typing import Any + +import numpy as np +from anomalib.data import NumpyImageBatch as PredictionResult +from loguru import logger + +from pydantic_models.sink import MqttSinkConfig +from services.dispatchers.base import BaseDispatcher + +try: + import paho.mqtt.client as mqtt +except ImportError: + mqtt = None # type: ignore[assignment] + + +MAX_RETRIES = 3 +RETRY_DELAY = 1 +CONNECT_TIMEOUT = 10 + + +class MqttDispatcher(BaseDispatcher): + def __init__( + self, + output_config: MqttSinkConfig, + mqtt_client: "mqtt.Client | None" = None, + track_messages: bool | None = False, + ) -> None: + """ + Initialize the MqttDispatcher. + + Args: + output_config: Configuration for the MQTT destination + mqtt_client: MQTT client + track_messages: Flag to track MQTT messages (useful for debugging/testing) + + Raises: + ImportError: If paho-mqtt is not installed + ConnectionError: If unable to connect to MQTT broker + """ + if mqtt is None: + raise ImportError("paho-mqtt is required for MQTT dispatcher.") + + super().__init__(output_config) + self.broker_host = output_config.broker_host + self.broker_port = output_config.broker_port + self.topic = output_config.topic + self.username, self.password = output_config.get_credentials() + + self._connected = False + self._connection_lock = threading.Lock() + self._connection_event = threading.Event() + self._track_messages = track_messages + self._published_messages: list[dict] = [] + + self.client = mqtt_client or self._create_default_client() + self._connect() + + def _create_default_client(self) -> "mqtt.Client": + client_id = f"dispatcher_{int(time.time())}" + client = mqtt.Client(client_id=client_id) + client.on_connect = self._on_connect + client.on_disconnect = self._on_disconnect + if self.username is not None and self.password is not None: + client.username_pw_set(self.username, self.password) + return client + + def _connect(self) -> None: + for attempt in range(MAX_RETRIES): + try: + logger.info( + "Connecting to MQTT broker at %s:%s (attempt %s)", self.broker_host, self.broker_port, attempt + 1 + ) + self.client.connect(self.broker_host, self.broker_port) + self.client.loop_start() + if self._connection_event.wait(CONNECT_TIMEOUT): + return + logger.warning(f"Connection timeout after {CONNECT_TIMEOUT} seconds") + except Exception as e: + logger.error("Connection failed %s", e) + time.sleep(RETRY_DELAY * (attempt + 1)) + raise ConnectionError("Failed to connect to MQTT broker") + + def _on_connect(self, _client: "mqtt.Client", _userdata: Any, _flags: dict[str, int], rc: int): + if rc == 0: + self._connected = True + self._connection_event.set() + logger.info("Connected to MQTT broker") + else: + logger.error(f"MQTT connect failed with code {rc}") + + def _on_disconnect(self, _client: "mqtt.Client", _userdata: Any, rc: int): + self._connected = False + self._connection_event.clear() + logger.warning(f"MQTT disconnected (rc={rc})") + + @property + def is_connected(self) -> bool: + return self._connected + + def __publish_message(self, topic: str, payload: dict[str, Any]) -> None: + if not self._connected: + logger.warning("Client not connected. Reconnecting...") + try: + self._connect() + except ConnectionError: + logger.error("Reconnect failed") + + try: + PredictionResult = self.client.publish(topic, json.dumps(payload)) + if PredictionResult.rc == mqtt.MQTT_ERR_SUCCESS and self._track_messages: + self._published_messages.append({"topic": topic, "payload": payload}) + logger.error(f"Publish failed: {mqtt.error_string(PredictionResult.rc)}") + except ValueError: + logger.error("Invalid payload for MQTT publish") + + def _dispatch( + self, + original_image: np.ndarray, + image_with_visualization: np.ndarray, + predictions: PredictionResult, + ) -> None: + payload = self._create_payload(original_image, image_with_visualization, predictions) + + self.__publish_message(self.topic, payload) + + def get_published_messages(self) -> list: + return self._published_messages.copy() + + def clear_published_messages(self) -> None: + self._published_messages.clear() + + def close(self) -> None: + err = self.client.loop_stop() + if err != mqtt.MQTT_ERR_SUCCESS: + logger.warning(f"Error stopping MQTT loop: {mqtt.error_string(err)}") + err = self.client.disconnect() + if err != mqtt.MQTT_ERR_SUCCESS: + logger.warning(f"Error disconnecting MQTT client: {mqtt.error_string(err)}") + self._connected = False + self._connection_event.clear() diff --git a/application/backend/src/services/dispatchers/webhook.py b/application/backend/src/services/dispatchers/webhook.py new file mode 100644 index 0000000000..2d7d48e4e2 --- /dev/null +++ b/application/backend/src/services/dispatchers/webhook.py @@ -0,0 +1,62 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""This module contains the WebhookDispatcher class for dispatching images and predictions to a webhook endpoint.""" + +from typing import Any + +import numpy as np +import requests +from anomalib.data import NumpyImageBatch as PredictionResult +from loguru import logger +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry + +from pydantic_models.sink import WebhookSinkConfig +from services.dispatchers.base import BaseDispatcher + +MAX_RETRIES = 3 +BACKOFF_FACTOR = 0.3 +RETRY_ON_STATUS = [500, 502, 503, 504] + + +class WebhookDispatcher(BaseDispatcher): + def __init__(self, output_config: WebhookSinkConfig) -> None: + """ + Initialize the WebhookDispatcher. + Args: + output_config: Configuration for the webhook-based output destination + """ + super().__init__(output_config=output_config) + self.webhook_url = output_config.webhook_url + self.http_method = output_config.http_method + self.headers = output_config.headers + self.timeout = output_config.timeout + self.session = requests.Session() + retries = Retry( + total=MAX_RETRIES, + backoff_factor=BACKOFF_FACTOR, + status_forcelist=RETRY_ON_STATUS, + allowed_methods=["PATCH", "POST", "PUT"], + ) + adapter = HTTPAdapter(max_retries=retries) + self.session.mount("http://", adapter) + self.session.mount("https://", adapter) + + def __send_to_webhook(self, payload: dict[str, Any]) -> None: + logger.debug(f"Sending payload to webhook at {self.webhook_url}") + response = self.session.request( + self.http_method, self.webhook_url, headers=self.headers, json=payload, timeout=self.timeout + ) + response.raise_for_status() + logger.debug(f"Response from webhook: {response.text}") + + def _dispatch( + self, + original_image: np.ndarray, + image_with_visualization: np.ndarray, + predictions: PredictionResult, + ) -> None: + payload = self._create_payload(original_image, image_with_visualization, predictions) + + self.__send_to_webhook(payload) diff --git a/application/backend/src/services/exceptions.py b/application/backend/src/services/exceptions.py new file mode 100644 index 0000000000..c1e25ea9d3 --- /dev/null +++ b/application/backend/src/services/exceptions.py @@ -0,0 +1,65 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from enum import StrEnum + + +class ResourceType(StrEnum): + """Enumeration for resource types.""" + + SOURCE = "Source" + SINK = "Sink" + MODEL = "Model" + PIPELINE = "Pipeline" + PROJECT = "Project" + DATASET_ITEM = "DatasetItem" + LABEL = "Label" + MEDIA = "Media" + + +class ResourceError(Exception): + """Base exception for resource-related errors.""" + + def __init__(self, resource_type: ResourceType, resource_id: str, message: str): + super().__init__(message) + self.message: str = message + self.resource_type = resource_type + self.resource_id = resource_id + + +class ResourceNotFoundError(ResourceError): + """Exception raised when a resource is not found.""" + + def __init__(self, resource_type: ResourceType, resource_id: str, message: str | None = None): + msg = message or f"{resource_type} with ID {resource_id} not found." + super().__init__(resource_type, resource_id, msg) + + +class ResourceInUseError(ResourceError): + """Exception raised when trying to delete a resource that is currently in use.""" + + def __init__(self, resource_type: ResourceType, resource_id: str, message: str | None = None): + msg = message or f"{resource_type} with ID {resource_id} cannot be deleted because it is in use." + super().__init__(resource_type, resource_id, msg) + + +class ResourceAlreadyExistsError(ResourceError): + """Exception raised when a resource with the same name already exists.""" + + def __init__(self, resource_type: ResourceType, resource_name: str, message: str | None = None): + msg = message or f"{resource_type} with name '{resource_name}' already exists." + super().__init__(resource_type, resource_name, msg) + + +class DeviceNotFoundError(Exception): + """Exception raised when a specified device is not found.""" + + def __init__(self, device_name: str): + super().__init__(f"Device '{device_name}' not found.") + + +class ActivePipelineConflictError(Exception): + """Exception raised when a pipeline cannot be activated.""" + + def __init__(self, pipeline_id: str, reason: str): + self.message: str = f"Cannot activate pipeline with ID {pipeline_id}: {reason}." + super().__init__(self.message) diff --git a/application/backend/src/services/job_service.py b/application/backend/src/services/job_service.py new file mode 100644 index 0000000000..d98203124f --- /dev/null +++ b/application/backend/src/services/job_service.py @@ -0,0 +1,147 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +import datetime +import json +import logging +from collections.abc import AsyncGenerator +from uuid import UUID + +import anyio +from sqlalchemy.exc import IntegrityError +from sse_starlette import ServerSentEvent + +from db import get_async_db_session_ctx +from exceptions import DuplicateJobException, ResourceNotFoundException +from pydantic_models import Job, JobList, JobType +from pydantic_models.job import JobCancelled, JobStatus, JobSubmitted, TrainJobPayload +from repositories import JobRepository + +logger = logging.getLogger(__name__) + + +class JobService: + @staticmethod + async def get_job_list(extra_filters: dict | None = None) -> JobList: + async with get_async_db_session_ctx() as session: + repo = JobRepository(session) + return JobList(jobs=await repo.get_all(extra_filters=extra_filters)) + + @staticmethod + async def get_job_by_id(job_id: UUID | str) -> Job | None: + async with get_async_db_session_ctx() as session: + repo = JobRepository(session) + return await repo.get_by_id(job_id) + + @staticmethod + async def submit_train_job(payload: TrainJobPayload) -> JobSubmitted: + async with get_async_db_session_ctx() as session: + repo = JobRepository(session) + if await repo.is_job_duplicate(project_id=payload.project_id, payload=payload): + raise DuplicateJobException + + try: + job = Job( + project_id=payload.project_id, + type=JobType.TRAINING, + payload=payload.model_dump(), + message="Training job submitted", + ) + saved_job = await repo.save(job) + return JobSubmitted(job_id=saved_job.id) + except IntegrityError: + raise ResourceNotFoundException(resource_id=payload.project_id, resource_name="project") + + @staticmethod + async def get_pending_train_job() -> Job | None: + async with get_async_db_session_ctx() as session: + repo = JobRepository(session) + return await repo.get_pending_job_by_type(JobType.TRAINING) + + @staticmethod + async def update_job_status( + job_id: UUID, + status: JobStatus, + message: str | None = None, + progress: int | None = None, + ) -> None: + async with get_async_db_session_ctx() as session: + repo = JobRepository(session) + job = await repo.get_by_id(job_id) + if job is None: + raise ResourceNotFoundException(resource_id=job_id, resource_name="job") + updates: dict = {"status": status} + if message is not None: + updates["message"] = message + progress_ = 100 if status is JobStatus.COMPLETED else progress + + if status in {JobStatus.COMPLETED, JobStatus.FAILED, JobStatus.CANCELED}: + updates["end_time"] = datetime.datetime.now(tz=datetime.UTC) + + if progress_ is not None: + updates["progress"] = progress_ + await repo.update(job, updates) + + @classmethod + async def is_job_still_running(cls, job_id: UUID | str) -> bool: + job = await cls.get_job_by_id(job_id=job_id) + if job is None: + raise ResourceNotFoundException(resource_id=job_id, resource_name="job") + return job.status == JobStatus.RUNNING + + @classmethod + async def stream_logs(cls, job_id: UUID | str) -> AsyncGenerator[ServerSentEvent]: + from core.logging.utils import get_job_logs_path # noqa: PLC0415 + + log_file = get_job_logs_path(job_id=job_id) + if not await anyio.Path(log_file).exists(): + raise ResourceNotFoundException(resource_id=job_id, resource_name="job_logs") + + # Cache job status and only check every 2 seconds + status_check_interval = 2.0 # seconds + last_status_check = 0.0 + cached_still_running = True + loop = asyncio.get_running_loop() + + async with await anyio.open_file(log_file) as f: + while True: + line = await f.readline() + now = loop.time() + # Only check job status every status_check_interval seconds + if now - last_status_check > status_check_interval: + cached_still_running = await cls.is_job_still_running(job_id=job_id) + last_status_check = now + still_running = cached_still_running + if not line: + # wait for more lines if job is still running + if still_running: + await asyncio.sleep(0.5) + continue + # No more lines are expected + else: + break + yield ServerSentEvent(data=line.rstrip()) + + @classmethod + async def stream_progress(cls, job_id: UUID | str) -> AsyncGenerator[ServerSentEvent]: + """Stream the progress of a job by its ID""" + still_running = True + while still_running: + job = await cls.get_job_by_id(job_id=job_id) + if job is None: + raise ResourceNotFoundException(resource_id=job_id, resource_name="job") + yield ServerSentEvent(data=json.dumps({"progress": job.progress, "message": job.message})) + still_running = job.status in {JobStatus.RUNNING, JobStatus.PENDING} + await asyncio.sleep(0.5) + + @classmethod + async def cancel_job(cls, job_id: UUID | str) -> JobCancelled: + """Cancel a job by its ID""" + async with get_async_db_session_ctx() as session: + repo = JobRepository(session) + job = await repo.get_by_id(job_id) + if job is None: + raise ResourceNotFoundException(resource_id=job_id, resource_name="job") + + await repo.update(job, {"status": JobStatus.CANCELED}) + return JobCancelled(job_id=job.id) diff --git a/application/backend/src/services/media_service.py b/application/backend/src/services/media_service.py new file mode 100644 index 0000000000..c406dce758 --- /dev/null +++ b/application/backend/src/services/media_service.py @@ -0,0 +1,190 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +import os +from io import BytesIO +from uuid import UUID, uuid4 + +from fastapi import UploadFile +from loguru import logger +from PIL import Image + +from db import get_async_db_session_ctx +from pydantic_models import Media, MediaList +from repositories import MediaRepository +from repositories.binary_repo import ImageBinaryRepository +from services import ResourceNotFoundError +from services.exceptions import ResourceType + +THUMBNAIL_SIZE = 256 # Max width/height for thumbnails in pixels + + +class MediaService: + @staticmethod + def _get_thumbnail_filename(media_id: UUID) -> str: + """Generate thumbnail filename for a given media ID.""" + return f"thumb_{media_id}.png" + + @staticmethod + async def get_media_list(project_id: UUID) -> MediaList: + async with get_async_db_session_ctx() as session: + repo = MediaRepository(session, project_id=project_id) + return MediaList(media=await repo.get_all()) + + @staticmethod + async def get_media_by_id(project_id: UUID, media_id: UUID) -> Media | None: + async with get_async_db_session_ctx() as session: + repo = MediaRepository(session, project_id=project_id) + return await repo.get_by_id(media_id) + + @classmethod + async def get_media_file_path(cls, project_id: UUID, media_id: UUID) -> str: + media = await cls.get_media_by_id(project_id=project_id, media_id=media_id) + if media is None: + raise FileNotFoundError(f"Media with ID {media_id} not found.") + bin_repo = ImageBinaryRepository(project_id=project_id) + return bin_repo.get_full_path(filename=media.filename) + + @classmethod + async def get_thumbnail_file_path(cls, project_id: UUID, media_id: UUID) -> str: + """Get the file path for a media's thumbnail.""" + media = await cls.get_media_by_id(project_id=project_id, media_id=media_id) + if media is None: + raise FileNotFoundError(f"Media with ID {media_id} not found.") + bin_repo = ImageBinaryRepository(project_id=project_id) + thumbnail_filename = cls._get_thumbnail_filename(media_id) + return bin_repo.get_full_path(filename=thumbnail_filename) + + @classmethod + async def upload_image(cls, project_id: UUID, file: UploadFile, image_bytes: bytes, is_anomalous: bool) -> Media: + # Generate unique filename and media ID + media_id = uuid4() + + if file.filename is None or file.size is None: + raise ValueError("File must have a filename and size") + + extension = list(os.path.splitext(file.filename)).pop().lower() + filename = f"{media_id}{extension}" + bin_repo = ImageBinaryRepository(project_id=project_id) + saved_media: Media | None = None + + # Extract image dimensions + def _get_image_size() -> tuple[int, int]: + with Image.open(BytesIO(image_bytes)) as img: + return img.size # Returns (width, height) + + width, height = await asyncio.to_thread(_get_image_size) + + async with get_async_db_session_ctx() as session: + media_repo = MediaRepository(session, project_id=project_id) + try: + # Save original file to filesystem + saved_file_path = await bin_repo.save_file( + filename=filename, + content=image_bytes, + ) + logger.info(f"Saved media file: {saved_file_path}") + + # Create media record in database + media = Media( + id=media_id, + project_id=project_id, + filename=filename, + size=file.size, + is_anomalous=is_anomalous, + width=width, + height=height, + ) + saved_media = await media_repo.save(media) + except Exception as e: + logger.error(f"Rolling back media upload due to error: {e}") + # Attempt to delete the files if they were saved + try: + await cls._delete_media_file(project_id=project_id, filename=filename) + except Exception as delete_error: + logger.error(f"Failed to delete media file during rollback: {delete_error}") + if saved_media is not None: + await media_repo.delete_by_id(saved_media.id) + raise e + return saved_media + + @classmethod + async def delete_media(cls, media_id: UUID, project_id: UUID) -> None: + async with get_async_db_session_ctx() as session: + media_repo = MediaRepository(session, project_id=project_id) + media = await media_repo.get_by_id(media_id) + if media is None: + raise ResourceNotFoundError(resource_type=ResourceType.MEDIA, resource_id=str(media_id)) + await media_repo.delete_by_id(media_id) + thumbnail_filename = cls._get_thumbnail_filename(media_id) + await cls._delete_media_file(project_id=project_id, filename=media.filename) + await cls._delete_media_file(project_id=project_id, filename=thumbnail_filename) + + @staticmethod + async def _delete_media_file(project_id: UUID, filename: str) -> None: + bin_repo = ImageBinaryRepository(project_id=project_id) + + try: + await bin_repo.delete_file(filename=filename) + except FileNotFoundError: + logger.warning(f"File `{filename}` not found during media deletion.") + except Exception as e: + logger.error(f"Error deleting file `{filename}`: {e}") + raise e + + @classmethod + async def generate_thumbnail( + cls, + project_id: UUID, + media_id: UUID, + image_bytes: bytes, + height_px: int = THUMBNAIL_SIZE, + width_px: int = THUMBNAIL_SIZE, + ) -> None: + """Create and persist a PNG thumbnail for a media item. + + Resizes the input image to fit within width_px x height_px while + preserving aspect ratio. Intended to run as a background task so the + upload path stays fast. + + Args: + project_id: Identifier of the owning project. + media_id: Identifier of the media item the thumbnail belongs to. + image_bytes: Original image bytes used to create the thumbnail. + height_px: Maximum thumbnail height in pixels. Defaults to + ``THUMBNAIL_SIZE``. + width_px: Maximum thumbnail width in pixels. Defaults to + ``THUMBNAIL_SIZE``. + + Returns: + None + + Raises: + Exception: If processing or persistence fails + """ + + def _create() -> bytes: + with Image.open(BytesIO(image_bytes)) as img: + # Preserve aspect ratio while fitting within the box + img.thumbnail((width_px, height_px)) + with BytesIO() as buf: + img.save(buf, format="PNG") + return buf.getvalue() + + thumbnail_bytes = await asyncio.to_thread(_create) + thumbnail_filename = cls._get_thumbnail_filename(media_id) + bin_repo = ImageBinaryRepository(project_id=project_id) + try: + thumbnail_path = await bin_repo.save_file( + filename=thumbnail_filename, + content=thumbnail_bytes, + ) + logger.info(f"Saved thumbnail: {thumbnail_path}") + except Exception as e: + logger.error(f"Rolling back media thumbnail generation due to error: {e}") + # Attempt to delete the files if they were saved + try: + await cls._delete_media_file(project_id=project_id, filename=thumbnail_filename) + except Exception as delete_error: + logger.error(f"Failed to delete media file during rollback: {delete_error}") + raise e diff --git a/application/backend/src/services/metrics_service.py b/application/backend/src/services/metrics_service.py new file mode 100644 index 0000000000..17f4b5095b --- /dev/null +++ b/application/backend/src/services/metrics_service.py @@ -0,0 +1,132 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import time +from collections import defaultdict +from datetime import UTC, datetime +from multiprocessing.shared_memory import SharedMemory +from multiprocessing.synchronize import Lock +from typing import NamedTuple +from uuid import UUID + +import numpy as np +from loguru import logger + +MAX_MEASUREMENTS = 1024 # max number of measurements to keep +DTYPE = np.dtype([ + ("model_id", "U36"), # 36 * 4 = 144 bytes for UUID string + ("latency_ms", np.dtype(float)), # 8 bytes for latency in milliseconds + ("timestamp", np.dtype(float)), # 8 bytes for timestamp (epoch time in seconds) +]) # 144 + 8 + 8 = 160 bytes per latency measurement +SIZE = DTYPE.itemsize * MAX_MEASUREMENTS # 160 * 1024 = 163840 bytes (160KB) allocated + + +class LatencyMeasurement(NamedTuple): + """Individual latency measurement""" + + model_id: str # UUID as 36 character string "00000000-0000-0000-0000-000000000000" + latency_ms: float + timestamp: float + + +class MetricsService: + """Process-safe metrics service using shared memory for model metric data""" + + def __init__(self, shm_name: str, lock: Lock, max_age_seconds: int = 60): + self._max_age_seconds = max_age_seconds + self._lock = lock + self._shm = SharedMemory(name=shm_name) + self._array: np.ndarray = np.ndarray((MAX_MEASUREMENTS,), dtype=DTYPE, buffer=self._shm.buf) + self._head = 0 # index for next write + + def update_max_age(self, max_age_seconds: int) -> None: + with self._lock: + self._max_age_seconds = max_age_seconds + + @staticmethod + def record_inference_start() -> float: + return time.perf_counter() + + def record_inference_end(self, model_id: UUID, start_time: float) -> None: + """ + Record the end of an inference and store the latency measurement. + + Args: + model_id: UUID of the model to record measurement for + start_time: Start time from record_inference_start() + """ + end_time = time.perf_counter() + latency_ms = (end_time - start_time) * 1000.0 + timestamp = datetime.now(UTC).timestamp() + + measurement = LatencyMeasurement(str(model_id), latency_ms, timestamp) + with self._lock: + idx = self._head % MAX_MEASUREMENTS + self._array[idx] = (measurement.model_id, measurement.latency_ms, measurement.timestamp) + self._head += 1 + logger.debug(f"Latency measurement recorded for model {model_id}: {latency_ms:.2f} ms") + + def get_latency_measurements(self, model_id: UUID, time_window: int = 60) -> list[float]: + """ + Retrieve latency measurements for a specific model within the given time window. + + Args: + model_id: UUID of the model to filter measurements + time_window: Time window in seconds to look back for measurements (default 60s) + + Returns: List of latency measurements in milliseconds + """ + str_model_id = str(model_id) + cutoff_time = datetime.now(UTC).timestamp() - time_window + with self._lock: + arr = self._array.copy() + result = [] + for entry in arr: + if entry["timestamp"] < cutoff_time or entry["timestamp"] == 0.0: + continue + if entry["model_id"] == str_model_id: + result.append(entry["latency_ms"]) + return result + + def get_throughput_measurements(self, model_id: UUID, time_window: int = 60) -> tuple[int, list[tuple[float, int]]]: + """ + Retrieve throughput measurements for a specific model within the given time window. + + Args: + model_id: UUID of the model to filter measurements + time_window: Time window in seconds to look back for measurements (default 60s) + + Returns: Tuple of (total_requests, list of (timestamp, inference_count) per second) + """ + str_model_id = str(model_id) + cutoff_time = datetime.now(UTC).timestamp() - time_window + with self._lock: + arr = self._array.copy() + + # Count inferences per second + inferences_per_second: dict[int, int] = defaultdict(int) + total_requests = 0 + + for entry in arr: + if entry["timestamp"] < cutoff_time or entry["timestamp"] == 0.0: + continue + if entry["model_id"] == str_model_id: + # Round timestamp to the nearest second + second_timestamp = int(entry["timestamp"]) + inferences_per_second[second_timestamp] += 1 + total_requests += 1 + + # Convert to list of (timestamp, count) tuples + throughput_data = [(float(ts), count) for ts, count in inferences_per_second.items()] + throughput_data.sort() # Sort by timestamp + + return total_requests, throughput_data + + def reset(self) -> None: + with self._lock: + self._max_age_seconds = 60 + self._array[:] = "00000000-0000-0000-0000-000000000000", 0.0, 0.0 + self._head = 0 + + def __del__(self): + self._shm.close() diff --git a/application/backend/src/services/model_service.py b/application/backend/src/services/model_service.py new file mode 100644 index 0000000000..1141d5c71c --- /dev/null +++ b/application/backend/src/services/model_service.py @@ -0,0 +1,217 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +import base64 +import io +from dataclasses import dataclass +from multiprocessing.synchronize import Event as EventClass +from uuid import UUID + +import cv2 +import numpy as np +import openvino.properties.hint as ov_hints +from anomalib.deploy import ExportType, OpenVINOInferencer +from loguru import logger +from PIL import Image + +from db import get_async_db_session_ctx +from pydantic_models import Model, ModelList, PredictionLabel, PredictionResponse +from repositories import JobRepository, ModelRepository +from repositories.binary_repo import ModelBinaryRepository +from services.exceptions import DeviceNotFoundError +from utils.devices import Devices + +DEFAULT_DEVICE = "AUTO" + + +@dataclass +class LoadedModel: + name: str + id: UUID + model: Model + device: str | None = None + + def __post_init__(self): + self.device = self.device or DEFAULT_DEVICE + + +class ModelService: + """Service for managing models and inference operations. + + Handles model CRUD operations, loading inference models, and running + predictions on images. Uses asyncio.to_thread for CPU-intensive operations + to maintain event loop responsiveness. + """ + + def __init__(self, mp_model_reload_event: EventClass | None = None) -> None: + self._mp_model_reload_event = mp_model_reload_event + + def activate_model(self) -> None: + """Notify workers to (re)load the active model. + + Sets the shared multiprocessing event so the inference worker reloads + the current active model lazily and only once. + """ + try: + if self._mp_model_reload_event is not None: + self._mp_model_reload_event.set() + except Exception as e: + # Best-effort signaling; avoid bubbling up to API layer + logger.debug(f"Failed to signal model reload event: {e}") + + @staticmethod + async def create_model(model: Model) -> Model: + async with get_async_db_session_ctx() as session: + repo = ModelRepository(session, project_id=model.project_id) + return await repo.save(model) + + @staticmethod + async def get_model_list(project_id: UUID) -> ModelList: + async with get_async_db_session_ctx() as session: + repo = ModelRepository(session, project_id=project_id) + return ModelList(models=await repo.get_all()) + + @staticmethod + async def get_model_by_id(project_id: UUID, model_id: UUID) -> Model | None: + async with get_async_db_session_ctx() as session: + repo = ModelRepository(session, project_id=project_id) + return await repo.get_by_id(model_id) + + async def delete_model(self, project_id: UUID, model_id: UUID, delete_artifacts: bool = True) -> None: + if delete_artifacts: + model_binary_repo = ModelBinaryRepository(project_id=project_id, model_id=model_id) + try: + await model_binary_repo.delete_model_folder() + except FileNotFoundError: + logger.warning( + "Model artifacts already absent on disk for model %s in project %s", model_id, project_id + ) + + async with get_async_db_session_ctx() as session: + repo = ModelRepository(session, project_id=project_id) + model = await repo.get_by_id(model_id) + + if model is None: + return + + job_repo = JobRepository(session) + train_job_id = model.train_job_id + + async with session.begin(): + await repo.delete_by_id(model_id) + + if train_job_id: + await job_repo.delete_by_id(train_job_id) + + self.activate_model() + + @classmethod + async def load_inference_model(cls, model: Model, device: str | None = None) -> OpenVINOInferencer: + """Load a model for inference using the anomalib OpenVINO inferencer. + + Args: + model: The model to load + device: Device to use for inference. If None, defaults to "AUTO" + """ + if model.format is not ExportType.OPENVINO: + raise NotImplementedError(f"Model format {model.format} is not supported for inference at this moment.") + + model_bin_repo = ModelBinaryRepository(project_id=model.project_id, model_id=model.id) + model_path = model_bin_repo.get_weights_file_path(format=model.format, name="model.xml") + device_name = device or DEFAULT_DEVICE + try: + return await asyncio.to_thread( + OpenVINOInferencer, + path=model_path, + device=device_name.upper(), # OV always expects uppercase device names + config={ov_hints.performance_mode: ov_hints.PerformanceMode.LATENCY}, + ) + except Exception as e: + if device and not Devices.is_device_supported_for_inference(device): + raise DeviceNotFoundError(device_name=device_name) from e + raise e + + @classmethod + async def predict_image( + cls, + model: Model, + image_bytes: bytes, + cached_models: dict[UUID, OpenVINOInferencer] | None = None, + device: str | None = None, + ) -> PredictionResponse: + """Run prediction on an image using the specified model. + + Uses asyncio.to_thread to run the entire CPU-intensive prediction pipeline + in a single thread, maintaining event loop responsiveness. + + Args: + model: The model to use for prediction + image_bytes: Raw image bytes from uploaded file + cached_models: Optional dict to cache loaded models (for performance) + device: Optional string indicating the device to use for inference + + Returns: + PredictionResponse: Structured prediction results + """ + # Determine if we can use cached model (must exist and device must match if specified) + use_cached = ( + cached_models is not None + and model.id in cached_models + and (device is None or cached_models[model.id].device == device) + ) + + if use_cached and cached_models is not None: + inference_model = cached_models[model.id] + else: + logger.info(f"Loading model with device: {device or DEFAULT_DEVICE}") + inference_model = await cls.load_inference_model(model, device=device) + if cached_models is not None: + cached_models[model.id] = inference_model + + # Run entire prediction pipeline in a single thread + # This includes image processing, model inference, and result processing + response_data = await asyncio.to_thread(cls._run_prediction_pipeline, inference_model, image_bytes) + + return PredictionResponse(**response_data) + + @staticmethod + def _run_prediction_pipeline(inference_model: OpenVINOInferencer, image_bytes: bytes) -> dict: + """Run the complete prediction pipeline in a single thread.""" + # Process image + npd = np.frombuffer(image_bytes, np.uint8) + bgr_image = cv2.imdecode(npd, -1) + if bgr_image is None: + raise ValueError("Failed to decode image") + + numpy_image = cv2.cvtColor(bgr_image, cv2.COLOR_BGR2RGB) + + # Run prediction + pred = inference_model.predict(numpy_image) + + # Process anomaly map + if pred.anomaly_map is None: + raise ValueError("Prediction returned no anomaly map") + + arr = pred.anomaly_map.squeeze() # Remove dimensions of size 1 + arr_scaled = (arr * 255).astype(np.uint8) # Scale to 0-255 and convert to uint8 + # convert to color map + heatmap = cv2.applyColorMap(arr_scaled, cv2.COLORMAP_JET) + # Add alpha channel with opacity weighted according to the anomaly score + heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGBA) + heatmap[:, :, 3] = arr_scaled + + im = Image.fromarray(heatmap) # Automatically detects RGBA mode + + # Convert to base64 + with io.BytesIO() as buf: + im.save(buf, format="PNG") + im_base64 = base64.b64encode(buf.getvalue()).decode("utf-8") + + # Create response data + if pred.pred_label is None or pred.pred_score is None: + raise ValueError("Prediction returned no label or score") + + label = PredictionLabel.ANOMALOUS if pred.pred_label.item() else PredictionLabel.NORMAL + score = float(pred.pred_score.item()) + + return {"anomaly_map": im_base64, "label": label, "score": score} diff --git a/application/backend/src/services/pipeline_metrics_service.py b/application/backend/src/services/pipeline_metrics_service.py new file mode 100644 index 0000000000..df3cbf7cdb --- /dev/null +++ b/application/backend/src/services/pipeline_metrics_service.py @@ -0,0 +1,94 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import statistics +from datetime import UTC, datetime, timedelta +from uuid import UUID + +from exceptions import ResourceNotFoundException +from pydantic_models import PipelineStatus +from pydantic_models.metrics import InferenceMetrics, LatencyMetrics, PipelineMetrics, ThroughputMetrics, TimeWindow + +from .metrics_service import MetricsService +from .pipeline_service import PipelineService + + +class PipelineMetricsService: + def __init__( + self, + pipeline_service: PipelineService, + metrics_service: MetricsService, + ) -> None: + self._pipeline_service: PipelineService = pipeline_service + self._metrics_service: MetricsService = metrics_service + + async def get_pipeline_metrics(self, pipeline_id: UUID, time_window: int = 60) -> PipelineMetrics: + """Calculate metrics for a pipeline over a specified time window.""" + # First check if pipeline exists + pipeline = await self._pipeline_service.get_pipeline_by_id(pipeline_id) + if pipeline is None: + raise ResourceNotFoundException(resource_id=pipeline_id, resource_name="pipeline") + if pipeline.status != PipelineStatus.RUNNING: + raise ValueError("Cannot get metrics for a pipeline that is not running.") + + # Calculate time window + end_time = datetime.now(UTC) + start_time = end_time - timedelta(seconds=time_window) + + # Get actual latency measurements from the metrics service + latency_samples = self._metrics_service.get_latency_measurements( + model_id=pipeline.model_id, # type: ignore[arg-type] # model is always there for running pipeline + time_window=time_window, + ) + + # Calculate latency metrics + if latency_samples: + latency_metrics = LatencyMetrics( + avg_ms=statistics.mean(latency_samples), + min_ms=min(latency_samples), + max_ms=max(latency_samples), + p95_ms=self._calculate_percentile(latency_samples, 95), + latest_ms=latency_samples[-1], + ) + else: + # No data available + latency_metrics = LatencyMetrics(avg_ms=None, min_ms=None, max_ms=None, p95_ms=None, latest_ms=None) + + # Get throughput measurements from the metrics service + total_requests, throughput_data = self._metrics_service.get_throughput_measurements( + model_id=pipeline.model_id, # type: ignore[arg-type] + time_window=time_window, + ) + if total_requests: + throughput_metrics = ThroughputMetrics( + avg_requests_per_second=total_requests / time_window if time_window > 0 else 0.0, + total_requests=total_requests, + max_requests_per_second=max((count for _, count in throughput_data), default=0), + ) + else: + # No data available + throughput_metrics = ThroughputMetrics( + avg_requests_per_second=None, total_requests=None, max_requests_per_second=None + ) + + window = TimeWindow(start=start_time, end=end_time, time_window=time_window) + inference_metrics = InferenceMetrics(latency=latency_metrics, throughput=throughput_metrics) + return PipelineMetrics(time_window=window, inference=inference_metrics) + + @staticmethod + def _calculate_percentile(data: list[float], percentile: int) -> float: + """Calculate the specified percentile of the data.""" + if not data: + return 0.0 + + sorted_data = sorted(data) + k = (len(sorted_data) - 1) * (percentile / 100.0) + floor_k = int(k) + ceil_k = floor_k + 1 + + if ceil_k >= len(sorted_data): + return sorted_data[-1] + + # Linear interpolation + fraction = k - floor_k + return sorted_data[floor_k] + fraction * (sorted_data[ceil_k] - sorted_data[floor_k]) diff --git a/application/backend/src/services/pipeline_service.py b/application/backend/src/services/pipeline_service.py new file mode 100644 index 0000000000..22e2de6255 --- /dev/null +++ b/application/backend/src/services/pipeline_service.py @@ -0,0 +1,109 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from multiprocessing.synchronize import Condition +from uuid import UUID + +from loguru import logger +from sqlalchemy.ext.asyncio.session import AsyncSession + +from db import get_async_db_session_ctx +from pydantic_models import Pipeline, PipelineStatus +from repositories import PipelineRepository +from services import ActivePipelineConflictError, ActivePipelineService, ResourceNotFoundError +from services.exceptions import ResourceType +from services.model_service import ModelService + +MSG_ERR_DELETE_RUNNING_PIPELINE = "Cannot delete a running pipeline." + + +class PipelineService: + def __init__( + self, + active_pipeline_service: ActivePipelineService, + config_changed_condition: Condition, + model_service: ModelService, + ) -> None: + self._active_pipeline_service: ActivePipelineService = active_pipeline_service + self._config_changed_condition: Condition = config_changed_condition + self._model_service: ModelService = model_service + + def _notify_source_changed(self) -> None: + with self._config_changed_condition: + self._config_changed_condition.notify_all() + + async def _notify_sink_changed(self) -> None: + await self._active_pipeline_service.reload() + + async def _notify_pipeline_changed(self) -> None: + self._notify_source_changed() + await self._notify_sink_changed() + + @staticmethod + async def get_pipeline_by_id(project_id: UUID, session: AsyncSession | None = None) -> Pipeline: + """Retrieve a pipeline by project ID.""" + if session is None: + async with get_async_db_session_ctx() as db_session: + repo = PipelineRepository(db_session) + pipeline = await repo.get_by_id(project_id) + else: + repo = PipelineRepository(session) + pipeline = await repo.get_by_id(project_id) + if not pipeline: + raise ResourceNotFoundError(resource_type=ResourceType.PIPELINE, resource_id=str(project_id)) + return pipeline + + async def update_pipeline(self, project_id: UUID, partial_config: dict) -> Pipeline: + """Update an existing pipeline.""" + async with get_async_db_session_ctx() as session: + pipeline = await self.get_pipeline_by_id(project_id, session) + repo = PipelineRepository(session) + updated = await repo.update(pipeline, partial_config) + await session.commit() + # notify source changes + if pipeline.source != updated.source: + self._notify_source_changed() + if pipeline.status.is_running and updated.status.is_running: + if pipeline.sink.id != updated.sink.id: # type: ignore[union-attr] # sink is always there for running pipeline + await self._notify_sink_changed() + # If the active model changes while running, notify inference to reload + if pipeline.model.id != updated.model.id: # type: ignore[union-attr] + self._model_service.activate_model() + elif pipeline.status != updated.status: + # If the pipeline is being activated or stopped + await self._notify_pipeline_changed() + # Intentionally call activate_model on status change regardless of whether a model exists. + self._model_service.activate_model() + if updated.inference_device != pipeline.inference_device: + # reload model on device change + self._model_service.activate_model() + return updated + + @staticmethod + async def get_active_pipeline() -> Pipeline | None: + """Retrieve the currently active (running) pipeline from the database.""" + async with get_async_db_session_ctx() as session: + return await PipelineRepository(session).get_active_pipeline() + + async def activate_pipeline(self, project_id: UUID, set_running: bool = False) -> Pipeline: + """Activate a pipeline. If set_running is True, set the pipeline status to RUNNING.""" + active_pipeline = await self.get_active_pipeline() + if active_pipeline and active_pipeline.project_id != project_id: + raise ActivePipelineConflictError( + pipeline_id=str(project_id), + reason=( + f"another pipeline is already active. " + f"Please disable the pipeline {active_pipeline.id} before activating a new one" + ), + ) + if active_pipeline and ( + (active_pipeline.status == PipelineStatus.ACTIVE and not set_running) + or (active_pipeline.status == PipelineStatus.RUNNING and set_running) + ): + logger.info( + f"Activating already {active_pipeline.status.value.lower()} pipeline `{active_pipeline.id}`, " + f"no changes made." + ) + return active_pipeline + new_status = PipelineStatus.RUNNING if set_running else PipelineStatus.ACTIVE + return await self.update_pipeline(project_id, {"status": new_status}) diff --git a/application/backend/src/services/project_service.py b/application/backend/src/services/project_service.py new file mode 100644 index 0000000000..78afc0645e --- /dev/null +++ b/application/backend/src/services/project_service.py @@ -0,0 +1,34 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from uuid import UUID + +from db import get_async_db_session_ctx +from pydantic_models import Project, ProjectList +from repositories import ProjectRepository + + +class ProjectService: + @staticmethod + async def get_project_list() -> ProjectList: + async with get_async_db_session_ctx() as session: + repo = ProjectRepository(session) + return ProjectList(projects=await repo.get_all()) + + @staticmethod + async def get_project_by_id(project_id: UUID) -> Project | None: + async with get_async_db_session_ctx() as session: + repo = ProjectRepository(session) + return await repo.get_by_id(project_id) + + @staticmethod + async def create_project(project: Project) -> Project: + async with get_async_db_session_ctx() as session: + repo = ProjectRepository(session) + return await repo.save(project) + + @staticmethod + async def delete_project(project_id: UUID) -> None: + async with get_async_db_session_ctx() as session: + repo = ProjectRepository(session) + await repo.delete_by_id(project_id) diff --git a/application/backend/src/services/training_service.py b/application/backend/src/services/training_service.py new file mode 100644 index 0000000000..1b0aefd02d --- /dev/null +++ b/application/backend/src/services/training_service.py @@ -0,0 +1,289 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +import os +from contextlib import redirect_stdout +from uuid import UUID + +from anomalib.data import Folder +from anomalib.data.utils import ValSplitMode +from anomalib.deploy import ExportType +from anomalib.engine import Engine +from anomalib.loggers import AnomalibTensorBoardLogger +from anomalib.models import get_model +from loguru import logger + +from pydantic_models import Job, JobStatus, JobType, Model +from repositories.binary_repo import ImageBinaryRepository, ModelBinaryRepository +from services import ModelService +from services.job_service import JobService +from utils.callbacks import GetiInspectProgressCallback, ProgressSyncParams +from utils.devices import Devices +from utils.experiment_loggers import TrackioLogger + + +class TrainingService: + """ + Service for managing model training jobs. + + Handles the complete training pipeline including job fetching, model training, + status updates, and error handling. Currently, using asyncio.to_thread for + CPU-intensive training to maintain event loop responsiveness. + + Note: asyncio.to_thread is used assuming single concurrent training job. + For true parallelism with multiple training jobs, consider ProcessPoolExecutor. + """ + + @classmethod + async def train_pending_job(cls) -> Model | None: + """ + Process the next pending training job from the queue. + + Fetches a pending job, executes training in a separate thread to maintain + event loop responsiveness, and updates job status accordingly. + + Returns: + Model: Trained model if successful, None if no pending jobs + """ + job_service = JobService() + job = await job_service.get_pending_train_job() + if job is None: + logger.trace("No pending training job") + return None + + # Run the training job with logging context + from core.logging.utils import job_logging_ctx # noqa: PLC0415 + + with job_logging_ctx(job_id=str(job.id)): + return await cls._run_training_job(job, job_service) + + @classmethod + async def _run_training_job(cls, job: Job, job_service: JobService) -> Model | None: + # Mark job as running + await job_service.update_job_status(job_id=job.id, status=JobStatus.RUNNING, message="Training started") + project_id = job.project_id + model_name = job.payload.get("model_name") + device = job.payload.get("device") + if model_name is None: + raise ValueError(f"Job {job.id} payload must contain 'model_name'") + + model_service = ModelService() + model = Model( + project_id=project_id, + name=str(model_name), + train_job_id=job.id, + ) + synchronization_parameters = ProgressSyncParams() + logger.info(f"Training model `{model_name}` for job `{job.id}`") + + synchronization_task: asyncio.Task[None] | None = None + try: + synchronization_task = asyncio.create_task( + cls._sync_progress_with_db( + job_service=job_service, job_id=job.id, synchronization_parameters=synchronization_parameters + ) + ) + # Use asyncio.to_thread to keep event loop responsive + # TODO: Consider ProcessPoolExecutor for true parallelism with multiple jobs + trained_model = await asyncio.to_thread( + cls._train_model, + model=model, + device=device, + synchronization_parameters=synchronization_parameters, + ) + + if synchronization_parameters.cancel_training_event.is_set(): + await cls._handle_job_cancellation(job_service=job_service, job=job, model=model) + return None + + if trained_model is None: + raise ValueError("Training failed - model is None") + + await job_service.update_job_status( + job_id=job.id, status=JobStatus.COMPLETED, message="Training completed successfully" + ) + return await model_service.create_model(trained_model) + except Exception as e: + logger.error("Failed to train pending training job: %s", e) + await job_service.update_job_status( + job_id=job.id, status=JobStatus.FAILED, message=f"Failed with exception: {str(e)}" + ) + if model.export_path: + logger.warning(f"Deleting partially created model with id: {model.id}") + await model_service.delete_model(project_id=project_id, model_id=model.id, delete_artifacts=True) + raise e + finally: + logger.debug("Syncing progress with db stopped") + if synchronization_task is not None and not synchronization_task.done(): + synchronization_task.cancel() + + @staticmethod + def _train_model( + model: Model, synchronization_parameters: ProgressSyncParams, device: str | None = None + ) -> Model | None: + """ + Execute CPU-intensive model training using anomalib. + + This synchronous function runs in a separate thread via asyncio.to_thread + to prevent blocking the event loop. Sets up the anomalib model, trains it + on the dataset, and exports it in OpenVINO format. + + Args: + model: Model object with training configuration + synchronization_parameters: Parameters for synchronization between the main process and the training process + device: Device to train on + + Returns: + Model: Trained model with updated export_path and is_ready=True + """ + from core.logging import global_log_config # noqa: PLC0415 + from core.logging.handlers import LoggerStdoutWriter # noqa: PLC0415 + + if device and not Devices.is_device_supported_for_training(device): + raise ValueError( + f"Device '{device}' is not supported for training. " + f"Supported devices: {', '.join(Devices.training_devices())}" + ) + + training_device = device or "auto" + logger.info(f"Training on device: {training_device}") + + model_binary_repo = ModelBinaryRepository(project_id=model.project_id, model_id=model.id) + image_binary_repo = ImageBinaryRepository(project_id=model.project_id) + image_folder_path = image_binary_repo.project_folder_path + model.export_path = model_binary_repo.model_folder_path + name = f"{model.project_id}-{model.name}" + + # Configure datamodule for anomalib training + datamodule = Folder( + name=name, + normal_dir=image_folder_path, + val_split_mode=ValSplitMode.SYNTHETIC, + ) + logger.info(f"Training from image folder: {image_folder_path} to model folder: {model.export_path}") + + # Initialize anomalib model and engine + anomalib_model = get_model(model=model.name) + + trackio = TrackioLogger(project=str(model.project_id), name=model.name) + tensorboard = AnomalibTensorBoardLogger(save_dir=global_log_config.tensorboard_log_path, name=name) + engine = Engine( + default_root_dir=model.export_path, + logger=[trackio, tensorboard], + devices=[0], # Only single GPU training is supported for now + max_epochs=10, + callbacks=[GetiInspectProgressCallback(synchronization_parameters)], + accelerator=training_device, + ) + + # Execute training and export + export_format = ExportType.OPENVINO + + # Capture pytorch stdout logs into logger + with redirect_stdout(LoggerStdoutWriter()): # type: ignore[type-var] + engine.fit(model=anomalib_model, datamodule=datamodule) + + # Find and set threshold metric + for callback in engine.trainer.callbacks: # type: ignore[attr-defined] + if threshold := getattr(callback, "normalized_pixel_threshold", None): + logger.debug(f"Found pixel threshold set to: {threshold}") + model.threshold = threshold.item() + break + + if synchronization_parameters.cancel_training_event.is_set(): + return None + + export_path = engine.export( + model=anomalib_model, + export_type=export_format, + export_root=model_binary_repo.model_folder_path, + ) + logger.info(f"Exporting model to {export_path}") + + model.is_ready = True + model.size = TrainingService._compute_export_size(model.export_path) + return model + + @staticmethod + async def _handle_job_cancellation(job_service: JobService, job: Job, model: Model) -> None: + """Mark job as cancelled and remove partially exported artifacts.""" + logger.info("Training job `%s` cancelled by user", job.id) + await job_service.update_job_status( + job_id=job.id, + status=JobStatus.CANCELED, + message="Training cancelled by user", + ) + + model_binary_repo = ModelBinaryRepository(project_id=job.project_id, model_id=model.id) + await model_binary_repo.delete_model_folder() + + @staticmethod + def _compute_export_size(path: str | None) -> int | None: + if path is None: + return None + + try: + if os.path.isfile(path): + return os.path.getsize(path) + if not os.path.isdir(path): + logger.warning(f"Cannot compute export size because `{path}` is not a directory") + return None + except OSError as error: + logger.error(f"Failed to access export path `{path}` while computing size: {error}") + return None + + def iter_file_sizes(): + for root, _, files in os.walk(path, followlinks=False): + for file_name in files: + file_path = os.path.join(root, file_name) + if os.path.islink(file_path): + continue + try: + yield os.path.getsize(file_path) + except OSError: + continue + + return sum(iter_file_sizes()) + + @classmethod + async def _sync_progress_with_db( + cls, + job_service: JobService, + job_id: UUID, + synchronization_parameters: ProgressSyncParams, + ) -> None: + try: + while True: + progress: int = synchronization_parameters.progress + message = synchronization_parameters.message + if not await job_service.is_job_still_running(job_id=job_id): + logger.debug("Job cancelled, stopping progress sync") + synchronization_parameters.set_cancel_training_event() + break + logger.debug(f"Syncing progress with db: {progress}% - {message}") + await job_service.update_job_status( + job_id=job_id, status=JobStatus.RUNNING, progress=progress, message=message + ) + await asyncio.sleep(0.5) + except Exception as e: + logger.exception("Failed to sync progress with db: %s", e) + await job_service.update_job_status(job_id=job_id, status=JobStatus.FAILED, message="Training failed") + raise + + @staticmethod + async def abort_orphan_jobs() -> None: + """ + Abort all running orphan training jobs (that do not belong to any worker). + + This method can be called during application shutdown/setup to ensure that + any orphan in-progress training jobs are marked as failed. + """ + query = {"status": JobStatus.RUNNING, "type": JobType.TRAINING} + running_jobs = await JobService.get_job_list(extra_filters=query) + for job in running_jobs.jobs: + logger.warning(f"Aborting orphan training job with id: {job.id}") + await JobService.update_job_status( + job_id=job.id, + status=JobStatus.FAILED, + message="Job aborted due to application shutdown", + ) diff --git a/application/backend/src/services/video_stream_service.py b/application/backend/src/services/video_stream_service.py new file mode 100644 index 0000000000..c40d07edcc --- /dev/null +++ b/application/backend/src/services/video_stream_service.py @@ -0,0 +1,41 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from loguru import logger + +from entities.images_folder_stream import ImagesFolderStream +from entities.ip_camera_stream import IPCameraStream +from entities.video_file_stream import VideoFileStream +from entities.video_stream import VideoStream +from entities.webcam_stream import WebcamStream +from pydantic_models import Source, SourceType + + +class VideoStreamService: + @staticmethod + def get_video_stream(input_config: Source) -> VideoStream | None: + video_stream: VideoStream | None + # TODO handle exceptions: if stream cannot be initialized, fallback to disconnected state + match input_config.source_type: + case SourceType.DISCONNECTED: + video_stream = None + case SourceType.WEBCAM: + video_stream = WebcamStream(device_id=input_config.device_id) + case SourceType.IP_CAMERA: + video_stream = IPCameraStream(config=input_config) + case SourceType.VIDEO_FILE: + video_stream = VideoFileStream(input_config.video_path) + case SourceType.IMAGES_FOLDER: + video_stream = ImagesFolderStream( + folder_path=input_config.images_folder_path, + ignore_existing_images=input_config.ignore_existing_images, + ) + case _: + raise ValueError(f"Unrecognized source type: {input_config.source_type}") + + if video_stream is not None: + logger.info(f"Initialized video stream for source type: {input_config.source_type}") + else: + logger.info("No video stream initialized") + + return video_stream diff --git a/application/backend/src/settings.py b/application/backend/src/settings.py new file mode 100644 index 0000000000..f1671de481 --- /dev/null +++ b/application/backend/src/settings.py @@ -0,0 +1,59 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""Application configuration management""" + +from functools import lru_cache +from pathlib import Path +from typing import Literal + +from pydantic import Field +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + """Application settings with environment variable support""" + + model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore") + + # Application + app_name: str = "Geti Inspect" + version: str = "0.1.0" + summary: str = "Geti Inspect server" + description: str = "Geti Inspect allows to fine-tune anomaly models at the edge." + openapi_url: str = "/rest_api/openapi.json" + debug: bool = Field(default=False, alias="DEBUG") + environment: Literal["dev", "prod"] = "dev" + data_dir: Path = Field(default=Path("data"), alias="DATA_DIR") + log_dir: Path = Field(default=Path("logs"), alias="LOG_DIR") + + # Server + host: str = Field(default="0.0.0.0", alias="HOST") # noqa: S104 + port: int = Field(default=8000, alias="PORT") + + # Database + database_file: str = Field(default="geti_inspect.db", alias="DATABASE_FILE", description="Database filename") + db_echo: bool = Field(default=False, alias="DB_ECHO") + + # Alembic + alembic_config_path: str = "src/alembic.ini" + alembic_script_location: str = "src/alembic" + + # Proxy settings + no_proxy: str = Field(default="localhost,127.0.0.1,::1", alias="no_proxy") + + @property + def database_url(self) -> str: + """Get database URL""" + return f"sqlite+aiosqlite:///./{self.data_dir / self.database_file}?journal_mode=WAL" + + @property + def sync_database_url(self) -> str: + """Get synchronous database URL""" + return f"sqlite:///{self.data_dir / self.database_file}" + + +@lru_cache +def get_settings() -> Settings: + """Get cached application settings""" + return Settings() diff --git a/application/backend/src/utils/__init__.py b/application/backend/src/utils/__init__.py new file mode 100644 index 0000000000..ac5fc504aa --- /dev/null +++ b/application/backend/src/utils/__init__.py @@ -0,0 +1,8 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from utils.queue import flush_queue +from utils.singleton import Singleton +from utils.visualization import Visualizer + +__all__ = ["Singleton", "Visualizer", "flush_queue"] diff --git a/application/backend/src/utils/asyncio_helpers.py b/application/backend/src/utils/asyncio_helpers.py new file mode 100644 index 0000000000..9d52cf728a --- /dev/null +++ b/application/backend/src/utils/asyncio_helpers.py @@ -0,0 +1,31 @@ +import asyncio +import concurrent.futures +from collections.abc import Callable +from typing import Any + + +async def run_in_process_pool(func: Callable, *args, **kwargs) -> Any: + """ + Run a synchronous function in a separate process using ProcessPoolExecutor. + This is useful for CPU-bound tasks that would block the event loop if run directly. + + This function safely handles the asyncio loop by using a different approach that + doesn't rely on get_running_loop() which can fail in certain contexts. + + Args: + func (callable): The synchronous function to be executed in a separate process. + *args: Positional arguments to pass to the function. + **kwargs: Keyword arguments to pass to the function. + + Returns: + The result of the function execution. + """ + # Try to get the running loop first, fall back to event loop if needed + try: + loop = asyncio.get_running_loop() + except RuntimeError: + # No running loop, get the event loop + loop = asyncio.get_event_loop() + + with concurrent.futures.ProcessPoolExecutor() as pool: + return await loop.run_in_executor(pool, func, *args, **kwargs) diff --git a/application/backend/src/utils/callbacks.py b/application/backend/src/utils/callbacks.py new file mode 100644 index 0000000000..d241066d18 --- /dev/null +++ b/application/backend/src/utils/callbacks.py @@ -0,0 +1,226 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +"""Lightning callback for sending progress to the frontend via the Plugin API.""" + +from __future__ import annotations + +import threading +from typing import TYPE_CHECKING, Any + +from lightning.pytorch.callbacks import Callback +from loguru import logger + +if TYPE_CHECKING: + from lightning.pytorch import LightningModule, Trainer + + +class ProgressSyncParams: + def __init__(self) -> None: + self._progress = 0 + self._message: str = "Initializing" + self._lock = threading.Lock() + self.cancel_training_event = threading.Event() + + @property + def message(self) -> str: + with self._lock: + return self._message + + @message.setter + def message(self, stage: str) -> None: + with self._lock: + self._message = f"Stage: {stage}" + logger.debug("Message updated: %s", self._message) + + @property + def progress(self) -> int: + with self._lock: + return self._progress + + @progress.setter + def progress(self, progress: int) -> None: + with self._lock: + self._progress = progress + logger.debug("Progress updated: %s", progress) + + def set_cancel_training_event(self) -> None: + with self._lock: + self.cancel_training_event.set() + logger.debug("Set cancel training event") + + +class GetiInspectProgressCallback(Callback): + """Callback for displaying training/validation/testing progress in the Geti Inspect UI. + + This callback sends progress events through a multiprocessing queue that the + main process polls and broadcasts via WebSocket to connected frontend clients. + + Args: + synchronization_parameters: Parameters for synchronization between the main process and the training process + + Example: + trainer = Trainer(callbacks=[GetiInspectProgressCallback(synchronization_parameters=ProgressSyncParams())]) + """ + + def __init__(self, synchronization_parameters: ProgressSyncParams) -> None: + """Initialize the callback with synchronization parameters. + Args: + synchronization_parameters: Parameters for synchronization between the main process and the training process + """ + self.synchronization_parameters = synchronization_parameters + + def _check_cancel_training(self, trainer: Trainer) -> None: + """Check if training should be canceled.""" + if self.synchronization_parameters.cancel_training_event.is_set(): + trainer.should_stop = True + + def _send_progress(self, progress: float, message: str) -> None: + """Send progress update to frontend via event queue. + Puts a generic event message into the multiprocessing queue which will + be picked up by the main process and broadcast via WebSocket. + Args: + progress: Progress value between 0.0 and 1.0 + message: The current training message + """ + # Convert progress to percentage (0-100) + progress_percent = int(progress * 100) + + try: + logger.debug("Sent progress: %s - %d%%", message, progress_percent) + self.synchronization_parameters.progress = progress_percent + self.synchronization_parameters.message = message + except Exception as e: + logger.warning("Failed to send progress to event queue: %s", e) + + # Training callbacks + def on_train_start(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when training starts.""" + del pl_module # unused + if trainer.state.stage is not None: + self._send_progress(0, trainer.state.stage.value) + else: + self._send_progress(0, "Training started") + self._check_cancel_training(trainer) + + def on_train_batch_start(self, trainer: Trainer, pl_module: LightningModule, batch: Any, batch_idx: int) -> None: + """Called when a training batch starts.""" + del pl_module, batch, batch_idx # unused + self._check_cancel_training(trainer) + + def on_train_epoch_end(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when a training epoch ends.""" + del pl_module # unused + # If max_epochs is not available, set progress to 0.5 + if trainer.state.stage is not None: + progress = ( + (trainer.current_epoch + 1) / trainer.max_epochs + if (trainer.max_epochs is not None and trainer.max_epochs > 0) + else 0.5 + ) + self._send_progress(progress, trainer.state.stage.value) + self._check_cancel_training(trainer) + + def on_train_end(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when training ends.""" + del pl_module # unused + if trainer.state.stage is not None: + self._send_progress(1.0, trainer.state.stage.value) + self._check_cancel_training(trainer) + + # Validation callbacks + def on_validation_start(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when validation starts.""" + del pl_module # unused + self._check_cancel_training(trainer) + + def on_validation_batch_start( + self, trainer: Trainer, pl_module: LightningModule, batch: Any, batch_idx: int, dataloader_idx: int = 0 + ) -> None: + """Called when a validation batch starts.""" + del pl_module, batch, batch_idx, dataloader_idx # unused + self._check_cancel_training(trainer) + + def on_validation_epoch_end(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when a validation epoch ends.""" + del pl_module # unused + self._check_cancel_training(trainer) + + def on_validation_end(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when validation ends.""" + del pl_module # unused + self._check_cancel_training(trainer) + + # Test callbacks + def on_test_start(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when testing starts.""" + del pl_module # unused + if trainer.state.stage is not None: + self._send_progress(0, trainer.state.stage.value) + else: + self._send_progress(0, "Testing started") + self._check_cancel_training(trainer) + + def on_test_batch_start( + self, trainer: Trainer, pl_module: LightningModule, batch: Any, batch_idx: int, dataloader_idx: int = 0 + ) -> None: + """Called when a test batch starts.""" + del pl_module, batch, batch_idx, dataloader_idx # unused + self._check_cancel_training(trainer) + + def on_test_epoch_end(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when a test epoch ends.""" + del pl_module # unused + # If max_epochs is not available, set progress to 0.5 + if trainer.state.stage is not None: + progress = ( + (trainer.current_epoch + 1) / trainer.max_epochs + if (trainer.max_epochs is not None and trainer.max_epochs > 0) + else 0.5 + ) + self._send_progress(progress, trainer.state.stage.value) + self._check_cancel_training(trainer) + + def on_test_end(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when testing ends.""" + del pl_module # unused + if trainer.state.stage is not None: + self._send_progress(1.0, trainer.state.stage.value) + self._check_cancel_training(trainer) + + # Predict callbacks + def on_predict_start(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when prediction starts.""" + del pl_module # unused + if trainer.state.stage is not None: + self._send_progress(0, trainer.state.stage.value) + else: + self._send_progress(0, "Prediction started") + self._check_cancel_training(trainer) + + def on_predict_batch_start( + self, trainer: Trainer, pl_module: LightningModule, batch: Any, batch_idx: int, dataloader_idx: int = 0 + ) -> None: + """Called when a prediction batch starts.""" + del pl_module, batch, batch_idx, dataloader_idx # unused + self._check_cancel_training(trainer) + + def on_predict_epoch_end(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when a prediction epoch ends.""" + del pl_module # unused + # If max_epochs is not available, set progress to 0.5 + if trainer.state.stage is not None: + progress = ( + (trainer.current_epoch + 1) / trainer.max_epochs + if (trainer.max_epochs is not None and trainer.max_epochs > 0) + else 0.5 + ) + self._send_progress(progress, trainer.state.stage.value) + self._check_cancel_training(trainer) + + def on_predict_end(self, trainer: Trainer, pl_module: LightningModule) -> None: + """Called when prediction ends.""" + del pl_module # unused + if trainer.state.stage is not None: + self._send_progress(1.0, trainer.state.stage.value) + self._check_cancel_training(trainer) diff --git a/application/backend/src/utils/devices.py b/application/backend/src/utils/devices.py new file mode 100644 index 0000000000..578dc36832 --- /dev/null +++ b/application/backend/src/utils/devices.py @@ -0,0 +1,69 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from functools import lru_cache + +import openvino as ov +from lightning.pytorch.accelerators import AcceleratorRegistry + + +class Devices: + """Utility class for device-related operations.""" + + @staticmethod + @lru_cache + def training_devices() -> list[str]: + """Get list of supported devices for training.""" + devices = [] + for device_name, device_info in AcceleratorRegistry.items(): + accelerator = device_info["accelerator"] + if accelerator.is_available(): + devices.append(device_name.casefold()) + return devices + + @staticmethod + @lru_cache + def inference_devices() -> list[str]: + """Get list of supported devices for inference.""" + ov_core = ov.Core() + return [device.casefold() for device in ov_core.available_devices] + + @classmethod + @lru_cache + def _is_device_supported(cls, device_name: str, for_training: bool = False) -> bool: + """Check if a device is supported for inference or training. + + Args: + device_name (str): Name of the device to check. + for_training (bool): If True, check for training devices; otherwise, check for inference devices. + + Returns: + bool: True if the device is supported, False otherwise. + """ + device_name = device_name.casefold() + if for_training: + return device_name in cls.training_devices() + return device_name in cls.inference_devices() + + @classmethod + def is_device_supported_for_inference(cls, device_name: str) -> bool: + """Check if a device is supported for inference. + + Args: + device_name (str): Name of the device to check. + + Returns: + bool: True if the device is supported for inference, False otherwise. + """ + return cls._is_device_supported(device_name, for_training=False) + + @classmethod + def is_device_supported_for_training(cls, device_name: str) -> bool: + """Check if a device is supported for training. + + Args: + device_name (str): Name of the device to check. + + Returns: + bool: True if the device is supported for training, False otherwise. + """ + return cls._is_device_supported(device_name, for_training=True) diff --git a/application/backend/src/utils/experiment_loggers/__init__.py b/application/backend/src/utils/experiment_loggers/__init__.py new file mode 100644 index 0000000000..25f4fdfa51 --- /dev/null +++ b/application/backend/src/utils/experiment_loggers/__init__.py @@ -0,0 +1,6 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from .trackio import TrackioLogger + +__all__ = ["TrackioLogger"] diff --git a/application/backend/src/utils/experiment_loggers/trackio.py b/application/backend/src/utils/experiment_loggers/trackio.py new file mode 100644 index 0000000000..d797209fac --- /dev/null +++ b/application/backend/src/utils/experiment_loggers/trackio.py @@ -0,0 +1,52 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from typing import Any + +import trackio +from lightning.pytorch.loggers import Logger as LightningLogger +from loguru import logger + + +class TrackioLogger(LightningLogger): + def __init__( + self, + project: str, + name: str | None = None, + ): + super().__init__() + self._project = project + self._name = name + + self._logger = logger.bind( + project=self._project, + name=self._name, + ) + # Initialize run + self.run = trackio.init( + project=self._project, + name=self._name, + ) + + @property + def name(self) -> str: + return self._name or self._project + + @property + def version(self) -> str: + # You might use some attribute of trackio.Run if exists + return f"run_{id(self.run)}" + + def log_hyperparams(self, params: dict[str, Any] | Any) -> None: + # Pass hyperparams via config if possible, or log as metrics + params_dict = dict(params) if not isinstance(params, dict) else params + # One option: include in config during init; if already initialized, + # log them as metrics at step=0 or custom logic + trackio.log({f"hyperparams/{k}": v for k, v in params_dict.items()}, step=0) + + def log_metrics(self, metrics: dict[str, float], step: int | None = None) -> None: + trackio.log(metrics, step=step) + + def finalize(self, _status: str) -> None: + # PyTorch Lightning calls logger.finalize("success" / "failed" etc.) + trackio.finish() diff --git a/application/backend/src/utils/queue.py b/application/backend/src/utils/queue.py new file mode 100644 index 0000000000..47cb335bcc --- /dev/null +++ b/application/backend/src/utils/queue.py @@ -0,0 +1,37 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import multiprocessing as mp +import queue + +from loguru import logger + + +def flush_queue(queue_obj: mp.Queue) -> None: + """Safely flush all items from a multiprocessing queue""" + flushed_count = 0 + + while True: + try: + queue_obj.get_nowait() + flushed_count += 1 + except queue.Empty: + # Queue is empty, we're done + break + except (OSError, ValueError, EOFError, BrokenPipeError) as e: + # Queue is closed/invalid or connection broken + logger.debug(f"Queue flush stopped due to: {e}") + break + except Exception as e: + logger.error(f"Unexpected error during queue flush: {e}") + break + + if flushed_count > 0: + logger.info(f"Flushed {flushed_count} items from queue") + + # https://docs.python.org/3/library/multiprocessing.html#all-start-methods + # section: Joining processes that use queues + # After flushing, call cancel_join_thread() to prevent the parent process from blocking + # indefinitely when joining child processes that used this queue. This avoids potential + # deadlocks if the queue's background thread adds more items during the flush. + queue_obj.cancel_join_thread() diff --git a/application/backend/src/utils/singleton.py b/application/backend/src/utils/singleton.py new file mode 100644 index 0000000000..bd9ed7a365 --- /dev/null +++ b/application/backend/src/utils/singleton.py @@ -0,0 +1,16 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from threading import Lock + + +class Singleton(type): + _instance = None + _lock = Lock() + + def __call__(cls, *args, **kwargs): + if cls._instance is None: # double-checked locking + with cls._lock: + if cls._instance is None: + cls._instance = super().__call__(*args, **kwargs) + return cls._instance diff --git a/application/backend/src/utils/visualization.py b/application/backend/src/utils/visualization.py new file mode 100644 index 0000000000..e4a99ad9fc --- /dev/null +++ b/application/backend/src/utils/visualization.py @@ -0,0 +1,125 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import base64 +from collections.abc import Callable + +import cv2 +import numpy as np +from loguru import logger + +from pydantic_models import PredictionResponse + + +class Visualizer: + @classmethod + def overlay_predictions( + cls, + original_image: np.ndarray, + prediction: PredictionResponse, + *overlays: Callable[..., np.ndarray], + **kwargs: object, + ) -> np.ndarray: + """Create a visualization by applying a sequence of overlay functions. + + Each overlay is a callable that accepts (image, prediction) and returns a new image. + If no overlays are provided, the default is anomaly heatmap followed by label. + """ + try: + visualization = original_image.copy() + + # Default overlays if none provided + if not overlays: + overlays = (cls.overlay_anomaly_heatmap, cls.draw_prediction_label) + + for overlay in overlays: + try: + visualization = overlay(visualization, prediction, **kwargs) + except Exception as e: # continue other overlays even if one fails + logger.debug(f"Overlay step failed: {e}") + + return visualization + except Exception as e: + logger.debug(f"Failed to create visualization: {e}") + return original_image + + @staticmethod + def overlay_anomaly_heatmap( + base_image: np.ndarray, + prediction: PredictionResponse, + threshold_value: int = 128, + alpha: float = 0.25, + ) -> np.ndarray: + """Overlay the anomaly heatmap onto the image. + + Steps: + - Decode base64 anomaly map to an image + - Convert to grayscale if needed, ensure uint8 + - Apply JET colormap and threshold mask + - Blend onto the base image using alpha + """ + try: + anomaly_map_base64 = prediction.anomaly_map + result = base_image.copy() + try: + anomaly_png_bytes = base64.b64decode(anomaly_map_base64) + anomaly_np = np.frombuffer(anomaly_png_bytes, dtype=np.uint8) + anomaly_img = cv2.imdecode(anomaly_np, cv2.IMREAD_UNCHANGED) + except Exception: + return result + + if anomaly_img is None: + return result + + try: + if anomaly_img.ndim == 3 and anomaly_img.shape[2] > 1: + anomaly_gray = cv2.cvtColor(anomaly_img, cv2.COLOR_BGR2GRAY) + else: + anomaly_gray = anomaly_img + + if anomaly_gray.dtype != np.uint8: + anomaly_gray = anomaly_gray.astype(np.uint8) + + heatmap = cv2.applyColorMap(anomaly_gray, cv2.COLORMAP_JET) + heatmap_resized = cv2.resize(heatmap, (result.shape[1], result.shape[0])) + + mask_gray = cv2.resize(anomaly_gray, (result.shape[1], result.shape[0])) + mask_bool = mask_gray >= threshold_value + + masked_heatmap = np.zeros_like(heatmap_resized) + try: + masked_heatmap[mask_bool] = heatmap_resized[mask_bool] + except Exception as e: + logger.debug(f"Failed to apply heatmap mask: {e}") + + result = cv2.addWeighted(result, 1.0, masked_heatmap, alpha, 0) + except Exception as e: + logger.debug(f"Failed to overlay heatmap: {e}") + return result + except Exception as e: + logger.debug(f"Failed in overlay_anomaly_heatmap: {e}") + return base_image + + @staticmethod + def draw_prediction_label( + base_image: np.ndarray, + prediction: PredictionResponse, + *, + position: tuple[int, int] = (10, 20), + font_scale: float = 2.0, + thickness: int = 3, + text_color: tuple[int, int, int] = (0, 255, 0), + background_color: tuple[int, int, int] = (0, 0, 0), + ) -> np.ndarray: + """Draw the prediction label with a background rectangle for readability.""" + try: + label_text = f"{prediction.label.value} ({prediction.score:.3f})" + result = base_image.copy() + font = cv2.FONT_HERSHEY_SIMPLEX + (text_w, text_h), _ = cv2.getTextSize(label_text, font, font_scale, thickness) + x, y = position[0], position[1] + text_h + cv2.rectangle(result, (x - 8, y - text_h - 8), (x - 8 + text_w + 16, y + 8), background_color, -1) + cv2.putText(result, label_text, (x, y), font, font_scale, text_color, thickness, cv2.LINE_AA) + return result + except Exception as e: + logger.debug(f"Failed to draw label: {e}") + return base_image diff --git a/application/backend/src/webrtc/__init__.py b/application/backend/src/webrtc/__init__.py new file mode 100644 index 0000000000..4057dc0163 --- /dev/null +++ b/application/backend/src/webrtc/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 diff --git a/application/backend/src/webrtc/manager.py b/application/backend/src/webrtc/manager.py new file mode 100644 index 0000000000..e0e514779b --- /dev/null +++ b/application/backend/src/webrtc/manager.py @@ -0,0 +1,92 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import asyncio +import queue +from typing import Any + +from aiortc import RTCPeerConnection, RTCSessionDescription +from aiortc.rtcconfiguration import RTCConfiguration, RTCIceServer +from loguru import logger + +from pydantic_models.webrtc import Answer, InputData, Offer +from webrtc.stream import InferenceVideoStreamTrack + + +class WebRTCManager: + """Manager for handling WebRTC connections.""" + + def __init__(self, stream_queue: queue.Queue) -> None: + self._peer_connections: dict[str, RTCPeerConnection] = {} + self._input_data: dict[str, Any] = {} + self._stream_queue = stream_queue + + async def handle_offer(self, offer: Offer) -> Answer: + """Create an SDP offer for a new WebRTC connection.""" + try: + # Configure RTCPeerConnection with proper ICE servers + pc = RTCPeerConnection(RTCConfiguration(iceServers=[RTCIceServer(urls=["stun:stun.l.google.com:19302"])])) + self._peer_connections[offer.webrtc_id] = pc + + # Add video track + track = InferenceVideoStreamTrack(self._stream_queue) + pc.addTrack(track) + + @pc.on("connectionstatechange") + async def connection_state_change() -> None: + if pc.connectionState in {"failed", "closed"}: + await self.cleanup_connection(offer.webrtc_id) + + # Validate offer before processing + if not offer.sdp or not offer.type: + raise ValueError("Invalid offer: missing SDP or type") + + # Set remote description from client's offer + await pc.setRemoteDescription(RTCSessionDescription(sdp=offer.sdp, type=offer.type)) + + # Create answer with proper configuration + answer = await pc.createAnswer() + + # Set local description with error handling + try: + await pc.setLocalDescription(answer) + except Exception as e: + logger.error(f"Failed to set local description: {e}") + # Try with a simpler answer configuration + answer = await pc.createAnswer() + await pc.setLocalDescription(answer) + + # Ensure local description is set + if pc.localDescription is None: + raise RuntimeError("Failed to create local description") + + return Answer(sdp=pc.localDescription.sdp, type=pc.localDescription.type) + except Exception as e: + logger.error(f"Error in handle_offer: {e}", exc_info=True) + # Clean up on error + if offer.webrtc_id in self._peer_connections: + await self.cleanup_connection(offer.webrtc_id) + raise + + def set_input(self, data: InputData) -> None: + """Set input data for specific WebRTC connection""" + self._input_data[data.webrtc_id] = { + "conf_threshold": data.conf_threshold, + "updated_at": asyncio.get_event_loop().time(), + } + + async def cleanup_connection(self, webrtc_id: str) -> None: + """Clean up a specific WebRTC connection by its ID.""" + if webrtc_id in self._peer_connections: + logger.debug(f"Cleaning up connection: {webrtc_id}") + pc = self._peer_connections.pop(webrtc_id) + await pc.close() + logger.debug(f"Connection {webrtc_id} successfully closed.") + self._input_data.pop(webrtc_id, None) + + async def cleanup(self) -> None: + """Clean up all connections""" + for pc in list(self._peer_connections.values()): + await pc.close() + self._peer_connections.clear() + self._input_data.clear() diff --git a/application/backend/src/webrtc/stream.py b/application/backend/src/webrtc/stream.py new file mode 100644 index 0000000000..6d772e005a --- /dev/null +++ b/application/backend/src/webrtc/stream.py @@ -0,0 +1,81 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import asyncio +import queue + +import numpy as np +from aiortc import VideoStreamTrack +from av import VideoFrame +from loguru import logger + +FALLBACK_FRAME = np.full((64, 64, 3), 16, dtype=np.uint8) + + +class InferenceVideoStreamTrack(VideoStreamTrack): + """A video stream track that provides frames with inference results over WebRTC.""" + + def __init__(self, stream_queue: queue.Queue[np.ndarray]): + super().__init__() + self._stream_queue = stream_queue + self._last_frame: np.ndarray | None = None + + async def recv(self) -> VideoFrame: + """ + Asynchronously receive the next video frame from the internal queue. + + This coroutine attempts to obtain a frame from ``self._stream_queue`` with a + 500ms timeout. If a new frame is received, it is cached in ``self._last_frame``. + If the queue is empty and no cached frame exists, the method uses the + ``FALLBACK_FRAME``, a small, 64 x 64, dark gray numpy array + representing a video frame. + + The received or fallback frame is wrapped in a ``VideoFrame`` object, with + its presentation timestamp (``pts``) and time base attached. + + Behavior: + - Pulls frames from ``_stream_queue`` using ``asyncio.to_thread`` (timeout: 500ms). + - On timeout, returns last cached frame if available. + - If no cached frame exists, returns ``FALLBACK_FRAME``. + - Ensures robust streaming when new frames are intermittently missing. + + Returns: + aiortc.VideoFrame: + Video frame object containing image data, presentation timestamp (``pts``), + and time base. + + Raises: + Exception: + Logs and propagates any errors during retrieval or conversion. + + Notes: + - Uses ``asyncio.to_thread`` to prevent blocking the event loop + when calling the synchronous ``queue.Queue.get`` method. + - Ensures resilience of streaming by falling back to cached or + dummy frames in case of delayed or missing input. + + """ + pts, time_base = await self.next_timestamp() + + try: + try: + logger.trace("Getting the frame from the stream_queue...") + frame_data = await asyncio.to_thread(self._stream_queue.get, True, 0.5) # wait for 500ms + self._last_frame = frame_data # cache the successful frame + except queue.Empty: + logger.trace("Empty queue. Using the last frame...") + if self._last_frame is None: + frame_data = FALLBACK_FRAME + else: + frame_data = self._last_frame + + logger.trace("Received the frame from the stream_queue.") + + # Convert numpy array to VideoFrame + frame = VideoFrame.from_ndarray(frame_data, format="bgr24") + frame.pts = pts + frame.time_base = time_base + return frame + except Exception as e: + logger.error(f"Error in recv: {e}") + raise diff --git a/application/backend/src/workers/__init__.py b/application/backend/src/workers/__init__.py new file mode 100644 index 0000000000..f2b6bc7b54 --- /dev/null +++ b/application/backend/src/workers/__init__.py @@ -0,0 +1,14 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from .dispatching import DispatchingWorker +from .inference import InferenceWorker +from .stream_loading import StreamLoader +from .training import TrainingWorker + +__all__ = [ + "DispatchingWorker", + "InferenceWorker", + "StreamLoader", + "TrainingWorker", +] diff --git a/application/backend/src/workers/base.py b/application/backend/src/workers/base.py new file mode 100644 index 0000000000..8a82611440 --- /dev/null +++ b/application/backend/src/workers/base.py @@ -0,0 +1,188 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +import abc +import asyncio +import multiprocessing as mp +import os +import signal +import threading +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterable + from multiprocessing.queues import Queue + from multiprocessing.synchronize import Event + +import loguru +from loguru import logger + + +def log_threads(log_level="DEBUG") -> None: # noqa: ANN001 + """Log all the alive threads associated with the current process""" + pid = os.getpid() + alive_threads = [thread for thread in threading.enumerate() if thread.is_alive()] + thread_list_msg = ( + f"Alive threads for process with pid '{pid}': " + f"{', '.join([str((thread.name, thread.ident)) for thread in alive_threads])}" + ) + logger.log(log_level, thread_list_msg) + + +class StoppableMixin: + """Mixin providing stop-aware functionality using external stop event.""" + + def should_stop(self) -> bool: + """Check if a stop has been requested.""" + if not hasattr(self, "_stop_event"): + raise AttributeError("StoppableMixin requires a '_stop_event' to be set.") + # Stop if parent process died + parent_process = mp.parent_process() + parent_died = parent_process is not None and not parent_process.is_alive() + return self._stop_event.is_set() or parent_died # type: ignore + + def stop_aware_sleep(self, seconds: float) -> bool: + """ + Sleep for the specified time, but wake up immediately if stop is requested. + + Args: + seconds: Maximum time to sleep in seconds + + Returns: + True if woke up due to stop request, False if timeout elapsed + """ + if not hasattr(self, "_stop_event"): + raise AttributeError("StoppableMixin requires _stop_event to be set") + return self._stop_event.wait(seconds) # type: ignore + + +class BaseProcessWorker(mp.Process, StoppableMixin, ABC): + """ + Reusable worker with a clean lifecycle: setup() -> run_loop() [until stop_event] -> teardown() + Subclasses only implement what's specific to their job. + """ + + # Override in subclasses for a nicer auto-name: + ROLE: str = "Worker" + + def __init__( + self, + *, + stop_event: Event, + queues_to_cancel: Iterable[Queue] | None = None, + logger_: loguru.Logger | None = None, + ) -> None: + super().__init__() + self._stop_event = stop_event + self._parent_pid = os.getpid() + self._queues_to_cancel = list(queues_to_cancel or []) + + # Platforms that use "spawn" for multiprocessing (e.g. Windows) cause logging concurrency issues. + # Therefore, we need to copy the logger with enqueue=True in child processes. + # https://loguru.readthedocs.io/en/stable/resources/recipes.html#compatibility-with-multiprocessing-using-enqueue-argument + global logger # noqa: PLW0603 + logger = logger_ or logger + + # Hooks to be implemented by subclasses + + def setup(self) -> None: + """Allocate resources and initialize settings. Called once in the child process.""" + # Logging needs to be re-setup in child processes because settings are non-pickable. + # Only required when multiprocessing uses "spawn" mode (Windows/MacOS) + from core.logging import setup_logging # noqa: PLC0415 + + setup_logging() + + @abstractmethod + async def run_loop(self) -> None: + """ + Main loop. Return only when asked to stop or on unrecoverable error. + self.should_stop() should be used the loop condition. + """ + ... + + def teardown(self) -> None: + """Release resources (optional).""" + + # Internal + final run orchestration + + @staticmethod + def _install_signal_policy() -> None: + """ + Ignore shutdown signals (SIGINT) in child processes. + + This function prevents child processes from handling shutdown signals directly, + ensuring that cleanup is coordinated through the parent process via the stop_event + mechanism. + """ + signal.signal(signal.SIGINT, signal.SIG_IGN) + + def _auto_name(self) -> str: + """Generate a name for the process based on its role and PIDs.""" + return "-".join([self.ROLE, str(self._parent_pid), str(os.getpid())]) + + def _cancel_queue_join_threads(self) -> None: + for q in self._queues_to_cancel: + try: + # https://docs.python.org/3/library/multiprocessing.html#all-start-methods + # section: Joining processes that use queues + # Call cancel_join_thread() to prevent the parent process from blocking + # indefinitely when joining child processes that used this queue. This avoids potential + # deadlocks if the queue's background thread adds more items during the flush. + q.cancel_join_thread() + logger.debug(f"Cancelled join thread for queue {getattr(q, 'name', q)!r}") + except Exception as e: + logger.warning(f"Failed cancelling queue join thread: {e}") + + def run(self) -> None: # final orchestration; should not be overridden in subclasses + self.setup() + with logger.contextualize(worker=self.__class__.__name__): + self._install_signal_policy() + self.name = self._auto_name() + logger.info(f"Starting {self.name}...") + try: + asyncio.run(self.run_loop()) + finally: + try: + self.teardown() + finally: + # always try to cancel any declared queues + self._cancel_queue_join_threads() + log_threads() + logger.info(f"Stopped {self.name}.") + + +class BaseThreadWorker(threading.Thread, StoppableMixin, abc.ABC): + ROLE: str = "Worker" + + def __init__(self, *, stop_event: Event, daemon: bool = False): + super().__init__(daemon=daemon) + self._stop_event = stop_event + self.name = f"{self.ROLE}-{os.getpid()}-thread" + + # hooks + def setup(self) -> None: + pass + + @abstractmethod + async def run_loop(self) -> None: ... + + def teardown(self) -> None: + pass + + # final run orchestration + def run(self) -> None: # final orchestrator + with logger.contextualize(worker=self.__class__.__name__): + logger.info(f"Starting {self.name}") + try: + self.setup() + asyncio.run(self.run_loop()) + except Exception as e: + logger.error(f"Unhandled exception in {self.name}: {str(e)}") + finally: + try: + self.teardown() + finally: + logger.info(f"Stopped {self.name}") diff --git a/application/backend/src/workers/dispatching.py b/application/backend/src/workers/dispatching.py new file mode 100644 index 0000000000..4b9c6c1036 --- /dev/null +++ b/application/backend/src/workers/dispatching.py @@ -0,0 +1,113 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +import copy +import multiprocessing as mp +import queue +from multiprocessing.synchronize import Condition as ConditionClass +from multiprocessing.synchronize import Event as EventClass + +from loguru import logger + +from entities.stream_data import StreamData +from pydantic_models import Sink, SinkType +from services import ActivePipelineService, DispatchService +from services.dispatchers import Dispatcher +from workers.base import BaseThreadWorker + + +class DispatchingWorker(BaseThreadWorker): + """ + A thread that pulls predictions from the queue and dispatches them to the configured outputs + and WebRTC visualization stream. + """ + + ROLE = "Dispatching" + + def __init__( + self, + pred_queue: mp.Queue, + rtc_stream_queue: queue.Queue, + stop_event: EventClass, + active_config_changed_condition: ConditionClass, + ) -> None: + super().__init__(stop_event=stop_event) + self._pred_queue = pred_queue + self._rtc_stream_queue = rtc_stream_queue + + self._active_config_changed_condition = active_config_changed_condition + self._active_pipeline_service: ActivePipelineService | None = None + + self._prev_sink_config: Sink | None = None + self._destinations: list[Dispatcher] = [] + + def _reset_sink_if_needed(self, sink_config: Sink) -> None: + if not self._prev_sink_config or sink_config != self._prev_sink_config: + logger.debug(f"Sink config changed from {self._prev_sink_config} to {sink_config}") + self._destinations = DispatchService.get_destinations(output_configs=[sink_config]) + self._prev_sink_config = copy.deepcopy(sink_config) + + @logger.catch() + async def run_loop(self) -> None: + self._active_pipeline_service = await ActivePipelineService.create( + config_changed_condition=self._active_config_changed_condition, start_daemon=True + ) + + while not self.should_stop(): + # Exit if parent process died (if ever run as a process) + parent_process = mp.parent_process() + if parent_process is not None and not parent_process.is_alive(): + break + + # Read from the queue + try: + stream_data: StreamData = self._pred_queue.get(timeout=1) + except queue.Empty: + logger.debug("Nothing to dispatch yet... retrying in 1 second") + await asyncio.sleep(1) + continue + + passthrough_mode = not self._active_pipeline_service.is_running + if passthrough_mode: + logger.debug("Passthrough mode; only dispatching to WebRTC stream") + # Only dispatch to WebRTC stream + try: + self._rtc_stream_queue.put(stream_data.frame_data, block=False) + except queue.Full: + logger.debug("Visualization queue is full; skipping") + continue + + sink_config = self._active_pipeline_service.sink_config + if sink_config.sink_type == SinkType.DISCONNECTED: + logger.trace("No sink available... retrying in 1 second") + await asyncio.sleep(1) + continue + + self._reset_sink_if_needed(sink_config) + + if stream_data.inference_data is None: + logger.error("Missing inference data in stream_data; skipping dispatch") + continue + + inference_data = stream_data.inference_data + if inference_data is None: + logger.error("No inference data available") + continue + + image_with_visualization = inference_data.visualized_prediction + prediction = inference_data.prediction + # Postprocess and dispatch results + async with asyncio.TaskGroup() as task_group: + for destination in self._destinations: + task_group.create_task( + destination.dispatch( + original_image=stream_data.frame_data, + image_with_visualization=image_with_visualization, + predictions=prediction, + ) + ) + # Dispatch to WebRTC stream + try: + self._rtc_stream_queue.put(image_with_visualization, block=False) + except queue.Full: + logger.debug("Visualization queue is full; skipping") diff --git a/application/backend/src/workers/inference.py b/application/backend/src/workers/inference.py new file mode 100644 index 0000000000..a670c9e0c3 --- /dev/null +++ b/application/backend/src/workers/inference.py @@ -0,0 +1,292 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +import asyncio +import queue as std_queue +from typing import TYPE_CHECKING, Any + +import cv2 +import loguru +from loguru import logger + +if TYPE_CHECKING: + import multiprocessing as mp + from multiprocessing.synchronize import Event as EventClass + from multiprocessing.synchronize import Lock + + import numpy as np + + +from db import get_async_db_session_ctx +from entities.stream_data import InferenceData, StreamData +from repositories import PipelineRepository +from services import ModelService +from services.exceptions import DeviceNotFoundError +from services.metrics_service import MetricsService +from services.model_service import LoadedModel +from utils.visualization import Visualizer +from workers.base import BaseProcessWorker + + +class InferenceWorker(BaseProcessWorker): + """A process that pulls frames from the frame queue, runs inference, and pushes results to the prediction queue.""" + + ROLE = "Inference" + + def __init__( + self, + frame_queue: mp.Queue, + pred_queue: mp.Queue, + stop_event: EventClass, + model_reload_event: EventClass, + shm_name: str, + shm_lock: Lock, + logger_: loguru.Logger | None = None, + ) -> None: + super().__init__(stop_event=stop_event, queues_to_cancel=[pred_queue], logger_=logger_) + self._frame_queue = frame_queue + self._pred_queue = pred_queue + self._model_reload_event = model_reload_event + self._shm_name = shm_name + self._shm_lock = shm_lock + + self._metrics_service: MetricsService | None = None + self._loaded_model: LoadedModel | None = None + self._last_model_obj_id = 0 # track the id of the Model object to install the callback only once + self._cached_models: dict[Any, object] = {} + self._model_check_interval: float = 5.0 # seconds between model refresh checks + self._is_passthrough_mode: bool = False + + def setup(self) -> None: + super().setup() + self._metrics_service = MetricsService(self._shm_name, self._shm_lock) + + async def _get_active_model(self) -> LoadedModel | None: + try: + async with get_async_db_session_ctx() as session: + repo = PipelineRepository(session) + pipeline = await repo.get_active_pipeline() + # Passthrough mode: pipeline is active but not running, so bypass inference + self._is_passthrough_mode = pipeline is None or ( + pipeline.status.is_active and not pipeline.status.is_running + ) + if pipeline is None or pipeline.model is None: + return None + + model = pipeline.model + return LoadedModel(name=model.name, id=model.id, model=model, device=pipeline.inference_device) + except Exception as e: + logger.error(f"Failed to query active pipeline/model: {e}", exc_info=True) + return None + + async def _model_refresh_daemon(self) -> None: + """Background daemon that periodically refreshes the active model reference. + + Runs independently every _model_check_interval seconds to detect + configuration changes without impacting frame processing performance. + """ + while not self.should_stop(): + await asyncio.sleep(self._model_check_interval) + + try: + previous_model_id = self._loaded_model.id if self._loaded_model else None + self._loaded_model = await self._get_active_model() + + # Log model transitions + new_model_id = self._loaded_model.id if self._loaded_model else None + if previous_model_id != new_model_id: + if new_model_id: + logger.info( + f"Model refresh daemon: Active model changed to " + f"'{self._loaded_model.name}' ({new_model_id})" # type: ignore[union-attr] + ) + else: + logger.info("Model refresh daemon: Switched to passthrough mode (no active model)") + logger.debug(f"Model refresh daemon running in {self._model_check_interval}s") + except Exception as e: + logger.error(f"Model refresh daemon error: {e}", exc_info=True) + # Continue running despite errors + + async def _handle_model_reload(self) -> None: + # Handle model reload signal: force reload currently active model + if self._model_reload_event.is_set(): + # The loop handles the case when the active model is switched again while reloading + while self._model_reload_event.is_set(): + self._model_reload_event.clear() + logger.info("Model reload event detected; reloading active model") + self._loaded_model = await self._get_active_model() + + if self._loaded_model is None: + raise RuntimeError("No active model configured.") + # Remove cached model to force reload + try: + self._cached_models.pop(self._loaded_model.id, None) + except Exception as e: + model_id = getattr(self._loaded_model, "id", "unknown") + logger.debug(f"Failed to evict cached model {model_id}: {e}") + # Preload the model for faster first inference + try: + inferencer = await ModelService.load_inference_model( + self._loaded_model.model, device=self._loaded_model.device + ) + self._cached_models[self._loaded_model.id] = inferencer + logger.info( + f"Reloaded inference model '{self._loaded_model.name}' ({self._loaded_model.id}) " + f"on device `{self._loaded_model.device}`", + ) + except DeviceNotFoundError: + # Load model using the default device + logger.warning( + f"Device '{self._loaded_model.device}' not found; " + f"loading model '{self._loaded_model.name}' ({self._loaded_model.id}) on default device" + ) + inferencer = await ModelService.load_inference_model(self._loaded_model.model) + self._cached_models[self._loaded_model.id] = inferencer + except Exception as e: + logger.error(f"Failed to reload model '{self._loaded_model.name}': {e}", exc_info=True) + # Leave cache empty; next predict will attempt to load again + + async def _get_next_frame(self) -> StreamData | None: + """Retrieve the next frame from the queue.""" + try: + return self._frame_queue.get(timeout=1) + except std_queue.Empty: + logger.debug("No frame available yet") + return None + except Exception as e: + logger.error(f"Failed to get frame from queue: {e}") + return None + + async def _handle_passthrough_mode(self, stream_data: StreamData) -> None: + """Handle frame in passthrough mode (no model loaded).""" + logger.debug("No active model configured; frame passthrough mode") + try: + self._pred_queue.put(stream_data, timeout=1) + except std_queue.Full: + logger.debug("Prediction queue is full (passthrough mode); dropping frame") + + async def _encode_frame(self, frame: np.ndarray) -> bytes | None: + """Encode frame to JPEG bytes for inference.""" + try: + success, buf = cv2.imencode(".jpg", frame) + if not success: + logger.warning("Failed to encode frame; skipping") + return None + return buf.tobytes() + except Exception as e: + logger.error(f"Error encoding frame: {e}", exc_info=True) + return None + + async def _run_inference(self, image_bytes: bytes) -> Any | None: + """Run inference on encoded image and record metrics.""" + if self._loaded_model is None: + logger.error("Cannot run inference: no active model configured") + return None + + start_t = MetricsService.record_inference_start() + try: + return await ModelService.predict_image( + self._loaded_model.model, + image_bytes, + self._cached_models, # type: ignore[arg-type] + ) + except Exception as e: + logger.error(f"Inference failed: {e}", exc_info=True) + return None + finally: + try: + if self._metrics_service is not None and self._loaded_model is not None: + self._metrics_service.record_inference_end(self._loaded_model.id, start_t) + except Exception as e: + logger.debug(f"Failed to record inference metric: {e}") + + async def _process_frame_with_model(self, stream_data: StreamData) -> None: + """Process frame with loaded model and attach inference data. + + Raises: + ValueError: If frame data is invalid or encoding fails + RuntimeError: If inference or model operations fail + """ + frame = stream_data.frame_data + if frame is None: + raise ValueError("Received empty frame data") + + # Encode frame + image_bytes = await self._encode_frame(frame) + if image_bytes is None: + raise ValueError("Failed to encode frame to JPEG") + + # Run inference + prediction_response = await self._run_inference(image_bytes) + if prediction_response is None or self._loaded_model is None: + raise RuntimeError("Inference failed or model became unavailable") + + # Build visualization + vis_frame: np.ndarray = Visualizer.overlay_predictions(frame, prediction_response) + + # Package inference data + stream_data.inference_data = InferenceData( + prediction=prediction_response, # type: ignore[arg-type] + visualized_prediction=vis_frame, + model_name=self._loaded_model.name, + ) + + async def _enqueue_result(self, stream_data: StreamData) -> None: + """Enqueue processed result to prediction queue.""" + try: + self._pred_queue.put(stream_data, timeout=1) + except std_queue.Full: + logger.debug("Prediction queue is full; dropping result") + + @logger.catch() + async def run_loop(self) -> None: + """Main processing loop for inference worker.""" + # Initialize model reference on startup + self._loaded_model = await self._get_active_model() + + # Start background daemon for periodic model refresh + refresh_task = asyncio.create_task(self._model_refresh_daemon()) + + try: + while not self.should_stop(): + # Pull next frame + stream_data = await self._get_next_frame() + if stream_data is None: + continue + + # Handle model reload (immediate refresh on events) + try: + await self._handle_model_reload() + except Exception as e: + logger.warning(f"Model reload handling failed: {e}") + await asyncio.sleep(1) + continue + + # Check passthrough mode (no inference) + if self._is_passthrough_mode: + await self._handle_passthrough_mode(stream_data) + continue + + if self._loaded_model is None: + logger.error("Cannot run inference: model is not loaded: retrying in 1 second") + await asyncio.sleep(1) + continue + + # Process frame with model and enqueue result + try: + await self._process_frame_with_model(stream_data) + await self._enqueue_result(stream_data) + except (ValueError, RuntimeError) as e: + logger.warning(f"Frame processing skipped: {e}") + continue + except Exception as e: + logger.error(f"Unexpected error during frame processing: {e}", exc_info=True) + continue + finally: + # Clean shutdown: cancel daemon task + refresh_task.cancel() + try: + await refresh_task + except asyncio.CancelledError: + logger.info("Model refresh daemon stopped") diff --git a/application/backend/src/workers/stream_loading.py b/application/backend/src/workers/stream_loading.py new file mode 100644 index 0000000000..1c11e1a983 --- /dev/null +++ b/application/backend/src/workers/stream_loading.py @@ -0,0 +1,111 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +import asyncio +import copy +import queue +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import multiprocessing as mp + from multiprocessing.synchronize import Condition as ConditionClass + from multiprocessing.synchronize import Event as EventClass + +import loguru +from loguru import logger + +from entities.stream_data import StreamData +from entities.video_stream import VideoStream +from pydantic_models import Source, SourceType +from services import ActivePipelineService, VideoStreamService +from workers.base import BaseProcessWorker + + +class StreamLoader(BaseProcessWorker): + """A process that loads frames from the video stream and injects them into the frame queue.""" + + ROLE = "StreamLoader" + + def __init__( + self, + frame_queue: mp.Queue, + stop_event: EventClass, + config_changed_condition: ConditionClass, + logger_: loguru.Logger | None = None, + ) -> None: + super().__init__(stop_event=stop_event, queues_to_cancel=[frame_queue], logger_=logger_) + self._frame_queue = frame_queue + self._config_changed_condition = config_changed_condition + + self._active_pipeline_service: ActivePipelineService | None = None + self._prev_source_config: Source | None = None + self._video_stream: VideoStream | None = None + + def _reset_stream_if_needed(self, source_config: Source) -> None: + if self._prev_source_config is None or source_config != self._prev_source_config: + logger.debug(f"Source configuration changed from {self._prev_source_config} to {source_config}") + if self._video_stream is not None: + self._video_stream.release() + self._video_stream = VideoStreamService.get_video_stream(input_config=source_config) + self._prev_source_config = copy.deepcopy(source_config) + + @logger.catch() + async def run_loop(self) -> None: + self._active_pipeline_service = await ActivePipelineService.create( + config_changed_condition=self._config_changed_condition, start_daemon=True + ) + + while not self.should_stop(): + try: + source_config = self._active_pipeline_service.source_config + + if source_config.source_type == SourceType.DISCONNECTED: + logger.trace("No source available... retrying in 1 second") + await asyncio.sleep(1) + continue + + self._reset_stream_if_needed(source_config) + + if self._video_stream is None: + logger.trace("No video stream available, retrying in 1 second...") + await asyncio.sleep(1) + continue + + # Acquire a frame and enqueue it + try: + stream_data = self._video_stream.get_data() + if stream_data is not None: + _enqueue_frame_with_retry( + self._frame_queue, stream_data, self._video_stream.is_real_time(), self._stop_event + ) + else: + await asyncio.sleep(0.1) + except Exception as e: + logger.error(f"Error acquiring frame. Details: `{str(e)}`") + await asyncio.sleep(1) + continue + except Exception as e: + logger.error(f"Error acquiring frame. Details: `{str(e)}`") + continue + + def teardown(self) -> None: + if self._video_stream is not None: + logger.debug("Releasing video stream...") + self._video_stream.release() + + +def _enqueue_frame_with_retry( + frame_queue: mp.Queue, payload: StreamData, is_real_time: bool, stop_event: EventClass +) -> None: + """Enqueue frame with retry logic for non-real-time streams""" + while not stop_event.is_set(): + try: + frame_queue.put(payload, timeout=1) + break + except queue.Full: + if is_real_time: + frame_queue.get(timeout=0.01) # Discard oldest frame + logger.debug("Frame queue is full, discarded oldest frame for real-time stream") + break + logger.debug("Frame queue is full, retrying...") diff --git a/application/backend/src/workers/training.py b/application/backend/src/workers/training.py new file mode 100644 index 0000000000..193980354f --- /dev/null +++ b/application/backend/src/workers/training.py @@ -0,0 +1,71 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from __future__ import annotations + +import asyncio +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from multiprocessing.synchronize import Event as EventClass + +import loguru +from loguru import logger + +from services.training_service import TrainingService +from workers.base import BaseProcessWorker + +MAX_CONCURRENT_TRAINING = 1 +SCHEDULE_INTERVAL_SEC = 5 + + +class TrainingWorker(BaseProcessWorker): + ROLE = "Training" + + def __init__(self, stop_event: EventClass, logger_: loguru.Logger | None = None): + super().__init__(stop_event=stop_event, logger_=logger_) + + @logger.catch() + async def run_loop(self) -> None: + """Main training loop that polls for jobs and manages concurrent training tasks.""" + training_service = TrainingService() + running_tasks: set[asyncio.Task] = set() + + while not self.should_stop(): + try: + # Clean up completed tasks + running_tasks = {task for task in running_tasks if not task.done()} + + # Start new training if under capacity limit + # Using async tasks allows: + # - Multiple training jobs to run concurrently + # - Event loop to remain responsive for shutdown signals + if len(running_tasks) < MAX_CONCURRENT_TRAINING: + running_tasks.add(asyncio.create_task(training_service.train_pending_job())) + except Exception as e: + logger.error(f"Error occurred in training loop: {e}", exc_info=True) + + # Check for shutdown signals frequently + for _ in range(SCHEDULE_INTERVAL_SEC * 2): + if self.should_stop(): + break + await asyncio.sleep(0.5) + + # Cancel any remaining tasks on shutdown + for task in running_tasks: + task.cancel() + if running_tasks: + try: + await asyncio.gather(*running_tasks, return_exceptions=True) + except Exception as e: + # Log exceptions during cancellation to ensure clean shutdown and aid debugging + logger.error(f"Exception during task cancellation: {e}", exc_info=True) + + def setup(self) -> None: + super().setup() + with logger.contextualize(worker=self.__class__.__name__): + asyncio.run(TrainingService.abort_orphan_jobs()) + + def teardown(self) -> None: + super().teardown() + with logger.contextualize(worker=self.__class__.__name__): + asyncio.run(TrainingService.abort_orphan_jobs()) diff --git a/application/backend/tests/__init__.py b/application/backend/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/application/backend/tests/unit/__init__.py b/application/backend/tests/unit/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/application/backend/tests/unit/conftest.py b/application/backend/tests/unit/conftest.py new file mode 100644 index 0000000000..58d59b1dea --- /dev/null +++ b/application/backend/tests/unit/conftest.py @@ -0,0 +1,162 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import uuid +from io import BytesIO +from unittest.mock import AsyncMock, MagicMock + +import pytest +from fastapi import UploadFile +from PIL import Image +from sqlalchemy.ext.asyncio.session import AsyncSession + +from pydantic_models import ( + Job, + JobList, + JobStatus, + JobType, + Media, + MediaList, + Model, + ModelList, + PredictionLabel, + PredictionResponse, + Project, + ProjectList, +) +from pydantic_models.job import TrainJobPayload + + +@pytest.fixture +def fxt_project(): + """Fixture for a test project.""" + return Project( + id=uuid.uuid4(), + name="Test Project", + ) + + +@pytest.fixture +def fxt_job(fxt_project): + """Fixture for a test job.""" + return Job( + project_id=fxt_project.id, + type=JobType.TRAINING, + payload={"model_name": "padim"}, + message="Test job created", + status=JobStatus.PENDING, + ) + + +@pytest.fixture +def fxt_job_payload(fxt_project): + """Fixture for a test job payload.""" + return TrainJobPayload( + project_id=fxt_project.id, + model_name="padim", + device=None, + ) + + +@pytest.fixture +def fxt_media(fxt_project): + """Fixture for a test media.""" + return Media( + id=uuid.uuid4(), + project_id=fxt_project.id, + filename="test_image.jpg", + size=1024, + is_anomalous=False, + width=800, + height=600, + ) + + +@pytest.fixture +def fxt_model(fxt_project): + """Fixture for a test model.""" + return Model( + id=uuid.uuid4(), + project_id=fxt_project.id, + name="padim", + format="openvino", + is_ready=True, + export_path="/path/to/model", + train_job_id=uuid.uuid4(), + size=0, + ) + + +@pytest.fixture +def fxt_prediction_response(): + """Fixture for a test prediction response.""" + return PredictionResponse( + anomaly_map="base64_encoded_image", + label=PredictionLabel.NORMAL, + score=0.1, + ) + + +@pytest.fixture +def fxt_upload_file(): + """Fixture for a test upload file.""" + file_content = b"fake image content" + upload_file = MagicMock(spec=UploadFile) + upload_file.filename = "test_image.jpg" + upload_file.size = len(file_content) + upload_file.read = AsyncMock(return_value=file_content) + return upload_file + + +@pytest.fixture +def fxt_db_session(): + """Fixture for a mock database session.""" + return AsyncMock(spec=AsyncSession) + + +@pytest.fixture +def fxt_project_list(fxt_project): + """Fixture for a test project list.""" + return ProjectList(projects=[fxt_project]) + + +@pytest.fixture +def fxt_job_list(fxt_job): + """Fixture for a test job list.""" + return JobList(jobs=[fxt_job]) + + +@pytest.fixture +def fxt_media_list(fxt_media): + """Fixture for a test media list.""" + return MediaList(media=[fxt_media]) + + +@pytest.fixture +def fxt_model_list(fxt_model): + """Fixture for a test model list.""" + return ModelList(models=[fxt_model]) + + +@pytest.fixture +def fxt_image_bytes(): + """Fixture for test image bytes.""" + # Create a valid image for testing + img = Image.new("RGB", (800, 600), color="blue") + with BytesIO() as buf: + img.save(buf, format="JPEG") + return buf.getvalue() + + +@pytest.fixture +def fxt_openvino_inferencer(): + """Fixture for a mock OpenVINO inferencer.""" + inferencer = MagicMock() + prediction = MagicMock() + prediction.anomaly_map = MagicMock() + prediction.anomaly_map.squeeze.return_value = [[0.1, 0.2], [0.3, 0.4]] + prediction.pred_label = MagicMock() + prediction.pred_label.item.return_value = 0 + prediction.pred_score = MagicMock() + prediction.pred_score.item.return_value = 0.1 + inferencer.predict.return_value = prediction + return inferencer diff --git a/application/backend/tests/unit/endpoints/conftest.py b/application/backend/tests/unit/endpoints/conftest.py new file mode 100644 index 0000000000..11cb2d3852 --- /dev/null +++ b/application/backend/tests/unit/endpoints/conftest.py @@ -0,0 +1,12 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from fastapi.testclient import TestClient + +from main import app + + +@pytest.fixture +def fxt_client(): + return TestClient(app, raise_server_exceptions=False) diff --git a/application/backend/tests/unit/endpoints/test_jobs.py b/application/backend/tests/unit/endpoints/test_jobs.py new file mode 100644 index 0000000000..213ad63d65 --- /dev/null +++ b/application/backend/tests/unit/endpoints/test_jobs.py @@ -0,0 +1,67 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +from unittest.mock import MagicMock + +import pytest +from fastapi import status +from sse_starlette import ServerSentEvent + +from api.dependencies import get_job_service +from main import app +from pydantic_models import JobList +from services import JobService + + +@pytest.fixture +def fxt_job_service() -> MagicMock: + job_service = MagicMock(spec=JobService) + app.dependency_overrides[get_job_service] = lambda: job_service + return job_service + + +def test_get_jobs(fxt_client, fxt_job_service, fxt_job): + fxt_job_service.get_job_list.return_value = JobList(jobs=[fxt_job]) + + response = fxt_client.get("/api/jobs") + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["jobs"]) == 1 + fxt_job_service.get_job_list.assert_called_once() + + +def test_get_jobs_empty(fxt_client, fxt_job_service, fxt_job): + fxt_job_service.get_job_list.return_value = JobList(jobs=[]) + + response = fxt_client.get("/api/jobs") + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["jobs"]) == 0 + fxt_job_service.get_job_list.assert_called_once() + + +def test_get_job_logs_success(fxt_client, fxt_job_service, fxt_job): + """Test successful log streaming endpoint.""" + + # Mock the stream_logs generator + async def mock_stream(): + yield ServerSentEvent(data='{"level": "INFO", "message": "Line 1"}') + yield ServerSentEvent(data='{"level": "INFO", "message": "Line 2"}') + + fxt_job_service.stream_logs.return_value = mock_stream() + + response = fxt_client.get(f"/api/jobs/{fxt_job.id}/logs") + assert response.status_code == status.HTTP_200_OK + + # Verify the streamed content + content = response.content.decode("utf-8") + lines = [line for line in content.split("\n") if line] + assert len(lines) == 4 # 2 events + 2 newlines + assert '"level": "INFO"' in lines[0] + assert '"message": "Line 1"' in lines[0] + + fxt_job_service.stream_logs.assert_called_once_with(job_id=fxt_job.id) + + +def test_get_job_logs_invalid_uuid(fxt_client, fxt_job_service): + """Test log streaming with invalid job ID.""" + response = fxt_client.get("/api/jobs/invalid-uuid/logs") + # FastAPI returns 400 for invalid UUID in path parameter + assert response.status_code == status.HTTP_400_BAD_REQUEST diff --git a/application/backend/tests/unit/endpoints/test_media.py b/application/backend/tests/unit/endpoints/test_media.py new file mode 100644 index 0000000000..12ba71096b --- /dev/null +++ b/application/backend/tests/unit/endpoints/test_media.py @@ -0,0 +1,124 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from unittest.mock import MagicMock +from uuid import uuid4 + +import pytest +from fastapi import status + +from api.dependencies import get_media_service +from main import app +from pydantic_models import Media, MediaList +from services import MediaService, ResourceNotFoundError +from services.exceptions import ResourceType + + +@pytest.fixture +def fxt_media(fxt_project): + return Media( + id=uuid4(), + project_id=fxt_project.id, + filename="test_image.jpg", + size=64, + is_anomalous=False, + width=800, + height=600, + ) + + +@pytest.fixture +def fxt_media_service() -> MagicMock: + media_service = MagicMock(spec=MediaService) + app.dependency_overrides[get_media_service] = lambda: media_service + return media_service + + +def test_get_media_list_empty(fxt_client, fxt_media_service): + project_id = uuid4() + fxt_media_service.get_media_list.return_value = MediaList(media=[]) + + response = fxt_client.get(f"/api/projects/{project_id}/images") + assert response.status_code == status.HTTP_200_OK + assert response.json() == {"media": []} + fxt_media_service.get_media_list.assert_called_once_with(project_id=project_id) + + +def test_get_media_list(fxt_client, fxt_media_service, fxt_media): + project_id = uuid4() + fxt_media_service.get_media_list.return_value = MediaList(media=[fxt_media]) + + response = fxt_client.get(f"/api/projects/{project_id}/images") + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["media"]) == 1 + fxt_media_service.get_media_list.assert_called_once_with(project_id=project_id) + + +def test_get_media_thumbnail_success(fxt_client, fxt_media_service, tmp_path): + """Test successful thumbnail retrieval.""" + project_id = uuid4() + media_id = uuid4() + thumbnail_path = tmp_path / f"thumb_{media_id}.png" + thumbnail_path.write_bytes(b"fake thumbnail content") + + fxt_media_service.get_thumbnail_file_path.return_value = str(thumbnail_path) + + response = fxt_client.get(f"/api/projects/{project_id}/images/{media_id}/thumbnail") + + assert response.status_code == status.HTTP_200_OK + assert response.content == b"fake thumbnail content" + fxt_media_service.get_thumbnail_file_path.assert_called_once_with(project_id=project_id, media_id=media_id) + + +def test_get_media_thumbnail_not_found(fxt_client, fxt_media_service): + """Test thumbnail retrieval raises FileNotFoundError when media not found.""" + project_id = uuid4() + media_id = uuid4() + + fxt_media_service.get_thumbnail_file_path.side_effect = FileNotFoundError("Media not found") + + response = fxt_client.get(f"/api/projects/{project_id}/images/{media_id}/thumbnail") + + assert response.status_code == status.HTTP_404_NOT_FOUND + assert response.json()["detail"] == f"Media with ID {media_id} not found" + fxt_media_service.get_thumbnail_file_path.assert_called_once_with(project_id=project_id, media_id=media_id) + + +def test_delete_media_success(fxt_client, fxt_media_service, fxt_media): + """Test successful media deletion.""" + project_id = fxt_media.project_id + media_id = fxt_media.id + + fxt_media_service.delete_media.return_value = None + + response = fxt_client.delete(f"/api/projects/{project_id}/images/{media_id}") + + assert response.status_code == status.HTTP_204_NO_CONTENT + assert response.content == b"" + fxt_media_service.delete_media.assert_called_once_with(media_id=media_id, project_id=project_id) + + +def test_delete_media_not_found(fxt_client, fxt_media_service): + """Test media deletion when media not found.""" + project_id = uuid4() + media_id = uuid4() + + fxt_media_service.delete_media.side_effect = ResourceNotFoundError( + resource_type=ResourceType.MEDIA, resource_id=str(media_id) + ) + + response = fxt_client.delete(f"/api/projects/{project_id}/images/{media_id}") + + assert response.status_code == status.HTTP_404_NOT_FOUND + assert response.json()["detail"] == f"Media with ID {media_id} not found." + fxt_media_service.delete_media.assert_called_once_with(media_id=media_id, project_id=project_id) + + +def test_delete_media_invalid_id(fxt_client, fxt_media_service): + """Test media deletion with invalid media ID format.""" + project_id = uuid4() + + response = fxt_client.delete(f"/api/projects/{project_id}/images/invalid-id") + + assert response.status_code == status.HTTP_400_BAD_REQUEST + fxt_media_service.delete_media.assert_not_called() diff --git a/application/backend/tests/unit/endpoints/test_models.py b/application/backend/tests/unit/endpoints/test_models.py new file mode 100644 index 0000000000..bd84cefbca --- /dev/null +++ b/application/backend/tests/unit/endpoints/test_models.py @@ -0,0 +1,75 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from unittest.mock import MagicMock +from uuid import uuid4 + +import pytest +from fastapi import status + +from api.dependencies import get_model_service +from main import app +from pydantic_models import Model, ModelList +from services import ModelService, ResourceNotFoundError +from services.exceptions import ResourceType + + +@pytest.fixture +def fxt_model(fxt_project): + return Model( + id=uuid4(), + name="test_model", + project_id=fxt_project.id, + export_path="/path/to/model", + train_job_id=uuid4(), + size=1024, + ) + + +@pytest.fixture +def fxt_model_service() -> MagicMock: + model_service = MagicMock(spec=ModelService) + app.dependency_overrides[get_model_service] = lambda: model_service + return model_service + + +def test_get_models_empty(fxt_client, fxt_model_service, fxt_project): + project_id = fxt_project.id + fxt_model_service.get_model_list.return_value = ModelList(models=[]) + + response = fxt_client.get(f"/api/projects/{project_id}/models") + assert response.status_code == status.HTTP_200_OK + assert response.json() == {"models": []} + fxt_model_service.get_model_list.assert_called_once_with(project_id=project_id) + + +def test_get_models(fxt_client, fxt_model_service, fxt_model, fxt_project): + project_id = fxt_project.id + fxt_model_service.get_model_list.return_value = ModelList(models=[fxt_model]) + + response = fxt_client.get(f"/api/projects/{project_id}/models") + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["models"]) == 1 + assert response.json()["models"][0]["size"] == 1024 + fxt_model_service.get_model_list.assert_called_once_with(project_id=project_id) + + +def test_delete_model_success(fxt_client, fxt_model_service, fxt_project): + project_id = fxt_project.id + model_id = uuid4() + + response = fxt_client.delete(f"/api/projects/{project_id}/models/{model_id}") + + assert response.status_code == status.HTTP_204_NO_CONTENT + fxt_model_service.delete_model.assert_called_once_with(project_id=project_id, model_id=model_id) + + +def test_delete_model_not_found(fxt_client, fxt_model_service, fxt_project): + project_id = fxt_project.id + model_id = uuid4() + fxt_model_service.delete_model.side_effect = ResourceNotFoundError(ResourceType.MODEL, str(model_id)) + + response = fxt_client.delete(f"/api/projects/{project_id}/models/{model_id}") + + assert response.status_code == status.HTTP_404_NOT_FOUND + assert "not found" in response.json()["detail"].lower() diff --git a/application/backend/tests/unit/endpoints/test_pipelines.py b/application/backend/tests/unit/endpoints/test_pipelines.py new file mode 100644 index 0000000000..58423d1ff4 --- /dev/null +++ b/application/backend/tests/unit/endpoints/test_pipelines.py @@ -0,0 +1,397 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from datetime import UTC, datetime +from unittest.mock import AsyncMock, MagicMock +from uuid import uuid4 + +import pytest +from fastapi import status +from pydantic import ValidationError + +from api.dependencies import get_pipeline_metrics_service, get_pipeline_service +from main import app +from pydantic_models.metrics import InferenceMetrics, LatencyMetrics, PipelineMetrics, ThroughputMetrics, TimeWindow +from pydantic_models.model import Model +from pydantic_models.pipeline import Pipeline, PipelineStatus +from pydantic_models.sink import FolderSinkConfig +from pydantic_models.source import VideoFileSourceConfig +from services import ActivePipelineConflictError, PipelineService +from services.pipeline_metrics_service import PipelineMetricsService + + +@pytest.fixture +def fxt_pipeline() -> Pipeline: + return Pipeline( + project_id=uuid4(), + status=PipelineStatus.IDLE, + ) + + +@pytest.fixture +def fxt_pipeline_service() -> MagicMock: + pipeline_service = MagicMock(spec=PipelineService) + # get_active_pipeline is an async static method, so we need to mock it as AsyncMock + pipeline_service.get_active_pipeline = AsyncMock(return_value=None) + app.dependency_overrides[get_pipeline_service] = lambda: pipeline_service + return pipeline_service + + +@pytest.fixture +def fxt_pipeline_metrics_service() -> MagicMock: + pipeline_metrics_service = MagicMock(spec=PipelineMetricsService) + app.dependency_overrides[get_pipeline_metrics_service] = lambda: pipeline_metrics_service + return pipeline_metrics_service + + +class TestPipelineEndpoints: + @pytest.mark.parametrize( + "http_method, service_method", + [ + ("get", "get_pipeline_by_id"), + ("patch", "update_pipeline"), + ], + ) + def test_pipeline_invalid_ids(self, http_method, service_method, fxt_pipeline_service, fxt_client): + response = getattr(fxt_client, http_method)("/api/projects/invalid-id/pipeline") + + assert response.status_code == status.HTTP_400_BAD_REQUEST + getattr(fxt_pipeline_service, service_method).assert_not_called() + + def test_get_pipeline_success(self, fxt_pipeline, fxt_pipeline_service, fxt_client): + fxt_pipeline_service.get_pipeline_by_id.return_value = fxt_pipeline + + response = fxt_client.get(f"/api/projects/{fxt_pipeline.project_id}/pipeline") + + assert response.status_code == status.HTTP_200_OK + fxt_pipeline_service.get_pipeline_by_id.assert_called_once_with(fxt_pipeline.project_id) + + def test_update_pipeline_success(self, fxt_pipeline, fxt_pipeline_service, fxt_client): + project_id, sink_id = fxt_pipeline.project_id, str(uuid4()) + fxt_pipeline_service.update_pipeline.return_value = fxt_pipeline + + response = fxt_client.patch(f"/api/projects/{project_id}/pipeline", json={"sink_id": sink_id}) + + assert response.status_code == status.HTTP_200_OK + fxt_pipeline_service.update_pipeline.assert_called_once_with(project_id, {"sink_id": sink_id}) + + def test_update_pipeline_status(self, fxt_pipeline, fxt_pipeline_service, fxt_client): + response = fxt_client.patch( + f"/api/projects/{fxt_pipeline.project_id}/pipeline", json={"status": PipelineStatus.IDLE} + ) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + fxt_pipeline_service.update_pipeline.assert_not_called() + + @pytest.mark.parametrize( + "operation, pipeline_status", + [ + ("run", PipelineStatus.RUNNING), + ("disable", PipelineStatus.IDLE), + ], + ) + def test_enable_pipeline(self, operation, pipeline_status, fxt_pipeline, fxt_pipeline_service, fxt_client): + project_id = fxt_pipeline.project_id + # Mock get_active_pipeline to return None (no active pipeline) for run operation + if operation == "run": + fxt_pipeline_service.get_active_pipeline = AsyncMock(return_value=None) + # activate_pipeline internally calls update_pipeline, so we make activate_pipeline call update_pipeline + + async def activate_pipeline_side_effect(proj_id, set_running=False): + await fxt_pipeline_service.update_pipeline(proj_id, {"status": pipeline_status}) + return fxt_pipeline + + fxt_pipeline_service.activate_pipeline = AsyncMock(side_effect=activate_pipeline_side_effect) + fxt_pipeline_service.update_pipeline = AsyncMock(return_value=fxt_pipeline) + response = fxt_client.post(f"/api/projects/{project_id}/pipeline:{operation}") + + assert response.status_code == status.HTTP_204_NO_CONTENT + if operation == "run": + fxt_pipeline_service.activate_pipeline.assert_called_once_with(project_id, set_running=True) + fxt_pipeline_service.update_pipeline.assert_called_once_with(project_id, {"status": pipeline_status}) + else: + fxt_pipeline_service.update_pipeline.assert_called_once_with(project_id, {"status": pipeline_status}) + + @pytest.mark.parametrize("operation", ["run", "disable"]) + def test_enable_pipeline_invalid_id(self, operation, fxt_pipeline, fxt_pipeline_service, fxt_client): + response = fxt_client.post(f"/api/projects/invalid-id/pipeline:{operation}") + + assert response.status_code == status.HTTP_400_BAD_REQUEST + fxt_pipeline_service.update_pipeline.assert_not_called() + + # Note: ResourceNotFoundError handling not implemented in current endpoints + # def test_enable_non_existent_pipeline( + # self, pipeline_op, pipeline_status, fxt_pipeline, fxt_pipeline_service, fxt_client + # ): + # project_id = fxt_pipeline.project_id + # fxt_pipeline_service.update_pipeline.side_effect = ResourceNotFoundError( + # resource_type=ResourceType.PIPELINE, + # resource_id=str(project_id), + # ) + # response = fxt_client.post(f"/api/projects/{project_id}/pipeline:{pipeline_op}") + # assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR + # fxt_pipeline_service.update_pipeline.assert_called_once_with(project_id, {"status": pipeline_status}) + + def test_cannot_enable_pipeline(self, fxt_pipeline, fxt_pipeline_service, fxt_client): + # Mock activate_pipeline to raise ValidationError (which happens when update_pipeline raises it) + fxt_pipeline_service.activate_pipeline.side_effect = ValidationError.from_exception_data( + "Pipeline", + [ + { + "type": "missing", + "loc": ("name",), + "msg": "Field required", + "input": {}, + } + ], + ) + + response = fxt_client.post(f"/api/projects/{fxt_pipeline.project_id}/pipeline:run") + + assert response.status_code == status.HTTP_409_CONFLICT + fxt_pipeline_service.activate_pipeline.assert_called_once_with(fxt_pipeline.project_id, set_running=True) + + def test_enable_pipeline_with_active_pipeline_different_project( + self, fxt_pipeline, fxt_pipeline_service, fxt_client + ): + """Test enabling a pipeline when another pipeline from a different project is already active.""" + other_project_id = uuid4() + # Create a valid RUNNING pipeline with required fields + source = VideoFileSourceConfig( + id=uuid4(), + project_id=other_project_id, + source_type="video_file", + name="Test Source", + video_path="/path/to/video.mp4", + ) + sink = FolderSinkConfig( + id=uuid4(), + project_id=other_project_id, + sink_type="folder", + name="Test Sink", + folder_path="/path/to/output", + output_formats=["image_original"], + rate_limit=0.2, + ) + model = Model( + id=uuid4(), + project_id=other_project_id, + name="Test Model", + format="openvino", + train_job_id=uuid4(), + ) + active_pipeline = Pipeline( + project_id=other_project_id, + source=source, + sink=sink, + model=model, + source_id=source.id, + sink_id=sink.id, + model_id=model.id, + status=PipelineStatus.RUNNING, + ) + # activate_pipeline raises ActivePipelineConflictError when there's an active pipeline from different project + fxt_pipeline_service.activate_pipeline.side_effect = ActivePipelineConflictError( + pipeline_id=str(fxt_pipeline.project_id), + reason=f"another pipeline is already active. Please disable the pipeline {active_pipeline.id} " + f"before activating a new one", + ) + + response = fxt_client.post(f"/api/projects/{fxt_pipeline.project_id}/pipeline:run") + + assert response.status_code == status.HTTP_409_CONFLICT + assert "Cannot activate pipeline" in response.json()["detail"] + fxt_pipeline_service.activate_pipeline.assert_called_once_with(fxt_pipeline.project_id, set_running=True) + + def test_enable_pipeline_with_active_pipeline_same_project(self, fxt_pipeline, fxt_pipeline_service, fxt_client): + """Test enabling a pipeline when the same pipeline is already active (re-enabling).""" + # Create a valid RUNNING pipeline with required fields + source = VideoFileSourceConfig( + id=uuid4(), + project_id=fxt_pipeline.project_id, + source_type="video_file", + name="Test Source", + video_path="/path/to/video.mp4", + ) + sink = FolderSinkConfig( + id=uuid4(), + project_id=fxt_pipeline.project_id, + sink_type="folder", + name="Test Sink", + folder_path="/path/to/output", + output_formats=["image_original"], + rate_limit=0.2, + ) + model = Model( + id=uuid4(), + project_id=fxt_pipeline.project_id, + name="Test Model", + format="openvino", + train_job_id=uuid4(), + ) + active_pipeline = Pipeline( + project_id=fxt_pipeline.project_id, + source=source, + sink=sink, + model=model, + source_id=source.id, + sink_id=sink.id, + model_id=model.id, + status=PipelineStatus.RUNNING, + ) + # activate_pipeline internally calls get_active_pipeline and update_pipeline + # Make activate_pipeline call update_pipeline when called + + async def activate_pipeline_side_effect(proj_id, set_running=False): + await fxt_pipeline_service.update_pipeline(proj_id, {"status": PipelineStatus.RUNNING}) + return active_pipeline + + fxt_pipeline_service.get_active_pipeline = AsyncMock(return_value=active_pipeline) + fxt_pipeline_service.update_pipeline = AsyncMock(return_value=active_pipeline) + fxt_pipeline_service.activate_pipeline = AsyncMock(side_effect=activate_pipeline_side_effect) + + response = fxt_client.post(f"/api/projects/{fxt_pipeline.project_id}/pipeline:run") + + assert response.status_code == status.HTTP_204_NO_CONTENT + fxt_pipeline_service.activate_pipeline.assert_called_once_with(fxt_pipeline.project_id, set_running=True) + # activate_pipeline internally calls update_pipeline when re-enabling + fxt_pipeline_service.update_pipeline.assert_called_once_with( + fxt_pipeline.project_id, {"status": PipelineStatus.RUNNING} + ) + + def test_get_pipeline_metrics_success(self, fxt_pipeline, fxt_pipeline_metrics_service, fxt_client): + """Test successful retrieval of pipeline metrics with default time window.""" + mock_metrics = PipelineMetrics( + time_window=TimeWindow(start=datetime.now(UTC), end=datetime.now(UTC), time_window=60), + inference=InferenceMetrics( + latency=LatencyMetrics(avg_ms=100.5, min_ms=50.0, max_ms=200.0, p95_ms=180.0, latest_ms=120.0), + throughput=ThroughputMetrics( + avg_requests_per_second=30.0, total_requests=1800, max_requests_per_second=45.0 + ), + ), + ) + fxt_pipeline_metrics_service.get_pipeline_metrics.return_value = mock_metrics + + response = fxt_client.get(f"/api/projects/{fxt_pipeline.project_id}/pipeline/metrics") + + assert response.status_code == status.HTTP_200_OK + fxt_pipeline_metrics_service.get_pipeline_metrics.assert_called_once_with(fxt_pipeline.project_id, 60) + + def test_get_pipeline_metrics_invalid_pipeline_id(self, fxt_pipeline_metrics_service, fxt_client): + """Test metrics endpoint with invalid pipeline ID format.""" + response = fxt_client.get("/api/projects/invalid-id/pipeline/metrics") + + assert response.status_code == status.HTTP_400_BAD_REQUEST + fxt_pipeline_metrics_service.get_pipeline_metrics.assert_not_called() + + # Note: ResourceNotFoundError handling not implemented in current endpoints + # def test_get_pipeline_metrics_pipeline_not_found(self, fxt_pipeline, fxt_pipeline_service, fxt_client): + # """Test metrics endpoint when pipeline doesn't exist.""" + # fxt_pipeline_service.get_pipeline_metrics.side_effect = ResourceNotFoundError( + # ResourceType.PIPELINE, str(fxt_pipeline.project_id) + # ) + # response = fxt_client.get(f"/api/projects/{fxt_pipeline.project_id}/pipeline/metrics") + # assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR + # fxt_pipeline_service.get_pipeline_metrics.assert_called_once_with(fxt_pipeline.project_id, 60) + + def test_get_pipeline_metrics_pipeline_not_running(self, fxt_pipeline, fxt_pipeline_metrics_service, fxt_client): + """Test metrics endpoint when pipeline is not in running state.""" + fxt_pipeline_metrics_service.get_pipeline_metrics.side_effect = ValueError( + "Cannot get metrics for a pipeline that is not running." + ) + + response = fxt_client.get(f"/api/projects/{fxt_pipeline.project_id}/pipeline/metrics") + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "Cannot get metrics for a pipeline that is not running" in response.json()["detail"] + fxt_pipeline_metrics_service.get_pipeline_metrics.assert_called_once_with(fxt_pipeline.project_id, 60) + + @pytest.mark.parametrize("invalid_time_window", [0, -1, 3601, 7200]) + def test_get_pipeline_metrics_invalid_time_window( + self, invalid_time_window, fxt_pipeline, fxt_pipeline_metrics_service, fxt_client + ): + """Test metrics endpoint with invalid time window values.""" + response = fxt_client.get( + f"/api/projects/{fxt_pipeline.project_id}/pipeline/metrics?time_window={invalid_time_window}" + ) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "Duration must be between 1 and 3600 seconds" in response.json()["detail"] + fxt_pipeline_metrics_service.get_pipeline_metrics.assert_not_called() + + @pytest.mark.parametrize("valid_time_window", [1, 30, 300, 1800, 3600]) + def test_get_pipeline_metrics_valid_time_windows( + self, valid_time_window, fxt_pipeline, fxt_pipeline_metrics_service, fxt_client + ): + """Test metrics endpoint with various valid time window values.""" + mock_metrics = PipelineMetrics( + time_window=TimeWindow(start=datetime.now(UTC), end=datetime.now(UTC), time_window=valid_time_window), + inference=InferenceMetrics( + latency=LatencyMetrics(avg_ms=100.0, min_ms=50.0, max_ms=200.0, p95_ms=180.0, latest_ms=120.0), + throughput=ThroughputMetrics( + avg_requests_per_second=30.0, total_requests=1800, max_requests_per_second=45.0 + ), + ), + ) + fxt_pipeline_metrics_service.get_pipeline_metrics.return_value = mock_metrics + + response = fxt_client.get( + f"/api/projects/{fxt_pipeline.project_id}/pipeline/metrics?time_window={valid_time_window}" + ) + + assert response.status_code == status.HTTP_200_OK + fxt_pipeline_metrics_service.get_pipeline_metrics.assert_called_once_with( + fxt_pipeline.project_id, valid_time_window + ) + + def test_get_pipeline_metrics_no_data_available(self, fxt_pipeline, fxt_pipeline_metrics_service, fxt_client): + """Test metrics endpoint when no latency data is available.""" + mock_metrics = PipelineMetrics( + time_window=TimeWindow(start=datetime.now(UTC), end=datetime.now(UTC), time_window=60), + inference=InferenceMetrics( + latency=LatencyMetrics(avg_ms=None, min_ms=None, max_ms=None, p95_ms=None, latest_ms=None), + throughput=ThroughputMetrics( + avg_requests_per_second=None, total_requests=None, max_requests_per_second=None + ), + ), + ) + fxt_pipeline_metrics_service.get_pipeline_metrics.return_value = mock_metrics + + response = fxt_client.get(f"/api/projects/{fxt_pipeline.project_id}/pipeline/metrics") + + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + + assert response_data["inference"]["latency"]["avg_ms"] is None + assert response_data["inference"]["latency"]["min_ms"] is None + assert response_data["inference"]["latency"]["max_ms"] is None + assert response_data["inference"]["latency"]["p95_ms"] is None + assert response_data["inference"]["latency"]["latest_ms"] is None + + fxt_pipeline_metrics_service.get_pipeline_metrics.assert_called_once_with(fxt_pipeline.project_id, 60) + + def test_get_pipeline_metrics_success_with_data(self, fxt_pipeline, fxt_pipeline_metrics_service, fxt_client): + """Test successful retrieval of pipeline metrics with latency data.""" + mock_metrics = PipelineMetrics( + time_window=TimeWindow(start=datetime.now(UTC), end=datetime.now(UTC), time_window=60), + inference=InferenceMetrics( + latency=LatencyMetrics(avg_ms=100.5, min_ms=50.0, max_ms=200.0, p95_ms=180.0, latest_ms=120.0), + throughput=ThroughputMetrics( + avg_requests_per_second=30.0, total_requests=1800, max_requests_per_second=45.0 + ), + ), + ) + fxt_pipeline_metrics_service.get_pipeline_metrics.return_value = mock_metrics + + response = fxt_client.get(f"/api/projects/{str(fxt_pipeline.project_id)}/pipeline/metrics") + + assert response.status_code == status.HTTP_200_OK + response_data = response.json() + + assert response_data["inference"]["latency"]["avg_ms"] == 100.5 + assert response_data["inference"]["latency"]["min_ms"] == 50.0 + assert response_data["inference"]["latency"]["max_ms"] == 200.0 + assert response_data["inference"]["latency"]["p95_ms"] == 180.0 + assert response_data["inference"]["latency"]["latest_ms"] == 120.0 + + fxt_pipeline_metrics_service.get_pipeline_metrics.assert_called_once_with(fxt_pipeline.project_id, 60) diff --git a/application/backend/tests/unit/endpoints/test_projects.py b/application/backend/tests/unit/endpoints/test_projects.py new file mode 100644 index 0000000000..5c44129b73 --- /dev/null +++ b/application/backend/tests/unit/endpoints/test_projects.py @@ -0,0 +1,38 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from unittest.mock import MagicMock +from uuid import uuid4 + +import pytest +from fastapi import status + +from api.dependencies import get_project_service +from main import app +from pydantic_models import ProjectList +from services import ProjectService + + +@pytest.fixture +def fxt_project_service() -> MagicMock: + project_service = MagicMock(spec=ProjectService) + app.dependency_overrides[get_project_service] = lambda: project_service + return project_service + + +def test_get_projects(fxt_client, fxt_project_service, fxt_project): + fxt_project_service.get_project_list.return_value = ProjectList(projects=[fxt_project]) + + response = fxt_client.get("/api/projects") + assert response.status_code == status.HTTP_200_OK + assert len(response.json()["projects"]) == 1 + fxt_project_service.get_project_list.assert_called_once() + + +def test_get_project_not_found(fxt_client, fxt_project_service): + project_id = uuid4() + fxt_project_service.get_project_by_id.return_value = None + + response = fxt_client.get(f"/api/projects/{project_id}") + assert response.status_code == status.HTTP_404_NOT_FOUND + fxt_project_service.get_project_by_id.assert_called_once_with(project_id) diff --git a/application/backend/tests/unit/endpoints/test_sources_sinks.py b/application/backend/tests/unit/endpoints/test_sources_sinks.py new file mode 100644 index 0000000000..2e0a317aba --- /dev/null +++ b/application/backend/tests/unit/endpoints/test_sources_sinks.py @@ -0,0 +1,413 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import io +from enum import StrEnum +from unittest.mock import MagicMock +from uuid import uuid4 + +import pytest +import yaml +from fastapi import status + +from api.dependencies import get_configuration_service +from main import app +from pydantic_models.sink import FolderSinkConfig, MqttSinkConfig, OutputFormat, SinkType +from pydantic_models.source import SourceType, VideoFileSourceConfig, WebcamSourceConfig +from services import ConfigurationService, ResourceAlreadyExistsError, ResourceInUseError, ResourceNotFoundError +from services.exceptions import ResourceType + + +class ConfigApiPath(StrEnum): + SINKS = "sinks" + SOURCES = "sources" + + +@pytest.fixture +def fxt_folder_sink(fxt_project) -> FolderSinkConfig: + return FolderSinkConfig( + id=uuid4(), + project_id=fxt_project.id, + sink_type=SinkType.FOLDER, + name="Test Folder Sink", + rate_limit=0.1, + output_formats=[OutputFormat.PREDICTIONS], + folder_path="/test/path", + ) + + +@pytest.fixture +def fxt_webcam_source(fxt_project) -> WebcamSourceConfig: + return WebcamSourceConfig( + id=uuid4(), + project_id=fxt_project.id, + source_type=SourceType.WEBCAM, + name="Test Webcam Source", + device_id=1, + ) + + +@pytest.fixture +def fxt_video_source(fxt_project) -> VideoFileSourceConfig: + return VideoFileSourceConfig( + id=uuid4(), + project_id=fxt_project.id, + source_type=SourceType.VIDEO_FILE, + name="Test Folder Source", + video_path="/test/video/path.mp4", + ) + + +@pytest.fixture +def fxt_mqtt_sink(fxt_project) -> MqttSinkConfig: + return MqttSinkConfig( + id=uuid4(), + project_id=fxt_project.id, + sink_type=SinkType.MQTT, + name="Test MQTT Sink", + rate_limit=0.2, + output_formats=[OutputFormat.IMAGE_WITH_PREDICTIONS], + broker_host="localhost", + broker_port=1883, + topic="test_topic", + ) + + +@pytest.fixture +def fxt_config_service() -> MagicMock: + config_service = MagicMock(spec=ConfigurationService) + app.dependency_overrides[get_configuration_service] = lambda: config_service + return config_service + + +class TestSourceAndSinkEndpoints: + @pytest.mark.parametrize( + "fixture_name, api_path, create_method", + [ + ("fxt_webcam_source", ConfigApiPath.SOURCES, "create_source"), + ("fxt_folder_sink", ConfigApiPath.SINKS, "create_sink"), + ], + ) + def test_create_config_success( + self, fixture_name, api_path, create_method, fxt_config_service, fxt_client, fxt_project, request + ): + fxt_config = request.getfixturevalue(fixture_name) + config_id = str(fxt_config.id) + getattr(fxt_config_service, create_method).return_value = fxt_config + + response = fxt_client.post( + f"/api/projects/{fxt_project.id}/{api_path}", json=fxt_config.model_dump(exclude={"id", "project_id"}) + ) + + assert response.status_code == status.HTTP_201_CREATED + assert response.json()["id"] == config_id + assert response.json()["name"] == fxt_config.name + getattr(fxt_config_service, create_method).assert_called_once() + + @pytest.mark.parametrize( + "api_path, create_method", + [ + (ConfigApiPath.SOURCES, "create_source"), + (ConfigApiPath.SINKS, "create_sink"), + ], + ) + def test_create_sink_validation_error(self, api_path, create_method, fxt_config_service, fxt_client, fxt_project): + response = fxt_client.post(f"/api/projects/{fxt_project.id}/{api_path}", json={"name": ""}) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + getattr(fxt_config_service, create_method).assert_not_called() + + @pytest.mark.parametrize( + "resource_type, api_path, fixture_name, create_method", + [ + (ResourceType.SOURCE, ConfigApiPath.SOURCES, "fxt_webcam_source", "create_source"), + (ResourceType.SINK, ConfigApiPath.SINKS, "fxt_folder_sink", "create_sink"), + ], + ) + def test_create_config_exists( + self, resource_type, api_path, fixture_name, create_method, fxt_config_service, fxt_client, fxt_project, request + ): + fxt_config = request.getfixturevalue(fixture_name) + getattr(fxt_config_service, create_method).side_effect = ResourceAlreadyExistsError( + resource_type=resource_type, resource_name="New Config" + ) + response = fxt_client.post( + f"/api/projects/{fxt_project.id}/{api_path}", json=fxt_config.model_dump(exclude={"id", "project_id"}) + ) + + assert response.status_code == status.HTTP_409_CONFLICT + getattr(fxt_config_service, create_method).assert_called_once() + + @pytest.mark.parametrize( + "fixtures, api_path, list_method", + [ + (["fxt_webcam_source", "fxt_video_source"], ConfigApiPath.SOURCES, "list_sources"), + (["fxt_folder_sink", "fxt_mqtt_sink"], ConfigApiPath.SINKS, "list_sinks"), + ], + ) + def test_list_configs(self, fixtures, api_path, list_method, fxt_config_service, fxt_client, fxt_project, request): + fxt_configs = [request.getfixturevalue(fixture) for fixture in fixtures] + getattr(fxt_config_service, list_method).return_value = fxt_configs + + response = fxt_client.get(f"/api/projects/{fxt_project.id}/{api_path}") + + assert response.status_code == status.HTTP_200_OK + assert len(response.json()) == 2 + getattr(fxt_config_service, list_method).assert_called_once() + + @pytest.mark.parametrize( + "fixture_name, api_path, get_method", + [ + ("fxt_webcam_source", ConfigApiPath.SOURCES, "get_source_by_id"), + ("fxt_folder_sink", ConfigApiPath.SINKS, "get_sink_by_id"), + ], + ) + def test_get_config_success( + self, fixture_name, api_path, get_method, fxt_config_service, fxt_client, fxt_project, request + ): + fxt_config = request.getfixturevalue(fixture_name) + config_id = str(fxt_config.id) + getattr(fxt_config_service, get_method).return_value = fxt_config + + response = fxt_client.get(f"/api/projects/{fxt_project.id}/{api_path}/{config_id}") + + assert response.status_code == status.HTTP_200_OK + assert response.json()["id"] == config_id + getattr(fxt_config_service, get_method).assert_called_once_with(fxt_config.id, fxt_project.id) + + @pytest.mark.parametrize( + "resource_type, api_path, get_method", + [ + (ResourceType.SOURCE, ConfigApiPath.SOURCES, "get_source_by_id"), + (ResourceType.SINK, ConfigApiPath.SINKS, "get_sink_by_id"), + ], + ) + def test_get_config_not_found( + self, resource_type, api_path, get_method, fxt_config_service, fxt_client, fxt_project + ): + config_id = uuid4() + getattr(fxt_config_service, get_method).side_effect = ResourceNotFoundError(resource_type, str(config_id)) + + response = fxt_client.get(f"/api/projects/{fxt_project.id}/{api_path}/{str(config_id)}") + + assert response.status_code == status.HTTP_404_NOT_FOUND + getattr(fxt_config_service, get_method).assert_called_once_with(config_id, fxt_project.id) + + @pytest.mark.parametrize("api_path", [ConfigApiPath.SOURCES, ConfigApiPath.SINKS]) + def test_get_config_invalid_uuid(self, api_path, fxt_client, fxt_project): + response = fxt_client.get(f"/api/projects/{fxt_project.id}/{api_path}/invalid-uuid") + assert response.status_code == status.HTTP_400_BAD_REQUEST + + @pytest.mark.parametrize( + "fixture_name, api_path, update_method, update_data", + [ + ("fxt_webcam_source", ConfigApiPath.SOURCES, "update_source", {"device_id": 5}), + ("fxt_folder_sink", ConfigApiPath.SINKS, "update_sink", {"folder_path": "/new/path"}), + ], + ) + def test_update_sink_success( + self, fixture_name, api_path, update_method, update_data, fxt_config_service, fxt_client, fxt_project, request + ): + fxt_config = request.getfixturevalue(fixture_name) + config_id = str(fxt_config.id) + updated_config = fxt_config.model_copy(update=update_data) + getattr(fxt_config_service, update_method).return_value = updated_config + + response = fxt_client.patch(f"/api/projects/{fxt_project.id}/{api_path}/{config_id}", json=update_data) + + assert response.status_code == status.HTTP_200_OK + key, value = next(iter(update_data.items())) + assert response.json()[key] == value + getattr(fxt_config_service, update_method).assert_called_once_with(fxt_config.id, fxt_project.id, update_data) + + @pytest.mark.parametrize( + "api_path, update_method, resource_type", + [ + (ConfigApiPath.SOURCES, "update_source", ResourceType.SOURCE), + (ConfigApiPath.SINKS, "update_sink", ResourceType.SINK), + ], + ) + def test_update_config_not_found( + self, api_path, update_method, resource_type, fxt_config_service, fxt_client, fxt_project + ): + config_id = str(uuid4()) + getattr(fxt_config_service, update_method).side_effect = ResourceNotFoundError(resource_type, config_id) + + response = fxt_client.patch(f"/api/projects/{fxt_project.id}/{api_path}/{config_id}", json={"name": "Updated"}) + + assert response.status_code == status.HTTP_404_NOT_FOUND + + @pytest.mark.parametrize( + "fixture_name, api_path, update_method, update_data", + [ + ("fxt_webcam_source", ConfigApiPath.SOURCES, "update_source", {"source_type": "folder"}), + ("fxt_folder_sink", ConfigApiPath.SINKS, "update_sink", {"sink_type": "mqtt"}), + ], + ) + def test_update_config_type_forbidden( + self, fixture_name, api_path, update_method, update_data, fxt_config_service, fxt_client, fxt_project, request + ): + fxt_config = request.getfixturevalue(fixture_name) + config_id = str(fxt_config.id) + + response = fxt_client.patch(f"/api/projects/{fxt_project.id}/{api_path}/{config_id}", json=update_data) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "_type" in response.json()["detail"] + getattr(fxt_config_service, update_method).assert_not_called() + + @pytest.mark.parametrize( + "fixture_name, api_path, delete_method", + [ + ("fxt_webcam_source", ConfigApiPath.SOURCES, "delete_source_by_id"), + ("fxt_folder_sink", ConfigApiPath.SINKS, "delete_sink_by_id"), + ], + ) + def test_delete_config_success( + self, fixture_name, api_path, delete_method, fxt_config_service, fxt_client, fxt_project, request + ): + fxt_config = request.getfixturevalue(fixture_name) + config_id = str(fxt_config.id) + getattr(fxt_config_service, delete_method).side_effect = None + + response = fxt_client.delete(f"/api/projects/{fxt_project.id}/{api_path}/{config_id}") + + assert response.status_code == status.HTTP_204_NO_CONTENT + getattr(fxt_config_service, delete_method).assert_called_once_with(fxt_config.id, fxt_project.id) + + @pytest.mark.parametrize( + "api_path, delete_method", + [ + (ConfigApiPath.SOURCES, "delete_source_by_id"), + (ConfigApiPath.SINKS, "delete_sink_by_id"), + ], + ) + def test_delete_config_invalid_id(self, api_path, delete_method, fxt_config_service, fxt_client, fxt_project): + getattr(fxt_config_service, delete_method).side_effect = None + + response = fxt_client.delete(f"/api/projects/{fxt_project.id}/{api_path}/invalid-id") + + assert response.status_code == status.HTTP_400_BAD_REQUEST + getattr(fxt_config_service, delete_method).assert_not_called() + + @pytest.mark.parametrize( + "api_path, delete_method, resource_type", + [ + (ConfigApiPath.SOURCES, "delete_source_by_id", ResourceType.SOURCE), + (ConfigApiPath.SINKS, "delete_sink_by_id", ResourceType.SINK), + ], + ) + def test_delete_config_not_found( + self, api_path, delete_method, resource_type, fxt_config_service, fxt_client, fxt_project + ): + config_id = str(uuid4()) + getattr(fxt_config_service, delete_method).side_effect = ResourceNotFoundError(resource_type, config_id) + + response = fxt_client.delete(f"/api/projects/{fxt_project.id}/{api_path}/{config_id}") + + assert response.status_code == status.HTTP_404_NOT_FOUND + + @pytest.mark.parametrize( + "fixture_name, api_path, delete_method, resource_type", + [ + ("fxt_webcam_source", ConfigApiPath.SOURCES, "delete_source_by_id", ResourceType.SOURCE), + ("fxt_folder_sink", ConfigApiPath.SINKS, "delete_sink_by_id", ResourceType.SINK), + ], + ) + def test_delete_config_in_use( + self, fixture_name, api_path, delete_method, resource_type, fxt_config_service, fxt_client, fxt_project, request + ): + fxt_config = request.getfixturevalue(fixture_name) + config_id = str(fxt_config.id) + err = ResourceInUseError(resource_type, config_id) + getattr(fxt_config_service, delete_method).side_effect = err + + response = fxt_client.delete(f"/api/projects/{fxt_project.id}/{api_path}/{config_id}") + + assert response.status_code == status.HTTP_409_CONFLICT + assert str(err) == response.json()["detail"] + + @pytest.mark.parametrize( + "fixture_name,api_path,get_method, expected_yaml", + [ + ( + "fxt_webcam_source", + ConfigApiPath.SOURCES, + "get_source_by_id", + "device_id: 1\nname: Test Webcam Source\nsource_type: webcam\n", + ), + ( + "fxt_folder_sink", + ConfigApiPath.SINKS, + "get_sink_by_id", + "folder_path: /test/path\nname: Test Folder Sink\noutput_formats:\n- predictions" + "\nrate_limit: 0.1\nsink_type: folder\n", + ), + ], + ) + def test_export_config_success( + self, fixture_name, api_path, get_method, expected_yaml, fxt_config_service, fxt_client, fxt_project, request + ): + fxt_config = request.getfixturevalue(fixture_name) + config_id = str(fxt_config.id) + getattr(fxt_config_service, get_method).return_value = fxt_config + + response = fxt_client.post(f"/api/projects/{fxt_project.id}/{api_path}/{config_id}:export") + + assert response.status_code == status.HTTP_200_OK + assert response.headers["content-type"] == "application/x-yaml" + assert f"{api_path[:-1]}_{config_id}.yaml" in response.headers["content-disposition"] + assert response.text == expected_yaml + getattr(fxt_config_service, get_method).assert_called_once_with(fxt_config.id, fxt_project.id) + + @pytest.mark.parametrize( + "fixture_name,api_path, create_method", + [ + ("fxt_webcam_source", ConfigApiPath.SOURCES, "create_source"), + ("fxt_folder_sink", ConfigApiPath.SINKS, "create_sink"), + ], + ) + def test_import_config_success( + self, fixture_name, api_path, create_method, fxt_config_service, fxt_client, fxt_project, request + ): + fxt_config = request.getfixturevalue(fixture_name) + sink_data = fxt_config.model_dump(exclude={"id", "project_id"}, mode="json") + yaml_content = yaml.safe_dump(sink_data) + getattr(fxt_config_service, create_method).return_value = fxt_config + + files = {"yaml_file": ("test.yaml", io.BytesIO(yaml_content.encode()), "application/x-yaml")} + response = fxt_client.post(f"/api/projects/{fxt_project.id}/{api_path}:import", files=files) + + assert response.status_code == status.HTTP_201_CREATED + assert response.json()["id"] == str(fxt_config.id) + getattr(fxt_config_service, create_method).assert_called_once() + + @pytest.mark.parametrize( + "api_path", + [ConfigApiPath.SOURCES, ConfigApiPath.SINKS], + ) + def test_import_config_invalid_yaml(self, api_path, fxt_client, fxt_project): + invalid_yaml = "invalid: yaml: content: [" + files = {"yaml_file": ("test.yaml", io.BytesIO(invalid_yaml.encode()), "application/x-yaml")} + + response = fxt_client.post(f"/api/projects/{fxt_project.id}/{api_path}:import", files=files) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "Invalid YAML format" in response.json()["detail"] + + @pytest.mark.parametrize( + "api_path, config_type", + [ + (ConfigApiPath.SOURCES, "source_type"), + (ConfigApiPath.SINKS, "sink_type"), + ], + ) + def test_import_disconnected_config_fails(self, api_path, config_type, fxt_client, fxt_project): + config_data = {config_type: "disconnected", "name": "Test"} + yaml_content = yaml.safe_dump(config_data) + files = {"yaml_file": ("test.yaml", io.BytesIO(yaml_content.encode()), "application/x-yaml")} + + response = fxt_client.post(f"/api/projects/{fxt_project.id}/{api_path}:import", files=files) + + assert response.status_code == status.HTTP_400_BAD_REQUEST + assert "DISCONNECTED cannot be imported" in response.json()["detail"] diff --git a/application/backend/tests/unit/endpoints/test_trainable_models.py b/application/backend/tests/unit/endpoints/test_trainable_models.py new file mode 100644 index 0000000000..113ec31dd4 --- /dev/null +++ b/application/backend/tests/unit/endpoints/test_trainable_models.py @@ -0,0 +1,19 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from unittest.mock import patch + +from fastapi import status + +from api.endpoints.trainable_models_endpoints import _get_trainable_models # noqa: PLC2701 + + +def test_list_trainable_models(fxt_client): + _get_trainable_models.cache_clear() + # Mock anomalib.models.list_models to return a predictable set + with patch("api.endpoints.trainable_models_endpoints.list_models", return_value={"padim", "patchcore"}): + response = fxt_client.get("/api/trainable-models") + + assert response.status_code == status.HTTP_200_OK + body = response.json() + assert body == {"trainable_models": ["padim", "patchcore"]} or body == {"trainable_models": ["patchcore", "padim"]} diff --git a/application/backend/tests/unit/endpoints/test_webrtc.py b/application/backend/tests/unit/endpoints/test_webrtc.py new file mode 100644 index 0000000000..943bb28d11 --- /dev/null +++ b/application/backend/tests/unit/endpoints/test_webrtc.py @@ -0,0 +1,63 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +from unittest.mock import MagicMock + +import pytest +from fastapi import status + +from api.dependencies import get_webrtc_manager +from main import app +from pydantic_models.webrtc import Answer, InputData, Offer +from webrtc.manager import WebRTCManager + + +@pytest.fixture +def fxt_webrtc_manager(): + webrtc_manager = MagicMock(spec=WebRTCManager) + app.dependency_overrides[get_webrtc_manager] = lambda: webrtc_manager + return webrtc_manager + + +@pytest.fixture +def fxt_offer() -> Offer: + return Offer(sdp="test_sdp", type="offer", webrtc_id="test_id") + + +@pytest.fixture +def fxt_answer() -> Answer: + return Answer(sdp="test_sdp", type="answer") + + +@pytest.fixture +def fxt_input_data() -> InputData: + return InputData(webrtc_id="test_id", conf_threshold=0.5) + + +class TestWebRTCEndpoints: + def test_create_webrtc_offer_success(self, fxt_client, fxt_webrtc_manager, fxt_offer, fxt_answer): + fxt_webrtc_manager.handle_offer.return_value = fxt_answer + resp = fxt_client.post("/api/webrtc/offer", json=fxt_offer.model_dump(mode="json")) + assert resp.status_code == status.HTTP_200_OK + assert resp.json() == fxt_answer.model_dump() + fxt_webrtc_manager.handle_offer.assert_called_once() + + def test_create_webrtc_offer_failure(self, fxt_client, fxt_webrtc_manager, fxt_offer): + fxt_webrtc_manager.handle_offer.side_effect = Exception("fail") + resp = fxt_client.post("/api/webrtc/offer", json=fxt_offer.model_dump(mode="json")) + assert resp.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR + assert "fail" in resp.json()["detail"] + fxt_webrtc_manager.handle_offer.assert_called_once() + + def test_create_webrtc_offer_invalid_payload(self, fxt_client): + resp = fxt_client.post("/api/webrtc/offer", json={"sdp": 123}) + assert resp.status_code == status.HTTP_400_BAD_REQUEST + + def test_webrtc_input_hook_success(self, fxt_client, fxt_webrtc_manager, fxt_input_data): + resp = fxt_client.post("/api/webrtc/input_hook", json=fxt_input_data.model_dump(mode="json")) + assert resp.status_code == status.HTTP_200_OK + fxt_webrtc_manager.set_input.assert_called_once() + + def test_webrtc_input_hook_invalid_payload(self, fxt_client, fxt_webrtc_manager): + resp = fxt_client.post("/api/webrtc/input_hook", json={"wrong": "field"}) + assert resp.status_code == status.HTTP_400_BAD_REQUEST diff --git a/application/backend/tests/unit/services/test_configuration_service.py b/application/backend/tests/unit/services/test_configuration_service.py new file mode 100644 index 0000000000..52b9b7110f --- /dev/null +++ b/application/backend/tests/unit/services/test_configuration_service.py @@ -0,0 +1,531 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +import uuid +from multiprocessing.synchronize import Condition +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from pydantic_models import Pipeline, PipelineStatus +from pydantic_models.sink import FolderSinkConfig +from pydantic_models.source import VideoFileSourceConfig +from repositories import PipelineRepository, SinkRepository, SourceRepository +from services import ActivePipelineService +from services.configuration_service import ConfigurationService, PipelineField +from services.exceptions import ResourceNotFoundError, ResourceType + + +@pytest.fixture +def fxt_source(fxt_project): + """Fixture for a test source.""" + return VideoFileSourceConfig( + id=uuid.uuid4(), + project_id=fxt_project.id, + source_type="video_file", + name="Test Source", + video_path="/path/to/video.mp4", + ) + + +@pytest.fixture +def fxt_sink(fxt_project): + """Fixture for a test sink.""" + return FolderSinkConfig( + id=uuid.uuid4(), + project_id=fxt_project.id, + sink_type="folder", + name="Test Sink", + folder_path="/path/to/output", + output_formats=["image_original"], + rate_limit=0.2, + ) + + +@pytest.fixture +def fxt_pipeline(fxt_project, fxt_source, fxt_sink, fxt_model): + """Fixture for a test pipeline with source and sink.""" + return Pipeline( + project_id=fxt_project.id, + source=fxt_source, + sink=fxt_sink, + model=fxt_model, + source_id=fxt_source.id, + sink_id=fxt_sink.id, + model_id=fxt_model.id, + status=PipelineStatus.RUNNING, + ) + + +@pytest.fixture +def fxt_condition(): + """Fixture for a mock condition.""" + return MagicMock(spec=Condition) + + +@pytest.fixture +def fxt_active_pipeline_service(): + """Fixture for a mock active pipeline service.""" + service = MagicMock(spec=ActivePipelineService) + # Use AsyncMock for async method + service.reload = AsyncMock(return_value=None) + return service + + +@pytest.fixture +def fxt_configuration_service(fxt_active_pipeline_service, fxt_condition): + """Fixture for ConfigurationService with mocked dependencies.""" + return ConfigurationService( + active_pipeline_service=fxt_active_pipeline_service, config_changed_condition=fxt_condition + ) + + +@pytest.fixture +def fxt_source_repository(): + """Fixture for a mock source repository.""" + mock_repo = MagicMock(spec=SourceRepository) + # Set up async methods to return coroutines + mock_repo.get_by_id = AsyncMock() + mock_repo.get_one = AsyncMock() + mock_repo.get_all = AsyncMock() + mock_repo.save = AsyncMock() + mock_repo.update = AsyncMock() + mock_repo.delete_by_id = AsyncMock() + return mock_repo + + +@pytest.fixture +def fxt_sink_repository(): + """Fixture for a mock sink repository.""" + mock_repo = MagicMock(spec=SinkRepository) + # Set up async methods to return coroutines + mock_repo.get_by_id = AsyncMock() + mock_repo.get_one = AsyncMock() + mock_repo.get_all = AsyncMock() + mock_repo.save = AsyncMock() + mock_repo.update = AsyncMock() + mock_repo.delete_by_id = AsyncMock() + return mock_repo + + +@pytest.fixture +def fxt_pipeline_repository(): + """Fixture for a mock pipeline repository.""" + mock_repo = MagicMock(spec=PipelineRepository) + # Set up async methods to return coroutines + mock_repo.get_by_id = AsyncMock() + mock_repo.get_active_pipeline = MagicMock() # This is mocked differently in tests due to implementation bug + return mock_repo + + +@pytest.fixture(autouse=True) +def mock_db_context(): + """Mock the database context for all tests.""" + with patch("services.configuration_service.get_async_db_session_ctx") as mock_db_ctx: + mock_session = AsyncMock() + mock_db_ctx.return_value.__aenter__.return_value = mock_session + mock_db_ctx.return_value.__aexit__.return_value = None + yield mock_db_ctx + + +class TestConfigurationService: + def test_init(self, fxt_active_pipeline_service, fxt_condition): + """Test ConfigurationService initialization.""" + service = ConfigurationService( + active_pipeline_service=fxt_active_pipeline_service, config_changed_condition=fxt_condition + ) + + assert service._active_pipeline_service == fxt_active_pipeline_service + assert service._config_changed_condition == fxt_condition + + def test_notify_source_changed(self, fxt_configuration_service, fxt_condition): + """Test source change notification.""" + fxt_configuration_service._notify_source_changed() + + fxt_condition.__enter__.assert_called_once() + fxt_condition.__exit__.assert_called_once() + fxt_condition.notify_all.assert_called_once() + + def test_notify_sink_changed_with_running_loop(self, fxt_configuration_service, fxt_active_pipeline_service): + """Test sink change notification when event loop is running.""" + mock_loop = MagicMock() + mock_task = MagicMock() + mock_loop.create_task.return_value = mock_task + + with patch("asyncio.get_running_loop", return_value=mock_loop): + fxt_configuration_service._notify_sink_changed() + + # Check that create_task was called with a coroutine (fire and forget) + mock_loop.create_task.assert_called_once() + + def test_notify_sink_changed_without_running_loop(self, fxt_configuration_service, fxt_active_pipeline_service): + """Test sink change notification when no event loop is running.""" + with ( + patch("asyncio.get_running_loop", side_effect=RuntimeError("No event loop")), + patch("asyncio.run") as mock_asyncio_run, + ): + # Mock asyncio.run to just call the coroutine directly + def mock_run(coro): + # Create a new event loop and run the coroutine + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + return loop.run_until_complete(coro) + finally: + loop.close() + + mock_asyncio_run.side_effect = mock_run + + fxt_configuration_service._notify_sink_changed() + + # Check that asyncio.run was called with a coroutine + mock_asyncio_run.assert_called_once() + + def test_on_config_changed_with_active_pipeline_matching(self, fxt_pipeline, fxt_pipeline_repository): + """Test config change notification when active pipeline matches.""" + # Mock the repository to return the pipeline via AsyncMock + fxt_pipeline_repository.get_active_pipeline = AsyncMock(return_value=fxt_pipeline) + notify_fn = MagicMock() + config_id = fxt_pipeline.source_id + + async def run_test(): + with patch("services.configuration_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + await ConfigurationService._on_config_changed( + config_id, PipelineField.SOURCE_ID, MagicMock(), notify_fn + ) + + fxt_pipeline_repository.get_active_pipeline.assert_called_once() + # The notification should be called because the pipeline's source_id matches the config_id + assert str(fxt_pipeline.source_id) == str(config_id) + notify_fn.assert_called_once() + + asyncio.run(run_test()) + + def test_on_config_changed_with_active_pipeline_not_matching(self, fxt_pipeline, fxt_pipeline_repository): + """Test config change notification when active pipeline doesn't match.""" + # Mock the async method to return the pipeline via AsyncMock + fxt_pipeline_repository.get_active_pipeline = AsyncMock(return_value=fxt_pipeline) + notify_fn = MagicMock() + different_config_id = uuid.uuid4() + + async def run_test(): + with patch("services.configuration_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + await ConfigurationService._on_config_changed( + different_config_id, PipelineField.SOURCE_ID, MagicMock(), notify_fn + ) + + fxt_pipeline_repository.get_active_pipeline.assert_called_once() + notify_fn.assert_not_called() + + asyncio.run(run_test()) + + def test_on_config_changed_without_active_pipeline(self, fxt_pipeline_repository): + """Test config change notification when no active pipeline.""" + fxt_pipeline_repository.get_active_pipeline = AsyncMock(return_value=None) + notify_fn = MagicMock() + config_id = uuid.uuid4() + + async def run_test(): + with patch("services.configuration_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + await ConfigurationService._on_config_changed( + config_id, PipelineField.SOURCE_ID, MagicMock(), notify_fn + ) + + fxt_pipeline_repository.get_active_pipeline.assert_called_once() + notify_fn.assert_not_called() + + asyncio.run(run_test()) + + def test_list_sources(self, fxt_configuration_service, fxt_source_repository, fxt_source, fxt_project): + """Test listing sources.""" + fxt_source_repository.get_all.return_value = [fxt_source] + + with patch("services.configuration_service.SourceRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_source_repository + + result = asyncio.run(fxt_configuration_service.list_sources(fxt_project.id)) + + assert result == [fxt_source] + fxt_source_repository.get_all.assert_called_once() + + def test_list_sinks(self, fxt_configuration_service, fxt_sink_repository, fxt_sink, fxt_project): + """Test listing sinks.""" + fxt_sink_repository.get_all.return_value = [fxt_sink] + + with patch("services.configuration_service.SinkRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_sink_repository + + result = asyncio.run(fxt_configuration_service.list_sinks(fxt_project.id)) + + assert result == [fxt_sink] + fxt_sink_repository.get_all.assert_called_once() + + def test_get_source_by_id_success(self, fxt_configuration_service, fxt_source_repository, fxt_source, fxt_project): + """Test getting source by ID successfully.""" + fxt_source_repository.get_by_id.return_value = fxt_source + + with patch("services.configuration_service.SourceRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_source_repository + + result = asyncio.run(fxt_configuration_service.get_source_by_id(fxt_source.id, fxt_project.id)) + + assert result == fxt_source + fxt_source_repository.get_by_id.assert_called_once_with(fxt_source.id) + + def test_get_source_by_id_with_session( + self, fxt_configuration_service, fxt_source_repository, fxt_source, fxt_db_session, fxt_project + ): + """Test getting source by ID with provided session.""" + fxt_source_repository.get_by_id.return_value = fxt_source + + with patch("services.configuration_service.SourceRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_source_repository + + result = asyncio.run( + fxt_configuration_service.get_source_by_id(fxt_source.id, fxt_project.id, fxt_db_session) + ) + + assert result == fxt_source + fxt_source_repository.get_by_id.assert_called_once_with(fxt_source.id) + + def test_get_source_by_id_not_found(self, fxt_configuration_service, fxt_source_repository, fxt_project): + """Test getting source by ID when not found.""" + fxt_source_repository.get_by_id.return_value = None + source_id = uuid.uuid4() + + with patch("services.configuration_service.SourceRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_source_repository + + with pytest.raises(ResourceNotFoundError) as exc_info: + asyncio.run(fxt_configuration_service.get_source_by_id(source_id, fxt_project.id)) + + assert exc_info.value.resource_type == ResourceType.SOURCE + assert str(source_id) in str(exc_info.value) + + def test_get_sink_by_id_success(self, fxt_configuration_service, fxt_sink_repository, fxt_sink, fxt_project): + """Test getting sink by ID successfully.""" + fxt_sink_repository.get_by_id.return_value = fxt_sink + + with patch("services.configuration_service.SinkRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_sink_repository + + result = asyncio.run(fxt_configuration_service.get_sink_by_id(fxt_sink.id, fxt_project.id)) + + assert result == fxt_sink + fxt_sink_repository.get_by_id.assert_called_once_with(fxt_sink.id) + + def test_get_sink_by_id_with_session( + self, fxt_configuration_service, fxt_sink_repository, fxt_sink, fxt_db_session, fxt_project + ): + """Test getting sink by ID with provided session.""" + fxt_sink_repository.get_by_id.return_value = fxt_sink + + with patch("services.configuration_service.SinkRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_sink_repository + + result = asyncio.run(fxt_configuration_service.get_sink_by_id(fxt_sink.id, fxt_project.id, fxt_db_session)) + + assert result == fxt_sink + fxt_sink_repository.get_by_id.assert_called_once_with(fxt_sink.id) + + def test_get_sink_by_id_not_found(self, fxt_configuration_service, fxt_sink_repository, fxt_project): + """Test getting sink by ID when not found.""" + fxt_sink_repository.get_by_id.return_value = None + sink_id = uuid.uuid4() + + with patch("services.configuration_service.SinkRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_sink_repository + + with pytest.raises(ResourceNotFoundError) as exc_info: + asyncio.run(fxt_configuration_service.get_sink_by_id(sink_id, fxt_project.id)) + + assert exc_info.value.resource_type == ResourceType.SINK + assert str(sink_id) in str(exc_info.value) + + def test_create_source(self, fxt_configuration_service, fxt_source_repository, fxt_source): + """Test creating a source.""" + fxt_source_repository.save.return_value = fxt_source + + with patch("services.configuration_service.SourceRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_source_repository + + result = asyncio.run(fxt_configuration_service.create_source(fxt_source)) + + assert result == fxt_source + fxt_source_repository.save.assert_called_once_with(fxt_source) + + def test_create_sink(self, fxt_configuration_service, fxt_sink_repository, fxt_sink): + """Test creating a sink.""" + fxt_sink_repository.save.return_value = fxt_sink + + with patch("services.configuration_service.SinkRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_sink_repository + + result = asyncio.run(fxt_configuration_service.create_sink(fxt_sink)) + + assert result == fxt_sink + fxt_sink_repository.save.assert_called_once_with(fxt_sink) + + def test_update_source_success( + self, fxt_configuration_service, fxt_source_repository, fxt_pipeline_repository, fxt_source, fxt_condition + ): + """Test updating a source successfully.""" + updated_source = fxt_source.model_copy(update={"name": "Updated Source"}) + fxt_source_repository.get_by_id.return_value = fxt_source + fxt_source_repository.update.return_value = updated_source + fxt_pipeline_repository.get_active_pipeline = AsyncMock(return_value=None) + + with ( + patch("services.configuration_service.SourceRepository") as mock_source_repo_class, + patch("services.configuration_service.PipelineRepository") as mock_pipeline_repo_class, + ): + mock_source_repo_class.return_value = fxt_source_repository + mock_pipeline_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run( + fxt_configuration_service.update_source( + fxt_source.id, fxt_source.project_id, {"name": "Updated Source"} + ) + ) + + assert result == updated_source + fxt_source_repository.get_by_id.assert_called_once_with(fxt_source.id) + fxt_source_repository.update.assert_called_once_with(fxt_source, {"name": "Updated Source"}) + + def test_update_sink_success( + self, + fxt_configuration_service, + fxt_sink_repository, + fxt_pipeline_repository, + fxt_sink, + fxt_active_pipeline_service, + ): + """Test updating a sink successfully.""" + updated_sink = fxt_sink.model_copy(update={"name": "Updated Sink"}) + fxt_sink_repository.get_by_id.return_value = fxt_sink + fxt_sink_repository.update.return_value = updated_sink + fxt_pipeline_repository.get_active_pipeline = AsyncMock(return_value=None) + + with ( + patch("services.configuration_service.SinkRepository") as mock_sink_repo_class, + patch("services.configuration_service.PipelineRepository") as mock_pipeline_repo_class, + ): + mock_sink_repo_class.return_value = fxt_sink_repository + mock_pipeline_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run( + fxt_configuration_service.update_sink(fxt_sink.id, fxt_sink.project_id, {"name": "Updated Sink"}) + ) + + assert result == updated_sink + fxt_sink_repository.get_by_id.assert_called_once_with(fxt_sink.id) + fxt_sink_repository.update.assert_called_once_with(fxt_sink, {"name": "Updated Sink"}) + + def test_delete_source_by_id(self, fxt_configuration_service, fxt_source_repository, fxt_source): + """Test deleting a source by ID.""" + fxt_source_repository.get_by_id.return_value = fxt_source + fxt_source_repository.delete_by_id.return_value = None + + with patch("services.configuration_service.SourceRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_source_repository + + asyncio.run(fxt_configuration_service.delete_source_by_id(fxt_source.id, fxt_source.project_id)) + + fxt_source_repository.get_by_id.assert_called_once_with(fxt_source.id) + fxt_source_repository.delete_by_id.assert_called_once_with(fxt_source.id) + + def test_delete_sink_by_id(self, fxt_configuration_service, fxt_sink_repository, fxt_sink): + """Test deleting a sink by ID.""" + fxt_sink_repository.get_by_id.return_value = fxt_sink + fxt_sink_repository.delete_by_id.return_value = None + + with patch("services.configuration_service.SinkRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_sink_repository + + asyncio.run(fxt_configuration_service.delete_sink_by_id(fxt_sink.id, fxt_sink.project_id)) + + fxt_sink_repository.get_by_id.assert_called_once_with(fxt_sink.id) + fxt_sink_repository.delete_by_id.assert_called_once_with(fxt_sink.id) + + def test_update_source_with_notification( + self, + fxt_configuration_service, + fxt_source_repository, + fxt_pipeline_repository, + fxt_source, + fxt_condition, + fxt_pipeline, + ): + """Test updating source calls the notification logic.""" + # Make sure the pipeline uses the same source ID + fxt_pipeline.source_id = fxt_source.id + updated_source = fxt_source.model_copy(update={"name": "Updated Source"}) + fxt_source_repository.get_by_id.return_value = fxt_source + fxt_source_repository.update.return_value = updated_source + fxt_pipeline_repository.get_active_pipeline = AsyncMock(return_value=fxt_pipeline) + + with ( + patch("services.configuration_service.SourceRepository") as mock_source_repo_class, + patch("services.configuration_service.PipelineRepository") as mock_pipeline_repo_class, + ): + mock_source_repo_class.return_value = fxt_source_repository + mock_pipeline_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run( + fxt_configuration_service.update_source( + fxt_source.id, fxt_source.project_id, {"name": "Updated Source"} + ) + ) + + assert result == updated_source + # Verify the update was successful + fxt_source_repository.get_by_id.assert_called_once_with(fxt_source.id) + fxt_source_repository.update.assert_called_once_with(fxt_source, {"name": "Updated Source"}) + # Note: Due to a bug in the actual implementation, notifications are not triggered + # This test documents the current behavior + + def test_update_sink_with_notification( + self, + fxt_configuration_service, + fxt_sink_repository, + fxt_pipeline_repository, + fxt_sink, + fxt_active_pipeline_service, + fxt_pipeline, + ): + """Test updating sink calls the notification logic.""" + # Make sure the pipeline uses the same sink ID + fxt_pipeline.sink_id = fxt_sink.id + updated_sink = fxt_sink.model_copy(update={"name": "Updated Sink"}) + fxt_sink_repository.get_by_id.return_value = fxt_sink + fxt_sink_repository.update.return_value = updated_sink + fxt_pipeline_repository.get_active_pipeline = AsyncMock(return_value=fxt_pipeline) + + with ( + patch("services.configuration_service.SinkRepository") as mock_sink_repo_class, + patch("services.configuration_service.PipelineRepository") as mock_pipeline_repo_class, + ): + mock_sink_repo_class.return_value = fxt_sink_repository + mock_pipeline_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run( + fxt_configuration_service.update_sink(fxt_sink.id, fxt_sink.project_id, {"name": "Updated Sink"}) + ) + + assert result == updated_sink + # Verify the update was successful + fxt_sink_repository.get_by_id.assert_called_once_with(fxt_sink.id) + fxt_sink_repository.update.assert_called_once_with(fxt_sink, {"name": "Updated Sink"}) + # Note: Due to a bug in the actual implementation, notifications are not triggered + # This test documents the current behavior + + def test_pipeline_field_enum_values(self): + """Test PipelineField enum has correct values.""" + assert PipelineField.SOURCE_ID == "source_id" + assert PipelineField.SINK_ID == "sink_id" diff --git a/application/backend/tests/unit/services/test_job_service.py b/application/backend/tests/unit/services/test_job_service.py new file mode 100644 index 0000000000..cc9e4ff2d9 --- /dev/null +++ b/application/backend/tests/unit/services/test_job_service.py @@ -0,0 +1,268 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +from datetime import UTC, datetime +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from freezegun import freeze_time +from sqlalchemy.exc import IntegrityError + +from exceptions import DuplicateJobException, ResourceNotFoundException +from pydantic_models import JobStatus, JobType +from repositories import JobRepository +from services import JobService + + +@pytest.fixture +def fxt_job_repository(): + """Fixture for a mock job repository.""" + mock_repo = MagicMock(spec=JobRepository) + # Set up async methods to return coroutines + mock_repo.get_by_id = AsyncMock() + mock_repo.get_one = AsyncMock() + mock_repo.get_all = AsyncMock() + mock_repo.save = AsyncMock() + mock_repo.update = AsyncMock() + mock_repo.delete_by_id = AsyncMock() + mock_repo.get_pending_job_by_type = AsyncMock() + return mock_repo + + +@pytest.fixture(autouse=True) +def mock_db_context(): + """Mock the database context for all tests.""" + with patch("services.job_service.get_async_db_session_ctx") as mock_db_ctx: + mock_session = AsyncMock() + mock_db_ctx.return_value.__aenter__.return_value = mock_session + mock_db_ctx.return_value.__aexit__.return_value = None + yield mock_db_ctx + + +class TestJobService: + def test_get_job_list(self, fxt_job_repository, fxt_job_list): + """Test getting job list.""" + fxt_job_repository.get_all.return_value = fxt_job_list.jobs + + with patch("services.job_service.JobRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_job_repository + + result = asyncio.run(JobService.get_job_list()) + + assert result == fxt_job_list + fxt_job_repository.get_all.assert_called_once() + + @pytest.mark.parametrize( + "job_exists,expected_result", + [ + (True, "job_found"), + (False, None), + ], + ) + def test_get_job_by_id(self, fxt_job_repository, fxt_job, job_exists, expected_result): + """Test getting job by ID with different scenarios.""" + if job_exists: + fxt_job_repository.get_by_id.return_value = fxt_job + job_id = fxt_job.id + else: + fxt_job_repository.get_by_id.return_value = None + job_id = "non-existent-id" + + with patch("services.job_service.JobRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_job_repository + + result = asyncio.run(JobService.get_job_by_id(job_id)) + + if job_exists: + assert result == fxt_job + else: + assert result is None + fxt_job_repository.get_by_id.assert_called_once_with(job_id) + + @pytest.mark.parametrize( + "is_duplicate,save_side_effect,expected_exception,expected_message", + [ + (False, None, None, "success"), + (True, None, DuplicateJobException, None), + ( + False, + IntegrityError("", {}, ValueError("Simulated database error")), + ResourceNotFoundException, + "project", + ), + ], + ) + def test_submit_train_job( + self, + fxt_job_repository, + fxt_job_payload, + fxt_job, + is_duplicate, + save_side_effect, + expected_exception, + expected_message, + ): + """Test job submission with different scenarios.""" + fxt_job_repository.is_job_duplicate.return_value = is_duplicate + if save_side_effect: + fxt_job_repository.save.side_effect = save_side_effect + else: + fxt_job_repository.save.return_value = fxt_job + + with patch("services.job_service.JobRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_job_repository + + if expected_exception: + with pytest.raises(expected_exception) as exc_info: + asyncio.run(JobService().submit_train_job(fxt_job_payload)) + + if expected_message: + assert expected_message in str(exc_info.value) + else: + result = asyncio.run(JobService().submit_train_job(fxt_job_payload)) + assert result.job_id == fxt_job.id + + fxt_job_repository.is_job_duplicate.assert_called_once_with( + project_id=fxt_job_payload.project_id, payload=fxt_job_payload + ) + if not is_duplicate: + fxt_job_repository.save.assert_called_once() + + @pytest.mark.parametrize( + "job_exists,expected_result", + [ + (True, "job_found"), + (False, None), + ], + ) + def test_get_pending_train_job(self, fxt_job_repository, fxt_job, job_exists, expected_result): + """Test getting pending training job with different scenarios.""" + if job_exists: + fxt_job_repository.get_pending_job_by_type.return_value = fxt_job + else: + fxt_job_repository.get_pending_job_by_type.return_value = None + + with patch("services.job_service.JobRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_job_repository + + result = asyncio.run(JobService.get_pending_train_job()) + + if job_exists: + assert result == fxt_job + else: + assert result is None + fxt_job_repository.get_pending_job_by_type.assert_called_once_with(JobType.TRAINING) + + @pytest.mark.parametrize( + "has_message,message", + [ + (True, "Test message"), + (False, None), + ], + ) + @freeze_time("2025-01-01 00:00:00") + def test_update_job_status_success(self, fxt_job_repository, fxt_job, has_message, message): + """Test updating job status successfully with and without message.""" + # Expected updates include end_time since status is COMPLETED + frozen_time = datetime(2025, 1, 1, 0, 0, 0, tzinfo=UTC) + expected_updates = { + "status": JobStatus.COMPLETED, + "end_time": frozen_time, + "progress": 100, + } + if has_message: + expected_updates["message"] = message + + # Create an updated job object that the repository would return + updated_job = fxt_job.model_copy(update=expected_updates) + fxt_job_repository.get_by_id.return_value = fxt_job + fxt_job_repository.update.return_value = updated_job + + with patch("services.job_service.JobRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_job_repository + + if has_message: + asyncio.run(JobService.update_job_status(fxt_job.id, JobStatus.COMPLETED, message)) + else: + asyncio.run(JobService.update_job_status(fxt_job.id, JobStatus.COMPLETED)) + + fxt_job_repository.get_by_id.assert_called_once_with(fxt_job.id) + fxt_job_repository.update.assert_called_once_with(fxt_job, expected_updates) + + def test_update_job_status_not_found(self, fxt_job_repository): + """Test updating job status when job not found.""" + fxt_job_repository.get_by_id.return_value = None + + with patch("services.job_service.JobRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_job_repository + + with pytest.raises(ResourceNotFoundException) as exc_info: + asyncio.run(JobService.update_job_status("non-existent-id", JobStatus.COMPLETED)) + + # Check that the exception was raised with correct parameters + assert "non-existent-id" in str(exc_info.value) + assert "job" in str(exc_info.value) + fxt_job_repository.get_by_id.assert_called_once_with("non-existent-id") + + def test_stream_logs_file_not_found(self, fxt_job): + """Test streaming logs when log file doesn't exist.""" + with ( + patch("core.logging.utils.get_job_logs_path") as mock_get_path, + patch("services.job_service.anyio.Path.exists") as mock_exists, + ): + mock_get_path.return_value = "/fake/path/job.log" + mock_exists.return_value = False + + with pytest.raises(ResourceNotFoundException) as exc_info: + + async def consume_stream(): + async for _ in JobService.stream_logs(fxt_job.id): + pass + + asyncio.run(consume_stream()) + + assert "job_logs" in str(exc_info.value) + + def test_stream_logs_success(self, fxt_job_repository, fxt_job): + """Test streaming logs successfully from a completed job.""" + log_lines = ['{"level": "INFO", "message": "Line 1"}', '{"level": "INFO", "message": "Line 2"}'] + + # Mock job as completed + completed_job = fxt_job.model_copy(update={"status": JobStatus.COMPLETED}) + fxt_job_repository.get_by_id.return_value = completed_job + + with ( + patch("core.logging.utils.get_job_logs_path") as mock_get_path, + patch("services.job_service.anyio.Path.exists") as mock_exists, + patch("services.job_service.anyio.open_file") as mock_open_file, + patch("services.job_service.JobRepository") as mock_repo_class, + ): + mock_get_path.return_value = "/fake/path/job.log" + mock_exists.return_value = True + mock_repo_class.return_value = fxt_job_repository + + # Mock file with async readline method + mock_file = MagicMock() + mock_file.readline = AsyncMock(side_effect=[*log_lines, ""]) # Empty string signals EOF + + # Create an async context manager + async_cm = MagicMock() + async_cm.__aenter__ = AsyncMock(return_value=mock_file) + async_cm.__aexit__ = AsyncMock(return_value=None) + + # anyio.open_file() is a coroutine that returns an async context manager when awaited + async def mock_anyio_open_file(*args, **kwargs): + return async_cm + + mock_open_file.side_effect = mock_anyio_open_file + + async def consume_stream(): + result = [] + async for line in JobService.stream_logs(fxt_job.id): + result.append(line.data) + return result + + result = asyncio.run(consume_stream()) + + assert len(result) == 2 + assert result == log_lines diff --git a/application/backend/tests/unit/services/test_media_service.py b/application/backend/tests/unit/services/test_media_service.py new file mode 100644 index 0000000000..d567783bf0 --- /dev/null +++ b/application/backend/tests/unit/services/test_media_service.py @@ -0,0 +1,308 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +from io import BytesIO +from unittest.mock import AsyncMock, MagicMock, patch +from uuid import uuid4 + +import pytest +from PIL import Image + +from repositories import MediaRepository +from repositories.binary_repo import ImageBinaryRepository +from services import MediaService, ResourceNotFoundError +from services.exceptions import ResourceType + + +@pytest.fixture +def fxt_media_repository(): + """Fixture for a mock media repository.""" + return MagicMock(spec=MediaRepository) + + +@pytest.fixture +def fxt_image_binary_repo(): + """Fixture for a mock image binary repository.""" + return MagicMock(spec=ImageBinaryRepository) + + +@pytest.fixture +def fxt_media_service(): + """Fixture for MediaService - all methods are static.""" + return MediaService + + +@pytest.fixture(autouse=True) +def mock_db_context(): + """Mock the database context for all tests.""" + with patch("services.media_service.get_async_db_session_ctx") as mock_db_ctx: + mock_session = AsyncMock() + mock_db_ctx.return_value.__aenter__.return_value = mock_session + mock_db_ctx.return_value.__aexit__.return_value = None + yield mock_db_ctx + + +class TestMediaService: + def test_get_media_list(self, fxt_media_service, fxt_media_repository, fxt_media_list, fxt_project): + """Test getting media list.""" + fxt_media_repository.get_all.return_value = fxt_media_list.media + + with patch("services.media_service.MediaRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_media_repository + + result = asyncio.run(fxt_media_service.get_media_list(fxt_project.id)) + + assert result == fxt_media_list + fxt_media_repository.get_all.assert_called_once() + + def test_get_media_by_id(self, fxt_media_service, fxt_media_repository, fxt_media, fxt_project): + """Test getting media by ID.""" + fxt_media_repository.get_by_id.return_value = fxt_media + + with patch("services.media_service.MediaRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_media_repository + + result = asyncio.run(fxt_media_service.get_media_by_id(fxt_project.id, fxt_media.id)) + + assert result == fxt_media + fxt_media_repository.get_by_id.assert_called_once_with(fxt_media.id) + + def test_get_media_by_id_not_found(self, fxt_media_service, fxt_media_repository, fxt_project): + """Test getting media by ID when not found.""" + fxt_media_repository.get_by_id.return_value = None + + with patch("services.media_service.MediaRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_media_repository + + result = asyncio.run(fxt_media_service.get_media_by_id(fxt_project.id, "non-existent-id")) + + assert result is None + fxt_media_repository.get_by_id.assert_called_once_with("non-existent-id") + + def test_get_media_file_path_success(self, fxt_media_service, fxt_media, fxt_project): + """Test getting media file path successfully.""" + with patch("services.media_service.ImageBinaryRepository") as mock_bin_repo_class: + mock_bin_repo = MagicMock() + mock_bin_repo_class.return_value = mock_bin_repo + mock_bin_repo.get_full_path.return_value = "/path/to/file.jpg" + + # Mock the get_media_by_id method + fxt_media_service.get_media_by_id = AsyncMock(return_value=fxt_media) + + result = asyncio.run(fxt_media_service.get_media_file_path(fxt_project.id, fxt_media.id)) + + assert result == "/path/to/file.jpg" + mock_bin_repo.get_full_path.assert_called_once_with(filename=fxt_media.filename) + + def test_get_media_file_path_not_found(self, fxt_media_service, fxt_project): + """Test getting media file path when media not found.""" + fxt_media_service.get_media_by_id = AsyncMock(return_value=None) + + with pytest.raises(FileNotFoundError) as exc_info: + asyncio.run(fxt_media_service.get_media_file_path(fxt_project.id, "non-existent-id")) + + assert "Media with ID non-existent-id not found" in str(exc_info.value) + + def test_upload_image_success(self, fxt_media_service, fxt_upload_file, fxt_project, fxt_image_bytes): + """Test successful image upload (no inline thumbnail generation).""" + mock_bin_repo = MagicMock() + mock_bin_repo.save_file = AsyncMock(return_value="/path/to/file.jpg") + + mock_media_repo = MagicMock() + mock_media_repo.save = AsyncMock(return_value=MagicMock()) + + with ( + patch("services.media_service.ImageBinaryRepository", return_value=mock_bin_repo), + patch("services.media_service.MediaRepository", return_value=mock_media_repo), + ): + result = asyncio.run( + fxt_media_service.upload_image(fxt_project.id, fxt_upload_file, fxt_image_bytes, False) + ) + + assert result is not None + assert mock_bin_repo.save_file.call_count == 1 # only original saved + mock_media_repo.save.assert_called_once() + + def test_upload_image_rollback_on_error(self, fxt_media_service, fxt_upload_file, fxt_project, fxt_image_bytes): + """Test image upload rollback on error.""" + mock_bin_repo = MagicMock() + mock_bin_repo.save_file = AsyncMock(return_value="/path/to/file.jpg") + mock_bin_repo.delete_file = AsyncMock() + + mock_media_repo = MagicMock() + mock_media_repo.save = AsyncMock(side_effect=Exception("Database error")) + + with ( + patch("services.media_service.ImageBinaryRepository", return_value=mock_bin_repo), + patch("services.media_service.MediaRepository", return_value=mock_media_repo), + pytest.raises(Exception, match="Database error"), + ): + asyncio.run(fxt_media_service.upload_image(fxt_project.id, fxt_upload_file, fxt_image_bytes, False)) + + # Verify rollback deletes both files + assert mock_bin_repo.delete_file.call_count == 1 + + def test_upload_image_rollback_file_not_found( + self, fxt_media_service, fxt_upload_file, fxt_project, fxt_image_bytes + ): + """Test image upload rollback when file deletion fails with FileNotFoundError.""" + mock_bin_repo = MagicMock() + mock_bin_repo.save_file = AsyncMock(return_value="/path/to/file.jpg") + mock_bin_repo.delete_file = AsyncMock(side_effect=FileNotFoundError()) + + mock_media_repo = MagicMock() + mock_media_repo.save = AsyncMock(side_effect=Exception("Database error")) + + with ( + patch("services.media_service.ImageBinaryRepository", return_value=mock_bin_repo), + patch("services.media_service.MediaRepository", return_value=mock_media_repo), + pytest.raises(Exception, match="Database error"), + ): + asyncio.run(fxt_media_service.upload_image(fxt_project.id, fxt_upload_file, fxt_image_bytes, False)) + + # Verify rollback attempted to delete both files + assert mock_bin_repo.delete_file.call_count == 1 + + def test_delete_media_success(self, fxt_media_service, fxt_media, fxt_project): + """Test successful media deletion.""" + mock_bin_repo = MagicMock() + mock_bin_repo.delete_file = AsyncMock() + + mock_media_repo = MagicMock() + mock_media_repo.get_by_id = AsyncMock(return_value=fxt_media) + mock_media_repo.delete_by_id = AsyncMock() + + with ( + patch("services.media_service.ImageBinaryRepository", return_value=mock_bin_repo), + patch("services.media_service.MediaRepository", return_value=mock_media_repo), + ): + asyncio.run(fxt_media_service.delete_media(fxt_media.id, fxt_project.id)) + + # Should delete both original and thumbnail + assert mock_bin_repo.delete_file.call_count == 2 + calls = mock_bin_repo.delete_file.call_args_list + assert calls[0][1]["filename"] == fxt_media.filename + assert calls[1][1]["filename"] == f"thumb_{fxt_media.id}.png" + mock_media_repo.delete_by_id.assert_called_once_with(fxt_media.id) + + def test_delete_media_not_found(self, fxt_media_service, fxt_project): + """Test media deletion when media not found.""" + # Mock the repository method + mock_media_repo = MagicMock() + mock_media_repo.get_by_id = AsyncMock(return_value=None) + + with patch("services.media_service.MediaRepository") as mock_repo_class: + mock_repo_class.return_value = mock_media_repo + + with pytest.raises(ResourceNotFoundError) as exc_info: + asyncio.run(fxt_media_service.delete_media(uuid4(), fxt_project.id)) + + assert exc_info.value.resource_type == ResourceType.MEDIA + + mock_media_repo.get_by_id.assert_called_once() + + def test_delete_media_file_not_found(self, fxt_media_service, fxt_media, fxt_project): + """Test media deletion when file not found.""" + mock_bin_repo = MagicMock() + mock_bin_repo.delete_file = AsyncMock(side_effect=FileNotFoundError()) + + mock_media_repo = MagicMock() + mock_media_repo.get_by_id = AsyncMock(return_value=fxt_media) + mock_media_repo.delete_by_id = AsyncMock() + + with ( + patch("services.media_service.ImageBinaryRepository", return_value=mock_bin_repo), + patch("services.media_service.MediaRepository", return_value=mock_media_repo), + ): + # Should not raise an exception, just log warning + asyncio.run(fxt_media_service.delete_media(fxt_media.id, fxt_project.id)) + + # Should try to delete both files (both raise FileNotFoundError) + assert mock_bin_repo.delete_file.call_count == 2 + + def test_delete_media_other_error(self, fxt_media_service, fxt_media, fxt_project): + """Test media deletion with other error.""" + with patch("services.media_service.ImageBinaryRepository") as mock_bin_repo_class: + mock_bin_repo = MagicMock() + mock_bin_repo_class.return_value = mock_bin_repo + mock_bin_repo.delete_file = AsyncMock(side_effect=Exception("File system error")) + + # Mock the repository method + mock_media_repo = MagicMock() + mock_media_repo.get_by_id = AsyncMock(return_value=fxt_media) + mock_media_repo.delete_by_id = AsyncMock() + + with patch("services.media_service.MediaRepository") as mock_repo_class: + mock_repo_class.return_value = mock_media_repo + + with pytest.raises(Exception, match="File system error"): + asyncio.run(fxt_media_service.delete_media(fxt_media.id, fxt_project.id)) + + def test_thumbnail_generation_success(self, fxt_media_service, fxt_project): + """Test thumbnail generation saves PNG and uses expected filename.""" + # Create valid image bytes + img = Image.new("RGB", (512, 512), color="red") + with BytesIO() as buf: + img.save(buf, format="JPEG") + image_bytes = buf.getvalue() + + mock_bin_repo = MagicMock() + + # Capture arguments to validate content and filename + async def _save_file(filename: str, content: bytes) -> str: # type: ignore[override] + assert filename.startswith("thumb_") and filename.endswith(".png") + assert content.startswith(b"\x89PNG") + return "/path/to/thumb.png" + + mock_bin_repo.save_file = AsyncMock(side_effect=_save_file) + + media_id = uuid4() + + with patch("services.media_service.ImageBinaryRepository", return_value=mock_bin_repo): + asyncio.run( + fxt_media_service.generate_thumbnail( + project_id=fxt_project.id, + media_id=media_id, + image_bytes=image_bytes, + ) + ) + + def test_thumbnail_upload_and_deletion(self, fxt_media_service, fxt_upload_file, fxt_project): + """Test manual thumbnail generation after upload and deletion of both files.""" + mock_bin_repo = MagicMock() + mock_bin_repo.save_file = AsyncMock(return_value="/path/to/file.jpg") + mock_bin_repo.delete_file = AsyncMock() + + mock_media_repo = MagicMock() + saved_media = MagicMock(id=uuid4(), filename="test.jpg") + mock_media_repo.save = AsyncMock(return_value=saved_media) + mock_media_repo.get_by_id = AsyncMock(return_value=saved_media) + mock_media_repo.delete_by_id = AsyncMock() + + # Valid image bytes for thumbnail generation + img = Image.new("RGB", (512, 512), color="blue") + with BytesIO() as buf: + img.save(buf, format="JPEG") + image_bytes = buf.getvalue() + + with ( + patch("services.media_service.ImageBinaryRepository", return_value=mock_bin_repo), + patch("services.media_service.MediaRepository", return_value=mock_media_repo), + ): + # Upload saves only original + result = asyncio.run(fxt_media_service.upload_image(fxt_project.id, fxt_upload_file, image_bytes, False)) + assert result is not None + assert mock_bin_repo.save_file.call_count == 1 + + # Generate and save thumbnail (background-equivalent) + asyncio.run( + fxt_media_service.generate_thumbnail( + project_id=fxt_project.id, + media_id=saved_media.id, + image_bytes=image_bytes, + ) + ) + + # Deletion removes both files + asyncio.run(fxt_media_service.delete_media(saved_media.id, fxt_project.id)) + assert mock_bin_repo.delete_file.call_count == 2 diff --git a/application/backend/tests/unit/services/test_model_service.py b/application/backend/tests/unit/services/test_model_service.py new file mode 100644 index 0000000000..37958ff24d --- /dev/null +++ b/application/backend/tests/unit/services/test_model_service.py @@ -0,0 +1,354 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +from unittest.mock import AsyncMock, MagicMock, patch + +import numpy as np +import openvino.properties.hint as ov_hints +import pytest +from anomalib.deploy import ExportType, OpenVINOInferencer + +from pydantic_models import PredictionLabel +from repositories import ModelRepository +from services import ModelService + + +@pytest.fixture +def fxt_model_repository(): + """Fixture for a mock model repository.""" + return MagicMock(spec=ModelRepository) + + +@pytest.fixture +def fxt_model_service(): + """Fixture for ModelService - most methods are static, predict_image is instance method.""" + return ModelService + + +@pytest.fixture +def fxt_mp_event(): + """Fixture for a mock multiprocessing event.""" + return MagicMock() + + +@pytest.fixture(autouse=True) +def mock_db_context(): + """Mock the database context for all tests.""" + with patch("services.model_service.get_async_db_session_ctx") as mock_db_ctx: + mock_session = AsyncMock() + mock_db_ctx.return_value.__aenter__.return_value = mock_session + mock_db_ctx.return_value.__aexit__.return_value = None + yield mock_db_ctx + + +class TestModelService: + def test_init_without_event(self, fxt_model_service): + """Test ModelService initialization without multiprocessing event.""" + service = fxt_model_service() + assert service._mp_model_reload_event is None + + def test_init_with_event(self, fxt_model_service, fxt_mp_event): + """Test ModelService initialization with multiprocessing event.""" + service = fxt_model_service(mp_model_reload_event=fxt_mp_event) + assert service._mp_model_reload_event == fxt_mp_event + + def test_activate_model_with_event(self, fxt_mp_event): + """Test activate_model with multiprocessing event set.""" + service = ModelService(mp_model_reload_event=fxt_mp_event) + service.activate_model() + fxt_mp_event.set.assert_called_once() + + def test_activate_model_without_event(self): + """Test activate_model without multiprocessing event (should not raise).""" + service = ModelService() + # Should not raise any exception + service.activate_model() + + def test_activate_model_event_exception(self, fxt_mp_event): + """Test activate_model when event.set() raises an exception.""" + fxt_mp_event.set.side_effect = Exception("Test exception") + service = ModelService(mp_model_reload_event=fxt_mp_event) + + # Should not raise the exception, just log it + service.activate_model() + fxt_mp_event.set.assert_called_once() + + def test_create_model(self, fxt_model_service, fxt_model_repository, fxt_model): + """Test creating a model.""" + fxt_model_repository.save.return_value = fxt_model + + with patch("services.model_service.ModelRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_model_repository + + result = asyncio.run(fxt_model_service.create_model(fxt_model)) + + assert result == fxt_model + fxt_model_repository.save.assert_called_once_with(fxt_model) + + def test_get_model_list(self, fxt_model_service, fxt_model_repository, fxt_model_list, fxt_project): + """Test getting model list.""" + fxt_model_repository.get_all.return_value = fxt_model_list.models + + with patch("services.model_service.ModelRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_model_repository + + result = asyncio.run(fxt_model_service.get_model_list(fxt_project.id)) + + assert result == fxt_model_list + fxt_model_repository.get_all.assert_called_once() + + def test_get_model_by_id(self, fxt_model_service, fxt_model_repository, fxt_model, fxt_project): + """Test getting model by ID.""" + fxt_model_repository.get_by_id.return_value = fxt_model + + with patch("services.model_service.ModelRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_model_repository + + result = asyncio.run(fxt_model_service.get_model_by_id(fxt_project.id, fxt_model.id)) + + assert result == fxt_model + fxt_model_repository.get_by_id.assert_called_once_with(fxt_model.id) + + def test_get_model_by_id_not_found(self, fxt_model_service, fxt_model_repository, fxt_project): + """Test getting model by ID when not found.""" + fxt_model_repository.get_by_id.return_value = None + + with patch("services.model_service.ModelRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_model_repository + + result = asyncio.run(fxt_model_service.get_model_by_id(fxt_project.id, "non-existent-id")) + + assert result is None + fxt_model_repository.get_by_id.assert_called_once_with("non-existent-id") + + def test_delete_model(self, fxt_model_service, fxt_model_repository, fxt_model, fxt_project): + """Test deleting a model.""" + fxt_model_repository.delete_by_id.return_value = None + + with patch("services.model_service.ModelRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_model_repository + + service = fxt_model_service() + asyncio.run(service.delete_model(fxt_project.id, fxt_model.id, delete_artifacts=False)) + + fxt_model_repository.delete_by_id.assert_called_once_with(fxt_model.id) + + def test_load_inference_model_success(self, fxt_model_service, fxt_model, fxt_openvino_inferencer): + """Test loading inference model successfully.""" + with patch("services.model_service.ModelBinaryRepository") as mock_bin_repo_class: + mock_bin_repo = MagicMock() + mock_bin_repo_class.return_value = mock_bin_repo + mock_bin_repo.get_weights_file_path.return_value = "/path/to/model.xml" + + with patch("services.model_service.asyncio.to_thread") as mock_to_thread: + mock_to_thread.return_value = fxt_openvino_inferencer + + result = asyncio.run(fxt_model_service.load_inference_model(fxt_model, "CPU")) + + assert result == fxt_openvino_inferencer + mock_bin_repo.get_weights_file_path.assert_called_once_with( + format=ExportType.OPENVINO, name="model.xml" + ) + mock_to_thread.assert_called_once_with( + OpenVINOInferencer, + path="/path/to/model.xml", + device="CPU", + config={ov_hints.performance_mode: ov_hints.PerformanceMode.LATENCY}, + ) + + def test_load_inference_model_unsupported_format(self, fxt_model_service, fxt_model): + """Test loading inference model with unsupported format.""" + fxt_model.format = "unsupported_format" + + with pytest.raises(NotImplementedError) as exc_info: + asyncio.run(fxt_model_service.load_inference_model(fxt_model)) + + assert "Model format unsupported_format is not supported" in str(exc_info.value) + + @pytest.mark.parametrize("device", ["CPU", "GPU", "AUTO"]) + def test_load_inference_model_with_different_devices( + self, fxt_model_service, fxt_model, fxt_openvino_inferencer, device + ): + """Test loading inference model with different devices.""" + with patch("services.model_service.ModelBinaryRepository") as mock_bin_repo_class: + mock_bin_repo = MagicMock() + mock_bin_repo_class.return_value = mock_bin_repo + mock_bin_repo.get_weights_file_path.return_value = "/path/to/model.xml" + + with patch("services.model_service.asyncio.to_thread") as mock_to_thread: + mock_to_thread.return_value = fxt_openvino_inferencer + + result = asyncio.run(fxt_model_service.load_inference_model(fxt_model, device)) + + assert result == fxt_openvino_inferencer + mock_to_thread.assert_called_once_with( + OpenVINOInferencer, + path="/path/to/model.xml", + device=device, + config={ov_hints.performance_mode: ov_hints.PerformanceMode.LATENCY}, + ) + + def test_predict_image_with_cached_model( + self, fxt_model_service, fxt_model, fxt_image_bytes, fxt_prediction_response, fxt_openvino_inferencer + ): + """Test prediction with cached model.""" + cached_models = {fxt_model.id: fxt_openvino_inferencer} + + with patch.object(fxt_model_service, "_run_prediction_pipeline") as mock_pipeline: + mock_pipeline.return_value = { + "anomaly_map": "base64_encoded_image", + "label": PredictionLabel.NORMAL, + "score": 0.1, + } + + result = asyncio.run(fxt_model_service().predict_image(fxt_model, fxt_image_bytes, cached_models)) + + assert result.anomaly_map == "base64_encoded_image" + assert result.label == PredictionLabel.NORMAL + assert result.score == 0.1 + mock_pipeline.assert_called_once() + + def test_predict_image_without_cached_model( + self, fxt_model_service, fxt_model, fxt_image_bytes, fxt_openvino_inferencer + ): + """Test prediction without cached model.""" + with patch.object(fxt_model_service, "load_inference_model") as mock_load_model: + mock_load_model.return_value = fxt_openvino_inferencer + + with patch.object(fxt_model_service, "_run_prediction_pipeline") as mock_pipeline: + mock_pipeline.return_value = { + "anomaly_map": "base64_encoded_image", + "label": PredictionLabel.NORMAL, + "score": 0.1, + } + + result = asyncio.run(fxt_model_service().predict_image(fxt_model, fxt_image_bytes, {})) + + assert result.anomaly_map == "base64_encoded_image" + assert result.label == PredictionLabel.NORMAL + assert result.score == 0.1 + mock_load_model.assert_called_once_with(fxt_model, device=None) + mock_pipeline.assert_called_once() + + def test_predict_image_caches_model(self, fxt_model_service, fxt_model, fxt_image_bytes, fxt_openvino_inferencer): + """Test that prediction caches the loaded model.""" + cached_models = {} + + with patch.object(fxt_model_service, "load_inference_model") as mock_load_model: + mock_load_model.return_value = fxt_openvino_inferencer + + with patch.object(fxt_model_service, "_run_prediction_pipeline") as mock_pipeline: + mock_pipeline.return_value = { + "anomaly_map": "base64_encoded_image", + "label": PredictionLabel.NORMAL, + "score": 0.1, + } + + asyncio.run(fxt_model_service().predict_image(fxt_model, fxt_image_bytes, cached_models)) + + assert fxt_model.id in cached_models + assert cached_models[fxt_model.id] == fxt_openvino_inferencer + + def test_predict_image_load_model_exception(self, fxt_model_service, fxt_model, fxt_image_bytes): + """Test prediction when load_inference_model raises an exception.""" + with patch.object(fxt_model_service, "load_inference_model") as mock_load_model: + mock_load_model.side_effect = Exception("Model loading failed") + + with pytest.raises(Exception) as exc_info: + asyncio.run(fxt_model_service().predict_image(fxt_model, fxt_image_bytes, {})) + + assert "Model loading failed" in str(exc_info.value) + mock_load_model.assert_called_once_with(fxt_model, device=None) + + @patch("services.model_service.cv2.imdecode") + @patch("services.model_service.cv2.cvtColor") + def test_run_prediction_pipeline(self, mock_cvt_color, mock_imdecode, fxt_openvino_inferencer, fxt_image_bytes): + """Test the prediction pipeline static method.""" + # Mock OpenCV functions to avoid actual image processing + mock_imdecode.return_value = np.array( + [[[100, 100, 100], [200, 200, 200]], [[150, 150, 150], [250, 250, 250]]], dtype=np.uint8 + ) + mock_cvt_color.return_value = np.array( + [[[100, 100, 100, 255], [200, 200, 200, 255]], [[150, 150, 150, 255], [250, 250, 250, 255]]], dtype=np.uint8 + ) + + # Create a test anomaly map + test_anomaly_map = np.array([[[0.1, 0.2], [0.3, 0.4]]]) + fxt_openvino_inferencer.predict.return_value.anomaly_map = test_anomaly_map + fxt_openvino_inferencer.predict.return_value.pred_label.item.return_value = 0 + fxt_openvino_inferencer.predict.return_value.pred_score.item.return_value = 0.1 + + result = ModelService._run_prediction_pipeline(fxt_openvino_inferencer, fxt_image_bytes) + + assert "anomaly_map" in result + assert result["label"] == PredictionLabel.NORMAL + assert result["score"] == 0.1 + assert isinstance(result["anomaly_map"], str) # Should be base64 encoded + + @patch("services.model_service.cv2.imdecode") + @patch("services.model_service.cv2.cvtColor") + def test_run_prediction_pipeline_anomalous( + self, mock_cvt_color, mock_imdecode, fxt_openvino_inferencer, fxt_image_bytes + ): + """Test the prediction pipeline with anomalous prediction.""" + # Mock OpenCV functions to avoid actual image processing + mock_imdecode.return_value = np.array( + [[[100, 100, 100], [200, 200, 200]], [[150, 150, 150], [250, 250, 250]]], dtype=np.uint8 + ) + mock_cvt_color.return_value = np.array( + [[[100, 100, 100, 255], [200, 200, 200, 255]], [[150, 150, 150, 255], [250, 250, 250, 255]]], dtype=np.uint8 + ) + + # Create a test anomaly map + test_anomaly_map = np.array([[[0.8, 0.9], [0.7, 0.6]]]) + fxt_openvino_inferencer.predict.return_value.anomaly_map = test_anomaly_map + fxt_openvino_inferencer.predict.return_value.pred_label.item.return_value = 1 + fxt_openvino_inferencer.predict.return_value.pred_score.item.return_value = 0.9 + + result = ModelService._run_prediction_pipeline(fxt_openvino_inferencer, fxt_image_bytes) + + assert "anomaly_map" in result + assert result["label"] == PredictionLabel.ANOMALOUS + assert result["score"] == 0.9 + assert isinstance(result["anomaly_map"], str) # Should be base64 encoded + + @patch("services.model_service.cv2.imdecode") + @patch("services.model_service.cv2.cvtColor") + def test_run_prediction_pipeline_edge_cases(self, mock_cvt_color, mock_imdecode, fxt_openvino_inferencer): + """Test prediction pipeline with edge cases.""" + # Test with empty image bytes + empty_bytes = b"" + mock_imdecode.return_value = None # cv2.imdecode returns None for invalid data + mock_cvt_color.return_value = None + + # This should handle the case where imdecode returns None + with pytest.raises(ValueError, match="Failed to decode image"): + ModelService._run_prediction_pipeline(fxt_openvino_inferencer, empty_bytes) + + @patch("services.model_service.cv2.imdecode") + @patch("services.model_service.cv2.cvtColor") + def test_run_prediction_pipeline_advanced_processing(self, mock_cvt_color, mock_imdecode, fxt_openvino_inferencer): + """Test prediction pipeline with advanced processing scenarios.""" + # Mock OpenCV functions + mock_imdecode.return_value = np.array( + [[[100, 100, 100], [200, 200, 200]], [[150, 150, 150], [250, 250, 250]]], dtype=np.uint8 + ) + mock_cvt_color.return_value = np.array( + [[[100, 100, 100, 255], [200, 200, 200, 255]], [[150, 150, 150, 255], [250, 250, 250, 255]]], dtype=np.uint8 + ) + + # Test with 4D anomaly map (should be squeezed to 2D) + test_anomaly_map = np.array([[[[0.1, 0.2], [0.3, 0.4]]]]) # 4D array + fxt_openvino_inferencer.predict.return_value.anomaly_map = test_anomaly_map + fxt_openvino_inferencer.predict.return_value.pred_label.item.return_value = 0 + fxt_openvino_inferencer.predict.return_value.pred_score.item.return_value = 0.123456789 + + result = ModelService._run_prediction_pipeline(fxt_openvino_inferencer, b"test_image") + + # Verify score precision handling + assert result["score"] == 0.123456789 + assert isinstance(result["score"], float) + + # Verify anomaly map processing + assert "anomaly_map" in result + assert isinstance(result["anomaly_map"], str) # Should be base64 encoded diff --git a/application/backend/tests/unit/services/test_pipeline_service.py b/application/backend/tests/unit/services/test_pipeline_service.py new file mode 100644 index 0000000000..ecbb76bc99 --- /dev/null +++ b/application/backend/tests/unit/services/test_pipeline_service.py @@ -0,0 +1,391 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +import uuid +from multiprocessing.synchronize import Condition +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from pydantic_models import Pipeline, PipelineStatus +from pydantic_models.model import Model +from pydantic_models.sink import FolderSinkConfig, MqttSinkConfig +from pydantic_models.source import VideoFileSourceConfig, WebcamSourceConfig +from repositories import PipelineRepository +from services import ActivePipelineService, ModelService, ResourceNotFoundError +from services.metrics_service import MetricsService +from services.pipeline_service import PipelineService + + +@pytest.fixture +def fxt_pipeline(fxt_project, fxt_model): + """Fixture for a test pipeline.""" + source = VideoFileSourceConfig( + id=uuid.uuid4(), + project_id=fxt_project.id, + source_type="video_file", + name="Test Source", + video_path="/path/to/video.mp4", + ) + sink = FolderSinkConfig( + id=uuid.uuid4(), + project_id=fxt_project.id, + sink_type="folder", + name="Test Sink", + folder_path="/path/to/output", + output_formats=["image_original"], + rate_limit=0.2, + ) + return Pipeline( + project_id=fxt_project.id, + source=source, + sink=sink, + model=fxt_model, + source_id=source.id, + sink_id=sink.id, + model_id=fxt_model.id, + status=PipelineStatus.RUNNING, + ) + + +@pytest.fixture +def fxt_idle_pipeline(fxt_project): + """Fixture for an idle pipeline.""" + return Pipeline(project_id=fxt_project.id, status=PipelineStatus.IDLE) + + +@pytest.fixture +def fxt_condition(): + """Fixture for a mock condition.""" + return MagicMock(spec=Condition) + + +@pytest.fixture +def fxt_active_pipeline_service(): + """Fixture for a mock active pipeline service.""" + service = MagicMock(spec=ActivePipelineService) + service.reload = AsyncMock() + return service + + +@pytest.fixture +def fxt_metrics_service(): + """Fixture for a mock metrics service.""" + service = MagicMock(spec=MetricsService) + service.get_latency_measurements.return_value = [10.5, 12.3, 8.7, 15.2, 9.1] + return service + + +@pytest.fixture +def fxt_model_service(): + """Fixture for a mock model service.""" + service = MagicMock(spec=ModelService) + service.activate_model = MagicMock() + return service + + +@pytest.fixture +def fxt_pipeline_service(fxt_active_pipeline_service, fxt_condition, fxt_model_service): + """Fixture for PipelineService with mocked dependencies.""" + return PipelineService( + active_pipeline_service=fxt_active_pipeline_service, + config_changed_condition=fxt_condition, + model_service=fxt_model_service, + ) + + +@pytest.fixture +def fxt_pipeline_repository(): + """Fixture for a mock pipeline repository.""" + return MagicMock(spec=PipelineRepository) + + +@pytest.fixture(autouse=True) +def mock_db_context(): + """Mock the database context for all tests.""" + with patch("services.pipeline_service.get_async_db_session_ctx") as mock_db_ctx: + mock_session = AsyncMock() + mock_db_ctx.return_value.__aenter__.return_value = mock_session + mock_db_ctx.return_value.__aexit__.return_value = None + yield mock_db_ctx + + +class TestPipelineService: + def test_init(self, fxt_active_pipeline_service, fxt_condition, fxt_model_service): + """Test PipelineService initialization.""" + service = PipelineService( + active_pipeline_service=fxt_active_pipeline_service, + config_changed_condition=fxt_condition, + model_service=fxt_model_service, + ) + + assert service._active_pipeline_service == fxt_active_pipeline_service + assert service._config_changed_condition == fxt_condition + assert service._model_service == fxt_model_service + + def test_notify_source_changed(self, fxt_pipeline_service, fxt_condition): + """Test source change notification.""" + fxt_pipeline_service._notify_source_changed() + + fxt_condition.__enter__.assert_called_once() + fxt_condition.__exit__.assert_called_once() + fxt_condition.notify_all.assert_called_once() + + def test_notify_sink_changed(self, fxt_pipeline_service, fxt_active_pipeline_service): + """Test sink change notification.""" + asyncio.run(fxt_pipeline_service._notify_sink_changed()) + + fxt_active_pipeline_service.reload.assert_called_once() + + def test_notify_pipeline_changed(self, fxt_pipeline_service, fxt_condition, fxt_active_pipeline_service): + """Test pipeline change notification.""" + asyncio.run(fxt_pipeline_service._notify_pipeline_changed()) + + fxt_condition.__enter__.assert_called_once() + fxt_condition.__exit__.assert_called_once() + fxt_condition.notify_all.assert_called_once() + fxt_active_pipeline_service.reload.assert_called_once() + + @pytest.mark.parametrize( + "has_session,session_param,expected_result,should_raise", + [ + (False, None, "pipeline_found", False), + (True, "fxt_db_session", "pipeline_found", False), + (False, None, None, True), + ], + ) + def test_get_pipeline_by_id( + self, + fxt_pipeline, + fxt_pipeline_repository, + fxt_db_session, + has_session, + session_param, + expected_result, + should_raise, + ): + """Test getting pipeline by ID with different scenarios.""" + if should_raise: + fxt_pipeline_repository.get_by_id.return_value = None + project_id = uuid.uuid4() + else: + fxt_pipeline_repository.get_by_id.return_value = fxt_pipeline + project_id = fxt_pipeline.project_id + + with patch("services.pipeline_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + if should_raise: + with pytest.raises(ResourceNotFoundError) as exc_info: + asyncio.run(PipelineService.get_pipeline_by_id(project_id)) + assert str(project_id) in str(exc_info.value) + assert "pipeline" in str(exc_info.value).lower() + else: + if has_session: + result = asyncio.run(PipelineService.get_pipeline_by_id(project_id, fxt_db_session)) + else: + result = asyncio.run(PipelineService.get_pipeline_by_id(project_id)) + assert result == fxt_pipeline + + fxt_pipeline_repository.get_by_id.assert_called_once_with(project_id) + + def test_update_pipeline_success(self, fxt_pipeline_service, fxt_pipeline, fxt_pipeline_repository): + """Test updating pipeline successfully.""" + updated_pipeline = fxt_pipeline.model_copy(update={"status": PipelineStatus.IDLE}) + fxt_pipeline_repository.get_by_id.return_value = fxt_pipeline + fxt_pipeline_repository.update.return_value = updated_pipeline + + with patch("services.pipeline_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run( + fxt_pipeline_service.update_pipeline(fxt_pipeline.project_id, {"status": PipelineStatus.IDLE}) + ) + + assert result == updated_pipeline + fxt_pipeline_repository.get_by_id.assert_called_once_with(fxt_pipeline.project_id) + fxt_pipeline_repository.update.assert_called_once_with(fxt_pipeline, {"status": PipelineStatus.IDLE}) + + def test_update_pipeline_running_to_running_source_change( + self, fxt_pipeline_service, fxt_pipeline, fxt_pipeline_repository, fxt_condition + ): + """Test updating running pipeline with source change.""" + new_source = WebcamSourceConfig( + id=uuid.uuid4(), + project_id=fxt_pipeline.project_id, + source_type="webcam", + name="New Source", + device_id=0, + ) + updated_pipeline = fxt_pipeline.model_copy(update={"source": new_source, "source_id": new_source.id}) + fxt_pipeline_repository.get_by_id.return_value = fxt_pipeline + fxt_pipeline_repository.update.return_value = updated_pipeline + + with patch("services.pipeline_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run( + fxt_pipeline_service.update_pipeline( + fxt_pipeline.project_id, {"source": new_source, "source_id": new_source.id} + ) + ) + + assert result == updated_pipeline + fxt_condition.__enter__.assert_called_once() + fxt_condition.__exit__.assert_called_once() + fxt_condition.notify_all.assert_called_once() + + def test_update_pipeline_running_to_running_sink_change( + self, fxt_pipeline_service, fxt_pipeline, fxt_pipeline_repository, fxt_active_pipeline_service + ): + """Test updating running pipeline with sink change.""" + new_sink = MqttSinkConfig( + id=uuid.uuid4(), + project_id=fxt_pipeline.project_id, + sink_type="mqtt", + name="New Sink", + broker_host="localhost", + broker_port=1883, + topic="anomaly_detection", + output_formats=["predictions"], + rate_limit=0.1, + ) + updated_pipeline = fxt_pipeline.model_copy(update={"sink": new_sink, "sink_id": new_sink.id}) + fxt_pipeline_repository.get_by_id.return_value = fxt_pipeline + fxt_pipeline_repository.update.return_value = updated_pipeline + + with patch("services.pipeline_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run( + fxt_pipeline_service.update_pipeline( + fxt_pipeline.project_id, {"sink": new_sink, "sink_id": new_sink.id} + ) + ) + + assert result == updated_pipeline + fxt_active_pipeline_service.reload.assert_called_once() + + def test_update_pipeline_running_to_running_model_change( + self, fxt_pipeline_service, fxt_pipeline, fxt_pipeline_repository, fxt_model_service + ): + """Test updating running pipeline with model change.""" + new_model = Model( + id=uuid.uuid4(), + project_id=fxt_pipeline.project_id, + name="new_model", + format="openvino", + train_job_id=uuid.uuid4(), + ) + updated_pipeline = fxt_pipeline.model_copy(update={"model": new_model, "model_id": new_model.id}) + fxt_pipeline_repository.get_by_id.return_value = fxt_pipeline + fxt_pipeline_repository.update.return_value = updated_pipeline + + with patch("services.pipeline_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run( + fxt_pipeline_service.update_pipeline( + fxt_pipeline.project_id, {"model": new_model, "model_id": new_model.id} + ) + ) + + assert result == updated_pipeline + fxt_model_service.activate_model.assert_called_once() + + def test_update_pipeline_status_change_to_running( + self, + fxt_pipeline_service, + fxt_idle_pipeline, + fxt_pipeline_repository, + fxt_condition, + fxt_active_pipeline_service, + fxt_model_service, + ): + """Test updating pipeline status from idle to running.""" + updated_pipeline = fxt_idle_pipeline.model_copy(update={"status": PipelineStatus.RUNNING}) + fxt_pipeline_repository.get_by_id.return_value = fxt_idle_pipeline + fxt_pipeline_repository.update.return_value = updated_pipeline + + with patch("services.pipeline_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run( + fxt_pipeline_service.update_pipeline(fxt_idle_pipeline.project_id, {"status": PipelineStatus.RUNNING}) + ) + + assert result == updated_pipeline + fxt_condition.__enter__.assert_called_once() + fxt_condition.__exit__.assert_called_once() + fxt_condition.notify_all.assert_called_once() + fxt_active_pipeline_service.reload.assert_called_once() + # activate_model is called on status change regardless of whether model exists + fxt_model_service.activate_model.assert_called_once() + + def test_update_pipeline_status_change_to_running_with_model( + self, + fxt_pipeline_service, + fxt_idle_pipeline, + fxt_model, + fxt_pipeline_repository, + fxt_condition, + fxt_active_pipeline_service, + fxt_model_service, + ): + """Test updating pipeline status to running with model.""" + updated_pipeline = fxt_idle_pipeline.model_copy(update={"status": PipelineStatus.RUNNING, "model": fxt_model}) + fxt_pipeline_repository.get_by_id.return_value = fxt_idle_pipeline + fxt_pipeline_repository.update.return_value = updated_pipeline + + with patch("services.pipeline_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run( + fxt_pipeline_service.update_pipeline( + fxt_idle_pipeline.project_id, {"status": PipelineStatus.RUNNING, "model": fxt_model} + ) + ) + + assert result == updated_pipeline + fxt_model_service.activate_model.assert_called_once() + + def test_get_active_pipeline(self, fxt_pipeline, fxt_pipeline_repository): + """Test getting the active pipeline from the database.""" + fxt_pipeline_repository.get_active_pipeline.return_value = fxt_pipeline + + with patch("services.pipeline_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run(PipelineService.get_active_pipeline()) + + assert result == fxt_pipeline + fxt_pipeline_repository.get_active_pipeline.assert_called_once() + + def test_get_active_pipeline_none(self, fxt_pipeline_repository): + """Test getting active pipeline when no pipeline is running.""" + fxt_pipeline_repository.get_active_pipeline.return_value = None + + with patch("services.pipeline_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run(PipelineService.get_active_pipeline()) + + assert result is None + fxt_pipeline_repository.get_active_pipeline.assert_called_once() + + def test_get_active_pipeline_returns_running_pipeline(self, fxt_pipeline, fxt_pipeline_repository): + """Test that get_active_pipeline returns a pipeline with RUNNING status and required fields.""" + fxt_pipeline_repository.get_active_pipeline.return_value = fxt_pipeline + + with patch("services.pipeline_service.PipelineRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_pipeline_repository + + result = asyncio.run(PipelineService.get_active_pipeline()) + + assert result is not None + assert result.status == PipelineStatus.RUNNING + assert result.source is not None + assert result.sink is not None + assert result.model is not None + assert result == fxt_pipeline + fxt_pipeline_repository.get_active_pipeline.assert_called_once() diff --git a/application/backend/tests/unit/services/test_project_service.py b/application/backend/tests/unit/services/test_project_service.py new file mode 100644 index 0000000000..4f91d56fbb --- /dev/null +++ b/application/backend/tests/unit/services/test_project_service.py @@ -0,0 +1,92 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from repositories import ProjectRepository +from services import ProjectService + + +@pytest.fixture +def fxt_project_repository(): + """Fixture for a mock project repository.""" + return MagicMock(spec=ProjectRepository) + + +@pytest.fixture +def fxt_project_service(): + """Fixture for ProjectService - all methods are static.""" + return ProjectService + + +@pytest.fixture(autouse=True) +def mock_db_context(): + """Mock the database context for all tests.""" + with patch("services.project_service.get_async_db_session_ctx") as mock_db_ctx: + mock_session = AsyncMock() + mock_db_ctx.return_value.__aenter__.return_value = mock_session + mock_db_ctx.return_value.__aexit__.return_value = None + yield mock_db_ctx + + +class TestProjectService: + def test_get_project_list(self, fxt_project_service, fxt_project_repository, fxt_project_list): + """Test getting project list.""" + fxt_project_repository.get_all.return_value = fxt_project_list.projects + + with patch("services.project_service.ProjectRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_project_repository + + result = asyncio.run(fxt_project_service.get_project_list()) + + assert result == fxt_project_list + fxt_project_repository.get_all.assert_called_once() + + def test_get_project_by_id(self, fxt_project_service, fxt_project_repository, fxt_project): + """Test getting project by ID.""" + fxt_project_repository.get_by_id.return_value = fxt_project + + with patch("services.project_service.ProjectRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_project_repository + + result = asyncio.run(fxt_project_service.get_project_by_id(fxt_project.id)) + + assert result == fxt_project + fxt_project_repository.get_by_id.assert_called_once_with(fxt_project.id) + + def test_get_project_by_id_not_found(self, fxt_project_service, fxt_project_repository): + """Test getting project by ID when not found.""" + fxt_project_repository.get_by_id.return_value = None + + with patch("services.project_service.ProjectRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_project_repository + + result = asyncio.run(fxt_project_service.get_project_by_id("non-existent-id")) + + assert result is None + fxt_project_repository.get_by_id.assert_called_once_with("non-existent-id") + + def test_create_project(self, fxt_project_service, fxt_project_repository, fxt_project): + """Test creating a project.""" + fxt_project_repository.save.return_value = fxt_project + + with patch("services.project_service.ProjectRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_project_repository + + result = asyncio.run(fxt_project_service.create_project(fxt_project)) + + assert result == fxt_project + fxt_project_repository.save.assert_called_once_with(fxt_project) + + def test_delete_project(self, fxt_project_service, fxt_project_repository, fxt_project): + """Test deleting a project.""" + fxt_project_repository.delete_by_id.return_value = None + + with patch("services.project_service.ProjectRepository") as mock_repo_class: + mock_repo_class.return_value = fxt_project_repository + + asyncio.run(fxt_project_service.delete_project(fxt_project.id)) + + fxt_project_repository.delete_by_id.assert_called_once_with(fxt_project.id) diff --git a/application/backend/tests/unit/services/test_training_service.py b/application/backend/tests/unit/services/test_training_service.py new file mode 100644 index 0000000000..ed2007282d --- /dev/null +++ b/application/backend/tests/unit/services/test_training_service.py @@ -0,0 +1,294 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +from typing import Any +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from pydantic_models import JobStatus +from repositories.binary_repo import ImageBinaryRepository, ModelBinaryRepository +from services import TrainingService +from utils.callbacks import ProgressSyncParams + + +@pytest.fixture +def fxt_model_binary_repo(): + """Fixture for a mock model binary repository.""" + return MagicMock(spec=ModelBinaryRepository) + + +@pytest.fixture +def fxt_image_binary_repo(): + """Fixture for a mock image binary repository.""" + return MagicMock(spec=ImageBinaryRepository) + + +@pytest.fixture +def fxt_mock_job_service(): + """Fixture for a mock job service instance.""" + mock_job_service = MagicMock() + mock_job_service.get_pending_train_job = AsyncMock() + mock_job_service.update_job_status = AsyncMock() + return mock_job_service + + +@pytest.fixture +def fxt_mock_model_service(): + """Fixture for a mock model service instance.""" + mock_model_service = MagicMock() + mock_model_service.create_model = AsyncMock() + mock_model_service.delete_model = AsyncMock() + return mock_model_service + + +@pytest.fixture +def fxt_mock_binary_repos(fxt_model_binary_repo, fxt_image_binary_repo): + """Fixture for mock binary repositories.""" + with ( + patch("services.training_service.ModelBinaryRepository") as mock_model_bin_repo_class, + patch("services.training_service.ImageBinaryRepository") as mock_image_bin_repo_class, + ): + mock_model_bin_repo_class.return_value = fxt_model_binary_repo + mock_image_bin_repo_class.return_value = fxt_image_binary_repo + yield mock_model_bin_repo_class, mock_image_bin_repo_class + + +@pytest.fixture +def fxt_mock_anomalib_components(): + """Fixture for mock Anomalib components.""" + with ( + patch("services.training_service.Folder") as mock_folder_class, + patch("services.training_service.get_model") as mock_get_model, + patch("services.training_service.Engine") as mock_engine_class, + ): + mock_folder = MagicMock() + mock_folder_class.return_value = mock_folder + + mock_anomalib_model = MagicMock() + mock_get_model.return_value = mock_anomalib_model + + mock_engine = MagicMock() + mock_engine_class.return_value = mock_engine + mock_engine.export.return_value = "/path/to/exported/model" + + yield { + "folder_class": mock_folder_class, + "folder": mock_folder, + "get_model": mock_get_model, + "anomalib_model": mock_anomalib_model, + "engine_class": mock_engine_class, + "engine": mock_engine, + } + + +@pytest.fixture +def fxt_mock_job_service_class(fxt_mock_job_service): + """Fixture that patches JobService class.""" + with patch("services.training_service.JobService") as mock_job_service_class: + mock_job_service_class.return_value = fxt_mock_job_service + yield mock_job_service_class + + +@pytest.fixture +def fxt_mock_model_service_class(fxt_mock_model_service): + """Fixture that patches ModelService class.""" + with patch("services.training_service.ModelService") as mock_model_service_class: + mock_model_service_class.return_value = fxt_mock_model_service + yield mock_model_service_class + + +class TestTrainingService: + def test_train_pending_job_no_pending_jobs(self, fxt_mock_job_service_class, fxt_mock_job_service): + """Test training when no pending jobs exist.""" + fxt_mock_job_service.get_pending_train_job.return_value = None + + result = asyncio.run(TrainingService.train_pending_job()) + + assert result is None + fxt_mock_job_service.get_pending_train_job.assert_called_once() + + def test_train_pending_job_success( + self, + fxt_job, + fxt_model, + fxt_mock_job_service_class, + fxt_mock_model_service_class, + fxt_mock_job_service, + fxt_mock_model_service, + ): + """Test successful training of a pending job.""" + fxt_job.payload = {"model_name": "padim"} + fxt_mock_job_service.get_pending_train_job.return_value = fxt_job + fxt_mock_model_service.create_model.return_value = fxt_model + + with patch("services.training_service.asyncio.to_thread") as mock_to_thread: + mock_to_thread.return_value = fxt_model + + result = asyncio.run(TrainingService.train_pending_job()) + + assert result == fxt_model + # Should be called twice: RUNNING then COMPLETED + assert fxt_mock_job_service.update_job_status.call_count == 2 + fxt_mock_job_service.update_job_status.assert_any_call( + job_id=fxt_job.id, status=JobStatus.RUNNING, message="Training started" + ) + fxt_mock_job_service.update_job_status.assert_any_call( + job_id=fxt_job.id, status=JobStatus.COMPLETED, message="Training completed successfully" + ) + fxt_mock_model_service.create_model.assert_called_once() + + @pytest.mark.parametrize( + "exception,expected_message", + [ + (Exception("Training failed"), "Training failed"), + (ValueError("Training failed - model is None"), "Training failed - model is None"), + ], + ) + def test_train_pending_job_training_failures( + self, + fxt_job, + fxt_mock_job_service_class, + fxt_mock_model_service_class, + fxt_mock_job_service, + exception, + expected_message, + ): + """Test training failure handling with different failure scenarios.""" + fxt_job.payload = {"model_name": "padim"} + fxt_mock_job_service.get_pending_train_job.return_value = fxt_job + + with patch("services.training_service.asyncio.to_thread") as mock_to_thread: + if isinstance(exception, ValueError) and "model is None" in str(exception): + mock_to_thread.return_value = None + else: + mock_to_thread.side_effect = exception + + with pytest.raises(type(exception), match=expected_message): + asyncio.run(TrainingService.train_pending_job()) + + # Should be called twice: RUNNING then FAILED + assert fxt_mock_job_service.update_job_status.call_count == 2 + fxt_mock_job_service.update_job_status.assert_any_call( + job_id=fxt_job.id, status=JobStatus.RUNNING, message="Training started" + ) + fxt_mock_job_service.update_job_status.assert_any_call( + job_id=fxt_job.id, status=JobStatus.FAILED, message=f"Failed with exception: {expected_message}" + ) + + def test_train_pending_job_cleanup_on_failure( + self, + fxt_job, + fxt_model, + fxt_mock_job_service_class, + fxt_mock_model_service_class, + fxt_mock_job_service, + fxt_mock_model_service, + fxt_mock_binary_repos, + ): + """Test cleanup when training fails and model has export_path.""" + fxt_job.payload = {"model_name": "padim"} + fxt_mock_job_service.get_pending_train_job.return_value = fxt_job + fxt_model.export_path = "/path/to/model" + + with patch("services.training_service.asyncio.to_thread") as mock_to_thread: + # Mock the training to succeed first, setting export_path, then fail + def mock_train_model(cls, model, synchronization_parameters: ProgressSyncParams, device=None): + model.export_path = "/path/to/model" + raise Exception("Training failed") + + mock_to_thread.side_effect = mock_train_model + + with pytest.raises(Exception, match="Training failed"): + asyncio.run(TrainingService.train_pending_job()) + + # Verify cleanup was called + fxt_mock_model_service.delete_model.assert_called_once() + call_args = fxt_mock_model_service.delete_model.call_args + assert call_args[1]["project_id"] == fxt_job.project_id + + def test_train_model_success( + self, + fxt_model, + fxt_model_binary_repo, + fxt_image_binary_repo, + fxt_mock_anomalib_components, + fxt_mock_binary_repos, + ): + """Test successful model training with platform-specific worker configuration.""" + # Setup platform mock + # Setup binary repo paths + fxt_image_binary_repo.project_folder_path = "/path/to/images" + fxt_model_binary_repo.model_folder_path = "/path/to/model" + + # Call the method + with patch.object(TrainingService, "_compute_export_size", return_value=123): + result = TrainingService._train_model(fxt_model, synchronization_parameters=ProgressSyncParams()) + + # Verify the result + assert result == fxt_model + assert fxt_model.is_ready is True + assert fxt_model.export_path == "/path/to/model" + assert fxt_model.size == 123 + + # Verify all components were called correctly + fxt_mock_anomalib_components["folder_class"].assert_called_once() + fxt_mock_anomalib_components["get_model"].assert_called_once_with(model=fxt_model.name) + + # Verify Engine was called with expected parameters + fxt_mock_anomalib_components["engine_class"].assert_called_once() + call_args = fxt_mock_anomalib_components["engine_class"].call_args + assert call_args[1]["default_root_dir"] == "/path/to/model" + assert "logger" in call_args[1] + assert len(call_args[1]["logger"]) == 2 # trackio and tensorboard + assert call_args[1]["max_epochs"] == 10 + + fxt_mock_anomalib_components["engine"].fit.assert_called_once_with( + model=fxt_mock_anomalib_components["anomalib_model"], datamodule=fxt_mock_anomalib_components["folder"] + ) + fxt_mock_anomalib_components["engine"].export.assert_called_once() + + def test_train_pending_job_cancelled( + self, + fxt_job, + fxt_mock_job_service_class, + fxt_mock_model_service_class, + fxt_mock_job_service, + fxt_mock_binary_repos, + ): + """Training should mark job as cancelled when cancellation flag is set.""" + fxt_job.payload = {"model_name": "padim"} + fxt_mock_job_service.get_pending_train_job.return_value = fxt_job + + async def fake_train_model(*args: Any, **kwargs: Any) -> MagicMock: + sync_params: ProgressSyncParams = kwargs["synchronization_parameters"] + sync_params.set_cancel_training_event() + return MagicMock() + + with ( + patch("services.training_service.asyncio.to_thread", side_effect=fake_train_model), + patch("services.training_service.TrainingService._sync_progress_with_db", new=AsyncMock()), + ): + result = asyncio.run(TrainingService.train_pending_job()) + + assert result is None + fxt_mock_job_service.update_job_status.assert_any_call( + job_id=fxt_job.id, status=JobStatus.RUNNING, message="Training started" + ) + fxt_mock_job_service.update_job_status.assert_any_call( + job_id=fxt_job.id, status=JobStatus.CANCELED, message="Training cancelled by user" + ) + + def test_train_model_cancelled_before_start( + self, + fxt_model, + fxt_mock_anomalib_components, + fxt_mock_binary_repos, + ): + """_train_model should abort early when cancellation is requested before training starts.""" + sync_params = ProgressSyncParams() + sync_params.set_cancel_training_event() + + result = TrainingService._train_model(fxt_model, synchronization_parameters=sync_params) + + assert result is None diff --git a/application/backend/tests/unit/workers/test_stream_loading.py b/application/backend/tests/unit/workers/test_stream_loading.py new file mode 100644 index 0000000000..d3bd0644db --- /dev/null +++ b/application/backend/tests/unit/workers/test_stream_loading.py @@ -0,0 +1,205 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +import multiprocessing as mp +import queue +import sys +import time +from unittest.mock import Mock, patch + +import numpy as np +import pytest + +from entities.stream_data import StreamData +from entities.video_stream import VideoStream +from pydantic_models.source import Source, SourceType +from workers.stream_loading import StreamLoader + + +@pytest.fixture +def mp_manager(): + """Multiprocessing manager fixture""" + manager = mp.Manager() + yield manager + manager.shutdown() + + +@pytest.fixture +def frame_queue(mp_manager): + """Frame queue fixture""" + return mp_manager.Queue(maxsize=2) + + +@pytest.fixture +def stop_event(): + """Stop event fixture""" + return mp.Event() + + +@pytest.fixture +def config_changed_condition(): + """Configuration changed condition fixture""" + return mp.Condition() + + +@pytest.fixture +def mock_config(): + """Mock configuration fixture""" + config = Mock(spec=Source) + config.source_type = SourceType.WEBCAM + config.device_id = 0 + return config + + +@pytest.fixture +def mock_stream_data(): + def create_sample(): + return StreamData( + frame_data=np.random.randint(0, 255, (480, 640, 3), dtype=np.uint8), + timestamp=time.time(), + source_metadata={}, + ) + + yield create_sample + + +@pytest.fixture +def mock_video_stream(mock_stream_data): + """Mock video stream fixture""" + stream = Mock(spec=VideoStream) + stream.get_data.return_value = mock_stream_data() + stream.is_real_time.return_value = True + stream.release.return_value = None + return stream + + +@pytest.fixture +def mock_services(mock_config, mock_video_stream): + with ( + patch("workers.stream_loading.ActivePipelineService") as mock_active_pipeline_service, + patch("workers.stream_loading.VideoStreamService") as mock_video_service, + ): + # Set up the mocks + mock_config_instance = Mock() + mock_config_instance.source_config = mock_config + + # Mock the async create method + async def mock_create(*args, **kwargs): + return mock_config_instance + + mock_active_pipeline_service.create = mock_create + + mock_video_service.get_video_stream.return_value = mock_video_stream + + yield { + "active_pipeline_service": mock_active_pipeline_service, + "video_service": mock_video_service, + "config_instance": mock_config_instance, + "video_stream": mock_video_stream, + } + + +@pytest.mark.skipif(sys.platform == "win32", reason="Multiprocessing 'fork' start method not available on Windows") +class TestStreamLoader: + """Unit tests for the StreamLoader worker""" + + @pytest.fixture(scope="session", autouse=True) + def set_multiprocessing_start_method(self): + # Set multiprocessing start method to 'fork' to ensure mocked objects and patches + # from the parent process are inherited by child processes. The default 'spawn' + # method creates isolated child processes that don't inherit mocked state. + try: + mp.set_start_method("fork", force=True) + except RuntimeError: + # Already set, ignore + pass + + def test_queue_full_realtime( + self, frame_queue, mock_stream_data, stop_event, config_changed_condition, mock_services + ): + """Test that frames are discarded when queue is full for real-time streams""" + + data1, data2 = mock_stream_data(), mock_stream_data() + frame_queue.put(data1) + frame_queue.put(data2) + initial_queue_size = frame_queue.qsize() + + # Create and start the worker + worker = StreamLoader( + frame_queue=frame_queue, + stop_event=stop_event, + config_changed_condition=config_changed_condition, + ) + + # Start the worker in a separate process + worker.start() + + # Let it run for a short time + # For real-time streams with full queue, it will discard old frames without adding new ones + time.sleep(2) + + # Stop the worker + stop_event.set() + worker.join(timeout=5) + + # For real-time streams, when queue is full, old frames are discarded + # The implementation discards old frame but doesn't add new one in same cycle + queue_contents = [] + while not frame_queue.empty(): + try: + queue_contents.append(frame_queue.get(timeout=0.1)) + except queue.Empty: + break + + # Queue should have fewer or equal frames compared to initial (some may be discarded) + assert len(queue_contents) <= initial_queue_size + assert not worker.is_alive(), "Worker process should terminate cleanly" + + def test_queue_empty(self, frame_queue, stop_event, config_changed_condition, mock_services): + """Test that stream frames are acquired when queue is empty""" + + # Create and start the worker + worker = StreamLoader( + frame_queue=frame_queue, + stop_event=stop_event, + config_changed_condition=config_changed_condition, + ) + + worker.start() + + # Let it run to acquire frames + time.sleep(2) + + # Stop the worker + stop_event.set() + worker.join(timeout=5) + + # Should have acquired frames + assert frame_queue.qsize() >= 1, "Queue should have at least one frame" + assert not worker.is_alive(), "Worker process should terminate cleanly" + + def test_cleanup(self, frame_queue, stop_event, config_changed_condition, mock_services): + """Test that resources are successfully released when worker finishes""" + + # Create and start the worker + worker = StreamLoader( + frame_queue=frame_queue, + stop_event=stop_event, + config_changed_condition=config_changed_condition, + ) + + worker.start() + + # Let it run briefly + time.sleep(1) + + # Stop the worker + stop_event.set() + worker.join(timeout=5) + + # Verify clean shutdown + assert not worker.is_alive(), "Worker process should terminate cleanly" + + # Verify the video stream was released (checked in teardown) + # Note: We can't directly verify mock calls across process boundaries, + # but the teardown method should have been called diff --git a/application/backend/tests/unit/workers/test_training.py b/application/backend/tests/unit/workers/test_training.py new file mode 100644 index 0000000000..f9f5e042b2 --- /dev/null +++ b/application/backend/tests/unit/workers/test_training.py @@ -0,0 +1,98 @@ +# Copyright (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +import asyncio +import multiprocessing as mp +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from workers.training import TrainingWorker + + +class TestTrainingWorker: + """Test cases for the TrainingWorker class.""" + + @pytest.fixture + def mock_stop_event(self): + """Fixture for a mock stop event.""" + return mp.Event() + + @pytest.fixture + def mock_training_service(self): + """Fixture for a mock training service.""" + service = MagicMock() + service.train_pending_job = AsyncMock(return_value=None) + return service + + def test_basic_operation(self, mock_stop_event, mock_training_service): + """Test basic loop operation - runs and calls training service.""" + + async def run_test(): + with patch("workers.training.TrainingService", return_value=mock_training_service): + worker = TrainingWorker(stop_event=mock_stop_event) + + # Set up stop event to trigger after a short time + async def set_stop_event(): + await asyncio.sleep(0.1) + mock_stop_event.set() + + # Start the stop event task + stop_task = asyncio.create_task(set_stop_event()) + + # Run the train loop + await worker.run_loop() + + # Wait for stop task to complete + await stop_task + + # Verify training service was called + mock_training_service.train_pending_job.assert_called() + + asyncio.run(run_test()) + + def test_handles_exceptions(self, mock_stop_event, mock_training_service): + """Test that loop handles exceptions gracefully and continues running.""" + + async def run_test(): + # Mock training service to raise an exception + mock_training_service.train_pending_job.side_effect = Exception("Training failed") + + with patch("workers.training.TrainingService", return_value=mock_training_service): + worker = TrainingWorker(stop_event=mock_stop_event) + + # Set up stop event to trigger after a short time + async def set_stop_event(): + await asyncio.sleep(0.1) + mock_stop_event.set() + + # Start the stop event task + stop_task = asyncio.create_task(set_stop_event()) + + # Run the train loop - should not raise exception + await worker.run_loop() + + # Wait for stop task to complete + await stop_task + + # Verify training service was called despite exception + mock_training_service.train_pending_job.assert_called() + + asyncio.run(run_test()) + + def test_shutdown_behavior(self, mock_stop_event, mock_training_service): + """Test that loop responds to stop_event and exits gracefully.""" + + async def run_test(): + # Set stop event immediately + mock_stop_event.set() + + with patch("workers.training.TrainingService", return_value=mock_training_service): + worker = TrainingWorker(stop_event=mock_stop_event) + + # Run the train loop + await worker.run_loop() + + # Verify training service was never called (loop exited immediately) + mock_training_service.train_pending_job.assert_not_called() + + asyncio.run(run_test()) diff --git a/application/backend/uv.lock b/application/backend/uv.lock new file mode 100644 index 0000000000..b0f15966d6 --- /dev/null +++ b/application/backend/uv.lock @@ -0,0 +1,5886 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'linux'", + "python_full_version < '3.14' and sys_platform == 'linux'", + "python_full_version >= '3.14' and sys_platform != 'linux'", + "python_full_version < '3.14' and sys_platform != 'linux'", +] + +[[package]] +name = "about-time" +version = "4.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3f/ccb16bdc53ebb81c1bf837c1ee4b5b0b69584fd2e4a802a2a79936691c0a/about-time-4.2.1.tar.gz", hash = "sha256:6a538862d33ce67d997429d14998310e1dbfda6cb7d9bbfbf799c4709847fece", size = 15380, upload-time = "2022-12-21T04:15:54.991Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/cd/7ee00d6aa023b1d0551da0da5fee3bc23c3eeea632fbfc5126d1fec52b7e/about_time-4.2.1-py3-none-any.whl", hash = "sha256:8bbf4c75fe13cbd3d72f49a03b02c5c7dca32169b6d49117c257e7eb3eaee341", size = 13295, upload-time = "2022-12-21T04:15:53.613Z" }, +] + +[[package]] +name = "absl-py" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/10/2a/c93173ffa1b39c1d0395b7e842bbdc62e556ca9d8d3b5572926f3e4ca752/absl_py-2.3.1.tar.gz", hash = "sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9", size = 116588, upload-time = "2025-07-03T09:31:44.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/aa/ba0014cc4659328dc818a28827be78e6d97312ab0cb98105a770924dc11e/absl_py-2.3.1-py3-none-any.whl", hash = "sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d", size = 135811, upload-time = "2025-07-03T09:31:42.253Z" }, +] + +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, + { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, + { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, + { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, + { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, + { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, +] + +[[package]] +name = "aioice" +version = "0.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "ifaddr" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/a2/45dfab1d5a7f96c48595a5770379acf406cdf02a2cd1ac1729b599322b08/aioice-0.10.1.tar.gz", hash = "sha256:5c8e1422103448d171925c678fb39795e5fe13d79108bebb00aa75a899c2094a", size = 44304, upload-time = "2025-04-13T08:15:25.629Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/58/af07dda649c22a1ae954ffb7aaaf4d4a57f1bf00ebdf62307affc0b8552f/aioice-0.10.1-py3-none-any.whl", hash = "sha256:f31ae2abc8608b1283ed5f21aebd7b6bd472b152ff9551e9b559b2d8efed79e9", size = 24872, upload-time = "2025-04-13T08:15:24.044Z" }, +] + +[[package]] +name = "aiortc" +version = "1.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aioice" }, + { name = "av" }, + { name = "cffi" }, + { name = "cryptography" }, + { name = "google-crc32c" }, + { name = "pyee" }, + { name = "pylibsrtp" }, + { name = "pyopenssl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/03/bc947d74c548e0c17cf94e5d5bdacaed0ee9e5b2bb7b8b8cf1ac7a7c01ec/aiortc-1.13.0.tar.gz", hash = "sha256:5d209975c22d0910fb5a0f0e2caa828f2da966c53580f7c7170ac3a16a871620", size = 1179894, upload-time = "2025-05-27T03:23:59.017Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/29/765633cab5f1888890f5f172d1d53009b9b14e079cdfa01a62d9896a9ea9/aiortc-1.13.0-py3-none-any.whl", hash = "sha256:9ccccec98796f6a96bd1c3dd437a06da7e0f57521c96bd56e4b965a91b03a0a0", size = 92910, upload-time = "2025-05-27T03:23:57.344Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "aiosqlite" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/7d/8bca2bf9a247c2c5dfeec1d7a5f40db6518f88d314b8bca9da29670d2671/aiosqlite-0.21.0.tar.gz", hash = "sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3", size = 13454, upload-time = "2025-02-03T07:30:16.235Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/10/6c25ed6de94c49f88a91fa5018cb4c0f3625f31d5be9f771ebe5cc7cd506/aiosqlite-0.21.0-py3-none-any.whl", hash = "sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0", size = 15792, upload-time = "2025-02-03T07:30:13.6Z" }, +] + +[[package]] +name = "alembic" +version = "1.16.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/ca/4dc52902cf3491892d464f5265a81e9dff094692c8a049a3ed6a05fe7ee8/alembic-1.16.5.tar.gz", hash = "sha256:a88bb7f6e513bd4301ecf4c7f2206fe93f9913f9b48dac3b78babde2d6fe765e", size = 1969868, upload-time = "2025-08-27T18:02:05.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/4a/4c61d4c84cfd9befb6fa08a702535b27b21fff08c946bc2f6139decbf7f7/alembic-1.16.5-py3-none-any.whl", hash = "sha256:e845dfe090c5ffa7b92593ae6687c5cb1a101e91fa53868497dbd79847f9dbe3", size = 247355, upload-time = "2025-08-27T18:02:07.37Z" }, +] + +[[package]] +name = "alive-progress" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "about-time" }, + { name = "graphemeu" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/26/d43128764a6f8fe1668c4f87aba6b1fe52bea81d05a35c84a70d3c70b6f7/alive-progress-3.3.0.tar.gz", hash = "sha256:457dd2428b48dacd49854022a46448d236a48f1b7277874071c39395307e830c", size = 116281, upload-time = "2025-07-20T02:10:39.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/85/ec72f6c885703d18f3b09769645e950e14c7d0cc0a0e35d94127983f666f/alive_progress-3.3.0-py3-none-any.whl", hash = "sha256:63dd33bb94cde15ad9e5b666dbba8fedf71b72a4935d6fb9a92931e69402c9ff", size = 78403, upload-time = "2025-07-20T02:10:37.318Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anomalib" +source = { editable = "../../" } +dependencies = [ + { name = "docstring-parser" }, + { name = "einops" }, + { name = "freia" }, + { name = "imagecodecs" }, + { name = "jsonargparse", extra = ["signatures"] }, + { name = "kornia" }, + { name = "lightning" }, + { name = "lightning-utilities" }, + { name = "matplotlib" }, + { name = "omegaconf" }, + { name = "opencv-python" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "rich" }, + { name = "rich-argparse" }, + { name = "scikit-image" }, + { name = "scikit-learn" }, + { name = "tifffile" }, + { name = "timm" }, + { name = "torchmetrics" }, +] + +[package.optional-dependencies] +full = [ + { name = "av" }, + { name = "comet-ml" }, + { name = "gitpython" }, + { name = "gradio" }, + { name = "ipykernel" }, + { name = "ipywidgets" }, + { name = "mlflow" }, + { name = "nncf" }, + { name = "notebook" }, + { name = "ollama" }, + { name = "onnx" }, + { name = "open-clip-torch" }, + { name = "openai" }, + { name = "openvino" }, + { name = "python-dotenv" }, + { name = "tensorboard" }, + { name = "transformers" }, + { name = "wandb" }, +] + +[package.metadata] +requires-dist = [ + { name = "av", marker = "extra == 'dev'", specifier = ">=10.0.0" }, + { name = "av", marker = "extra == 'full'", specifier = ">=10.0.0" }, + { name = "av", marker = "extra == 'video'", specifier = ">=10.0.0" }, + { name = "comet-ml", marker = "extra == 'dev'", specifier = ">=3.31.7" }, + { name = "comet-ml", marker = "extra == 'full'", specifier = ">=3.31.7" }, + { name = "comet-ml", marker = "extra == 'loggers'", specifier = ">=3.31.7" }, + { name = "commitizen", marker = "extra == 'dev'" }, + { name = "commitizen", marker = "extra == 'test'" }, + { name = "coverage", extras = ["toml"], marker = "extra == 'dev'" }, + { name = "coverage", extras = ["toml"], marker = "extra == 'test'" }, + { name = "docstring-parser" }, + { name = "einops", specifier = ">=0.3.2" }, + { name = "freia", specifier = ">=0.2" }, + { name = "geti-inspect", marker = "python_full_version >= '3.13' and extra == 'application'", editable = "." }, + { name = "gitpython", marker = "extra == 'dev'" }, + { name = "gitpython", marker = "extra == 'full'" }, + { name = "gitpython", marker = "extra == 'notebooks'" }, + { name = "gradio", marker = "extra == 'dev'", specifier = ">=4" }, + { name = "gradio", marker = "extra == 'full'", specifier = ">=4" }, + { name = "gradio", marker = "extra == 'loggers'", specifier = ">=4" }, + { name = "imagecodecs" }, + { name = "ipykernel", marker = "extra == 'dev'" }, + { name = "ipykernel", marker = "extra == 'full'" }, + { name = "ipykernel", marker = "extra == 'notebooks'" }, + { name = "ipywidgets", marker = "extra == 'dev'" }, + { name = "ipywidgets", marker = "extra == 'full'" }, + { name = "ipywidgets", marker = "extra == 'notebooks'" }, + { name = "jsonargparse", extras = ["signatures"], specifier = ">=4.27.7" }, + { name = "kornia", specifier = ">=0.6.6" }, + { name = "lightning", specifier = ">=2.2" }, + { name = "lightning-utilities" }, + { name = "matplotlib", specifier = ">=3.4.3" }, + { name = "mlflow", marker = "extra == 'dev'", specifier = ">=1.0.0" }, + { name = "mlflow", marker = "extra == 'full'", specifier = ">=1.0.0" }, + { name = "mlflow", marker = "extra == 'loggers'", specifier = ">=1.0.0" }, + { name = "myst-parser", extras = ["linkify"], marker = "extra == 'dev'" }, + { name = "myst-parser", extras = ["linkify"], marker = "extra == 'docs'" }, + { name = "nbsphinx", marker = "extra == 'dev'" }, + { name = "nbsphinx", marker = "extra == 'docs'" }, + { name = "nncf", marker = "extra == 'dev'", specifier = ">=2.10.0" }, + { name = "nncf", marker = "extra == 'full'", specifier = ">=2.10.0" }, + { name = "nncf", marker = "extra == 'openvino'", specifier = ">=2.10.0" }, + { name = "notebook", marker = "extra == 'dev'" }, + { name = "notebook", marker = "extra == 'full'" }, + { name = "notebook", marker = "extra == 'notebooks'" }, + { name = "ollama", marker = "extra == 'dev'", specifier = ">=0.4.0" }, + { name = "ollama", marker = "extra == 'full'", specifier = ">=0.4.0" }, + { name = "ollama", marker = "extra == 'vlm'", specifier = ">=0.4.0" }, + { name = "omegaconf", specifier = ">=2.1.1" }, + { name = "onnx", marker = "extra == 'dev'", specifier = ">=1.16.0" }, + { name = "onnx", marker = "extra == 'full'", specifier = ">=1.16.0" }, + { name = "onnx", marker = "extra == 'openvino'", specifier = ">=1.16.0" }, + { name = "open-clip-torch", marker = "extra == 'clip'", specifier = ">=2.23.0,<2.26.1" }, + { name = "open-clip-torch", marker = "extra == 'dev'", specifier = ">=2.23.0,<2.26.1" }, + { name = "open-clip-torch", marker = "extra == 'full'", specifier = ">=2.23.0,<2.26.1" }, + { name = "openai", marker = "extra == 'dev'" }, + { name = "openai", marker = "extra == 'full'" }, + { name = "openai", marker = "extra == 'vlm'" }, + { name = "opencv-python", specifier = ">=4.5.3.56" }, + { name = "openvino", marker = "extra == 'dev'", specifier = ">=2024.0" }, + { name = "openvino", marker = "extra == 'full'", specifier = ">=2024.0" }, + { name = "openvino", marker = "extra == 'openvino'", specifier = ">=2024.0" }, + { name = "pandas", specifier = ">=1.1.0" }, + { name = "pandoc", marker = "extra == 'dev'" }, + { name = "pandoc", marker = "extra == 'docs'" }, + { name = "pillow", specifier = ">=10.1.0" }, + { name = "pre-commit", marker = "extra == 'dev'" }, + { name = "pre-commit", marker = "extra == 'test'" }, + { name = "pytest", marker = "extra == 'dev'" }, + { name = "pytest", marker = "extra == 'test'" }, + { name = "pytest-cov", marker = "extra == 'dev'" }, + { name = "pytest-cov", marker = "extra == 'test'" }, + { name = "pytest-json-report", marker = "extra == 'dev'" }, + { name = "pytest-json-report", marker = "extra == 'test'" }, + { name = "pytest-mock", marker = "extra == 'dev'" }, + { name = "pytest-mock", marker = "extra == 'test'" }, + { name = "pytest-sugar", marker = "extra == 'dev'" }, + { name = "pytest-sugar", marker = "extra == 'test'" }, + { name = "pytest-timeout", marker = "extra == 'dev'" }, + { name = "pytest-timeout", marker = "extra == 'test'" }, + { name = "pytest-xdist", marker = "extra == 'dev'" }, + { name = "pytest-xdist", marker = "extra == 'test'" }, + { name = "python-dotenv", marker = "extra == 'dev'" }, + { name = "python-dotenv", marker = "extra == 'full'" }, + { name = "python-dotenv", marker = "extra == 'vlm'" }, + { name = "pytorch-triton-rocm", marker = "sys_platform == 'linux' and extra == 'rocm'", index = "https://download.pytorch.org/whl/rocm6.1", conflict = { package = "anomalib", extra = "rocm" } }, + { name = "pytorch-triton-xpu", marker = "(sys_platform == 'linux' and extra == 'xpu') or (sys_platform == 'win32' and extra == 'xpu')", index = "https://download.pytorch.org/whl/xpu", conflict = { package = "anomalib", extra = "xpu" } }, + { name = "rich", specifier = ">=13.5.2" }, + { name = "rich-argparse" }, + { name = "scikit-image" }, + { name = "scikit-learn" }, + { name = "sphinx", marker = "extra == 'dev'" }, + { name = "sphinx", marker = "extra == 'docs'" }, + { name = "sphinx-autodoc-typehints", marker = "extra == 'dev'" }, + { name = "sphinx-autodoc-typehints", marker = "extra == 'docs'" }, + { name = "sphinx-book-theme", marker = "extra == 'dev'" }, + { name = "sphinx-book-theme", marker = "extra == 'docs'" }, + { name = "sphinx-copybutton", marker = "extra == 'dev'" }, + { name = "sphinx-copybutton", marker = "extra == 'docs'" }, + { name = "sphinx-design", marker = "extra == 'dev'" }, + { name = "sphinx-design", marker = "extra == 'docs'" }, + { name = "tensorboard", marker = "extra == 'dev'" }, + { name = "tensorboard", marker = "extra == 'full'" }, + { name = "tensorboard", marker = "extra == 'loggers'" }, + { name = "tifffile" }, + { name = "timm" }, + { name = "torch", marker = "extra == 'cpu'", specifier = ">=2.4.0,<=2.8.0", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "anomalib", extra = "cpu" } }, + { name = "torch", marker = "extra == 'cu118'", specifier = ">=2.4.0,<=2.8.0", index = "https://download.pytorch.org/whl/cu118", conflict = { package = "anomalib", extra = "cu118" } }, + { name = "torch", marker = "extra == 'cu121'", specifier = ">=2.4.0,<=2.8.0", index = "https://download.pytorch.org/whl/cu121", conflict = { package = "anomalib", extra = "cu121" } }, + { name = "torch", marker = "extra == 'cu124'", specifier = ">=2.4.0,<=2.8.0", index = "https://download.pytorch.org/whl/cu124", conflict = { package = "anomalib", extra = "cu124" } }, + { name = "torch", marker = "extra == 'cu130'", specifier = ">=2.4.0", index = "https://download.pytorch.org/whl/cu130", conflict = { package = "anomalib", extra = "cu130" } }, + { name = "torch", marker = "extra == 'rocm'", specifier = ">=2.4.0,<=2.8.0", index = "https://download.pytorch.org/whl/rocm6.1", conflict = { package = "anomalib", extra = "rocm" } }, + { name = "torch", marker = "extra == 'xpu'", specifier = ">=2.4.0,<=2.8.0", index = "https://download.pytorch.org/whl/xpu", conflict = { package = "anomalib", extra = "xpu" } }, + { name = "torchmetrics", specifier = ">=1.8.2" }, + { name = "torchvision", marker = "extra == 'cpu'", specifier = ">=0.19.0", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "anomalib", extra = "cpu" } }, + { name = "torchvision", marker = "extra == 'cu118'", specifier = ">=0.19.0", index = "https://download.pytorch.org/whl/cu118", conflict = { package = "anomalib", extra = "cu118" } }, + { name = "torchvision", marker = "extra == 'cu121'", specifier = ">=0.19.0", index = "https://download.pytorch.org/whl/cu121", conflict = { package = "anomalib", extra = "cu121" } }, + { name = "torchvision", marker = "extra == 'cu124'", specifier = ">=0.19.0", index = "https://download.pytorch.org/whl/cu124", conflict = { package = "anomalib", extra = "cu124" } }, + { name = "torchvision", marker = "extra == 'cu130'", specifier = ">=0.19.0", index = "https://download.pytorch.org/whl/cu130", conflict = { package = "anomalib", extra = "cu130" } }, + { name = "torchvision", marker = "extra == 'rocm'", specifier = ">=0.19.0", index = "https://download.pytorch.org/whl/rocm6.1", conflict = { package = "anomalib", extra = "rocm" } }, + { name = "torchvision", marker = "extra == 'xpu'", specifier = ">=0.19.0", index = "https://download.pytorch.org/whl/xpu", conflict = { package = "anomalib", extra = "xpu" } }, + { name = "tox", marker = "extra == 'dev'" }, + { name = "tox", marker = "extra == 'test'" }, + { name = "transformers", marker = "extra == 'dev'" }, + { name = "transformers", marker = "extra == 'full'" }, + { name = "transformers", marker = "extra == 'vlm'" }, + { name = "wandb", marker = "extra == 'dev'" }, + { name = "wandb", marker = "extra == 'full'" }, + { name = "wandb", marker = "extra == 'loggers'" }, +] +provides-extras = ["application", "clip", "cpu", "cu118", "cu121", "cu124", "cu130", "dev", "docs", "full", "loggers", "notebooks", "openvino", "rocm", "test", "video", "vlm", "xpu"] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034, upload-time = "2021-11-06T17:52:23.524Z" } + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, +] + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960, upload-time = "2023-09-30T22:11:18.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419, upload-time = "2023-09-30T22:11:16.072Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, +] + +[[package]] +name = "async-lru" +version = "2.0.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/4d/71ec4d3939dc755264f680f6c2b4906423a304c3d18e96853f0a595dfe97/async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb", size = 10380, upload-time = "2025-03-16T17:25:36.919Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/49/d10027df9fce941cb8184e78a02857af36360d33e1721df81c5ed2179a1a/async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943", size = 6069, upload-time = "2025-03-16T17:25:35.422Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[package]] +name = "audioop-lts" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" }, + { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" }, + { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" }, + { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" }, + { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" }, + { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" }, + { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" }, + { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" }, + { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" }, + { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" }, + { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" }, + { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104, upload-time = "2025-08-05T16:42:58.518Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754, upload-time = "2025-08-05T16:43:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332, upload-time = "2025-08-05T16:43:01.666Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396, upload-time = "2025-08-05T16:43:02.991Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811, upload-time = "2025-08-05T16:43:04.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483, upload-time = "2025-08-05T16:43:05.085Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885, upload-time = "2025-08-05T16:43:06.197Z" }, + { url = "https://files.pythonhosted.org/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899, upload-time = "2025-08-05T16:43:07.291Z" }, + { url = "https://files.pythonhosted.org/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998, upload-time = "2025-08-05T16:43:08.335Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046, upload-time = "2025-08-05T16:43:09.367Z" }, + { url = "https://files.pythonhosted.org/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843, upload-time = "2025-08-05T16:43:10.749Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490, upload-time = "2025-08-05T16:43:12.131Z" }, + { url = "https://files.pythonhosted.org/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297, upload-time = "2025-08-05T16:43:13.139Z" }, + { url = "https://files.pythonhosted.org/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331, upload-time = "2025-08-05T16:43:14.19Z" }, + { url = "https://files.pythonhosted.org/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697, upload-time = "2025-08-05T16:43:15.193Z" }, + { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" }, +] + +[[package]] +name = "authlib" +version = "1.6.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/3f/1d3bbd0bf23bdd99276d4def22f29c27a914067b4cf66f753ff9b8bbd0f3/authlib-1.6.5.tar.gz", hash = "sha256:6aaf9c79b7cc96c900f0b284061691c5d4e61221640a948fe690b556a6d6d10b", size = 164553, upload-time = "2025-10-02T13:36:09.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" }, +] + +[[package]] +name = "autograd" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/1c/3c24ec03c8ba4decc742b1df5a10c52f98c84ca8797757f313e7bdcdf276/autograd-1.8.0.tar.gz", hash = "sha256:107374ded5b09fc8643ac925348c0369e7b0e73bbed9565ffd61b8fd04425683", size = 2562146, upload-time = "2025-05-05T12:49:02.502Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/ea/e16f0c423f7d83cf8b79cae9452040fb7b2e020c7439a167ee7c317de448/autograd-1.8.0-py3-none-any.whl", hash = "sha256:4ab9084294f814cf56c280adbe19612546a35574d67c574b04933c7d2ecb7d78", size = 51478, upload-time = "2025-05-05T12:49:00.585Z" }, +] + +[[package]] +name = "av" +version = "14.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/86/f6/0b473dab52dfdea05f28f3578b1c56b6c796ce85e76951bab7c4e38d5a74/av-14.4.0.tar.gz", hash = "sha256:3ecbf803a7fdf67229c0edada0830d6bfaea4d10bfb24f0c3f4e607cd1064b42", size = 3892203, upload-time = "2025-05-16T19:13:35.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/4c/b0205f77352312ff457ecdf31723dbf4403b7a03fc1659075d6d32f23ef7/av-14.4.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3d2aea7c602b105363903e4017103bc4b60336e7aff80e1c22e8b4ec09fd125f", size = 19917341, upload-time = "2025-05-16T19:10:18.826Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c4/9e783bd7d47828e9c67f9c773c99de45c5ae01b3e942f1abf6cbaf530267/av-14.4.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:38c18f036aeb6dc9abf5e867d998c867f9ec93a5f722b60721fdffc123bbb2ae", size = 23715363, upload-time = "2025-05-16T19:10:21.42Z" }, + { url = "https://files.pythonhosted.org/packages/b5/26/b2b406a676864d06b1c591205782d8527e7c99e5bc51a09862c3576e0087/av-14.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58c1e18c8be73b6eada2d9ec397852ec74ebe51938451bdf83644a807189d6c8", size = 33496968, upload-time = "2025-05-16T19:10:24.178Z" }, + { url = "https://files.pythonhosted.org/packages/89/09/0a032bbe30c7049fca243ec8cf01f4be49dd6e7f7b9c3c7f0cc13f83c9d3/av-14.4.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4c32ff03a357feb030634f093089a73cb474b04efe7fbfba31f229cb2fab115", size = 32075498, upload-time = "2025-05-16T19:10:27.384Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/0fee20f74c1f48086366e59dbd37fa0684cd0f3c782a65cbb719d26c7acd/av-14.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af31d16ae25964a6a02e09cc132b9decd5ee493c5dcb21bcdf0d71b2d6adbd59", size = 35224910, upload-time = "2025-05-16T19:10:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/9e/19/1c4a201c75a2a431a85a43fd15d1fad55a28c22d596461d861c8d70f9b92/av-14.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9fb297009e528f4851d25f3bb2781b2db18b59b10aed10240e947b77c582fb7", size = 36172918, upload-time = "2025-05-16T19:10:32.789Z" }, + { url = "https://files.pythonhosted.org/packages/00/48/26b7e5d911c807f5f017a285362470ba16f44e8ea46f8b09ab5e348dd15b/av-14.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:573314cb9eafec2827dc98c416c965330dc7508193adbccd281700d8673b9f0a", size = 34414492, upload-time = "2025-05-16T19:10:36.023Z" }, + { url = "https://files.pythonhosted.org/packages/6d/26/2f4badfa5b5b7b8f5f83d562b143a83ed940fa458eea4cad495ce95c9741/av-14.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f82ab27ee57c3b80eb50a5293222307dfdc02f810ea41119078cfc85ea3cf9a8", size = 37245826, upload-time = "2025-05-16T19:10:39.562Z" }, + { url = "https://files.pythonhosted.org/packages/f4/02/88dbb6f5a05998b730d2e695b05060297af127ac4250efbe0739daa446d5/av-14.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f682003bbcaac620b52f68ff0e85830fff165dea53949e217483a615993ca20", size = 27898395, upload-time = "2025-05-16T19:13:02.653Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, +] + +[[package]] +name = "bleach" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083, upload-time = "2024-10-29T18:30:40.477Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406, upload-time = "2024-10-29T18:30:38.186Z" }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "brotli" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270, upload-time = "2023-09-07T14:05:41.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681, upload-time = "2024-10-18T12:32:34.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475, upload-time = "2024-10-18T12:32:36.485Z" }, + { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173, upload-time = "2024-10-18T12:32:37.978Z" }, + { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803, upload-time = "2024-10-18T12:32:39.606Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946, upload-time = "2024-10-18T12:32:41.679Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707, upload-time = "2024-10-18T12:32:43.478Z" }, + { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231, upload-time = "2024-10-18T12:32:45.224Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157, upload-time = "2024-10-18T12:32:46.894Z" }, + { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122, upload-time = "2024-10-18T12:32:48.844Z" }, + { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206, upload-time = "2024-10-18T12:32:51.198Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804, upload-time = "2024-10-18T12:32:52.661Z" }, + { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517, upload-time = "2024-10-18T12:32:54.066Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/61/e4fad8155db4a04bfb4734c7c8ff0882f078f24294d42798b3568eb63bff/cachetools-6.2.0.tar.gz", hash = "sha256:38b328c0889450f05f5e120f56ab68c8abaf424e1275522b138ffc93253f7e32", size = 30988, upload-time = "2025-08-25T18:57:30.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/56/3124f61d37a7a4e7cc96afc5492c78ba0cb551151e530b54669ddd1436ef/cachetools-6.2.0-py3-none-any.whl", hash = "sha256:1c76a8960c0041fcc21097e357f882197c79da0dbff766e7317890a65d7d8ba6", size = 11276, upload-time = "2025-08-25T18:57:29.684Z" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/39/069100b84d7418bc358d81669d5748efb14b9cceacd2f9c75f550424132f/cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64", size = 22113, upload-time = "2025-01-14T17:02:05.085Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e", size = 20992, upload-time = "2025-01-14T17:02:02.417Z" }, +] + +[[package]] +name = "cma" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/b8/33083c988054b23c2df09f2b45f922410a86f60bd4be1ab36c74f72d753b/cma-4.4.0.tar.gz", hash = "sha256:de89664f2a8522c74e40e19d26be51380d41082ea2dcefbd5943e0d0d90bd92c", size = 292657, upload-time = "2025-09-20T20:40:32.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/b4/b3c1258c014cb18fdc1920f08bada2eccb7a49e39b9f68c3552ec8acfadf/cma-4.4.0-py3-none-any.whl", hash = "sha256:46da7f95056b02496f4117269026dce3953ef0ae89717267540566effb85b052", size = 303789, upload-time = "2025-09-20T20:40:26.583Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comet-ml" +version = "3.53.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dulwich" }, + { name = "everett", extra = ["ini"] }, + { name = "jsonschema" }, + { name = "psutil" }, + { name = "python-box" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "rich" }, + { name = "semantic-version" }, + { name = "sentry-sdk" }, + { name = "setuptools" }, + { name = "simplejson" }, + { name = "urllib3" }, + { name = "wrapt" }, + { name = "wurlitzer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/07/5603bcc9fd8ba6bf5cd4c82881afbf61973efa3939123e05bfe7784027c4/comet_ml-3.53.0.tar.gz", hash = "sha256:29831eea50cd8f99f25da07486c936493c06013900b91c2bf124b3973dd6e2d0", size = 564526, upload-time = "2025-09-23T11:27:09.569Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/6f/e54e281cda223d1799bf4d8867435a10928ff85b99af95e137fec418b1b1/comet_ml-3.53.0-py3-none-any.whl", hash = "sha256:4d18a007c56f4d84c30c165bb4310038a8158df3c1f93381d00f481054655327", size = 764649, upload-time = "2025-09-23T11:27:08.014Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "configobj" +version = "5.0.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/c4/c7f9e41bc2e5f8eeae4a08a01c91b2aea3dfab40a3e14b25e87e7db8d501/configobj-5.0.9.tar.gz", hash = "sha256:03c881bbf23aa07bccf1b837005975993c4ab4427ba57f959afdd9d1a2386848", size = 101518, upload-time = "2024-09-21T12:47:46.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/c4/0679472c60052c27efa612b4cd3ddd2a23e885dcdc73461781d2c802d39e/configobj-5.0.9-py2.py3-none-any.whl", hash = "sha256:1ba10c5b6ee16229c79a05047aeda2b55eb4e80d7c7d8ecf17ec1ca600c79882", size = 35615, upload-time = "2024-11-26T14:03:32.972Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, +] + +[[package]] +name = "cryptography" +version = "45.0.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/35/c495bffc2056f2dadb32434f1feedd79abde2a7f8363e1974afa9c33c7e2/cryptography-45.0.7.tar.gz", hash = "sha256:4b1654dfc64ea479c242508eb8c724044f1e964a47d1d1cacc5132292d851971", size = 744980, upload-time = "2025-09-01T11:15:03.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/91/925c0ac74362172ae4516000fe877912e33b5983df735ff290c653de4913/cryptography-45.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3be4f21c6245930688bd9e162829480de027f8bf962ede33d4f8ba7d67a00cee", size = 7041105, upload-time = "2025-09-01T11:13:59.684Z" }, + { url = "https://files.pythonhosted.org/packages/fc/63/43641c5acce3a6105cf8bd5baeceeb1846bb63067d26dae3e5db59f1513a/cryptography-45.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67285f8a611b0ebc0857ced2081e30302909f571a46bfa7a3cc0ad303fe015c6", size = 4205799, upload-time = "2025-09-01T11:14:02.517Z" }, + { url = "https://files.pythonhosted.org/packages/bc/29/c238dd9107f10bfde09a4d1c52fd38828b1aa353ced11f358b5dd2507d24/cryptography-45.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:577470e39e60a6cd7780793202e63536026d9b8641de011ed9d8174da9ca5339", size = 4430504, upload-time = "2025-09-01T11:14:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/62/62/24203e7cbcc9bd7c94739428cd30680b18ae6b18377ae66075c8e4771b1b/cryptography-45.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4bd3e5c4b9682bc112d634f2c6ccc6736ed3635fc3319ac2bb11d768cc5a00d8", size = 4209542, upload-time = "2025-09-01T11:14:06.309Z" }, + { url = "https://files.pythonhosted.org/packages/cd/e3/e7de4771a08620eef2389b86cd87a2c50326827dea5528feb70595439ce4/cryptography-45.0.7-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:465ccac9d70115cd4de7186e60cfe989de73f7bb23e8a7aa45af18f7412e75bf", size = 3889244, upload-time = "2025-09-01T11:14:08.152Z" }, + { url = "https://files.pythonhosted.org/packages/96/b8/bca71059e79a0bb2f8e4ec61d9c205fbe97876318566cde3b5092529faa9/cryptography-45.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:16ede8a4f7929b4b7ff3642eba2bf79aa1d71f24ab6ee443935c0d269b6bc513", size = 4461975, upload-time = "2025-09-01T11:14:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/58/67/3f5b26937fe1218c40e95ef4ff8d23c8dc05aa950d54200cc7ea5fb58d28/cryptography-45.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8978132287a9d3ad6b54fcd1e08548033cc09dc6aacacb6c004c73c3eb5d3ac3", size = 4209082, upload-time = "2025-09-01T11:14:11.229Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e4/b3e68a4ac363406a56cf7b741eeb80d05284d8c60ee1a55cdc7587e2a553/cryptography-45.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b6a0e535baec27b528cb07a119f321ac024592388c5681a5ced167ae98e9fff3", size = 4460397, upload-time = "2025-09-01T11:14:12.924Z" }, + { url = "https://files.pythonhosted.org/packages/22/49/2c93f3cd4e3efc8cb22b02678c1fad691cff9dd71bb889e030d100acbfe0/cryptography-45.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a24ee598d10befaec178efdff6054bc4d7e883f615bfbcd08126a0f4931c83a6", size = 4337244, upload-time = "2025-09-01T11:14:14.431Z" }, + { url = "https://files.pythonhosted.org/packages/04/19/030f400de0bccccc09aa262706d90f2ec23d56bc4eb4f4e8268d0ddf3fb8/cryptography-45.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:fa26fa54c0a9384c27fcdc905a2fb7d60ac6e47d14bc2692145f2b3b1e2cfdbd", size = 4568862, upload-time = "2025-09-01T11:14:16.185Z" }, + { url = "https://files.pythonhosted.org/packages/29/56/3034a3a353efa65116fa20eb3c990a8c9f0d3db4085429040a7eef9ada5f/cryptography-45.0.7-cp311-abi3-win32.whl", hash = "sha256:bef32a5e327bd8e5af915d3416ffefdbe65ed975b646b3805be81b23580b57b8", size = 2936578, upload-time = "2025-09-01T11:14:17.638Z" }, + { url = "https://files.pythonhosted.org/packages/b3/61/0ab90f421c6194705a99d0fa9f6ee2045d916e4455fdbb095a9c2c9a520f/cryptography-45.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:3808e6b2e5f0b46d981c24d79648e5c25c35e59902ea4391a0dcb3e667bf7443", size = 3405400, upload-time = "2025-09-01T11:14:18.958Z" }, + { url = "https://files.pythonhosted.org/packages/63/e8/c436233ddf19c5f15b25ace33979a9dd2e7aa1a59209a0ee8554179f1cc0/cryptography-45.0.7-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bfb4c801f65dd61cedfc61a83732327fafbac55a47282e6f26f073ca7a41c3b2", size = 7021824, upload-time = "2025-09-01T11:14:20.954Z" }, + { url = "https://files.pythonhosted.org/packages/bc/4c/8f57f2500d0ccd2675c5d0cc462095adf3faa8c52294ba085c036befb901/cryptography-45.0.7-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:81823935e2f8d476707e85a78a405953a03ef7b7b4f55f93f7c2d9680e5e0691", size = 4202233, upload-time = "2025-09-01T11:14:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ac/59b7790b4ccaed739fc44775ce4645c9b8ce54cbec53edf16c74fd80cb2b/cryptography-45.0.7-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3994c809c17fc570c2af12c9b840d7cea85a9fd3e5c0e0491f4fa3c029216d59", size = 4423075, upload-time = "2025-09-01T11:14:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/d4f07ea21434bf891faa088a6ac15d6d98093a66e75e30ad08e88aa2b9ba/cryptography-45.0.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dad43797959a74103cb59c5dac71409f9c27d34c8a05921341fb64ea8ccb1dd4", size = 4204517, upload-time = "2025-09-01T11:14:25.679Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/924a723299848b4c741c1059752c7cfe09473b6fd77d2920398fc26bfb53/cryptography-45.0.7-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ce7a453385e4c4693985b4a4a3533e041558851eae061a58a5405363b098fcd3", size = 3882893, upload-time = "2025-09-01T11:14:27.1Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/4dab2ff0a871cc2d81d3ae6d780991c0192b259c35e4d83fe1de18b20c70/cryptography-45.0.7-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b04f85ac3a90c227b6e5890acb0edbaf3140938dbecf07bff618bf3638578cf1", size = 4450132, upload-time = "2025-09-01T11:14:28.58Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/b2882b65db8fc944585d7fb00d67cf84a9cef4e77d9ba8f69082e911d0de/cryptography-45.0.7-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:48c41a44ef8b8c2e80ca4527ee81daa4c527df3ecbc9423c41a420a9559d0e27", size = 4204086, upload-time = "2025-09-01T11:14:30.572Z" }, + { url = "https://files.pythonhosted.org/packages/5d/fa/1d5745d878048699b8eb87c984d4ccc5da4f5008dfd3ad7a94040caca23a/cryptography-45.0.7-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f3df7b3d0f91b88b2106031fd995802a2e9ae13e02c36c1fc075b43f420f3a17", size = 4449383, upload-time = "2025-09-01T11:14:32.046Z" }, + { url = "https://files.pythonhosted.org/packages/36/8b/fc61f87931bc030598e1876c45b936867bb72777eac693e905ab89832670/cryptography-45.0.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd342f085542f6eb894ca00ef70236ea46070c8a13824c6bde0dfdcd36065b9b", size = 4332186, upload-time = "2025-09-01T11:14:33.95Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/09700ddad7443ccb11d674efdbe9a832b4455dc1f16566d9bd3834922ce5/cryptography-45.0.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1993a1bb7e4eccfb922b6cd414f072e08ff5816702a0bdb8941c247a6b1b287c", size = 4561639, upload-time = "2025-09-01T11:14:35.343Z" }, + { url = "https://files.pythonhosted.org/packages/71/ed/8f4c1337e9d3b94d8e50ae0b08ad0304a5709d483bfcadfcc77a23dbcb52/cryptography-45.0.7-cp37-abi3-win32.whl", hash = "sha256:18fcf70f243fe07252dcb1b268a687f2358025ce32f9f88028ca5c364b123ef5", size = 2926552, upload-time = "2025-09-01T11:14:36.929Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ff/026513ecad58dacd45d1d24ebe52b852165a26e287177de1d545325c0c25/cryptography-45.0.7-cp37-abi3-win_amd64.whl", hash = "sha256:7285a89df4900ed3bfaad5679b1e668cb4b38a8de1ccbfc84b05f34512da0a90", size = 3392742, upload-time = "2025-09-01T11:14:38.368Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "cyclopts" +version = "3.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "docstring-parser", marker = "python_full_version < '4'" }, + { name = "rich" }, + { name = "rich-rst" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/ca/7782da3b03242d5f0a16c20371dff99d4bd1fedafe26bc48ff82e42be8c9/cyclopts-3.24.0.tar.gz", hash = "sha256:de6964a041dfb3c57bf043b41e68c43548227a17de1bad246e3a0bfc5c4b7417", size = 76131, upload-time = "2025-09-08T15:40:57.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/8b/2c95f0645c6f40211896375e6fa51f504b8ccb29c21f6ae661fe87ab044e/cyclopts-3.24.0-py3-none-any.whl", hash = "sha256:809d04cde9108617106091140c3964ee6fceb33cecdd537f7ffa360bde13ed71", size = 86154, upload-time = "2025-09-08T15:40:56.41Z" }, +] + +[[package]] +name = "databricks-sdk" +version = "0.67.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/5b/df3e5424d833e4f3f9b42c409ef8b513e468c9cdf06c2a9935c6cbc4d128/databricks_sdk-0.67.0.tar.gz", hash = "sha256:f923227babcaad428b0c2eede2755ebe9deb996e2c8654f179eb37f486b37a36", size = 761000, upload-time = "2025-09-25T13:32:10.858Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/ca/2aff3817041483fb8e4f75a74a36ff4ca3a826e276becd1179a591b6348f/databricks_sdk-0.67.0-py3-none-any.whl", hash = "sha256:ef49e49db45ed12c015a32a6f9d4ba395850f25bb3dcffdcaf31a5167fe03ee2", size = 718422, upload-time = "2025-09-25T13:32:09.011Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/ad/71e708ff4ca377c4230530d6a7aa7992592648c122a2cd2b321cf8b35a76/debugpy-1.8.17.tar.gz", hash = "sha256:fd723b47a8c08892b1a16b2c6239a8b96637c62a59b94bb5dab4bac592a58a8e", size = 1644129, upload-time = "2025-09-17T16:33:20.633Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/76/597e5cb97d026274ba297af8d89138dfd9e695767ba0e0895edb20963f40/debugpy-1.8.17-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:857c1dd5d70042502aef1c6d1c2801211f3ea7e56f75e9c335f434afb403e464", size = 2538386, upload-time = "2025-09-17T16:33:54.594Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/ce5c34fcdfec493701f9d1532dba95b21b2f6394147234dce21160bd923f/debugpy-1.8.17-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:3bea3b0b12f3946e098cce9b43c3c46e317b567f79570c3f43f0b96d00788088", size = 4292100, upload-time = "2025-09-17T16:33:56.353Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/7873cf2146577ef71d2a20bf553f12df865922a6f87b9e8ee1df04f01785/debugpy-1.8.17-cp313-cp313-win32.whl", hash = "sha256:e34ee844c2f17b18556b5bbe59e1e2ff4e86a00282d2a46edab73fd7f18f4a83", size = 5277002, upload-time = "2025-09-17T16:33:58.231Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/18c79a1cee5ff539a94ec4aa290c1c069a5580fd5cfd2fb2e282f8e905da/debugpy-1.8.17-cp313-cp313-win_amd64.whl", hash = "sha256:6c5cd6f009ad4fca8e33e5238210dc1e5f42db07d4b6ab21ac7ffa904a196420", size = 5319047, upload-time = "2025-09-17T16:34:00.586Z" }, + { url = "https://files.pythonhosted.org/packages/de/45/115d55b2a9da6de812696064ceb505c31e952c5d89c4ed1d9bb983deec34/debugpy-1.8.17-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:045290c010bcd2d82bc97aa2daf6837443cd52f6328592698809b4549babcee1", size = 2536899, upload-time = "2025-09-17T16:34:02.657Z" }, + { url = "https://files.pythonhosted.org/packages/5a/73/2aa00c7f1f06e997ef57dc9b23d61a92120bec1437a012afb6d176585197/debugpy-1.8.17-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:b69b6bd9dba6a03632534cdf67c760625760a215ae289f7489a452af1031fe1f", size = 4268254, upload-time = "2025-09-17T16:34:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/86/b5/ed3e65c63c68a6634e3ba04bd10255c8e46ec16ebed7d1c79e4816d8a760/debugpy-1.8.17-cp314-cp314-win32.whl", hash = "sha256:5c59b74aa5630f3a5194467100c3b3d1c77898f9ab27e3f7dc5d40fc2f122670", size = 5277203, upload-time = "2025-09-17T16:34:06.65Z" }, + { url = "https://files.pythonhosted.org/packages/b0/26/394276b71c7538445f29e792f589ab7379ae70fd26ff5577dfde71158e96/debugpy-1.8.17-cp314-cp314-win_amd64.whl", hash = "sha256:893cba7bb0f55161de4365584b025f7064e1f88913551bcd23be3260b231429c", size = 5318493, upload-time = "2025-09-17T16:34:08.483Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d0/89247ec250369fc76db477720a26b2fce7ba079ff1380e4ab4529d2fe233/debugpy-1.8.17-py2.py3-none-any.whl", hash = "sha256:60c7dca6571efe660ccb7a9508d73ca14b8796c4ed484c2002abba714226cfef", size = 5283210, upload-time = "2025-09-17T16:34:25.835Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "deprecated" +version = "1.2.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" }, +] + +[[package]] +name = "dill" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "docker" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/9b/4a2ea29aeba62471211598dac5d96825bb49348fa07e906ea930394a83ce/docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", size = 117834, upload-time = "2024-05-23T11:13:57.216Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, +] + +[[package]] +name = "docstring-parser" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/c0/89fe6215b443b919cb98a5002e107cb5026854ed1ccb6b5833e0768419d1/docutils-0.22.2.tar.gz", hash = "sha256:9fdb771707c8784c8f2728b67cb2c691305933d68137ef95a75db5f4dfbc213d", size = 2289092, upload-time = "2025-09-20T17:55:47.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/dd/f95350e853a4468ec37478414fc04ae2d61dad7a947b3015c3dcc51a09b9/docutils-0.22.2-py3-none-any.whl", hash = "sha256:b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8", size = 632667, upload-time = "2025-09-20T17:55:43.052Z" }, +] + +[[package]] +name = "dulwich" +version = "0.24.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/d0/b1275f9609e0d32800daf72786e5bec5602e71dde8db4995d0420d6b5cec/dulwich-0.24.2.tar.gz", hash = "sha256:d474844cf81bf95a6537a80aeec59d714d5d77d8e83d6d37991e2bde54746ca7", size = 883284, upload-time = "2025-09-26T09:11:38.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/e8/6a2ba2df778f39e302d583b4bb7b0898e2d9b8d152138f11754a32cae16b/dulwich-0.24.2-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:e237e3724d80be09e8e45f9f9a86342c9c0911ad3fd71fb66f17b20df47c0049", size = 1219066, upload-time = "2025-09-26T09:11:14.953Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bb/dd56611414566483e94add8e27974bfb49a340659bf06a03b55cecada74c/dulwich-0.24.2-cp313-cp313-android_21_x86_64.whl", hash = "sha256:8e65c091aacf0d9da167e50f97e203947ae9e439ee8b020915b1decf9ecfdd6d", size = 1219056, upload-time = "2025-09-26T09:11:17.177Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f5/7ccbf02e1067447418dca45d77ae73a3515c52a54187410107e68c78c494/dulwich-0.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:150b17b30a78c09f105b787ce74ccb094e9a27ada77d25a9df78758b858ee034", size = 1130898, upload-time = "2025-09-26T09:11:19.199Z" }, + { url = "https://files.pythonhosted.org/packages/4a/05/8f6909bdd0566b8a5a3faeecc9468efdcd2baa7f17716bdd693e0a13511e/dulwich-0.24.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:540928778a36d468cd98d6d6251af307e9f0743e824fe87189936ca0a88b10fb", size = 1216337, upload-time = "2025-09-26T09:11:20.944Z" }, + { url = "https://files.pythonhosted.org/packages/a8/98/844f9777bcec46683c99b7dd35c880af7dc8fa2aecb1ca2caed4e17f9afb/dulwich-0.24.2-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:fbc6c4ba4b54919d32ba516ea397008e9d1c4d774b87355c5e0fd320d3d86396", size = 1241749, upload-time = "2025-09-26T09:11:22.647Z" }, + { url = "https://files.pythonhosted.org/packages/19/c2/bcb0c89ed9b38c7de1863df5cac036073ca17eb2aa2e38f1df2d6907629b/dulwich-0.24.2-cp313-cp313-win32.whl", hash = "sha256:bc5452099ecb27970c17f8fede63f426509e7668361b9c7b0f495d498bf67c02", size = 816176, upload-time = "2025-09-26T09:11:24.635Z" }, + { url = "https://files.pythonhosted.org/packages/34/b3/26f9b65faf4f368b282e943aaf7f73bacaf584c24c7a6ff48958740e2e40/dulwich-0.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:09c86b889a344667d050a8e0849816ed2f376d33ec9f8be302129a79ca65845b", size = 832605, upload-time = "2025-09-26T09:11:26.208Z" }, + { url = "https://files.pythonhosted.org/packages/ec/42/58ce26920e32f17c39541fe0b8dd7b758bbf59063ff9bacb2496201c006b/dulwich-0.24.2-py3-none-any.whl", hash = "sha256:07426d209e870bd869d6eefb03573cf4879e62b30f690c53cc525b602ea413e5", size = 495365, upload-time = "2025-09-26T09:11:37.402Z" }, +] + +[[package]] +name = "einops" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/81/df4fbe24dff8ba3934af99044188e20a98ed441ad17a274539b74e82e126/einops-0.8.1.tar.gz", hash = "sha256:de5d960a7a761225532e0f1959e5315ebeafc0cd43394732f103ca44b9837e84", size = 54805, upload-time = "2025-02-09T03:17:00.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl", hash = "sha256:919387eb55330f5757c6bea9165c5ff5cfe63a642682ea788a6d472576d81737", size = 64359, upload-time = "2025-02-09T03:17:01.998Z" }, +] + +[[package]] +name = "email-validator" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, +] + +[[package]] +name = "everett" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/b4/c7c61c0b243c4277d19299cd1bccee8b2b57d04073c0d8625799fe47f5c9/everett-3.1.0.tar.gz", hash = "sha256:46175da5bcb06c193aa129e59714bca981344ff067c3a8bc2e625bc0b3dc01f6", size = 73796, upload-time = "2022-10-26T15:15:00.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/9a/d882fd7562208456236fb2e62b762bf16fbc9ecde842bb871f676ca0f7e1/everett-3.1.0-py2.py3-none-any.whl", hash = "sha256:db13891b849e45e54faea93ee79881d12458c5378f5b9b7f806eeff03ce1de3c", size = 35702, upload-time = "2022-10-26T15:14:58.698Z" }, +] + +[package.optional-dependencies] +ini = [ + { name = "configobj" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fancycompleter" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/4c/d11187dee93eff89d082afda79b63c79320ae1347e49485a38f05ad359d0/fancycompleter-0.11.1.tar.gz", hash = "sha256:5b4ad65d76b32b1259251516d0f1cb2d82832b1ff8506697a707284780757f69", size = 341776, upload-time = "2025-05-26T12:59:11.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/c3/6f0e3896f193528bbd2b4d2122d4be8108a37efab0b8475855556a8c4afa/fancycompleter-0.11.1-py3-none-any.whl", hash = "sha256:44243d7fab37087208ca5acacf8f74c0aa4d733d04d593857873af7513cdf8a6", size = 11207, upload-time = "2025-05-26T12:59:09.857Z" }, +] + +[[package]] +name = "fastapi" +version = "0.118.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/3c/2b9345a6504e4055eaa490e0b41c10e338ad61d9aeaae41d97807873cdf2/fastapi-0.118.0.tar.gz", hash = "sha256:5e81654d98c4d2f53790a7d32d25a7353b30c81441be7d0958a26b5d761fa1c8", size = 310536, upload-time = "2025-09-29T03:37:23.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/54e2bdaad22ca91a59455251998d43094d5c3d3567c52c7c04774b3f43f2/fastapi-0.118.0-py3-none-any.whl", hash = "sha256:705137a61e2ef71019d2445b123aa8845bd97273c395b744d5a7dfe559056855", size = 97694, upload-time = "2025-09-29T03:37:21.338Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "email-validator" }, + { name = "fastapi-cli", extra = ["standard"] }, + { name = "httpx" }, + { name = "jinja2" }, + { name = "python-multipart" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "fastapi-cli" +version = "0.0.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rich-toolkit" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/4e/3f61850012473b097fc5297d681bd85788e186fadb8555b67baf4c7707f4/fastapi_cli-0.0.13.tar.gz", hash = "sha256:312addf3f57ba7139457cf0d345c03e2170cc5a034057488259c33cd7e494529", size = 17780, upload-time = "2025-09-20T16:37:31.089Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/36/7432750f3638324b055496d2c952000bea824259fca70df5577a6a3c172f/fastapi_cli-0.0.13-py3-none-any.whl", hash = "sha256:219b73ccfde7622559cef1d43197da928516acb4f21f2ec69128c4b90057baba", size = 11142, upload-time = "2025-09-20T16:37:29.695Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "fastapi-cloud-cli" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "fastapi-cloud-cli" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic", extra = ["email"] }, + { name = "rich-toolkit" }, + { name = "rignore" }, + { name = "sentry-sdk" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/5f/17b403148a23dd708e3166f534136f4d3918942e168aca66659311eb0678/fastapi_cloud_cli-0.3.0.tar.gz", hash = "sha256:17c7f8baa16b2f907696bf77d49df4a04e8715bbf5233024f273870f3ff1ca4d", size = 24388, upload-time = "2025-10-02T13:25:52.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/59/7d12c5173fe2eed21e99bb1a6eb7e4f301951db870a4d915d126e0b6062d/fastapi_cloud_cli-0.3.0-py3-none-any.whl", hash = "sha256:572677dbe38b6d4712d30097a8807b383d648ca09eb58e4a07cef4a517020832", size = 19921, upload-time = "2025-10-02T13:25:51.164Z" }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, +] + +[[package]] +name = "fastmcp" +version = "2.12.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "authlib" }, + { name = "cyclopts" }, + { name = "exceptiongroup" }, + { name = "httpx" }, + { name = "mcp" }, + { name = "openapi-core" }, + { name = "openapi-pydantic" }, + { name = "pydantic", extra = ["email"] }, + { name = "pyperclip" }, + { name = "python-dotenv" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/b2/57845353a9bc63002995a982e66f3d0be4ec761e7bcb89e7d0638518d42a/fastmcp-2.12.4.tar.gz", hash = "sha256:b55fe89537038f19d0f4476544f9ca5ac171033f61811cc8f12bdeadcbea5016", size = 7167745, upload-time = "2025-09-26T16:43:27.71Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/c7/562ff39f25de27caec01e4c1e88cbb5fcae5160802ba3d90be33165df24f/fastmcp-2.12.4-py3-none-any.whl", hash = "sha256:56188fbbc1a9df58c537063f25958c57b5c4d715f73e395c41b51550b247d140", size = 329090, upload-time = "2025-09-26T16:43:25.314Z" }, +] + +[[package]] +name = "ffmpy" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/f6/67cadf1686030be511004e75fa1c1397f8f193cd4d15d4788edef7c28621/ffmpy-0.6.1.tar.gz", hash = "sha256:b5830fd05f72bace05b8fb28724d54a7a63c5119d7f74ca36a75df33f749142d", size = 4958, upload-time = "2025-07-22T12:08:22.276Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/d4/1806897b31c480efc4e97c22506ac46c716084f573aef780bb7fb7a16e8a/ffmpy-0.6.1-py3-none-any.whl", hash = "sha256:69a37e2d7d6feb840e233d5640f3499a8b0a8657336774c86e4c52a3219222d4", size = 5512, upload-time = "2025-07-22T12:08:21.176Z" }, +] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "flaky" +version = "3.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/c5/ef69119a01427204ff2db5fc8f98001087bcce719bbb94749dcd7b191365/flaky-3.8.1.tar.gz", hash = "sha256:47204a81ec905f3d5acfbd61daeabcada8f9d4031616d9bcb0618461729699f5", size = 25248, upload-time = "2024-03-12T22:17:59.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/b8/b830fc43663246c3f3dd1ae7dca4847b96ed992537e85311e27fa41ac40e/flaky-3.8.1-py2.py3-none-any.whl", hash = "sha256:194ccf4f0d3a22b2de7130f4b62e45e977ac1b5ccad74d4d48f3005dcc38815e", size = 19139, upload-time = "2024-03-12T22:17:51.59Z" }, +] + +[[package]] +name = "flask" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, +] + +[[package]] +name = "fonttools" +version = "4.60.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb", size = 2825777, upload-time = "2025-09-29T21:12:01.22Z" }, + { url = "https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4", size = 2348080, upload-time = "2025-09-29T21:12:03.785Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c", size = 4903082, upload-time = "2025-09-29T21:12:06.382Z" }, + { url = "https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77", size = 4960125, upload-time = "2025-09-29T21:12:09.314Z" }, + { url = "https://files.pythonhosted.org/packages/8e/37/f3b840fcb2666f6cb97038793606bdd83488dca2d0b0fc542ccc20afa668/fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199", size = 4901454, upload-time = "2025-09-29T21:12:11.931Z" }, + { url = "https://files.pythonhosted.org/packages/fd/9e/eb76f77e82f8d4a46420aadff12cec6237751b0fb9ef1de373186dcffb5f/fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c", size = 5044495, upload-time = "2025-09-29T21:12:15.241Z" }, + { url = "https://files.pythonhosted.org/packages/f8/b3/cede8f8235d42ff7ae891bae8d619d02c8ac9fd0cfc450c5927a6200c70d/fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272", size = 2217028, upload-time = "2025-09-29T21:12:17.96Z" }, + { url = "https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac", size = 2266200, upload-time = "2025-09-29T21:12:20.14Z" }, + { url = "https://files.pythonhosted.org/packages/9a/83/752ca11c1aa9a899b793a130f2e466b79ea0cf7279c8d79c178fc954a07b/fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", size = 2822830, upload-time = "2025-09-29T21:12:24.406Z" }, + { url = "https://files.pythonhosted.org/packages/57/17/bbeab391100331950a96ce55cfbbff27d781c1b85ebafb4167eae50d9fe3/fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", size = 2345524, upload-time = "2025-09-29T21:12:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2e/d4831caa96d85a84dd0da1d9f90d81cec081f551e0ea216df684092c6c97/fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", size = 4843490, upload-time = "2025-09-29T21:12:29.123Z" }, + { url = "https://files.pythonhosted.org/packages/49/13/5e2ea7c7a101b6fc3941be65307ef8df92cbbfa6ec4804032baf1893b434/fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", size = 4944184, upload-time = "2025-09-29T21:12:31.414Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2b/cf9603551c525b73fc47c52ee0b82a891579a93d9651ed694e4e2cd08bb8/fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", size = 4890218, upload-time = "2025-09-29T21:12:33.936Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2f/933d2352422e25f2376aae74f79eaa882a50fb3bfef3c0d4f50501267101/fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", size = 4999324, upload-time = "2025-09-29T21:12:36.637Z" }, + { url = "https://files.pythonhosted.org/packages/38/99/234594c0391221f66216bc2c886923513b3399a148defaccf81dc3be6560/fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", size = 2220861, upload-time = "2025-09-29T21:12:39.108Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1d/edb5b23726dde50fc4068e1493e4fc7658eeefcaf75d4c5ffce067d07ae5/fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", size = 2270934, upload-time = "2025-09-29T21:12:41.339Z" }, + { url = "https://files.pythonhosted.org/packages/fb/da/1392aaa2170adc7071fe7f9cfd181a5684a7afcde605aebddf1fb4d76df5/fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", size = 2894340, upload-time = "2025-09-29T21:12:43.774Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a7/3b9f16e010d536ce567058b931a20b590d8f3177b2eda09edd92e392375d/fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", size = 2375073, upload-time = "2025-09-29T21:12:46.437Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b5/e9bcf51980f98e59bb5bb7c382a63c6f6cac0eec5f67de6d8f2322382065/fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", size = 4849758, upload-time = "2025-09-29T21:12:48.694Z" }, + { url = "https://files.pythonhosted.org/packages/e3/dc/1d2cf7d1cba82264b2f8385db3f5960e3d8ce756b4dc65b700d2c496f7e9/fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", size = 5085598, upload-time = "2025-09-29T21:12:51.081Z" }, + { url = "https://files.pythonhosted.org/packages/5d/4d/279e28ba87fb20e0c69baf72b60bbf1c4d873af1476806a7b5f2b7fac1ff/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", size = 4957603, upload-time = "2025-09-29T21:12:53.423Z" }, + { url = "https://files.pythonhosted.org/packages/78/d4/ff19976305e0c05aa3340c805475abb00224c954d3c65e82c0a69633d55d/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", size = 4974184, upload-time = "2025-09-29T21:12:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/63/22/8553ff6166f5cd21cfaa115aaacaa0dc73b91c079a8cfd54a482cbc0f4f5/fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", size = 2282241, upload-time = "2025-09-29T21:12:58.179Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cb/fa7b4d148e11d5a72761a22e595344133e83a9507a4c231df972e657579b/fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", size = 2345760, upload-time = "2025-09-29T21:13:00.375Z" }, + { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, +] + +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, +] + +[[package]] +name = "freezegun" +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/dd/23e2f4e357f8fd3bdff613c1fe4466d21bfb00a6177f238079b17f7b1c84/freezegun-1.5.5.tar.gz", hash = "sha256:ac7742a6cc6c25a2c35e9292dfd554b897b517d2dec26891a2e8debf205cb94a", size = 35914, upload-time = "2025-08-09T10:39:08.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/2e/b41d8a1a917d6581fc27a35d05561037b048e47df50f27f8ac9c7e27a710/freezegun-1.5.5-py3-none-any.whl", hash = "sha256:cd557f4a75cf074e84bc374249b9dd491eaeacd61376b9eb3c423282211619d2", size = 19266, upload-time = "2025-08-09T10:39:06.636Z" }, +] + +[[package]] +name = "freia" +version = "0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "scipy" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/76/5660f714a3a8c8df9c3301161b53fbdaa7a911ee0f660eaa65fa5a5b36f9/FrEIA-0.2.tar.gz", hash = "sha256:e9f1732b4a238b85b0ad3bc14b279a653a6063d14152028e164b989582026bdc", size = 34270, upload-time = "2022-07-04T08:51:18.645Z" } + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19", size = 304847, upload-time = "2025-09-02T19:10:49.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "ftfy" +version = "6.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/d3/8650919bc3c7c6e90ee3fa7fd618bf373cbbe55dff043bd67353dbb20cd8/ftfy-6.3.1.tar.gz", hash = "sha256:9b3c3d90f84fb267fe64d375a07b7f8912d817cf86009ae134aa03e1819506ec", size = 308927, upload-time = "2024-10-26T00:50:35.149Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821, upload-time = "2024-10-26T00:50:33.425Z" }, +] + +[[package]] +name = "geti-inspect" +version = "1.0.0" +source = { virtual = "." } +dependencies = [ + { name = "aiofiles" }, + { name = "aiortc" }, + { name = "aiosqlite" }, + { name = "alembic" }, + { name = "anomalib", extra = ["full"] }, + { name = "fastapi", extra = ["standard"] }, + { name = "jsonschema" }, + { name = "loguru" }, + { name = "opencv-python-headless" }, + { name = "openvino" }, + { name = "psutil" }, + { name = "pydantic-settings" }, + { name = "sqlalchemy", extra = ["asyncio"] }, + { name = "torchmetrics" }, + { name = "trackio" }, + { name = "uvicorn" }, + { name = "uvloop" }, + { name = "watchdog" }, +] + +[package.optional-dependencies] +mqtt = [ + { name = "paho-mqtt" }, +] + +[package.dev-dependencies] +dev = [ + { name = "flaky" }, + { name = "freezegun" }, + { name = "httpx" }, + { name = "pdbpp" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "testfixtures" }, +] +lint = [ + { name = "mypy" }, + { name = "ruff" }, + { name = "types-pyyaml" }, + { name = "types-requests" }, +] + +[package.metadata] +requires-dist = [ + { name = "aiofiles", specifier = "==24.1.0" }, + { name = "aiortc", specifier = "~=1.13.0" }, + { name = "aiosqlite", specifier = "~=0.21" }, + { name = "alembic", specifier = "~=1.16.4" }, + { name = "anomalib", extras = ["full"], editable = "../../" }, + { name = "fastapi", extras = ["standard"], specifier = "~=0.116" }, + { name = "jsonschema", specifier = ">=4.18.0a10" }, + { name = "loguru", specifier = "==0.7.3" }, + { name = "opencv-python-headless", specifier = "~=4.12" }, + { name = "openvino", specifier = "==2025.2.0" }, + { name = "paho-mqtt", marker = "extra == 'mqtt'", specifier = "~=2.1.0" }, + { name = "psutil", specifier = "~=7.0.0" }, + { name = "pydantic-settings", specifier = "~=2.10.1" }, + { name = "sqlalchemy", extras = ["asyncio"], specifier = "~=2.0" }, + { name = "torchmetrics", specifier = ">=1.8.2" }, + { name = "trackio", specifier = "~=0.6.0" }, + { name = "uvicorn", specifier = "~=0.17" }, + { name = "uvloop", specifier = "==0.21.0" }, + { name = "watchdog", specifier = "==6.0.0" }, +] +provides-extras = ["mqtt"] + +[package.metadata.requires-dev] +dev = [ + { name = "flaky", specifier = "~=3.8" }, + { name = "freezegun", specifier = "~=1.5" }, + { name = "httpx", specifier = "~=0.24" }, + { name = "pdbpp", specifier = "~=0.10" }, + { name = "pre-commit", specifier = "~=4.1" }, + { name = "pytest", specifier = "~=8.3" }, + { name = "testfixtures", specifier = "~=8.2" }, +] +lint = [ + { name = "mypy", specifier = "~=1.17" }, + { name = "ruff", specifier = "~=0.11.2" }, + { name = "types-pyyaml", specifier = "~=6.0.12" }, + { name = "types-requests", specifier = "~=2.32.4" }, +] + +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/c8/dd58967d119baab745caec2f9d853297cec1989ec1d63f677d3880632b88/gitpython-3.1.45.tar.gz", hash = "sha256:85b0ee964ceddf211c41b9f27a49086010a190fd8132a24e21f362a4b36a791c", size = 215076, upload-time = "2025-07-24T03:45:54.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/61/d4b89fec821f72385526e1b9d9a3a0385dda4a72b206d28049e2c7cd39b8/gitpython-3.1.45-py3-none-any.whl", hash = "sha256:8908cb2e02fb3b93b7eb0f2827125cb699869470432cc885f019b8fd0fccff77", size = 208168, upload-time = "2025-07-24T03:45:52.517Z" }, +] + +[[package]] +name = "google-auth" +version = "2.41.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/af/5129ce5b2f9688d2fa49b463e544972a7c82b0fdb50980dafee92e121d9f/google_auth-2.41.1.tar.gz", hash = "sha256:b76b7b1f9e61f0cb7e88870d14f6a94aeef248959ef6992670efee37709cbfd2", size = 292284, upload-time = "2025-09-30T22:51:26.363Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/a4/7319a2a8add4cc352be9e3efeff5e2aacee917c85ca2fa1647e29089983c/google_auth-2.41.1-py2.py3-none-any.whl", hash = "sha256:754843be95575b9a19c604a848a41be03f7f2afd8c019f716dc1f51ee41c639d", size = 221302, upload-time = "2025-09-30T22:51:24.212Z" }, +] + +[[package]] +name = "google-crc32c" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495, upload-time = "2025-03-26T14:29:13.32Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/72/b8d785e9184ba6297a8620c8a37cf6e39b81a8ca01bb0796d7cbb28b3386/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35", size = 30467, upload-time = "2025-03-26T14:36:06.909Z" }, + { url = "https://files.pythonhosted.org/packages/34/25/5f18076968212067c4e8ea95bf3b69669f9fc698476e5f5eb97d5b37999f/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638", size = 30309, upload-time = "2025-03-26T15:06:15.318Z" }, + { url = "https://files.pythonhosted.org/packages/92/83/9228fe65bf70e93e419f38bdf6c5ca5083fc6d32886ee79b450ceefd1dbd/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb", size = 33133, upload-time = "2025-03-26T14:41:34.388Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ca/1ea2fd13ff9f8955b85e7956872fdb7050c4ace8a2306a6d177edb9cf7fe/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6", size = 32773, upload-time = "2025-03-26T14:41:35.19Z" }, + { url = "https://files.pythonhosted.org/packages/89/32/a22a281806e3ef21b72db16f948cad22ec68e4bdd384139291e00ff82fe2/google_crc32c-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db", size = 33475, upload-time = "2025-03-26T14:29:11.771Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c5/002975aff514e57fc084ba155697a049b3f9b52225ec3bc0f542871dd524/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3", size = 33243, upload-time = "2025-03-26T14:41:35.975Z" }, + { url = "https://files.pythonhosted.org/packages/61/cb/c585282a03a0cea70fcaa1bf55d5d702d0f2351094d663ec3be1c6c67c52/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9", size = 32870, upload-time = "2025-03-26T14:41:37.08Z" }, +] + +[[package]] +name = "gradio" +version = "5.49.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "anyio" }, + { name = "audioop-lts" }, + { name = "brotli" }, + { name = "fastapi" }, + { name = "ffmpy" }, + { name = "gradio-client" }, + { name = "groovy" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "numpy" }, + { name = "orjson" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "pydub" }, + { name = "python-multipart" }, + { name = "pyyaml" }, + { name = "ruff" }, + { name = "safehttpx" }, + { name = "semantic-version" }, + { name = "starlette" }, + { name = "tomlkit" }, + { name = "typer" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/bd/d26fa30c686a215167e0591816e68031d6d8edff53a5839c56f881277e33/gradio-5.49.0.tar.gz", hash = "sha256:19702c824350640201d9f4b150b3efd219363ebe6390bd3c94c9894880d20d15", size = 73795339, upload-time = "2025-10-03T19:48:33.357Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/39/164ee843cca852adfcc7c7517ed3c836bcf165b9bc538a2717546177cc7b/gradio-5.49.0-py3-none-any.whl", hash = "sha256:50cba9411c02e89768ad24ee3cd5898783cb60e8abf426bda9e04bef53e0d966", size = 63523635, upload-time = "2025-10-03T19:48:27.679Z" }, +] + +[package.optional-dependencies] +oauth = [ + { name = "authlib" }, + { name = "itsdangerous" }, +] + +[[package]] +name = "gradio-client" +version = "1.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "packaging" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/a9/a3beb0ece8c05c33e6376b790fa42e0dd157abca8220cf639b249a597467/gradio_client-1.13.3.tar.gz", hash = "sha256:869b3e67e0f7a0f40df8c48c94de99183265cf4b7b1d9bd4623e336d219ffbe7", size = 323253, upload-time = "2025-09-26T19:51:21.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/0b/337b74504681b5dde39f20d803bb09757f9973ecdc65fd4e819d4b11faf7/gradio_client-1.13.3-py3-none-any.whl", hash = "sha256:3f63e4d33a2899c1a12b10fe3cf77b82a6919ff1a1fb6391f6aa225811aa390c", size = 325350, upload-time = "2025-09-26T19:51:20.288Z" }, +] + +[[package]] +name = "graphemeu" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/20/d012f71e7d00e0d5bb25176b9a96c5313d0e30cf947153bfdfa78089f4bb/graphemeu-0.7.2.tar.gz", hash = "sha256:42bbe373d7c146160f286cd5f76b1a8ad29172d7333ce10705c5cc282462a4f8", size = 307626, upload-time = "2025-01-15T09:48:59.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/18/36503ea63e1ecd0a95590d7b6b8b7d227a1e4541a154e1612a231def1bdc/graphemeu-0.7.2-py3-none-any.whl", hash = "sha256:1444520f6899fd30114fc2a39f297d86d10fa0f23bf7579f772f8bc7efaa2542", size = 22670, upload-time = "2025-01-15T09:48:57.241Z" }, +] + +[[package]] +name = "graphene" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "graphql-core" }, + { name = "graphql-relay" }, + { name = "python-dateutil" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/f6/bf62ff950c317ed03e77f3f6ddd7e34aaa98fe89d79ebd660c55343d8054/graphene-3.4.3.tar.gz", hash = "sha256:2a3786948ce75fe7e078443d37f609cbe5bb36ad8d6b828740ad3b95ed1a0aaa", size = 44739, upload-time = "2024-11-09T20:44:25.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/e0/61d8e98007182e6b2aca7cf65904721fb2e4bce0192272ab9cb6f69d8812/graphene-3.4.3-py2.py3-none-any.whl", hash = "sha256:820db6289754c181007a150db1f7fff544b94142b556d12e3ebc777a7bf36c71", size = 114894, upload-time = "2024-11-09T20:44:23.851Z" }, +] + +[[package]] +name = "graphql-core" +version = "3.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/16/7574029da84834349b60ed71614d66ca3afe46e9bf9c7b9562102acb7d4f/graphql_core-3.2.6.tar.gz", hash = "sha256:c08eec22f9e40f0bd61d805907e3b3b1b9a320bc606e23dc145eebca07c8fbab", size = 505353, upload-time = "2025-01-26T16:36:27.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/4f/7297663840621022bc73c22d7d9d80dbc78b4db6297f764b545cd5dd462d/graphql_core-3.2.6-py3-none-any.whl", hash = "sha256:78b016718c161a6fb20a7d97bbf107f331cd1afe53e45566c59f776ed7f0b45f", size = 203416, upload-time = "2025-01-26T16:36:24.868Z" }, +] + +[[package]] +name = "graphql-relay" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "graphql-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/13/98fbf8d67552f102488ffc16c6f559ce71ea15f6294728d33928ab5ff14d/graphql-relay-3.2.0.tar.gz", hash = "sha256:1ff1c51298356e481a0be009ccdff249832ce53f30559c1338f22a0e0d17250c", size = 50027, upload-time = "2022-04-16T11:03:45.447Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/16/a4cf06adbc711bd364a73ce043b0b08d8fa5aae3df11b6ee4248bcdad2e0/graphql_relay-3.2.0-py3-none-any.whl", hash = "sha256:c9b22bd28b170ba1fe674c74384a8ff30a76c8e26f88ac3aa1584dd3179953e5", size = 16940, upload-time = "2022-04-16T11:03:43.895Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" }, + { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" }, + { url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +] + +[[package]] +name = "groovy" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/36/bbdede67400277bef33d3ec0e6a31750da972c469f75966b4930c753218f/groovy-0.1.2.tar.gz", hash = "sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083", size = 17325, upload-time = "2025-02-28T20:24:56.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/27/3d6dcadc8a3214d8522c1e7f6a19554e33659be44546d44a2f7572ac7d2a/groovy-0.1.2-py3-none-any.whl", hash = "sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64", size = 14090, upload-time = "2025-02-28T20:24:55.152Z" }, +] + +[[package]] +name = "grpcio" +version = "1.75.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/f7/8963848164c7604efb3a3e6ee457fdb3a469653e19002bd24742473254f8/grpcio-1.75.1.tar.gz", hash = "sha256:3e81d89ece99b9ace23a6916880baca613c03a799925afb2857887efa8b1b3d2", size = 12731327, upload-time = "2025-09-26T09:03:36.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/74/bac4ab9f7722164afdf263ae31ba97b8174c667153510322a5eba4194c32/grpcio-1.75.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:3bed22e750d91d53d9e31e0af35a7b0b51367e974e14a4ff229db5b207647884", size = 5672779, upload-time = "2025-09-26T09:02:19.11Z" }, + { url = "https://files.pythonhosted.org/packages/a6/52/d0483cfa667cddaa294e3ab88fd2c2a6e9dc1a1928c0e5911e2e54bd5b50/grpcio-1.75.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5b8f381eadcd6ecaa143a21e9e80a26424c76a0a9b3d546febe6648f3a36a5ac", size = 11470623, upload-time = "2025-09-26T09:02:22.117Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e4/d1954dce2972e32384db6a30273275e8c8ea5a44b80347f9055589333b3f/grpcio-1.75.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5bf4001d3293e3414d0cf99ff9b1139106e57c3a66dfff0c5f60b2a6286ec133", size = 6248838, upload-time = "2025-09-26T09:02:26.426Z" }, + { url = "https://files.pythonhosted.org/packages/06/43/073363bf63826ba8077c335d797a8d026f129dc0912b69c42feaf8f0cd26/grpcio-1.75.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f82ff474103e26351dacfe8d50214e7c9322960d8d07ba7fa1d05ff981c8b2d", size = 6922663, upload-time = "2025-09-26T09:02:28.724Z" }, + { url = "https://files.pythonhosted.org/packages/c2/6f/076ac0df6c359117676cacfa8a377e2abcecec6a6599a15a672d331f6680/grpcio-1.75.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0ee119f4f88d9f75414217823d21d75bfe0e6ed40135b0cbbfc6376bc9f7757d", size = 6436149, upload-time = "2025-09-26T09:02:30.971Z" }, + { url = "https://files.pythonhosted.org/packages/6b/27/1d08824f1d573fcb1fa35ede40d6020e68a04391709939e1c6f4193b445f/grpcio-1.75.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:664eecc3abe6d916fa6cf8dd6b778e62fb264a70f3430a3180995bf2da935446", size = 7067989, upload-time = "2025-09-26T09:02:33.233Z" }, + { url = "https://files.pythonhosted.org/packages/c6/98/98594cf97b8713feb06a8cb04eeef60b4757e3e2fb91aa0d9161da769843/grpcio-1.75.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c32193fa08b2fbebf08fe08e84f8a0aad32d87c3ad42999c65e9449871b1c66e", size = 8010717, upload-time = "2025-09-26T09:02:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7e/bb80b1bba03c12158f9254762cdf5cced4a9bc2e8ed51ed335915a5a06ef/grpcio-1.75.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5cebe13088b9254f6e615bcf1da9131d46cfa4e88039454aca9cb65f639bd3bc", size = 7463822, upload-time = "2025-09-26T09:02:38.26Z" }, + { url = "https://files.pythonhosted.org/packages/23/1c/1ea57fdc06927eb5640f6750c697f596f26183573069189eeaf6ef86ba2d/grpcio-1.75.1-cp313-cp313-win32.whl", hash = "sha256:4b4c678e7ed50f8ae8b8dbad15a865ee73ce12668b6aaf411bf3258b5bc3f970", size = 3938490, upload-time = "2025-09-26T09:02:40.268Z" }, + { url = "https://files.pythonhosted.org/packages/4b/24/fbb8ff1ccadfbf78ad2401c41aceaf02b0d782c084530d8871ddd69a2d49/grpcio-1.75.1-cp313-cp313-win_amd64.whl", hash = "sha256:5573f51e3f296a1bcf71e7a690c092845fb223072120f4bdb7a5b48e111def66", size = 4642538, upload-time = "2025-09-26T09:02:42.519Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1b/9a0a5cecd24302b9fdbcd55d15ed6267e5f3d5b898ff9ac8cbe17ee76129/grpcio-1.75.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:c05da79068dd96723793bffc8d0e64c45f316248417515f28d22204d9dae51c7", size = 5673319, upload-time = "2025-09-26T09:02:44.742Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ec/9d6959429a83fbf5df8549c591a8a52bb313976f6646b79852c4884e3225/grpcio-1.75.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06373a94fd16ec287116a825161dca179a0402d0c60674ceeec8c9fba344fe66", size = 11480347, upload-time = "2025-09-26T09:02:47.539Z" }, + { url = "https://files.pythonhosted.org/packages/09/7a/26da709e42c4565c3d7bf999a9569da96243ce34a8271a968dee810a7cf1/grpcio-1.75.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4484f4b7287bdaa7a5b3980f3c7224c3c622669405d20f69549f5fb956ad0421", size = 6254706, upload-time = "2025-09-26T09:02:50.4Z" }, + { url = "https://files.pythonhosted.org/packages/f1/08/dcb26a319d3725f199c97e671d904d84ee5680de57d74c566a991cfab632/grpcio-1.75.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2720c239c1180eee69f7883c1d4c83fc1a495a2535b5fa322887c70bf02b16e8", size = 6922501, upload-time = "2025-09-26T09:02:52.711Z" }, + { url = "https://files.pythonhosted.org/packages/78/66/044d412c98408a5e23cb348845979a2d17a2e2b6c3c34c1ec91b920f49d0/grpcio-1.75.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:07a554fa31c668cf0e7a188678ceeca3cb8fead29bbe455352e712ec33ca701c", size = 6437492, upload-time = "2025-09-26T09:02:55.542Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9d/5e3e362815152aa1afd8b26ea613effa005962f9da0eec6e0e4527e7a7d1/grpcio-1.75.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3e71a2105210366bfc398eef7f57a664df99194f3520edb88b9c3a7e46ee0d64", size = 7081061, upload-time = "2025-09-26T09:02:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/1e/1a/46615682a19e100f46e31ddba9ebc297c5a5ab9ddb47b35443ffadb8776c/grpcio-1.75.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8679aa8a5b67976776d3c6b0521e99d1c34db8a312a12bcfd78a7085cb9b604e", size = 8010849, upload-time = "2025-09-26T09:03:00.548Z" }, + { url = "https://files.pythonhosted.org/packages/67/8e/3204b94ac30b0f675ab1c06540ab5578660dc8b690db71854d3116f20d00/grpcio-1.75.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aad1c774f4ebf0696a7f148a56d39a3432550612597331792528895258966dc0", size = 7464478, upload-time = "2025-09-26T09:03:03.096Z" }, + { url = "https://files.pythonhosted.org/packages/b7/97/2d90652b213863b2cf466d9c1260ca7e7b67a16780431b3eb1d0420e3d5b/grpcio-1.75.1-cp314-cp314-win32.whl", hash = "sha256:62ce42d9994446b307649cb2a23335fa8e927f7ab2cbf5fcb844d6acb4d85f9c", size = 4012672, upload-time = "2025-09-26T09:03:05.477Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/e2e6e9fc1c985cd1a59e6996a05647c720fe8a03b92f5ec2d60d366c531e/grpcio-1.75.1-cp314-cp314-win_amd64.whl", hash = "sha256:f86e92275710bea3000cb79feca1762dc0ad3b27830dd1a74e82ab321d4ee464", size = 4772475, upload-time = "2025-09-26T09:03:07.661Z" }, +] + +[[package]] +name = "gunicorn" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.1.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/31/feeddfce1748c4a233ec1aa5b7396161c07ae1aa9b7bdbc9a72c3c7dd768/hf_xet-1.1.10.tar.gz", hash = "sha256:408aef343800a2102374a883f283ff29068055c111f003ff840733d3b715bb97", size = 487910, upload-time = "2025-09-12T20:10:27.12Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/a2/343e6d05de96908366bdc0081f2d8607d61200be2ac802769c4284cc65bd/hf_xet-1.1.10-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:686083aca1a6669bc85c21c0563551cbcdaa5cf7876a91f3d074a030b577231d", size = 2761466, upload-time = "2025-09-12T20:10:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/31/f9/6215f948ac8f17566ee27af6430ea72045e0418ce757260248b483f4183b/hf_xet-1.1.10-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71081925383b66b24eedff3013f8e6bbd41215c3338be4b94ba75fd75b21513b", size = 2623807, upload-time = "2025-09-12T20:10:21.118Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/86397573efefff941e100367bbda0b21496ffcdb34db7ab51912994c32a2/hf_xet-1.1.10-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6bceb6361c80c1cc42b5a7b4e3efd90e64630bcf11224dcac50ef30a47e435", size = 3186960, upload-time = "2025-09-12T20:10:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/01/a7/0b2e242b918cc30e1f91980f3c4b026ff2eedaf1e2ad96933bca164b2869/hf_xet-1.1.10-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eae7c1fc8a664e54753ffc235e11427ca61f4b0477d757cc4eb9ae374b69f09c", size = 3087167, upload-time = "2025-09-12T20:10:17.255Z" }, + { url = "https://files.pythonhosted.org/packages/4a/25/3e32ab61cc7145b11eee9d745988e2f0f4fafda81b25980eebf97d8cff15/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0a0005fd08f002180f7a12d4e13b22be277725bc23ed0529f8add5c7a6309c06", size = 3248612, upload-time = "2025-09-12T20:10:24.093Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3d/ab7109e607ed321afaa690f557a9ada6d6d164ec852fd6bf9979665dc3d6/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f900481cf6e362a6c549c61ff77468bd59d6dd082f3170a36acfef2eb6a6793f", size = 3353360, upload-time = "2025-09-12T20:10:25.563Z" }, + { url = "https://files.pythonhosted.org/packages/ee/0e/471f0a21db36e71a2f1752767ad77e92d8cde24e974e03d662931b1305ec/hf_xet-1.1.10-cp37-abi3-win_amd64.whl", hash = "sha256:5f54b19cc347c13235ae7ee98b330c26dd65ef1df47e5316ffb1e87713ca7045", size = 2804691, upload-time = "2025-09-12T20:10:28.433Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, + { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, + { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, + { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, + { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/fa/66bd985dd0b7c109a3bcb89272ee0bfb7e2b4d06309ad7b38ff866734b2a/httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e", size = 12998, upload-time = "2025-06-24T13:21:05.71Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/0a/6269e3473b09aed2dab8aa1a600c70f31f00ae1349bee30658f7e358a159/httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37", size = 8054, upload-time = "2025-06-24T13:21:04.772Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.35.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/7e/a0a97de7c73671863ca6b3f61fa12518caf35db37825e43d63a70956738c/huggingface_hub-0.35.3.tar.gz", hash = "sha256:350932eaa5cc6a4747efae85126ee220e4ef1b54e29d31c3b45c5612ddf0b32a", size = 461798, upload-time = "2025-09-29T14:29:58.625Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/a0/651f93d154cb72323358bf2bbae3e642bdb5d2f1bfc874d096f7cb159fa0/huggingface_hub-0.35.3-py3-none-any.whl", hash = "sha256:0e3a01829c19d86d03793e4577816fe3bdfc1602ac62c7fb220d593d351224ba", size = 564262, upload-time = "2025-09-29T14:29:55.813Z" }, +] + +[[package]] +name = "identify" +version = "2.6.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "ifaddr" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/fb4c578f4a3256561548cd825646680edcadb9440f3f68add95ade1eb791/ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4", size = 10485, upload-time = "2022-06-15T21:40:27.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/1f/19ebc343cc71a7ffa78f17018535adc5cbdd87afb31d7c34874680148b32/ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748", size = 12314, upload-time = "2022-06-15T21:40:25.756Z" }, +] + +[[package]] +name = "imagecodecs" +version = "2025.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/d1/bac78c9bdecbc12bcaa96363dd6d7b358de0b4af512d08c16f6792e7a6ea/imagecodecs-2025.8.2.tar.gz", hash = "sha256:2af272aac90c370326a7e2fffcbbbd32d42de07576959a2a98d60110267dfe6c", size = 9490135, upload-time = "2025-08-03T06:08:38.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/69/77a5c020030dbba1a566264b9201f091534b4b10e9ac5725b7bd40895a8b/imagecodecs-2025.8.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:06a1b54847cbf595ed58879936eabdab990f1780b80d03a8d4edb57e61c768b0", size = 12454348, upload-time = "2025-08-03T06:07:26.135Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5d/c5dd7f0706dc48d38deefe4cba05ac78236b662a8ef86f9c0cc569b9db96/imagecodecs-2025.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbf3abd281034e29034b773407c27eff35dd94832a9b6e3c97490db079e3c0a8", size = 10103114, upload-time = "2025-08-03T06:07:28.657Z" }, + { url = "https://files.pythonhosted.org/packages/60/40/8b656577120f758ce4b177917b57c76f15e695ff0e63584f641db2063bbe/imagecodecs-2025.8.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:248bb2ea3e43690196acdb1dae5aa40213dbd00c47255295d57da80770ceeaa7", size = 25602557, upload-time = "2025-08-03T06:07:32.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/de/6c1cf78cc0ecc45d98a0eb0d8920df7b90719f8643c7ed9b1bb700f95890/imagecodecs-2025.8.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52230fcd0c331b0167ccd14d7ce764bb780006b65bf69761d8bde6863419fdbf", size = 26544468, upload-time = "2025-08-03T06:07:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/51/14/bda4974256e31eca65e33446026c91d54d1124aa59ce042fc326836597ec/imagecodecs-2025.8.2-cp313-cp313-win32.whl", hash = "sha256:151f9e0879ed8a025b19fcffbf4785dacf29002d5fec94d318e84ba154ddd54c", size = 18534480, upload-time = "2025-08-03T06:07:39.901Z" }, + { url = "https://files.pythonhosted.org/packages/a6/bb/ada8851ee56ab835562fb96f764f055e16a5d43a81ebc95207f6b2f3c1d4/imagecodecs-2025.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:d167c265caaf36f9f090e55b68a7c0ec4e5b436923cdc047358743f450534154", size = 22662134, upload-time = "2025-08-03T06:07:43.845Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b6/687ad4e137fe637213540cf71bf6a3957cc48667ce6d96d6d9dcb8409305/imagecodecs-2025.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:37199e19d61c7a6c0cf901859d7a81c4449d18047a15ca9d2ebe17176c8d1b69", size = 17968181, upload-time = "2025-08-03T06:07:47.254Z" }, + { url = "https://files.pythonhosted.org/packages/63/5f/2be51d6ea6e6cae13d8d4ce77d5076ef72e492f670368bb193db35e146ce/imagecodecs-2025.8.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:7a3c89b5f5c946d5649892e15b5c89aeca357d048331c3a4ae89009320d2704a", size = 12447596, upload-time = "2025-08-03T06:07:49.804Z" }, + { url = "https://files.pythonhosted.org/packages/6e/75/d9cd579b1fd5fdef5742567236adb49df15beaf8f66219412f21fb86a64c/imagecodecs-2025.8.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a4d2249e4d25a57da7da336c68efc72a7e15f9271dc6c13c322947f30383b8e", size = 10107274, upload-time = "2025-08-03T06:07:52.787Z" }, + { url = "https://files.pythonhosted.org/packages/57/46/011e41f99f2301091f902afd917a4a8079056510dfcb8b8529d96e4232c8/imagecodecs-2025.8.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31ebfca1bd01c6e4db25c0a91301ddc9aeca1dc63642db689543a9700a5869e8", size = 25579702, upload-time = "2025-08-03T06:07:56.392Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9b/b2d38a3902b2a61cfdbe1bca7aa939b77e32349d00aba7a4014d1dfd8cb9/imagecodecs-2025.8.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be84bd8f3cf1552efc92f2465af5ab40a14a788d48482ef3a702e9dae5f4cd0b", size = 26372160, upload-time = "2025-08-03T06:08:00.079Z" }, + { url = "https://files.pythonhosted.org/packages/c2/96/a22ce5b43a93d12b21f2ec9e374cd4a7edf9347630c7ed90bef7c4ca9f5d/imagecodecs-2025.8.2-cp314-cp314-win32.whl", hash = "sha256:ce57af27547c42dfb888562a1a22dc51a6103c20b3fb69ac4c26121acc741ade", size = 18802212, upload-time = "2025-08-03T06:08:03.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0c/d364bff2ffb8f2864e9f7fa6dd05b57c25a4c341ba1e2f08b3dea1dc8a6d/imagecodecs-2025.8.2-cp314-cp314-win_amd64.whl", hash = "sha256:b93d77293c0aa9e661d42f3203b13ea135d5bf9f0936fbbe90780ed1c67322d3", size = 23052776, upload-time = "2025-08-03T06:08:06.975Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4b/54d1e3a2b9d11c8a875c98b543dc5527c259c174b05987b1e79ccfc107b9/imagecodecs-2025.8.2-cp314-cp314-win_arm64.whl", hash = "sha256:d7a53983d4df035761dc652e44c912092bddc5b115d6f5f612df301ce94fcd55", size = 18374009, upload-time = "2025-08-03T06:08:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/c614d151d577c17f38bb8ba9388f56eaccc5c3708b3109ed103209350bc8/imagecodecs-2025.8.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9e2b138c2826cf58d4088513fe61bf2311d6206e0eb865a37cd27749812bcc2a", size = 12582934, upload-time = "2025-08-03T06:08:13.466Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d2/9129c759a5d11c0c0f119fe08871823a623bb382c2996486db5442c3d61b/imagecodecs-2025.8.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa4750e23d69c95b99eff991ca3f28176b77b2e9ccf14eb326942afa4258d349", size = 10277782, upload-time = "2025-08-03T06:08:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/31/39/838c2fc76e9d775b385b6d11b7882d162c895bd1c62aaf57143c79956c7f/imagecodecs-2025.8.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2ca3bb477946836af844dfe89d2bfb21c69e2e08904560f927dbb4298cfb76c9", size = 26631262, upload-time = "2025-08-03T06:08:19.518Z" }, + { url = "https://files.pythonhosted.org/packages/d7/38/c3f0ab9374b1b74c125ac3e77a8ee84b9a1e9eb7429dca0f026a792d02ed/imagecodecs-2025.8.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd14eb5626cf7b307f9442da130c2a5340cd088718b71d35ce7389409b2e5341", size = 27053693, upload-time = "2025-08-03T06:08:24.422Z" }, + { url = "https://files.pythonhosted.org/packages/df/00/88399944e570369c39e626020200136ae20d9fcbb439e83ec0f25d301721/imagecodecs-2025.8.2-cp314-cp314t-win32.whl", hash = "sha256:4d8378e3068b12abc4cd9dbde8867ce80dad89b9f11a7bd24321c139eaea30aa", size = 19110281, upload-time = "2025-08-03T06:08:28.128Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ec/bbeb61bb3b8c6853c74c501e787cc5be025454be8679fda385b3ee451c8b/imagecodecs-2025.8.2-cp314-cp314t-win_amd64.whl", hash = "sha256:4d1002282afc6b89616915e9741fa8bbd283e214a268f03b14a74acb43aad451", size = 23430830, upload-time = "2025-08-03T06:08:31.963Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c0/93445b41eb2063414ebb443aa93bccaff2868252a8c55c8c0ea72a7be96a/imagecodecs-2025.8.2-cp314-cp314t-win_arm64.whl", hash = "sha256:f8bb58c7da65f93fee53d4b279d4030db2804b97a42c5135abc82f02ebfb4c3a", size = 18497460, upload-time = "2025-08-03T06:08:35.487Z" }, +] + +[[package]] +name = "imageio" +version = "2.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963, upload-time = "2025-01-20T02:42:37.089Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796, upload-time = "2025-01-20T02:42:34.931Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, +] + +[[package]] +name = "importlib-resources" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "ipykernel" +version = "6.30.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/76/11082e338e0daadc89c8ff866185de11daf67d181901038f9e139d109761/ipykernel-6.30.1.tar.gz", hash = "sha256:6abb270161896402e76b91394fcdce5d1be5d45f456671e5080572f8505be39b", size = 166260, upload-time = "2025-08-04T15:47:35.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl", hash = "sha256:aa6b9fb93dca949069d8b85b6c79b2518e32ac583ae9c7d37c51d119e18b3fb4", size = 117484, upload-time = "2025-08-04T15:47:32.622Z" }, +] + +[[package]] +name = "ipython" +version = "9.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/34/29b18c62e39ee2f7a6a3bba7efd952729d8aadd45ca17efc34453b717665/ipython-9.6.0.tar.gz", hash = "sha256:5603d6d5d356378be5043e69441a072b50a5b33b4503428c77b04cb8ce7bc731", size = 4396932, upload-time = "2025-09-29T10:55:53.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl", hash = "sha256:5f77efafc886d2f023442479b8149e7d86547ad0a979e9da9f045d252f648196", size = 616170, upload-time = "2025-09-29T10:55:47.676Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "ipywidgets" +version = "8.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "comm" }, + { name = "ipython" }, + { name = "jupyterlab-widgets" }, + { name = "traitlets" }, + { name = "widgetsnbextension" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/48/d3dbac45c2814cb73812f98dd6b38bbcc957a4e7bb31d6ea9c03bf94ed87/ipywidgets-8.1.7.tar.gz", hash = "sha256:15f1ac050b9ccbefd45dccfbb2ef6bed0029d8278682d569d71b8dd96bee0376", size = 116721, upload-time = "2025-05-05T12:42:03.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/6a/9166369a2f092bd286d24e6307de555d63616e8ddb373ebad2b5635ca4cd/ipywidgets-8.1.7-py3-none-any.whl", hash = "sha256:764f2602d25471c213919b8a1997df04bef869251db4ca8efba1b76b1bd9f7bb", size = 139806, upload-time = "2025-05-05T12:41:56.833Z" }, +] + +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/c0/a3bb4cc13aced219dd18191ea66e874266bd8aa7b96744e495e1c733aa2d/jiter-0.11.0.tar.gz", hash = "sha256:1d9637eaf8c1d6a63d6562f2a6e5ab3af946c66037eb1b894e8fad75422266e4", size = 167094, upload-time = "2025-09-15T09:20:38.212Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/c4/d530e514d0f4f29b2b68145e7b389cbc7cac7f9c8c23df43b04d3d10fa3e/jiter-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4441a91b80a80249f9a6452c14b2c24708f139f64de959943dfeaa6cb915e8eb", size = 305021, upload-time = "2025-09-15T09:19:43.523Z" }, + { url = "https://files.pythonhosted.org/packages/7a/77/796a19c567c5734cbfc736a6f987affc0d5f240af8e12063c0fb93990ffa/jiter-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ff85fc6d2a431251ad82dbd1ea953affb5a60376b62e7d6809c5cd058bb39471", size = 314384, upload-time = "2025-09-15T09:19:44.849Z" }, + { url = "https://files.pythonhosted.org/packages/14/9c/824334de0b037b91b6f3fa9fe5a191c83977c7ec4abe17795d3cb6d174cf/jiter-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5e86126d64706fd28dfc46f910d496923c6f95b395138c02d0e252947f452bd", size = 337389, upload-time = "2025-09-15T09:19:46.094Z" }, + { url = "https://files.pythonhosted.org/packages/a2/95/ed4feab69e6cf9b2176ea29d4ef9d01a01db210a3a2c8a31a44ecdc68c38/jiter-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad8bd82165961867a10f52010590ce0b7a8c53da5ddd8bbb62fef68c181b921", size = 360519, upload-time = "2025-09-15T09:19:47.494Z" }, + { url = "https://files.pythonhosted.org/packages/b5/0c/2ad00f38d3e583caba3909d95b7da1c3a7cd82c0aa81ff4317a8016fb581/jiter-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b42c2cd74273455ce439fd9528db0c6e84b5623cb74572305bdd9f2f2961d3df", size = 487198, upload-time = "2025-09-15T09:19:49.116Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8b/919b64cf3499b79bdfba6036da7b0cac5d62d5c75a28fb45bad7819e22f0/jiter-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0062dab98172dd0599fcdbf90214d0dcde070b1ff38a00cc1b90e111f071982", size = 377835, upload-time = "2025-09-15T09:19:50.468Z" }, + { url = "https://files.pythonhosted.org/packages/29/7f/8ebe15b6e0a8026b0d286c083b553779b4dd63db35b43a3f171b544de91d/jiter-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb948402821bc76d1f6ef0f9e19b816f9b09f8577844ba7140f0b6afe994bc64", size = 347655, upload-time = "2025-09-15T09:19:51.726Z" }, + { url = "https://files.pythonhosted.org/packages/8e/64/332127cef7e94ac75719dda07b9a472af6158ba819088d87f17f3226a769/jiter-0.11.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25a5b1110cca7329fd0daf5060faa1234be5c11e988948e4f1a1923b6a457fe1", size = 386135, upload-time = "2025-09-15T09:19:53.075Z" }, + { url = "https://files.pythonhosted.org/packages/20/c8/557b63527442f84c14774159948262a9d4fabb0d61166f11568f22fc60d2/jiter-0.11.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:bf11807e802a214daf6c485037778843fadd3e2ec29377ae17e0706ec1a25758", size = 516063, upload-time = "2025-09-15T09:19:54.447Z" }, + { url = "https://files.pythonhosted.org/packages/86/13/4164c819df4a43cdc8047f9a42880f0ceef5afeb22e8b9675c0528ebdccd/jiter-0.11.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:dbb57da40631c267861dd0090461222060960012d70fd6e4c799b0f62d0ba166", size = 508139, upload-time = "2025-09-15T09:19:55.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/70/6e06929b401b331d41ddb4afb9f91cd1168218e3371972f0afa51c9f3c31/jiter-0.11.0-cp313-cp313-win32.whl", hash = "sha256:8e36924dad32c48d3c5e188d169e71dc6e84d6cb8dedefea089de5739d1d2f80", size = 206369, upload-time = "2025-09-15T09:19:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/f4/0d/8185b8e15de6dce24f6afae63380e16377dd75686d56007baa4f29723ea1/jiter-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:452d13e4fd59698408087235259cebe67d9d49173b4dacb3e8d35ce4acf385d6", size = 202538, upload-time = "2025-09-15T09:19:58.35Z" }, + { url = "https://files.pythonhosted.org/packages/13/3a/d61707803260d59520721fa326babfae25e9573a88d8b7b9cb54c5423a59/jiter-0.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:089f9df9f69532d1339e83142438668f52c97cd22ee2d1195551c2b1a9e6cf33", size = 313737, upload-time = "2025-09-15T09:19:59.638Z" }, + { url = "https://files.pythonhosted.org/packages/cd/cc/c9f0eec5d00f2a1da89f6bdfac12b8afdf8d5ad974184863c75060026457/jiter-0.11.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29ed1fe69a8c69bf0f2a962d8d706c7b89b50f1332cd6b9fbda014f60bd03a03", size = 346183, upload-time = "2025-09-15T09:20:01.442Z" }, + { url = "https://files.pythonhosted.org/packages/a6/87/fc632776344e7aabbab05a95a0075476f418c5d29ab0f2eec672b7a1f0ac/jiter-0.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a4d71d7ea6ea8786291423fe209acf6f8d398a0759d03e7f24094acb8ab686ba", size = 204225, upload-time = "2025-09-15T09:20:03.102Z" }, + { url = "https://files.pythonhosted.org/packages/ee/3b/e7f45be7d3969bdf2e3cd4b816a7a1d272507cd0edd2d6dc4b07514f2d9a/jiter-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9a6dff27eca70930bdbe4cbb7c1a4ba8526e13b63dc808c0670083d2d51a4a72", size = 304414, upload-time = "2025-09-15T09:20:04.357Z" }, + { url = "https://files.pythonhosted.org/packages/06/32/13e8e0d152631fcc1907ceb4943711471be70496d14888ec6e92034e2caf/jiter-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b1ae2a7593a62132c7d4c2abbee80bbbb94fdc6d157e2c6cc966250c564ef774", size = 314223, upload-time = "2025-09-15T09:20:05.631Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7e/abedd5b5a20ca083f778d96bba0d2366567fcecb0e6e34ff42640d5d7a18/jiter-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b13a431dba4b059e9e43019d3022346d009baf5066c24dcdea321a303cde9f0", size = 337306, upload-time = "2025-09-15T09:20:06.917Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e2/30d59bdc1204c86aa975ec72c48c482fee6633120ee9c3ab755e4dfefea8/jiter-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:af62e84ca3889604ebb645df3b0a3f3bcf6b92babbff642bd214616f57abb93a", size = 360565, upload-time = "2025-09-15T09:20:08.283Z" }, + { url = "https://files.pythonhosted.org/packages/fe/88/567288e0d2ed9fa8f7a3b425fdaf2cb82b998633c24fe0d98f5417321aa8/jiter-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f3b32bb723246e6b351aecace52aba78adb8eeb4b2391630322dc30ff6c773", size = 486465, upload-time = "2025-09-15T09:20:09.613Z" }, + { url = "https://files.pythonhosted.org/packages/18/6e/7b72d09273214cadd15970e91dd5ed9634bee605176107db21e1e4205eb1/jiter-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:adcab442f4a099a358a7f562eaa54ed6456fb866e922c6545a717be51dbed7d7", size = 377581, upload-time = "2025-09-15T09:20:10.884Z" }, + { url = "https://files.pythonhosted.org/packages/58/52/4db456319f9d14deed325f70102577492e9d7e87cf7097bda9769a1fcacb/jiter-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9967c2ab338ee2b2c0102fd379ec2693c496abf71ffd47e4d791d1f593b68e2", size = 347102, upload-time = "2025-09-15T09:20:12.175Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b4/433d5703c38b26083aec7a733eb5be96f9c6085d0e270a87ca6482cbf049/jiter-0.11.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e7d0bed3b187af8b47a981d9742ddfc1d9b252a7235471ad6078e7e4e5fe75c2", size = 386477, upload-time = "2025-09-15T09:20:13.428Z" }, + { url = "https://files.pythonhosted.org/packages/c8/7a/a60bfd9c55b55b07c5c441c5085f06420b6d493ce9db28d069cc5b45d9f3/jiter-0.11.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:f6fe0283e903ebc55f1a6cc569b8c1f3bf4abd026fed85e3ff8598a9e6f982f0", size = 516004, upload-time = "2025-09-15T09:20:14.848Z" }, + { url = "https://files.pythonhosted.org/packages/2e/46/f8363e5ecc179b4ed0ca6cb0a6d3bfc266078578c71ff30642ea2ce2f203/jiter-0.11.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5821e3d66606b29ae5b497230b304f1376f38137d69e35f8d2bd5f310ff73", size = 507855, upload-time = "2025-09-15T09:20:16.176Z" }, + { url = "https://files.pythonhosted.org/packages/90/33/396083357d51d7ff0f9805852c288af47480d30dd31d8abc74909b020761/jiter-0.11.0-cp314-cp314-win32.whl", hash = "sha256:c2d13ba7567ca8799f17c76ed56b1d49be30df996eb7fa33e46b62800562a5e2", size = 205802, upload-time = "2025-09-15T09:20:17.661Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/eb06ca556b2551d41de7d03bf2ee24285fa3d0c58c5f8d95c64c9c3281b1/jiter-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fb4790497369d134a07fc763cc88888c46f734abdd66f9fdf7865038bf3a8f40", size = 313405, upload-time = "2025-09-15T09:20:18.918Z" }, + { url = "https://files.pythonhosted.org/packages/af/22/7ab7b4ec3a1c1f03aef376af11d23b05abcca3fb31fbca1e7557053b1ba2/jiter-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e2bbf24f16ba5ad4441a9845e40e4ea0cb9eed00e76ba94050664ef53ef4406", size = 347102, upload-time = "2025-09-15T09:20:20.16Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/5d/447af5ea094b9e4c4054f82e223ada074c552335b9b4b2d14bd9b35a67c4/joblib-1.5.2.tar.gz", hash = "sha256:3faa5c39054b2f03ca547da9b2f52fde67c06240c31853f306aea97f13647b55", size = 331077, upload-time = "2025-08-27T12:15:46.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" }, +] + +[[package]] +name = "json5" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/ae/929aee9619e9eba9015207a9d2c1c54db18311da7eb4dcf6d41ad6f0eb67/json5-0.12.1.tar.gz", hash = "sha256:b2743e77b3242f8d03c143dd975a6ec7c52e2f2afe76ed934e53503dd4ad4990", size = 52191, upload-time = "2025-08-12T19:47:42.583Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/e2/05328bd2621be49a6fed9e3030b1e51a2d04537d3f816d211b9cc53c5262/json5-0.12.1-py3-none-any.whl", hash = "sha256:d9c9b3bc34a5f54d43c35e11ef7cb87d8bdd098c6ace87117a7b7e83e705c1d5", size = 36119, upload-time = "2025-08-12T19:47:41.131Z" }, +] + +[[package]] +name = "jsonargparse" +version = "4.41.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/86/60032fb5623df8d938d31f733e4b3385c40791d85d0fae3dfb4e3be10c42/jsonargparse-4.41.0.tar.gz", hash = "sha256:ba1806bf0ed0ad1975e403dffb18b3a755fee3bfe4e7b36d8cce2f297b552b5f", size = 206634, upload-time = "2025-09-04T03:46:34.715Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/fc/6ac1943f22f3d2e14703335d64a7d68496f0480c82a3077cde44b2c55ef5/jsonargparse-4.41.0-py3-none-any.whl", hash = "sha256:cd49b6a2fea723ee4d80f9df034f51af226128a7f166be8755d6acdeb3e077a7", size = 228528, upload-time = "2025-09-04T03:46:32.668Z" }, +] + +[package.optional-dependencies] +signatures = [ + { name = "docstring-parser" }, + { name = "typeshed-client" }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "rfc3987-syntax" }, + { name = "uri-template" }, + { name = "webcolors" }, +] + +[[package]] +name = "jsonschema-path" +version = "0.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pathable" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/45/41ebc679c2a4fced6a722f624c18d658dee42612b83ea24c1caf7c0eb3a8/jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001", size = 11159, upload-time = "2025-01-24T14:33:16.547Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/58/3485da8cb93d2f393bce453adeef16896751f14ba3e2024bc21dc9597646/jsonschema_path-0.3.4-py3-none-any.whl", hash = "sha256:f502191fdc2b22050f9a81c9237be9d27145b9001c55842bece5e94e382e52f8", size = 14810, upload-time = "2025-01-24T14:33:14.652Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/1b/72906d554acfeb588332eaaa6f61577705e9ec752ddb486f302dafa292d9/jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941", size = 88923, upload-time = "2025-05-27T07:38:16.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880, upload-time = "2025-05-27T07:38:15.137Z" }, +] + +[[package]] +name = "jupyter-events" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"] }, + { name = "packaging" }, + { name = "python-json-logger" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" }, +] + +[[package]] +name = "jupyter-lsp" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/5a/9066c9f8e94ee517133cd98dba393459a16cd48bba71a82f16a65415206c/jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245", size = 54823, upload-time = "2025-08-27T17:47:34.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f", size = 76687, upload-time = "2025-08-27T17:47:33.15Z" }, +] + +[[package]] +name = "jupyter-server" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "jupyter-events" }, + { name = "jupyter-server-terminals" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "prometheus-client" }, + { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'linux'" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'linux'" }, + { name = "terminado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430, upload-time = "2024-03-12T14:37:03.049Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656, upload-time = "2024-03-12T14:37:00.708Z" }, +] + +[[package]] +name = "jupyterlab" +version = "4.4.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-lru" }, + { name = "httpx" }, + { name = "ipykernel" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyter-lsp" }, + { name = "jupyter-server" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "packaging" }, + { name = "setuptools" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/b2/7dad2d0049a904d17c070226a4f78f81905f93bfe09503722d210ccf9335/jupyterlab-4.4.9.tar.gz", hash = "sha256:ea55aca8269909016d5fde2dc09b97128bc931230183fe7e2920ede5154ad9c2", size = 22966654, upload-time = "2025-09-26T17:28:20.158Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/fd/ac0979ebd1b1975c266c99b96930b0a66609c3f6e5d76979ca6eb3073896/jupyterlab-4.4.9-py3-none-any.whl", hash = "sha256:394c902827350c017430a8370b9f40c03c098773084bc53930145c146d3d2cb2", size = 12292552, upload-time = "2025-09-26T17:28:15.663Z" }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, +] + +[[package]] +name = "jupyterlab-server" +version = "2.27.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "jinja2" }, + { name = "json5" }, + { name = "jsonschema" }, + { name = "jupyter-server" }, + { name = "packaging" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/c9/a883ce65eb27905ce77ace410d83587c82ea64dc85a48d1f7ed52bcfa68d/jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4", size = 76173, upload-time = "2024-07-16T17:02:04.149Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/09/2032e7d15c544a0e3cd831c51d77a8ca57f7555b2e1b2922142eddb02a84/jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4", size = 59700, upload-time = "2024-07-16T17:02:01.115Z" }, +] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/7d/160595ca88ee87ac6ba95d82177d29ec60aaa63821d3077babb22ce031a5/jupyterlab_widgets-3.0.15.tar.gz", hash = "sha256:2920888a0c2922351a9202817957a68c07d99673504d6cd37345299e971bb08b", size = 213149, upload-time = "2025-05-05T12:32:31.004Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/6a/ca128561b22b60bd5a0c4ea26649e68c8556b82bc70a0c396eebc977fe86/jupyterlab_widgets-3.0.15-py3-none-any.whl", hash = "sha256:d59023d7d7ef71400d51e6fee9a88867f6e65e10a4201605d2d7f3e8f012a31c", size = 216571, upload-time = "2025-05-05T12:32:29.534Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, + { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, + { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, + { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, + { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, + { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, + { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, + { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, + { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, + { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, + { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, + { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, + { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, + { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, + { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, + { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, + { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, + { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, + { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, + { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, +] + +[[package]] +name = "kornia" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "kornia-rs" }, + { name = "packaging" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/49/3c3aa9c68262b87551d50cdeefff027394316abfe11937a729371a39f50a/kornia-0.8.1.tar.gz", hash = "sha256:9ce5a54a11df661794934a293f89f8b8d49e83dd09b0b9419f6082ab07afe433", size = 652542, upload-time = "2025-05-08T11:13:25.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/95/a2e359cd08ea24498be59dcd5980e290cdf658ce534bcb7ae13b8ef694e6/kornia-0.8.1-py2.py3-none-any.whl", hash = "sha256:5dcb00faa795dfb45a3630d771387290bc4f40473451352ca250e5bcc81af3d1", size = 1078646, upload-time = "2025-05-08T11:13:22.826Z" }, +] + +[[package]] +name = "kornia-rs" +version = "0.1.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/28/6e0cc55dfe9967228cfacbeee79d2a31d8ed5bd6bcc478ec0a0f790f1e71/kornia_rs-0.1.9.tar.gz", hash = "sha256:c7e45e84eb3c2454055326f86329e48a68743507460fb7e39315397fa6eeb9a0", size = 108371, upload-time = "2025-05-07T11:42:08.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/12/83ebd43c8fb42d5ad1e66ce66af9c80e9b99e95758f7b0b1ceb8be8eebde/kornia_rs-0.1.9-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e82f5d90ad5b3d02d28535d19bf612e25ca559003d56fad1d12cd173be5d8418", size = 2554277, upload-time = "2025-05-07T11:42:54.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b3/d61868697048f92df3b1ca0fbee791b92c305ba7fae3755521d44dd9ffe1/kornia_rs-0.1.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:876f0805ed8d05bd94d6b9a6c43161cdc74bc5c4508f5b737d6975d1dcf9016d", size = 2299896, upload-time = "2025-05-07T11:42:42.859Z" }, + { url = "https://files.pythonhosted.org/packages/98/3a/5f37388ed4fae3f34702e1af25398cdd7c071b4bb250f780e7a2a6378e70/kornia_rs-0.1.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00021bb1941766e1e9e8c2cdbebcf33a08f8de3586e6efc791b9580a7e52b5ed", size = 2486072, upload-time = "2025-05-07T11:42:14.6Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d8/a286db51c1269f632b3e4fdb08cb7319476dbe576796be19fdfbdfd4df1c/kornia_rs-0.1.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9feaa6d410bc4d0609f3c1f2c0c010743706c2f5eed6f5ded10f2b8fec32acdf", size = 2760634, upload-time = "2025-05-07T11:42:28.998Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6f/c3d7da7a06b71a1eb1eb017a1944bcdc9504ef5cdbb0e3345aceabd24b7a/kornia_rs-0.1.9-cp313-cp313-win_amd64.whl", hash = "sha256:4d063e9d74d2b198f080940cd1679cfb66004cd59bb7cc20e0bcf5874ce3d200", size = 2276574, upload-time = "2025-05-07T11:43:06.117Z" }, + { url = "https://files.pythonhosted.org/packages/46/0d/09cb9deeac45e1ac89aacfa1a9417eba3a27015282ca841f4df197e727b2/kornia_rs-0.1.9-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f8d03da3dba89fd290f4df012f49262cde128e3682632b5c498b34909d875190", size = 2555142, upload-time = "2025-05-07T11:42:56.093Z" }, + { url = "https://files.pythonhosted.org/packages/db/88/679db8feae7e2ad19317aaef84d6ff2129a37c601b47012ff67041daf7e3/kornia_rs-0.1.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cef15e7db4cd05ebedc25cd62616c38346f71f7e8a2fb751feacc8726e7e417e", size = 2301310, upload-time = "2025-05-07T11:42:44.105Z" }, + { url = "https://files.pythonhosted.org/packages/19/58/1ba746ac8a8a25d30bf3205c59b7a008dbcbe95533d21a33ad9097afe56d/kornia_rs-0.1.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d6ea40aa2027f62e90c590112fc4be4a787092359ed041479d2b015c0483c97", size = 2487761, upload-time = "2025-05-07T11:42:16.59Z" }, + { url = "https://files.pythonhosted.org/packages/0c/53/84462d5aaf0ad52cb30791d38e144ce8a79519c201ff204f903d54f1dd46/kornia_rs-0.1.9-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9282a70c9e81710f04fdb23617c73191324ccc070c7f96d62efb5bf66dc242a", size = 2758585, upload-time = "2025-05-07T11:42:30.465Z" }, + { url = "https://files.pythonhosted.org/packages/5e/51/755dda17b63604816a34ee3b9336d1c6ccb54f3e9bcbc8aafd98412fcb8f/kornia_rs-0.1.9-cp313-cp313t-win_amd64.whl", hash = "sha256:cd6b9860ca2fd6103340f4f977f74fa96562d89563e36c00059f2d96c7cea464", size = 2275851, upload-time = "2025-05-07T11:43:07.424Z" }, +] + +[[package]] +name = "lark" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/37/a13baf0135f348af608c667633cbe5d13aa2c5c15a56ae9ad3e6cba45ae3/lark-1.3.0.tar.gz", hash = "sha256:9a3839d0ca5e1faf7cfa3460e420e859b66bcbde05b634e73c369c8244c5fa48", size = 259551, upload-time = "2025-09-22T13:45:05.072Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl", hash = "sha256:80661f261fb2584a9828a097a2432efd575af27d20be0fd35d17f0fe37253831", size = 113002, upload-time = "2025-09-22T13:45:03.747Z" }, +] + +[[package]] +name = "lazy-loader" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, +] + +[[package]] +name = "lazy-object-proxy" +version = "1.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681, upload-time = "2025-08-22T13:50:06.783Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/26/b74c791008841f8ad896c7f293415136c66cc27e7c7577de4ee68040c110/lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e", size = 26745, upload-time = "2025-08-22T13:42:44.982Z" }, + { url = "https://files.pythonhosted.org/packages/9b/52/641870d309e5d1fb1ea7d462a818ca727e43bfa431d8c34b173eb090348c/lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e", size = 71537, upload-time = "2025-08-22T13:42:46.141Z" }, + { url = "https://files.pythonhosted.org/packages/47/b6/919118e99d51c5e76e8bf5a27df406884921c0acf2c7b8a3b38d847ab3e9/lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655", size = 71141, upload-time = "2025-08-22T13:42:47.375Z" }, + { url = "https://files.pythonhosted.org/packages/e5/47/1d20e626567b41de085cf4d4fb3661a56c159feaa73c825917b3b4d4f806/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff", size = 69449, upload-time = "2025-08-22T13:42:48.49Z" }, + { url = "https://files.pythonhosted.org/packages/58/8d/25c20ff1a1a8426d9af2d0b6f29f6388005fc8cd10d6ee71f48bff86fdd0/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be", size = 70744, upload-time = "2025-08-22T13:42:49.608Z" }, + { url = "https://files.pythonhosted.org/packages/c0/67/8ec9abe15c4f8a4bcc6e65160a2c667240d025cbb6591b879bea55625263/lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1", size = 26568, upload-time = "2025-08-22T13:42:57.719Z" }, + { url = "https://files.pythonhosted.org/packages/23/12/cd2235463f3469fd6c62d41d92b7f120e8134f76e52421413a0ad16d493e/lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65", size = 27391, upload-time = "2025-08-22T13:42:50.62Z" }, + { url = "https://files.pythonhosted.org/packages/60/9e/f1c53e39bbebad2e8609c67d0830cc275f694d0ea23d78e8f6db526c12d3/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9", size = 80552, upload-time = "2025-08-22T13:42:51.731Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b6/6c513693448dcb317d9d8c91d91f47addc09553613379e504435b4cc8b3e/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66", size = 82857, upload-time = "2025-08-22T13:42:53.225Z" }, + { url = "https://files.pythonhosted.org/packages/12/1c/d9c4aaa4c75da11eb7c22c43d7c90a53b4fca0e27784a5ab207768debea7/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847", size = 80833, upload-time = "2025-08-22T13:42:54.391Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ae/29117275aac7d7d78ae4f5a4787f36ff33262499d486ac0bf3e0b97889f6/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac", size = 79516, upload-time = "2025-08-22T13:42:55.812Z" }, + { url = "https://files.pythonhosted.org/packages/19/40/b4e48b2c38c69392ae702ae7afa7b6551e0ca5d38263198b7c79de8b3bdf/lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f", size = 27656, upload-time = "2025-08-22T13:42:56.793Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3a/277857b51ae419a1574557c0b12e0d06bf327b758ba94cafc664cb1e2f66/lazy_object_proxy-1.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c9defba70ab943f1df98a656247966d7729da2fe9c2d5d85346464bf320820a3", size = 26582, upload-time = "2025-08-22T13:49:49.366Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/c5e0fa43535bb9c87880e0ba037cdb1c50e01850b0831e80eb4f4762f270/lazy_object_proxy-1.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6763941dbf97eea6b90f5b06eb4da9418cc088fce0e3883f5816090f9afcde4a", size = 71059, upload-time = "2025-08-22T13:49:50.488Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/7dcad19c685963c652624702f1a968ff10220b16bfcc442257038216bf55/lazy_object_proxy-1.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fdc70d81235fc586b9e3d1aeef7d1553259b62ecaae9db2167a5d2550dcc391a", size = 71034, upload-time = "2025-08-22T13:49:54.224Z" }, + { url = "https://files.pythonhosted.org/packages/12/ac/34cbfb433a10e28c7fd830f91c5a348462ba748413cbb950c7f259e67aa7/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0a83c6f7a6b2bfc11ef3ed67f8cbe99f8ff500b05655d8e7df9aab993a6abc95", size = 69529, upload-time = "2025-08-22T13:49:55.29Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6a/11ad7e349307c3ca4c0175db7a77d60ce42a41c60bcb11800aabd6a8acb8/lazy_object_proxy-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:256262384ebd2a77b023ad02fbcc9326282bcfd16484d5531154b02bc304f4c5", size = 70391, upload-time = "2025-08-22T13:49:56.35Z" }, + { url = "https://files.pythonhosted.org/packages/59/97/9b410ed8fbc6e79c1ee8b13f8777a80137d4bc189caf2c6202358e66192c/lazy_object_proxy-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7601ec171c7e8584f8ff3f4e440aa2eebf93e854f04639263875b8c2971f819f", size = 26988, upload-time = "2025-08-22T13:49:57.302Z" }, +] + +[[package]] +name = "lightning" +version = "2.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pytorch-lightning" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/dd/86bb3bebadcdbc6e6e5a63657f0a03f74cd065b5ea965896679f76fec0b4/lightning-2.5.5.tar.gz", hash = "sha256:4d3d66c5b1481364a7e6a1ce8ddde1777a04fa740a3145ec218a9941aed7dd30", size = 640770, upload-time = "2025-09-05T16:01:21.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/d0/4b4fbafc3b18df91207a6e46782d9fd1905f9f45cb2c3b8dfbb239aef781/lightning-2.5.5-py3-none-any.whl", hash = "sha256:69eb248beadd7b600bf48eff00a0ec8af171ec7a678d23787c4aedf12e225e8f", size = 828490, upload-time = "2025-09-05T16:01:17.845Z" }, +] + +[[package]] +name = "lightning-utilities" +version = "0.15.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/39/6fc58ca81492db047149b4b8fd385aa1bfb8c28cd7cacb0c7eb0c44d842f/lightning_utilities-0.15.2.tar.gz", hash = "sha256:cdf12f530214a63dacefd713f180d1ecf5d165338101617b4742e8f22c032e24", size = 31090, upload-time = "2025-08-06T13:57:39.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/73/3d757cb3fc16f0f9794dd289bcd0c4a031d9cf54d8137d6b984b2d02edf3/lightning_utilities-0.15.2-py3-none-any.whl", hash = "sha256:ad3ab1703775044bbf880dbf7ddaaac899396c96315f3aa1779cec9d618a9841", size = 29431, upload-time = "2025-08-06T13:57:38.046Z" }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + +[[package]] +name = "mako" +version = "1.3.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/38/bd5b78a920a64d708fe6bc8e0a2c075e1389d53bef8413725c63ba041535/mako-1.3.10.tar.gz", hash = "sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28", size = 392474, upload-time = "2025-04-10T12:44:31.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/fb/99f81ac72ae23375f22b7afdb7642aba97c00a713c217124420147681a2f/mako-1.3.10-py3-none-any.whl", hash = "sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59", size = 78509, upload-time = "2025-04-10T12:50:53.297Z" }, +] + +[[package]] +name = "markdown" +version = "3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a0/59/c3e6453a9676ffba145309a73c462bb407f4400de7de3f2b41af70720a3c/matplotlib-3.10.6.tar.gz", hash = "sha256:ec01b645840dd1996df21ee37f208cd8ba57644779fa20464010638013d3203c", size = 34804264, upload-time = "2025-08-30T00:14:25.137Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/db/18380e788bb837e724358287b08e223b32bc8dccb3b0c12fa8ca20bc7f3b/matplotlib-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:819e409653c1106c8deaf62e6de6b8611449c2cd9939acb0d7d4e57a3d95cc7a", size = 8273231, upload-time = "2025-08-30T00:13:13.881Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/38dd49445b297e0d4f12a322c30779df0d43cb5873c7847df8a82e82ec67/matplotlib-3.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59c8ac8382fefb9cb71308dde16a7c487432f5255d8f1fd32473523abecfecdf", size = 8128730, upload-time = "2025-08-30T00:13:15.556Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84e82d9e0fd70c70bc55739defbd8055c54300750cbacf4740c9673a24d6933a", size = 8698539, upload-time = "2025-08-30T00:13:17.297Z" }, + { url = "https://files.pythonhosted.org/packages/71/34/44c7b1f075e1ea398f88aeabcc2907c01b9cc99e2afd560c1d49845a1227/matplotlib-3.10.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25f7a3eb42d6c1c56e89eacd495661fc815ffc08d9da750bca766771c0fd9110", size = 9529702, upload-time = "2025-08-30T00:13:19.248Z" }, + { url = "https://files.pythonhosted.org/packages/b5/7f/e5c2dc9950c7facaf8b461858d1b92c09dd0cf174fe14e21953b3dda06f7/matplotlib-3.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f9c862d91ec0b7842920a4cfdaaec29662195301914ea54c33e01f1a28d014b2", size = 9593742, upload-time = "2025-08-30T00:13:21.181Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1d/70c28528794f6410ee2856cd729fa1f1756498b8d3126443b0a94e1a8695/matplotlib-3.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:1b53bd6337eba483e2e7d29c5ab10eee644bc3a2491ec67cc55f7b44583ffb18", size = 8122753, upload-time = "2025-08-30T00:13:23.44Z" }, + { url = "https://files.pythonhosted.org/packages/e8/74/0e1670501fc7d02d981564caf7c4df42974464625935424ca9654040077c/matplotlib-3.10.6-cp313-cp313-win_arm64.whl", hash = "sha256:cbd5eb50b7058b2892ce45c2f4e92557f395c9991f5c886d1bb74a1582e70fd6", size = 7992973, upload-time = "2025-08-30T00:13:26.632Z" }, + { url = "https://files.pythonhosted.org/packages/b1/4e/60780e631d73b6b02bd7239f89c451a72970e5e7ec34f621eda55cd9a445/matplotlib-3.10.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:acc86dd6e0e695c095001a7fccff158c49e45e0758fdf5dcdbb0103318b59c9f", size = 8316869, upload-time = "2025-08-30T00:13:28.262Z" }, + { url = "https://files.pythonhosted.org/packages/f8/15/baa662374a579413210fc2115d40c503b7360a08e9cc254aa0d97d34b0c1/matplotlib-3.10.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e228cd2ffb8f88b7d0b29e37f68ca9aaf83e33821f24a5ccc4f082dd8396bc27", size = 8178240, upload-time = "2025-08-30T00:13:30.007Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3f/3c38e78d2aafdb8829fcd0857d25aaf9e7dd2dfcf7ec742765b585774931/matplotlib-3.10.6-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:658bc91894adeab669cf4bb4a186d049948262987e80f0857216387d7435d833", size = 8711719, upload-time = "2025-08-30T00:13:31.72Z" }, + { url = "https://files.pythonhosted.org/packages/96/4b/2ec2bbf8cefaa53207cc56118d1fa8a0f9b80642713ea9390235d331ede4/matplotlib-3.10.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8913b7474f6dd83ac444c9459c91f7f0f2859e839f41d642691b104e0af056aa", size = 9541422, upload-time = "2025-08-30T00:13:33.611Z" }, + { url = "https://files.pythonhosted.org/packages/83/7d/40255e89b3ef11c7871020563b2dd85f6cb1b4eff17c0f62b6eb14c8fa80/matplotlib-3.10.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:091cea22e059b89f6d7d1a18e2c33a7376c26eee60e401d92a4d6726c4e12706", size = 9594068, upload-time = "2025-08-30T00:13:35.833Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a9/0213748d69dc842537a113493e1c27daf9f96bd7cc316f933dc8ec4de985/matplotlib-3.10.6-cp313-cp313t-win_amd64.whl", hash = "sha256:491e25e02a23d7207629d942c666924a6b61e007a48177fdd231a0097b7f507e", size = 8200100, upload-time = "2025-08-30T00:13:37.668Z" }, + { url = "https://files.pythonhosted.org/packages/be/15/79f9988066ce40b8a6f1759a934ea0cde8dc4adc2262255ee1bc98de6ad0/matplotlib-3.10.6-cp313-cp313t-win_arm64.whl", hash = "sha256:3d80d60d4e54cda462e2cd9a086d85cd9f20943ead92f575ce86885a43a565d5", size = 8042142, upload-time = "2025-08-30T00:13:39.426Z" }, + { url = "https://files.pythonhosted.org/packages/7c/58/e7b6d292beae6fb4283ca6fb7fa47d7c944a68062d6238c07b497dd35493/matplotlib-3.10.6-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:70aaf890ce1d0efd482df969b28a5b30ea0b891224bb315810a3940f67182899", size = 8273802, upload-time = "2025-08-30T00:13:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f6/7882d05aba16a8cdd594fb9a03a9d3cca751dbb6816adf7b102945522ee9/matplotlib-3.10.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1565aae810ab79cb72e402b22facfa6501365e73ebab70a0fdfb98488d2c3c0c", size = 8131365, upload-time = "2025-08-30T00:13:42.664Z" }, + { url = "https://files.pythonhosted.org/packages/94/bf/ff32f6ed76e78514e98775a53715eca4804b12bdcf35902cdd1cf759d324/matplotlib-3.10.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3b23315a01981689aa4e1a179dbf6ef9fbd17143c3eea77548c2ecfb0499438", size = 9533961, upload-time = "2025-08-30T00:13:44.372Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c3/6bf88c2fc2da7708a2ff8d2eeb5d68943130f50e636d5d3dcf9d4252e971/matplotlib-3.10.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:30fdd37edf41a4e6785f9b37969de57aea770696cb637d9946eb37470c94a453", size = 9804262, upload-time = "2025-08-30T00:13:46.614Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7a/e05e6d9446d2d577b459427ad060cd2de5742d0e435db3191fea4fcc7e8b/matplotlib-3.10.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bc31e693da1c08012c764b053e702c1855378e04102238e6a5ee6a7117c53a47", size = 9595508, upload-time = "2025-08-30T00:13:48.731Z" }, + { url = "https://files.pythonhosted.org/packages/39/fb/af09c463ced80b801629fd73b96f726c9f6124c3603aa2e480a061d6705b/matplotlib-3.10.6-cp314-cp314-win_amd64.whl", hash = "sha256:05be9bdaa8b242bc6ff96330d18c52f1fc59c6fb3a4dd411d953d67e7e1baf98", size = 8252742, upload-time = "2025-08-30T00:13:50.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f9/b682f6db9396d9ab8f050c0a3bfbb5f14fb0f6518f08507c04cc02f8f229/matplotlib-3.10.6-cp314-cp314-win_arm64.whl", hash = "sha256:f56a0d1ab05d34c628592435781d185cd99630bdfd76822cd686fb5a0aecd43a", size = 8124237, upload-time = "2025-08-30T00:13:54.3Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d2/b69b4a0923a3c05ab90527c60fdec899ee21ca23ede7f0fb818e6620d6f2/matplotlib-3.10.6-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:94f0b4cacb23763b64b5dace50d5b7bfe98710fed5f0cef5c08135a03399d98b", size = 8316956, upload-time = "2025-08-30T00:13:55.932Z" }, + { url = "https://files.pythonhosted.org/packages/28/e9/dc427b6f16457ffaeecb2fc4abf91e5adb8827861b869c7a7a6d1836fa73/matplotlib-3.10.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cc332891306b9fb39462673d8225d1b824c89783fee82840a709f96714f17a5c", size = 8178260, upload-time = "2025-08-30T00:14:00.942Z" }, + { url = "https://files.pythonhosted.org/packages/c4/89/1fbd5ad611802c34d1c7ad04607e64a1350b7fb9c567c4ec2c19e066ed35/matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee1d607b3fb1590deb04b69f02ea1d53ed0b0bf75b2b1a5745f269afcbd3cdd3", size = 9541422, upload-time = "2025-08-30T00:14:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/65fec8716025b22c1d72d5a82ea079934c76a547696eaa55be6866bc89b1/matplotlib-3.10.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:376a624a218116461696b27b2bbf7a8945053e6d799f6502fc03226d077807bf", size = 9803678, upload-time = "2025-08-30T00:14:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b0/40fb2b3a1ab9381bb39a952e8390357c8be3bdadcf6d5055d9c31e1b35ae/matplotlib-3.10.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:83847b47f6524c34b4f2d3ce726bb0541c48c8e7692729865c3df75bfa0f495a", size = 9594077, upload-time = "2025-08-30T00:14:07.012Z" }, + { url = "https://files.pythonhosted.org/packages/76/34/c4b71b69edf5b06e635eee1ed10bfc73cf8df058b66e63e30e6a55e231d5/matplotlib-3.10.6-cp314-cp314t-win_amd64.whl", hash = "sha256:c7e0518e0d223683532a07f4b512e2e0729b62674f1b3a1a69869f98e6b1c7e3", size = 8342822, upload-time = "2025-08-30T00:14:09.041Z" }, + { url = "https://files.pythonhosted.org/packages/e8/62/aeabeef1a842b6226a30d49dd13e8a7a1e81e9ec98212c0b5169f0a12d83/matplotlib-3.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:4dd83e029f5b4801eeb87c64efd80e732452781c16a9cf7415b7b63ec8f374d7", size = 8172588, upload-time = "2025-08-30T00:14:11.166Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, +] + +[[package]] +name = "mcp" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/a1/b1f328da3b153683d2ec34f849b4b6eac2790fb240e3aef06ff2fab3df9d/mcp-1.16.0.tar.gz", hash = "sha256:39b8ca25460c578ee2cdad33feeea122694cfdf73eef58bee76c42f6ef0589df", size = 472918, upload-time = "2025-10-02T16:58:20.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/0e/7cebc88e17daf94ebe28c95633af595ccb2864dc2ee7abd75542d98495cc/mcp-1.16.0-py3-none-any.whl", hash = "sha256:ec917be9a5d31b09ba331e1768aa576e0af45470d657a0319996a20a57d7d633", size = 167266, upload-time = "2025-10-02T16:58:19.039Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mistune" +version = "3.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/02/a7fb8b21d4d55ac93cdcde9d3638da5dd0ebdd3a4fed76c7725e10b81cbe/mistune-3.1.4.tar.gz", hash = "sha256:b5a7f801d389f724ec702840c11d8fc48f2b33519102fc7ee739e8177b672164", size = 94588, upload-time = "2025-08-29T07:20:43.594Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/f0/8282d9641415e9e33df173516226b404d367a0fc55e1a60424a152913abc/mistune-3.1.4-py3-none-any.whl", hash = "sha256:93691da911e5d9d2e23bc54472892aff676df27a75274962ff9edc210364266d", size = 53481, upload-time = "2025-08-29T07:20:42.218Z" }, +] + +[[package]] +name = "ml-dtypes" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/a7/aad060393123cfb383956dca68402aff3db1e1caffd5764887ed5153f41b/ml_dtypes-0.5.3.tar.gz", hash = "sha256:95ce33057ba4d05df50b1f3cfefab22e351868a843b3b15a46c65836283670c9", size = 692316, upload-time = "2025-07-29T18:39:19.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/87/1bcc98a66de7b2455dfb292f271452cac9edc4e870796e0d87033524d790/ml_dtypes-0.5.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5103856a225465371fe119f2fef737402b705b810bd95ad5f348e6e1a6ae21af", size = 663781, upload-time = "2025-07-29T18:38:42.984Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2c/bd2a79ba7c759ee192b5601b675b180a3fd6ccf48ffa27fe1782d280f1a7/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cae435a68861660af81fa3c5af16b70ca11a17275c5b662d9c6f58294e0f113", size = 4956217, upload-time = "2025-07-29T18:38:44.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/f3/091ba84e5395d7fe5b30c081a44dec881cd84b408db1763ee50768b2ab63/ml_dtypes-0.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6936283b56d74fbec431ca57ce58a90a908fdbd14d4e2d22eea6d72bb208a7b7", size = 4933109, upload-time = "2025-07-29T18:38:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/bc/24/054036dbe32c43295382c90a1363241684c4d6aaa1ecc3df26bd0c8d5053/ml_dtypes-0.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:d0f730a17cf4f343b2c7ad50cee3bd19e969e793d2be6ed911f43086460096e4", size = 208187, upload-time = "2025-07-29T18:38:48.24Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/7dc3ec6794a4a9004c765e0c341e32355840b698f73fd2daff46f128afc1/ml_dtypes-0.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:2db74788fc01914a3c7f7da0763427280adfc9cd377e9604b6b64eb8097284bd", size = 161559, upload-time = "2025-07-29T18:38:50.493Z" }, + { url = "https://files.pythonhosted.org/packages/12/91/e6c7a0d67a152b9330445f9f0cf8ae6eee9b83f990b8c57fe74631e42a90/ml_dtypes-0.5.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:93c36a08a6d158db44f2eb9ce3258e53f24a9a4a695325a689494f0fdbc71770", size = 689321, upload-time = "2025-07-29T18:38:52.03Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6c/b7b94b84a104a5be1883305b87d4c6bd6ae781504474b4cca067cb2340ec/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e44a3761f64bc009d71ddb6d6c71008ba21b53ab6ee588dadab65e2fa79eafc", size = 5274495, upload-time = "2025-07-29T18:38:53.797Z" }, + { url = "https://files.pythonhosted.org/packages/5b/38/6266604dffb43378055394ea110570cf261a49876fc48f548dfe876f34cc/ml_dtypes-0.5.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdf40d2aaabd3913dec11840f0d0ebb1b93134f99af6a0a4fd88ffe924928ab4", size = 5285422, upload-time = "2025-07-29T18:38:56.603Z" }, + { url = "https://files.pythonhosted.org/packages/7c/88/8612ff177d043a474b9408f0382605d881eeb4125ba89d4d4b3286573a83/ml_dtypes-0.5.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:aec640bd94c4c85c0d11e2733bd13cbb10438fb004852996ec0efbc6cacdaf70", size = 661182, upload-time = "2025-07-29T18:38:58.414Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2b/0569a5e88b29240d373e835107c94ae9256fb2191d3156b43b2601859eff/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bda32ce212baa724e03c68771e5c69f39e584ea426bfe1a701cb01508ffc7035", size = 4956187, upload-time = "2025-07-29T18:39:00.611Z" }, + { url = "https://files.pythonhosted.org/packages/51/66/273c2a06ae44562b104b61e6b14444da00061fd87652506579d7eb2c40b1/ml_dtypes-0.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c205cac07d24a29840c163d6469f61069ce4b065518519216297fc2f261f8db9", size = 4930911, upload-time = "2025-07-29T18:39:02.405Z" }, + { url = "https://files.pythonhosted.org/packages/93/ab/606be3e87dc0821bd360c8c1ee46108025c31a4f96942b63907bb441b87d/ml_dtypes-0.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:cd7c0bb22d4ff86d65ad61b5dd246812e8993fbc95b558553624c33e8b6903ea", size = 216664, upload-time = "2025-07-29T18:39:03.927Z" }, + { url = "https://files.pythonhosted.org/packages/30/a2/e900690ca47d01dffffd66375c5de8c4f8ced0f1ef809ccd3b25b3e6b8fa/ml_dtypes-0.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:9d55ea7f7baf2aed61bf1872116cefc9d0c3693b45cae3916897ee27ef4b835e", size = 160203, upload-time = "2025-07-29T18:39:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/53/21/783dfb51f40d2660afeb9bccf3612b99f6a803d980d2a09132b0f9d216ab/ml_dtypes-0.5.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:e12e29764a0e66a7a31e9b8bf1de5cc0423ea72979f45909acd4292de834ccd3", size = 689324, upload-time = "2025-07-29T18:39:07.567Z" }, + { url = "https://files.pythonhosted.org/packages/09/f7/a82d249c711abf411ac027b7163f285487f5e615c3e0716c61033ce996ab/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19f6c3a4f635c2fc9e2aa7d91416bd7a3d649b48350c51f7f715a09370a90d93", size = 5275917, upload-time = "2025-07-29T18:39:09.339Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3c/541c4b30815ab90ebfbb51df15d0b4254f2f9f1e2b4907ab229300d5e6f2/ml_dtypes-0.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ab039ffb40f3dc0aeeeba84fd6c3452781b5e15bef72e2d10bcb33e4bbffc39", size = 5285284, upload-time = "2025-07-29T18:39:11.532Z" }, +] + +[[package]] +name = "mlflow" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "cryptography" }, + { name = "docker" }, + { name = "fastmcp" }, + { name = "flask" }, + { name = "graphene" }, + { name = "gunicorn", marker = "sys_platform != 'win32'" }, + { name = "matplotlib" }, + { name = "mlflow-skinny" }, + { name = "mlflow-tracing" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "sqlalchemy" }, + { name = "waitress", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/6b/94e454bf1ff34eb503701c3cb20742a72abab33957392f1f2b3e9b4d5601/mlflow-3.4.0.tar.gz", hash = "sha256:a564f9296b860fe710c0574f9f309b53ae30662eb969994df2453b198fa4c3bb", size = 26061019, upload-time = "2025-09-17T06:24:29.411Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/fe/1ed27f800cd1709a272c6e26b78ec3d77a5ba482171ea1b5bfbcf4c067c0/mlflow-3.4.0-py3-none-any.whl", hash = "sha256:065ca7f9acda7bdfbc01deefdcb31172c91ff954ad76405a9d1f9d67dea4c33c", size = 26726629, upload-time = "2025-09-17T06:24:26.457Z" }, +] + +[[package]] +name = "mlflow-skinny" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "click" }, + { name = "cloudpickle" }, + { name = "databricks-sdk" }, + { name = "fastapi" }, + { name = "gitpython" }, + { name = "importlib-metadata" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlparse" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/90/ddfcfba5b64fb2a9a874998fcd0a1a6e4013b95744eaeeb7a0b8a78f25c5/mlflow_skinny-3.4.0.tar.gz", hash = "sha256:1730207e64811b00ebfa2d5b9b899212a7e6a06e8cd49eb3f90888ff7e7bc3a7", size = 1851246, upload-time = "2025-09-17T06:11:11.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/94/7acd7c6970cc75da1fd3b550e43d8b99068032022f47b0ef224a137ec679/mlflow_skinny-3.4.0-py3-none-any.whl", hash = "sha256:51e06c1f717093501a9a1b2d5b7bea382bd1b7c3542a52f824c510263f86f0c7", size = 2221734, upload-time = "2025-09-17T06:11:09.89Z" }, +] + +[[package]] +name = "mlflow-tracing" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "databricks-sdk" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "packaging" }, + { name = "protobuf" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/61/a2c17b64534728995302c5a3f7abe9fcfa848beeffdc8c069e0dbcafa30e/mlflow_tracing-3.4.0.tar.gz", hash = "sha256:805537d43387717c355bcc07c065941f1614ed037de75b73c168cdf60d5e6e08", size = 1011159, upload-time = "2025-09-17T06:13:41.108Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/96/403b1191ccf587f19a8c94085477600d6e6b3d61a7aff46f353b20b450f9/mlflow_tracing-3.4.0-py3-none-any.whl", hash = "sha256:06e4a423373c96507f3e40d00a564665a375e0d78856917e52dd78d8b833edf2", size = 1220253, upload-time = "2025-09-17T06:13:39.199Z" }, +] + +[[package]] +name = "more-itertools" +version = "10.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "multidict" +version = "6.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848, upload-time = "2025-08-11T12:07:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060, upload-time = "2025-08-11T12:07:21.163Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269, upload-time = "2025-08-11T12:07:22.392Z" }, + { url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158, upload-time = "2025-08-11T12:07:23.636Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076, upload-time = "2025-08-11T12:07:25.049Z" }, + { url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694, upload-time = "2025-08-11T12:07:26.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350, upload-time = "2025-08-11T12:07:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250, upload-time = "2025-08-11T12:07:29.303Z" }, + { url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900, upload-time = "2025-08-11T12:07:30.764Z" }, + { url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355, upload-time = "2025-08-11T12:07:32.205Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061, upload-time = "2025-08-11T12:07:33.623Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675, upload-time = "2025-08-11T12:07:34.958Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247, upload-time = "2025-08-11T12:07:36.588Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960, upload-time = "2025-08-11T12:07:39.735Z" }, + { url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078, upload-time = "2025-08-11T12:07:41.525Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708, upload-time = "2025-08-11T12:07:43.405Z" }, + { url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912, upload-time = "2025-08-11T12:07:45.082Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076, upload-time = "2025-08-11T12:07:46.746Z" }, + { url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812, upload-time = "2025-08-11T12:07:48.402Z" }, + { url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313, upload-time = "2025-08-11T12:07:49.679Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777, upload-time = "2025-08-11T12:07:51.318Z" }, + { url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321, upload-time = "2025-08-11T12:07:52.965Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954, upload-time = "2025-08-11T12:07:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612, upload-time = "2025-08-11T12:07:55.914Z" }, + { url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528, upload-time = "2025-08-11T12:07:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329, upload-time = "2025-08-11T12:07:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928, upload-time = "2025-08-11T12:08:01.037Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228, upload-time = "2025-08-11T12:08:02.96Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869, upload-time = "2025-08-11T12:08:04.746Z" }, + { url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446, upload-time = "2025-08-11T12:08:06.332Z" }, + { url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299, upload-time = "2025-08-11T12:08:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926, upload-time = "2025-08-11T12:08:09.467Z" }, + { url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383, upload-time = "2025-08-11T12:08:10.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775, upload-time = "2025-08-11T12:08:12.439Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100, upload-time = "2025-08-11T12:08:13.823Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501, upload-time = "2025-08-11T12:08:15.173Z" }, + { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, +] + +[[package]] +name = "mypy" +version = "1.18.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" }, + { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" }, + { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" }, + { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "narwhals" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/05/79a5b5a795f36c1aaa002d194c1ef71e5d95f7e1900155bbfde734815ab9/narwhals-2.8.0.tar.gz", hash = "sha256:52e0b22d54718264ae703bd9293af53b04abc995a1414908c3b807ba8c913858", size = 574277, upload-time = "2025-10-13T08:44:28.81Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl", hash = "sha256:6304856676ba4a79fd34148bda63aed8060dd6edb1227edf3659ce5e091de73c", size = 415852, upload-time = "2025-10-13T08:44:25.421Z" }, +] + +[[package]] +name = "natsort" +version = "8.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" }, +] + +[[package]] +name = "nbclient" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424, upload-time = "2024-12-19T10:32:27.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434, upload-time = "2024-12-19T10:32:24.139Z" }, +] + +[[package]] +name = "nbconvert" +version = "7.16.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", extra = ["css"] }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "ninja" +version = "1.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/73/79a0b22fc731989c708068427579e840a6cf4e937fe7ae5c5d0b7356ac22/ninja-1.13.0.tar.gz", hash = "sha256:4a40ce995ded54d9dc24f8ea37ff3bf62ad192b547f6c7126e7e25045e76f978", size = 242558, upload-time = "2025-08-11T15:10:19.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/74/d02409ed2aa865e051b7edda22ad416a39d81a84980f544f8de717cab133/ninja-1.13.0-py3-none-macosx_10_9_universal2.whl", hash = "sha256:fa2a8bfc62e31b08f83127d1613d10821775a0eb334197154c4d6067b7068ff1", size = 310125, upload-time = "2025-08-11T15:09:50.971Z" }, + { url = "https://files.pythonhosted.org/packages/8e/de/6e1cd6b84b412ac1ef327b76f0641aeb5dcc01e9d3f9eee0286d0c34fd93/ninja-1.13.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3d00c692fb717fd511abeb44b8c5d00340c36938c12d6538ba989fe764e79630", size = 177467, upload-time = "2025-08-11T15:09:52.767Z" }, + { url = "https://files.pythonhosted.org/packages/c8/83/49320fb6e58ae3c079381e333575fdbcf1cca3506ee160a2dcce775046fa/ninja-1.13.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:be7f478ff9f96a128b599a964fc60a6a87b9fa332ee1bd44fa243ac88d50291c", size = 187834, upload-time = "2025-08-11T15:09:54.115Z" }, + { url = "https://files.pythonhosted.org/packages/56/c7/ba22748fb59f7f896b609cd3e568d28a0a367a6d953c24c461fe04fc4433/ninja-1.13.0-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:60056592cf495e9a6a4bea3cd178903056ecb0943e4de45a2ea825edb6dc8d3e", size = 202736, upload-time = "2025-08-11T15:09:55.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/22/d1de07632b78ac8e6b785f41fa9aad7a978ec8c0a1bf15772def36d77aac/ninja-1.13.0-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1c97223cdda0417f414bf864cfb73b72d8777e57ebb279c5f6de368de0062988", size = 179034, upload-time = "2025-08-11T15:09:57.394Z" }, + { url = "https://files.pythonhosted.org/packages/ed/de/0e6edf44d6a04dabd0318a519125ed0415ce437ad5a1ec9b9be03d9048cf/ninja-1.13.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fb46acf6b93b8dd0322adc3a4945452a4e774b75b91293bafcc7b7f8e6517dfa", size = 180716, upload-time = "2025-08-11T15:09:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/54/28/938b562f9057aaa4d6bfbeaa05e81899a47aebb3ba6751e36c027a7f5ff7/ninja-1.13.0-py3-none-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4be9c1b082d244b1ad7ef41eb8ab088aae8c109a9f3f0b3e56a252d3e00f42c1", size = 146843, upload-time = "2025-08-11T15:10:00.046Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fb/d06a3838de4f8ab866e44ee52a797b5491df823901c54943b2adb0389fbb/ninja-1.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6739d3352073341ad284246f81339a384eec091d9851a886dfa5b00a6d48b3e2", size = 154402, upload-time = "2025-08-11T15:10:01.657Z" }, + { url = "https://files.pythonhosted.org/packages/31/bf/0d7808af695ceddc763cf251b84a9892cd7f51622dc8b4c89d5012779f06/ninja-1.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:11be2d22027bde06f14c343f01d31446747dbb51e72d00decca2eb99be911e2f", size = 552388, upload-time = "2025-08-11T15:10:03.349Z" }, + { url = "https://files.pythonhosted.org/packages/9d/70/c99d0c2c809f992752453cce312848abb3b1607e56d4cd1b6cded317351a/ninja-1.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:aa45b4037b313c2f698bc13306239b8b93b4680eb47e287773156ac9e9304714", size = 472501, upload-time = "2025-08-11T15:10:04.735Z" }, + { url = "https://files.pythonhosted.org/packages/9f/43/c217b1153f0e499652f5e0766da8523ce3480f0a951039c7af115e224d55/ninja-1.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f8e1e8a1a30835eeb51db05cf5a67151ad37542f5a4af2a438e9490915e5b72", size = 638280, upload-time = "2025-08-11T15:10:06.512Z" }, + { url = "https://files.pythonhosted.org/packages/8c/45/9151bba2c8d0ae2b6260f71696330590de5850e5574b7b5694dce6023e20/ninja-1.13.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:3d7d7779d12cb20c6d054c61b702139fd23a7a964ec8f2c823f1ab1b084150db", size = 642420, upload-time = "2025-08-11T15:10:08.35Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/95752eb635bb8ad27d101d71bef15bc63049de23f299e312878fc21cb2da/ninja-1.13.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:d741a5e6754e0bda767e3274a0f0deeef4807f1fec6c0d7921a0244018926ae5", size = 585106, upload-time = "2025-08-11T15:10:09.818Z" }, + { url = "https://files.pythonhosted.org/packages/c1/31/aa56a1a286703800c0cbe39fb4e82811c277772dc8cd084f442dd8e2938a/ninja-1.13.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:e8bad11f8a00b64137e9b315b137d8bb6cbf3086fbdc43bf1f90fd33324d2e96", size = 707138, upload-time = "2025-08-11T15:10:11.366Z" }, + { url = "https://files.pythonhosted.org/packages/34/6f/5f5a54a1041af945130abdb2b8529cbef0cdcbbf9bcf3f4195378319d29a/ninja-1.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b4f2a072db3c0f944c32793e91532d8948d20d9ab83da9c0c7c15b5768072200", size = 581758, upload-time = "2025-08-11T15:10:13.295Z" }, + { url = "https://files.pythonhosted.org/packages/95/97/51359c77527d45943fe7a94d00a3843b81162e6c4244b3579fe8fc54cb9c/ninja-1.13.0-py3-none-win32.whl", hash = "sha256:8cfbb80b4a53456ae8a39f90ae3d7a2129f45ea164f43fadfa15dc38c4aef1c9", size = 267201, upload-time = "2025-08-11T15:10:15.158Z" }, + { url = "https://files.pythonhosted.org/packages/29/45/c0adfbfb0b5895aa18cec400c535b4f7ff3e52536e0403602fc1a23f7de9/ninja-1.13.0-py3-none-win_amd64.whl", hash = "sha256:fb8ee8719f8af47fed145cced4a85f0755dd55d45b2bddaf7431fa89803c5f3e", size = 309975, upload-time = "2025-08-11T15:10:16.697Z" }, + { url = "https://files.pythonhosted.org/packages/df/93/a7b983643d1253bb223234b5b226e69de6cda02b76cdca7770f684b795f5/ninja-1.13.0-py3-none-win_arm64.whl", hash = "sha256:3c0b40b1f0bba764644385319028650087b4c1b18cdfa6f45cb39a3669b81aa9", size = 290806, upload-time = "2025-08-11T15:10:18.018Z" }, +] + +[[package]] +name = "nncf" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "natsort" }, + { name = "networkx" }, + { name = "ninja" }, + { name = "numpy" }, + { name = "openvino-telemetry" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "psutil" }, + { name = "pydot" }, + { name = "pymoo" }, + { name = "rich" }, + { name = "safetensors" }, + { name = "scikit-learn" }, + { name = "scipy" }, + { name = "tabulate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/7f/f9c16a16c6553c03ab2ac2f8bf73d550784c89e03721ba68fee8ccaa4b20/nncf-2.18.0.tar.gz", hash = "sha256:b9e8852c3ac00bca3ad41698c245ce222ab7b531ec1089c0e83a3b4284283c1c", size = 995062, upload-time = "2025-09-04T14:37:46.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/7b/afe5d308bc8ee7bc521113cad2f3ee5b3c6936f294b1d76cbab1c31de5b9/nncf-2.18.0-py3-none-any.whl", hash = "sha256:7744b5d4c4991fee5cfcd934968cb429d3a8196782168cce7d40ecbedfea4d7f", size = 1452120, upload-time = "2025-09-04T14:37:45.551Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + +[[package]] +name = "notebook" +version = "7.4.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, + { name = "jupyterlab" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/09/f6f64ba156842ef68d3ea763fa171a2f7e7224f200a15dd4af5b83c34756/notebook-7.4.7.tar.gz", hash = "sha256:3f0a04027dfcee8a876de48fba13ab77ec8c12f72f848a222ed7f5081b9e342a", size = 13937702, upload-time = "2025-09-27T08:00:22.536Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/d7/06d13087e20388926e7423d2489e728d2e59f2453039cdb0574a7c070e76/notebook-7.4.7-py3-none-any.whl", hash = "sha256:362b7c95527f7dd3c4c84d410b782872fd9c734fb2524c11dd92758527b6eda6", size = 14342894, upload-time = "2025-09-27T08:00:18.496Z" }, +] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.8.4.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.10.2.21" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.3.83" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.13.1.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.9.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.3.90" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.8.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.27.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/5b/4e4fff7bad39adf89f735f2bc87248c81db71205b62bcc0d5ca5b606b3c3/nvidia_nccl_cu12-2.27.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adf27ccf4238253e0b826bce3ff5fa532d65fc42322c8bfdfaf28024c0fbe039", size = 322364134, upload-time = "2025-06-03T21:58:04.013Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, +] + +[[package]] +name = "ollama" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/47/f9ee32467fe92744474a8c72e138113f3b529fc266eea76abfdec9a33f3b/ollama-0.6.0.tar.gz", hash = "sha256:da2b2d846b5944cfbcee1ca1e6ee0585f6c9d45a2fe9467cbcd096a37383da2f", size = 50811, upload-time = "2025-09-24T22:46:02.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/c1/edc9f41b425ca40b26b7c104c5f6841a4537bb2552bfa6ca66e81405bb95/ollama-0.6.0-py3-none-any.whl", hash = "sha256:534511b3ccea2dff419ae06c3b58d7f217c55be7897c8ce5868dfb6b219cf7a0", size = 14130, upload-time = "2025-09-24T22:46:01.19Z" }, +] + +[[package]] +name = "omegaconf" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "antlr4-python3-runtime" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/48/6388f1bb9da707110532cb70ec4d2822858ddfb44f1cdf1233c20a80ea4b/omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7", size = 3298120, upload-time = "2022-12-08T20:59:22.753Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500, upload-time = "2022-12-08T20:59:19.686Z" }, +] + +[[package]] +name = "onnx" +version = "1.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "protobuf" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/bf/b0a63ee9f3759dcd177b28c6f2cb22f2aecc6d9b3efecaabc298883caa5f/onnx-1.19.0.tar.gz", hash = "sha256:aa3f70b60f54a29015e41639298ace06adf1dd6b023b9b30f1bca91bb0db9473", size = 11949859, upload-time = "2025-08-27T02:34:27.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/29/d7b731f63d243f815d9256dce0dca3c151dcaa1ac59f73e6ee06c9afbe91/onnx-1.19.0-cp313-cp313-macosx_12_0_universal2.whl", hash = "sha256:9aed51a4b01acc9ea4e0fe522f34b2220d59e9b2a47f105ac8787c2e13ec5111", size = 18322412, upload-time = "2025-08-27T02:33:36.723Z" }, + { url = "https://files.pythonhosted.org/packages/58/f5/d3106becb42cb374f0e17ff4c9933a97f1ee1d6a798c9452067f7d3ff61b/onnx-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce2cdc3eb518bb832668c4ea9aeeda01fbaa59d3e8e5dfaf7aa00f3d37119404", size = 18026565, upload-time = "2025-08-27T02:33:39.493Z" }, + { url = "https://files.pythonhosted.org/packages/83/fa/b086d17bab3900754c7ffbabfb244f8e5e5da54a34dda2a27022aa2b373b/onnx-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b546bd7958734b6abcd40cfede3d025e9c274fd96334053a288ab11106bd0aa", size = 18202077, upload-time = "2025-08-27T02:33:42.115Z" }, + { url = "https://files.pythonhosted.org/packages/35/f2/5e2dfb9d4cf873f091c3f3c6d151f071da4295f9893fbf880f107efe3447/onnx-1.19.0-cp313-cp313-win32.whl", hash = "sha256:03086bffa1cf5837430cf92f892ca0cd28c72758d8905578c2bf8ffaf86c6743", size = 16333198, upload-time = "2025-08-27T02:33:45.172Z" }, + { url = "https://files.pythonhosted.org/packages/79/67/b3751a35c2522f62f313156959575619b8fa66aa883db3adda9d897d8eb2/onnx-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:1715b51eb0ab65272e34ef51cb34696160204b003566cd8aced2ad20a8f95cb8", size = 16453836, upload-time = "2025-08-27T02:33:47.779Z" }, + { url = "https://files.pythonhosted.org/packages/14/b9/1df85effc960fbbb90bb7bc36eb3907c676b104bc2f88bce022bcfdaef63/onnx-1.19.0-cp313-cp313-win_arm64.whl", hash = "sha256:6bf5acdb97a3ddd6e70747d50b371846c313952016d0c41133cbd8f61b71a8d5", size = 16425877, upload-time = "2025-08-27T02:33:50.357Z" }, + { url = "https://files.pythonhosted.org/packages/23/2b/089174a1427be9149f37450f8959a558ba20f79fca506ba461d59379d3a1/onnx-1.19.0-cp313-cp313t-macosx_12_0_universal2.whl", hash = "sha256:46cf29adea63e68be0403c68de45ba1b6acc9bb9592c5ddc8c13675a7c71f2cb", size = 18348546, upload-time = "2025-08-27T02:33:56.132Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d6/3458f0e3a9dc7677675d45d7d6528cb84ad321c8670cc10c69b32c3e03da/onnx-1.19.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:246f0de1345498d990a443d55a5b5af5101a3e25a05a2c3a5fe8b7bd7a7d0707", size = 18033067, upload-time = "2025-08-27T02:33:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/6e4130e1b4b29465ee1fb07d04e8d6f382227615c28df8f607ba50909e2a/onnx-1.19.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae0d163ffbc250007d984b8dd692a4e2e4506151236b50ca6e3560b612ccf9ff", size = 18205741, upload-time = "2025-08-27T02:34:01.538Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d8/f64d010fd024b2a2b11ce0c4ee179e4f8f6d4ccc95f8184961c894c22af1/onnx-1.19.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7c151604c7cca6ae26161c55923a7b9b559df3344938f93ea0074d2d49e7fe78", size = 16453839, upload-time = "2025-08-27T02:34:06.515Z" }, + { url = "https://files.pythonhosted.org/packages/67/ec/8761048eabef4dad55af4c002c672d139b9bd47c3616abaed642a1710063/onnx-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:236bc0e60d7c0f4159300da639953dd2564df1c195bce01caba172a712e75af4", size = 18027605, upload-time = "2025-08-27T02:34:08.962Z" }, +] + +[[package]] +name = "open-clip-torch" +version = "2.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ftfy" }, + { name = "huggingface-hub" }, + { name = "protobuf" }, + { name = "regex" }, + { name = "sentencepiece" }, + { name = "timm" }, + { name = "torch" }, + { name = "torchvision" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/a3/4dc94bfbfc45cbc49f0b7cac1aee1f2b15420013b26e18a94fdcdd906a2a/open_clip_torch-2.24.0.tar.gz", hash = "sha256:1ae2482aee313827c399eb8a4e735f0b0cd31e4c62085ce2dbfa3a13190219ff", size = 1479567, upload-time = "2024-01-08T10:35:55.753Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/d2/6ae2ee32d0d2ea9982774920e0ef96d439ee332f459f6d8a941149b1b4ad/open_clip_torch-2.24.0-py3-none-any.whl", hash = "sha256:2537dbe76c8008caa46652bc97cb32bceeae56baff6289e7b4eb22539a80c801", size = 1497056, upload-time = "2024-01-08T10:35:53.143Z" }, +] + +[[package]] +name = "openai" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/dd/4d4d46a06943e37c95b6e388237e1e38d1e9aab264ff070f86345d60b7a4/openai-2.1.0.tar.gz", hash = "sha256:47f3463a5047340a989b4c0cd5378054acfca966ff61a96553b22f098e3270a2", size = 572998, upload-time = "2025-10-02T20:43:15.385Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/83/88f64fc8f037885efa8a629d1215f5bc1f037453bab4d4f823b5533319eb/openai-2.1.0-py3-none-any.whl", hash = "sha256:33172e8c06a4576144ba4137a493807a9ca427421dcabc54ad3aa656daf757d3", size = 964939, upload-time = "2025-10-02T20:43:13.568Z" }, +] + +[[package]] +name = "openapi-core" +version = "0.19.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "isodate" }, + { name = "jsonschema" }, + { name = "jsonschema-path" }, + { name = "more-itertools" }, + { name = "openapi-schema-validator" }, + { name = "openapi-spec-validator" }, + { name = "parse" }, + { name = "typing-extensions" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/35/1acaa5f2fcc6e54eded34a2ec74b479439c4e469fc4e8d0e803fda0234db/openapi_core-0.19.5.tar.gz", hash = "sha256:421e753da56c391704454e66afe4803a290108590ac8fa6f4a4487f4ec11f2d3", size = 103264, upload-time = "2025-03-20T20:17:28.193Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/6f/83ead0e2e30a90445ee4fc0135f43741aebc30cca5b43f20968b603e30b6/openapi_core-0.19.5-py3-none-any.whl", hash = "sha256:ef7210e83a59394f46ce282639d8d26ad6fc8094aa904c9c16eb1bac8908911f", size = 106595, upload-time = "2025-03-20T20:17:26.77Z" }, +] + +[[package]] +name = "openapi-pydantic" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" }, +] + +[[package]] +name = "openapi-schema-validator" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-specifications" }, + { name = "rfc3339-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/f3/5507ad3325169347cd8ced61c232ff3df70e2b250c49f0fe140edb4973c6/openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee", size = 11550, upload-time = "2025-01-10T18:08:22.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/c6/ad0fba32775ae749016829dace42ed80f4407b171da41313d1a3a5f102e4/openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3", size = 8755, upload-time = "2025-01-10T18:08:19.758Z" }, +] + +[[package]] +name = "openapi-spec-validator" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "jsonschema-path" }, + { name = "lazy-object-proxy" }, + { name = "openapi-schema-validator" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/af/fe2d7618d6eae6fb3a82766a44ed87cd8d6d82b4564ed1c7cfb0f6378e91/openapi_spec_validator-0.7.2.tar.gz", hash = "sha256:cc029309b5c5dbc7859df0372d55e9d1ff43e96d678b9ba087f7c56fc586f734", size = 36855, upload-time = "2025-06-07T14:48:56.299Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713, upload-time = "2025-06-07T14:48:54.077Z" }, +] + +[[package]] +name = "opencv-python" +version = "4.12.0.88" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/71/25c98e634b6bdeca4727c7f6d6927b056080668c5008ad3c8fc9e7f8f6ec/opencv-python-4.12.0.88.tar.gz", hash = "sha256:8b738389cede219405f6f3880b851efa3415ccd674752219377353f017d2994d", size = 95373294, upload-time = "2025-07-07T09:20:52.389Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/68/3da40142e7c21e9b1d4e7ddd6c58738feb013203e6e4b803d62cdd9eb96b/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:f9a1f08883257b95a5764bf517a32d75aec325319c8ed0f89739a57fae9e92a5", size = 37877727, upload-time = "2025-07-07T09:13:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/33/7c/042abe49f58d6ee7e1028eefc3334d98ca69b030e3b567fe245a2b28ea6f/opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:812eb116ad2b4de43ee116fcd8991c3a687f099ada0b04e68f64899c09448e81", size = 57326471, upload-time = "2025-07-07T09:13:41.26Z" }, + { url = "https://files.pythonhosted.org/packages/62/3a/440bd64736cf8116f01f3b7f9f2e111afb2e02beb2ccc08a6458114a6b5d/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:51fd981c7df6af3e8f70b1556696b05224c4e6b6777bdd2a46b3d4fb09de1a92", size = 45887139, upload-time = "2025-07-07T09:13:50.761Z" }, + { url = "https://files.pythonhosted.org/packages/68/1f/795e7f4aa2eacc59afa4fb61a2e35e510d06414dd5a802b51a012d691b37/opencv_python-4.12.0.88-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:092c16da4c5a163a818f120c22c5e4a2f96e0db4f24e659c701f1fe629a690f9", size = 67041680, upload-time = "2025-07-07T09:14:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/02/96/213fea371d3cb2f1d537612a105792aa0a6659fb2665b22cad709a75bd94/opencv_python-4.12.0.88-cp37-abi3-win32.whl", hash = "sha256:ff554d3f725b39878ac6a2e1fa232ec509c36130927afc18a1719ebf4fbf4357", size = 30284131, upload-time = "2025-07-07T09:14:08.819Z" }, + { url = "https://files.pythonhosted.org/packages/fa/80/eb88edc2e2b11cd2dd2e56f1c80b5784d11d6e6b7f04a1145df64df40065/opencv_python-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:d98edb20aa932fd8ebd276a72627dad9dc097695b3d435a4257557bbb49a79d2", size = 39000307, upload-time = "2025-07-07T09:14:16.641Z" }, +] + +[[package]] +name = "opencv-python-headless" +version = "4.12.0.88" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a4/63/6861102ec149c3cd298f4d1ea7ce9d6adbc7529221606ff1dab991a19adb/opencv-python-headless-4.12.0.88.tar.gz", hash = "sha256:cfdc017ddf2e59b6c2f53bc12d74b6b0be7ded4ec59083ea70763921af2b6c09", size = 95379675, upload-time = "2025-07-07T09:21:06.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/7d/414e243c5c8216a5277afd104a319cc1291c5e23f5eeef512db5629ee7f4/opencv_python_headless-4.12.0.88-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:1e58d664809b3350c1123484dd441e1667cd7bed3086db1b9ea1b6f6cb20b50e", size = 37877864, upload-time = "2025-07-07T09:14:41.693Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/7e162714beed1cd5e7b5eb66fcbcba2f065c51b1d9da2463024c84d2f7c0/opencv_python_headless-4.12.0.88-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:365bb2e486b50feffc2d07a405b953a8f3e8eaa63865bc650034e5c71e7a5154", size = 57326608, upload-time = "2025-07-07T09:14:51.885Z" }, + { url = "https://files.pythonhosted.org/packages/69/4e/116720df7f1f7f3b59abc608ca30fbec9d2b3ae810afe4e4d26483d9dfa0/opencv_python_headless-4.12.0.88-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:aeb4b13ecb8b4a0beb2668ea07928160ea7c2cd2d9b5ef571bbee6bafe9cc8d0", size = 33145800, upload-time = "2025-07-07T09:15:00.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/53/e19c21e0c4eb1275c3e2c97b081103b6dfb3938172264d283a519bf728b9/opencv_python_headless-4.12.0.88-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:236c8df54a90f4d02076e6f9c1cc763d794542e886c576a6fee46ec8ff75a7a9", size = 54023419, upload-time = "2025-07-07T09:15:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9c/a76fd5414de6ec9f21f763a600058a0c3e290053cea87e0275692b1375c0/opencv_python_headless-4.12.0.88-cp37-abi3-win32.whl", hash = "sha256:fde2cf5c51e4def5f2132d78e0c08f9c14783cd67356922182c6845b9af87dbd", size = 30225230, upload-time = "2025-07-07T09:15:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/f2/35/0858e9e71b36948eafbc5e835874b63e515179dc3b742cbe3d76bc683439/opencv_python_headless-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:86b413bdd6c6bf497832e346cd5371995de148e579b9774f8eba686dee3f5528", size = 38923559, upload-time = "2025-07-07T09:15:25.229Z" }, +] + +[[package]] +name = "opentelemetry-api" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/04/05040d7ce33a907a2a02257e601992f0cdf11c73b33f13c4492bf6c3d6d5/opentelemetry_api-1.37.0.tar.gz", hash = "sha256:540735b120355bd5112738ea53621f8d5edb35ebcd6fe21ada3ab1c61d1cd9a7", size = 64923, upload-time = "2025-09-11T10:29:01.662Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/48/28ed9e55dcf2f453128df738210a980e09f4e468a456fa3c763dbc8be70a/opentelemetry_api-1.37.0-py3-none-any.whl", hash = "sha256:accf2024d3e89faec14302213bc39550ec0f4095d1cf5ca688e1bfb1c8612f47", size = 65732, upload-time = "2025-09-11T10:28:41.826Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dd/ea/a75f36b463a36f3c5a10c0b5292c58b31dbdde74f6f905d3d0ab2313987b/opentelemetry_proto-1.37.0.tar.gz", hash = "sha256:30f5c494faf66f77faeaefa35ed4443c5edb3b0aa46dad073ed7210e1a789538", size = 46151, upload-time = "2025-09-11T10:29:11.04Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/25/f89ea66c59bd7687e218361826c969443c4fa15dfe89733f3bf1e2a9e971/opentelemetry_proto-1.37.0-py3-none-any.whl", hash = "sha256:8ed8c066ae8828bbf0c39229979bdf583a126981142378a9cbe9d6fd5701c6e2", size = 72534, upload-time = "2025-09-11T10:28:56.831Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/62/2e0ca80d7fe94f0b193135375da92c640d15fe81f636658d2acf373086bc/opentelemetry_sdk-1.37.0.tar.gz", hash = "sha256:cc8e089c10953ded765b5ab5669b198bbe0af1b3f89f1007d19acd32dc46dda5", size = 170404, upload-time = "2025-09-11T10:29:11.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/62/9f4ad6a54126fb00f7ed4bb5034964c6e4f00fcd5a905e115bd22707e20d/opentelemetry_sdk-1.37.0-py3-none-any.whl", hash = "sha256:8f3c3c22063e52475c5dbced7209495c2c16723d016d39287dfc215d1771257c", size = 131941, upload-time = "2025-09-11T10:28:57.83Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.58b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/1b/90701d91e6300d9f2fb352153fb1721ed99ed1f6ea14fa992c756016e63a/opentelemetry_semantic_conventions-0.58b0.tar.gz", hash = "sha256:6bd46f51264279c433755767bb44ad00f1c9e2367e1b42af563372c5a6fa0c25", size = 129867, upload-time = "2025-09-11T10:29:12.597Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/90/68152b7465f50285d3ce2481b3aec2f82822e3f52e5152eeeaf516bab841/opentelemetry_semantic_conventions-0.58b0-py3-none-any.whl", hash = "sha256:5564905ab1458b96684db1340232729fce3b5375a06e140e8904c78e4f815b28", size = 207954, upload-time = "2025-09-11T10:28:59.218Z" }, +] + +[[package]] +name = "openvino" +version = "2025.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "openvino-telemetry" }, + { name = "packaging" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/93/39bdf98039bb6464c1cef3f4eb1e8044fcc01b7c14a6d7bc1d0c509cc455/openvino-2025.2.0-19140-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:fe9ec680e7af68fd7f523020ede139b3938faef4fb438a1452d44be4ef661efb", size = 38455966, upload-time = "2025-06-18T08:53:42.85Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ad/76ada3d87c8495b674ffb2ef239392243bac45d88e5504e449a9991dc451/openvino-2025.2.0-19140-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:08ae91950340924d4658106da96142197db4b0a481ba5e70eb817ef16cca33b9", size = 31057992, upload-time = "2025-06-18T08:53:45.58Z" }, + { url = "https://files.pythonhosted.org/packages/ab/04/38b51322b5f276281e11ee568e51b163888095e314b86c07da7a9718b9eb/openvino-2025.2.0-19140-cp313-cp313-manylinux2014_x86_64.whl", hash = "sha256:69a9fc29e2c34d89bc6d04fa0698d766f13e00f4596649100a3f7b6cfe29b0d1", size = 47577538, upload-time = "2025-06-18T08:53:48.306Z" }, + { url = "https://files.pythonhosted.org/packages/82/a3/73f89ca704246dc846a58fdd97831a6a47e66abc545c4f8a937894eca09a/openvino-2025.2.0-19140-cp313-cp313-manylinux_2_31_aarch64.whl", hash = "sha256:c1b71f969a98be4557c8ea3626e6f6e14410b9a399fafc6e37b42be999bfe866", size = 26860456, upload-time = "2025-06-18T08:53:50.926Z" }, + { url = "https://files.pythonhosted.org/packages/59/55/ba80b0379438f4d9d07b0735053fc1fd25d745492aa787539fc7e3024dd1/openvino-2025.2.0-19140-cp313-cp313-win_amd64.whl", hash = "sha256:0eaf04b585527b8c7b8b5eea338398cf003c556c4a7a5a7e41d7095528b7bd47", size = 39545978, upload-time = "2025-06-18T08:53:53.423Z" }, +] + +[[package]] +name = "openvino-telemetry" +version = "2025.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/8a/89d82f1a9d913fb266c2e6dc2f6030935db24b7152963a8db6c4f039787f/openvino_telemetry-2025.2.0.tar.gz", hash = "sha256:8bf8127218e51e99547bf38b8fb85a8b31c9bf96e6f3a82eb0b3b6a34155977c", size = 18894, upload-time = "2025-07-07T10:29:51.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ac/5ab0ca0aa269ad3c73f7bfc3801b10e5f56f75a31bf68c1ae8bd51cf70a4/openvino_telemetry-2025.2.0-py3-none-any.whl", hash = "sha256:bcb667e83a44f202ecf4cfa49281715c6d7e21499daec04ff853b7f964833599", size = 25227, upload-time = "2025-07-07T10:29:50.189Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394, upload-time = "2025-08-26T17:46:43.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/79/8932b27293ad35919571f77cb3693b5906cf14f206ef17546052a241fdf6/orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810", size = 238127, upload-time = "2025-08-26T17:45:38.146Z" }, + { url = "https://files.pythonhosted.org/packages/1c/82/cb93cd8cf132cd7643b30b6c5a56a26c4e780c7a145db6f83de977b540ce/orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43", size = 127494, upload-time = "2025-08-26T17:45:39.57Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b8/2d9eb181a9b6bb71463a78882bcac1027fd29cf62c38a40cc02fc11d3495/orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27", size = 123017, upload-time = "2025-08-26T17:45:40.876Z" }, + { url = "https://files.pythonhosted.org/packages/b4/14/a0e971e72d03b509190232356d54c0f34507a05050bd026b8db2bf2c192c/orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f", size = 127898, upload-time = "2025-08-26T17:45:42.188Z" }, + { url = "https://files.pythonhosted.org/packages/8e/af/dc74536722b03d65e17042cc30ae586161093e5b1f29bccda24765a6ae47/orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c", size = 130742, upload-time = "2025-08-26T17:45:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/7a3b63b6677bce089fe939353cda24a7679825c43a24e49f757805fc0d8a/orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be", size = 132377, upload-time = "2025-08-26T17:45:45.525Z" }, + { url = "https://files.pythonhosted.org/packages/fc/cd/ce2ab93e2e7eaf518f0fd15e3068b8c43216c8a44ed82ac2b79ce5cef72d/orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d", size = 135313, upload-time = "2025-08-26T17:45:46.821Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b4/f98355eff0bd1a38454209bbc73372ce351ba29933cb3e2eba16c04b9448/orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2", size = 132908, upload-time = "2025-08-26T17:45:48.126Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/8f5182d7bc2a1bed46ed960b61a39af8389f0ad476120cd99e67182bfb6d/orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f", size = 130905, upload-time = "2025-08-26T17:45:49.414Z" }, + { url = "https://files.pythonhosted.org/packages/1a/60/c41ca753ce9ffe3d0f67b9b4c093bdd6e5fdb1bc53064f992f66bb99954d/orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee", size = 403812, upload-time = "2025-08-26T17:45:51.085Z" }, + { url = "https://files.pythonhosted.org/packages/dd/13/e4a4f16d71ce1868860db59092e78782c67082a8f1dc06a3788aef2b41bc/orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e", size = 146277, upload-time = "2025-08-26T17:45:52.851Z" }, + { url = "https://files.pythonhosted.org/packages/8d/8b/bafb7f0afef9344754a3a0597a12442f1b85a048b82108ef2c956f53babd/orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633", size = 135418, upload-time = "2025-08-26T17:45:54.806Z" }, + { url = "https://files.pythonhosted.org/packages/60/d4/bae8e4f26afb2c23bea69d2f6d566132584d1c3a5fe89ee8c17b718cab67/orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b", size = 136216, upload-time = "2025-08-26T17:45:57.182Z" }, + { url = "https://files.pythonhosted.org/packages/88/76/224985d9f127e121c8cad882cea55f0ebe39f97925de040b75ccd4b33999/orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae", size = 131362, upload-time = "2025-08-26T17:45:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cf/0dce7a0be94bd36d1346be5067ed65ded6adb795fdbe3abd234c8d576d01/orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce", size = 125989, upload-time = "2025-08-26T17:45:59.95Z" }, + { url = "https://files.pythonhosted.org/packages/ef/77/d3b1fef1fc6aaeed4cbf3be2b480114035f4df8fa1a99d2dac1d40d6e924/orjson-3.11.3-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4", size = 238115, upload-time = "2025-08-26T17:46:01.669Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6d/468d21d49bb12f900052edcfbf52c292022d0a323d7828dc6376e6319703/orjson-3.11.3-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e", size = 127493, upload-time = "2025-08-26T17:46:03.466Z" }, + { url = "https://files.pythonhosted.org/packages/67/46/1e2588700d354aacdf9e12cc2d98131fb8ac6f31ca65997bef3863edb8ff/orjson-3.11.3-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d", size = 122998, upload-time = "2025-08-26T17:46:04.803Z" }, + { url = "https://files.pythonhosted.org/packages/3b/94/11137c9b6adb3779f1b34fd98be51608a14b430dbc02c6d41134fbba484c/orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229", size = 132915, upload-time = "2025-08-26T17:46:06.237Z" }, + { url = "https://files.pythonhosted.org/packages/10/61/dccedcf9e9bcaac09fdabe9eaee0311ca92115699500efbd31950d878833/orjson-3.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451", size = 130907, upload-time = "2025-08-26T17:46:07.581Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/0e935539aa7b08b3ca0f817d73034f7eb506792aae5ecc3b7c6e679cdf5f/orjson-3.11.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167", size = 403852, upload-time = "2025-08-26T17:46:08.982Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2b/50ae1a5505cd1043379132fdb2adb8a05f37b3e1ebffe94a5073321966fd/orjson-3.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077", size = 146309, upload-time = "2025-08-26T17:46:10.576Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1d/a473c158e380ef6f32753b5f39a69028b25ec5be331c2049a2201bde2e19/orjson-3.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872", size = 135424, upload-time = "2025-08-26T17:46:12.386Z" }, + { url = "https://files.pythonhosted.org/packages/da/09/17d9d2b60592890ff7382e591aa1d9afb202a266b180c3d4049b1ec70e4a/orjson-3.11.3-cp314-cp314-win32.whl", hash = "sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d", size = 136266, upload-time = "2025-08-26T17:46:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/15/58/358f6846410a6b4958b74734727e582ed971e13d335d6c7ce3e47730493e/orjson-3.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804", size = 131351, upload-time = "2025-08-26T17:46:15.27Z" }, + { url = "https://files.pythonhosted.org/packages/28/01/d6b274a0635be0468d4dbd9cafe80c47105937a0d42434e805e67cd2ed8b/orjson-3.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc", size = 125985, upload-time = "2025-08-26T17:46:16.67Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "paho-mqtt" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/15/0a6214e76d4d32e7f663b109cf71fb22561c2be0f701d67f93950cd40542/paho_mqtt-2.1.0.tar.gz", hash = "sha256:12d6e7511d4137555a3f6ea167ae846af2c7357b10bc6fa4f7c3968fc1723834", size = 148848, upload-time = "2024-04-29T19:52:55.591Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219, upload-time = "2024-04-29T19:52:48.345Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, +] + +[[package]] +name = "parse" +version = "1.20.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/78/d9b09ba24bb36ef8b83b71be547e118d46214735b6dfb39e4bfde0e9b9dd/parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce", size = 29391, upload-time = "2024-06-11T04:41:57.34Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/31/ba45bf0b2aa7898d81cbbfac0e88c267befb59ad91a19e36e1bc5578ddb1/parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558", size = 20126, upload-time = "2024-06-11T04:41:55.057Z" }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "pathable" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/67/93/8f2c2075b180c12c1e9f6a09d1a985bc2036906b13dff1d8917e395f2048/pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2", size = 8124, upload-time = "2025-01-10T18:43:13.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" }, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pdbpp" +version = "0.11.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fancycompleter" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/4c/118ef9534ac0632859b48c305d8c5dc9d6f963564fdfa66bc785c560247c/pdbpp-0.11.7.tar.gz", hash = "sha256:cb6604ac31a35ed0f2a29650a8c022b26284620be3e01cfd41b683b91da1ff14", size = 76026, upload-time = "2025-07-18T09:36:02.781Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/e9/704bbc08aace64fee536e4c2c20f63f64f6fdbad72938c5ed46c9723a9f1/pdbpp-0.11.7-py3-none-any.whl", hash = "sha256:51916b63693898cf4881b36b4501c83947758d73f582f1f84893662b163bdb75", size = 30545, upload-time = "2025-07-18T09:36:01.478Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, +] + +[[package]] +name = "plotly" +version = "6.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "narwhals" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/63/961d47c9ffd592a575495891cdcf7875dc0903ebb33ac238935714213789/plotly-6.3.1.tar.gz", hash = "sha256:dd896e3d940e653a7ce0470087e82c2bd903969a55e30d1b01bb389319461bb0", size = 6956460, upload-time = "2025-10-02T16:10:34.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/93/023955c26b0ce614342d11cc0652f1e45e32393b6ab9d11a664a60e9b7b7/plotly-6.3.1-py3-none-any.whl", hash = "sha256:8b4420d1dcf2b040f5983eed433f95732ed24930e496d36eb70d211923532e64", size = 9833698, upload-time = "2025-10-02T16:10:22.584Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, +] + +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/c8/d70cd26d845c6d85479d8f5a11a0fd7151e9bc4794cc5e6eb5a790f12df8/propcache-0.4.0.tar.gz", hash = "sha256:c1ad731253eb738f9cadd9fa1844e019576c70bca6a534252e97cf33a57da529", size = 45187, upload-time = "2025-10-04T21:57:39.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/dd/f405b0fe84d29d356895bc048404d3321a2df849281cf3f932158c9346ac/propcache-0.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2d01fd53e89cb3d71d20b8c225a8c70d84660f2d223afc7ed7851a4086afe6d", size = 77565, upload-time = "2025-10-04T21:55:52.907Z" }, + { url = "https://files.pythonhosted.org/packages/c0/48/dfb2c45e1b0d92228c9c66fa929af7316c15cbe69a7e438786aaa60c1b3c/propcache-0.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7dfa60953169d2531dd8ae306e9c27c5d4e5efe7a2ba77049e8afdaece062937", size = 44602, upload-time = "2025-10-04T21:55:54.406Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/b15e88b4463df45a7793fb04e2b5497334f8fcc24e281c221150a0af9aff/propcache-0.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:227892597953611fce2601d49f1d1f39786a6aebc2f253c2de775407f725a3f6", size = 46168, upload-time = "2025-10-04T21:55:55.537Z" }, + { url = "https://files.pythonhosted.org/packages/40/ac/983e69cce8800251aab85858069cf9359b22222a9cda47591e03e2f24eec/propcache-0.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e0a5bc019014531308fb67d86066d235daa7551baf2e00e1ea7b00531f6ea85", size = 207997, upload-time = "2025-10-04T21:55:57.022Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9c/5586a7a54e7e0b9a87fdd8ba935961f398c0e6eaecd57baaa8eca468a236/propcache-0.4.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6ebc6e2e65c31356310ddb6519420eaa6bb8c30fbd809d0919129c89dcd70f4c", size = 210948, upload-time = "2025-10-04T21:55:58.397Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ba/644e367f8a86461d45bd023ace521180938e76515040550af9b44085e99a/propcache-0.4.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1927b78dd75fc31a7fdc76cc7039e39f3170cb1d0d9a271e60f0566ecb25211a", size = 217988, upload-time = "2025-10-04T21:56:00.251Z" }, + { url = "https://files.pythonhosted.org/packages/24/0e/1e21af74b4732d002b0452605bdf31d6bf990fd8b720cb44e27a97d80db5/propcache-0.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b113feeda47f908562d9a6d0e05798ad2f83d4473c0777dafa2bc7756473218", size = 204442, upload-time = "2025-10-04T21:56:01.93Z" }, + { url = "https://files.pythonhosted.org/packages/fd/30/ae2eec96995a8a760acb9a0b6c92b9815f1fc885c7d8481237ccb554eab0/propcache-0.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4596c12aa7e3bb2abf158ea8f79eb0fb4851606695d04ab846b2bb386f5690a1", size = 199371, upload-time = "2025-10-04T21:56:03.25Z" }, + { url = "https://files.pythonhosted.org/packages/45/1d/a18fac8cb04f8379ccb79cf15aac31f4167a270d1cd1111f33c0d38ce4fb/propcache-0.4.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6d1f67dad8cc36e8abc2207a77f3f952ac80be7404177830a7af4635a34cbc16", size = 196638, upload-time = "2025-10-04T21:56:04.619Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/3549a2b6f74dce6f21b2664d078bd26ceb876aae9c58f3c017cf590f0ee3/propcache-0.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6229ad15366cd8b6d6b4185c55dd48debf9ca546f91416ba2e5921ad6e210a6", size = 203651, upload-time = "2025-10-04T21:56:06.153Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f0/90ea14d518c919fc154332742a9302db3004af4f1d3df688676959733283/propcache-0.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2a4bf309d057327f1f227a22ac6baf34a66f9af75e08c613e47c4d775b06d6c7", size = 205726, upload-time = "2025-10-04T21:56:07.955Z" }, + { url = "https://files.pythonhosted.org/packages/f6/de/8efc1dbafeb42108e7af744822cdca944b990869e9da70e79efb21569d6b/propcache-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c2e274f3d1cbb2ddcc7a55ce3739af0f8510edc68a7f37981b2258fa1eedc833", size = 199576, upload-time = "2025-10-04T21:56:09.43Z" }, + { url = "https://files.pythonhosted.org/packages/d7/38/4d79fe3477b050398fb8d8f59301ed116d8c6ea3c4dbf09498c679103f90/propcache-0.4.0-cp313-cp313-win32.whl", hash = "sha256:f114a3e1f8034e2957d34043b7a317a8a05d97dfe8fddb36d9a2252c0117dbbc", size = 37474, upload-time = "2025-10-04T21:56:10.74Z" }, + { url = "https://files.pythonhosted.org/packages/36/9b/a283daf665a1945cff1b03d1104e7c9ee92bb7b6bbcc6518b24fcdac8bd0/propcache-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ba68c57cde9c667f6b65b98bc342dfa7240b1272ffb2c24b32172ee61b6d281", size = 40685, upload-time = "2025-10-04T21:56:11.896Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f7/def8fc0b4d7a89f1628f337cb122bb9a946c5ed97760f2442b27b7fa5a69/propcache-0.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb77a85253174bf73e52c968b689d64be62d71e8ac33cabef4ca77b03fb4ef92", size = 37046, upload-time = "2025-10-04T21:56:13.021Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6b/f6e8b36b58d17dfb6c505b9ae1163fcf7a4cf98825032fdc77bba4ab5c4a/propcache-0.4.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c0e1c218fff95a66ad9f2f83ad41a67cf4d0a3f527efe820f57bde5fda616de4", size = 81274, upload-time = "2025-10-04T21:56:14.206Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c5/1fd0baa222b8faf53ba04dd4f34de33ea820b80e34f87c7960666bae5f4f/propcache-0.4.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5710b1c01472542bb024366803812ca13e8774d21381bcfc1f7ae738eeb38acc", size = 46232, upload-time = "2025-10-04T21:56:15.337Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6b/7aa5324983cab7666ed58fc32c68a0430468a18e02e3f04e7a879c002414/propcache-0.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d7f008799682e8826ce98f25e8bc43532d2cd26c187a1462499fa8d123ae054f", size = 48239, upload-time = "2025-10-04T21:56:16.768Z" }, + { url = "https://files.pythonhosted.org/packages/24/0f/58c192301c0436762ed5fed5a3edadb0ae399cb73528fb9c1b5cb8e53523/propcache-0.4.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0596d2ae99d74ca436553eb9ce11fe4163dc742fcf8724ebe07d7cb0db679bb1", size = 275804, upload-time = "2025-10-04T21:56:18.066Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b9/092ee32064ebfabedae4251952787e63e551075af1a1205e8061b3ed5838/propcache-0.4.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab9c1bd95ebd1689f0e24f2946c495808777e9e8df7bb3c1dfe3e9eb7f47fe0d", size = 273996, upload-time = "2025-10-04T21:56:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/43/82/becf618ed28e732f3bba3df172cd290a1afbd99f291074f747fd5bd031bb/propcache-0.4.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a8ef2ea819549ae2e8698d2ec229ae948d7272feea1cb2878289f767b6c585a4", size = 280266, upload-time = "2025-10-04T21:56:21.136Z" }, + { url = "https://files.pythonhosted.org/packages/51/be/b370930249a9332a81b5c4c550dac614b7e11b6c160080777e903d57e197/propcache-0.4.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71a400b2f0b079438cc24f9a27f02eff24d8ef78f2943f949abc518b844ade3d", size = 263186, upload-time = "2025-10-04T21:56:22.787Z" }, + { url = "https://files.pythonhosted.org/packages/33/b6/546fd3e31770aed3aed1c01b120944c689edb510aeb7a25472edc472ce23/propcache-0.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c2735d3305e6cecab6e53546909edf407ad3da5b9eeaf483f4cf80142bb21be", size = 260721, upload-time = "2025-10-04T21:56:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/80/70/3751930d16e5984490c73ca65b80777e4b26e7a0015f2d41f31d75959a71/propcache-0.4.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:72b51340047ac43b3cf388eebd362d052632260c9f73a50882edbb66e589fd44", size = 247516, upload-time = "2025-10-04T21:56:25.577Z" }, + { url = "https://files.pythonhosted.org/packages/59/90/4bc96ce6476f67e2e6b72469f328c92b53259a0e4d1d5386d71a36e9258c/propcache-0.4.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:184c779363740d6664982ad05699f378f7694220e2041996f12b7c2a4acdcad0", size = 262675, upload-time = "2025-10-04T21:56:27.065Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d1/f16d096869c5f1c93d67fc37488c0c814add0560574f6877653a10239cde/propcache-0.4.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:a60634a9de41f363923c6adfb83105d39e49f7a3058511563ed3de6748661af6", size = 263379, upload-time = "2025-10-04T21:56:28.517Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2a/da5cd1bc1c6412939c457ea65bbe7e034045c395d98ff8ff880d06ec4553/propcache-0.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8119244d122241a9c4566bce49bb20408a6827044155856735cf14189a7da", size = 257694, upload-time = "2025-10-04T21:56:30.051Z" }, + { url = "https://files.pythonhosted.org/packages/a5/11/938e67c07189b662a6c72551d48285a02496de885408392447c25657dd47/propcache-0.4.0-cp313-cp313t-win32.whl", hash = "sha256:515b610a364c8cdd2b72c734cc97dece85c416892ea8d5c305624ac8734e81db", size = 41321, upload-time = "2025-10-04T21:56:31.406Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6e/72b11a4dcae68c728b15126cc5bc830bf275c84836da2633412b768d07e0/propcache-0.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7ea86eb32e74f9902df57e8608e8ac66f1e1e1d24d1ed2ddeb849888413b924d", size = 44846, upload-time = "2025-10-04T21:56:32.5Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/0ef3c025e0621e703ef71b69e0085181a3124bcc1beef29e0ffef59ed7f4/propcache-0.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c1443fa4bb306461a3a8a52b7de0932a2515b100ecb0ebc630cc3f87d451e0a9", size = 39689, upload-time = "2025-10-04T21:56:33.686Z" }, + { url = "https://files.pythonhosted.org/packages/60/89/7699d8e9f8c222bbef1fae26afd72d448353f164a52125d5f87dd9fec2c7/propcache-0.4.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de8e310d24b5a61de08812dd70d5234da1458d41b059038ee7895a9e4c8cae79", size = 77977, upload-time = "2025-10-04T21:56:34.836Z" }, + { url = "https://files.pythonhosted.org/packages/77/c5/2758a498199ce46d6d500ba4391a8594df35400cc85738aa9f0c9b8366db/propcache-0.4.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:55a54de5266bc44aa274915cdf388584fa052db8748a869e5500ab5993bac3f4", size = 44715, upload-time = "2025-10-04T21:56:36.075Z" }, + { url = "https://files.pythonhosted.org/packages/0d/da/5a44e10282a28c2dd576e5e1a2c7bb8145587070ddab7375fb643f7129d7/propcache-0.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:88d50d662c917ec2c9d3858920aa7b9d5bfb74ab9c51424b775ccbe683cb1b4e", size = 46463, upload-time = "2025-10-04T21:56:37.227Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5a/b2c314f655f46c10c204dc0d69e19fadfb1cc4d40ab33f403698a35c3281/propcache-0.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae3adf88a66f5863cf79394bc359da523bb27a2ed6ba9898525a6a02b723bfc5", size = 206980, upload-time = "2025-10-04T21:56:38.828Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4e/f6643ec2cd5527b92c93488f9b67a170494736bb1c5460136399d709ce5a/propcache-0.4.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f088e21d15b3abdb9047e4b7b7a0acd79bf166893ac2b34a72ab1062feb219e", size = 211385, upload-time = "2025-10-04T21:56:40.2Z" }, + { url = "https://files.pythonhosted.org/packages/71/41/362766a346c3f8d3bbeb7899e1ff40f18844e0fe37e9f6f536553cf6b6be/propcache-0.4.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4efbaf10793fd574c76a5732c75452f19d93df6e0f758c67dd60552ebd8614b", size = 215315, upload-time = "2025-10-04T21:56:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/ff/98/17385d51816d56fa6acc035d8625fbf833b6a795d7ef7fb37ea3f62db6c9/propcache-0.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:681a168d06284602d56e97f09978057aa88bcc4177352b875b3d781df4efd4cb", size = 201416, upload-time = "2025-10-04T21:56:42.947Z" }, + { url = "https://files.pythonhosted.org/packages/7a/83/801178ca1c29e217564ee507ff2a49d3f24a4dd85c9b9d681fd1d62b15f2/propcache-0.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a7f06f077fc4ef37e8a37ca6bbb491b29e29db9fb28e29cf3896aad10dbd4137", size = 197726, upload-time = "2025-10-04T21:56:44.313Z" }, + { url = "https://files.pythonhosted.org/packages/d2/38/c8743917bca92b7e5474366b6b04c7b3982deac32a0fe4b705f2e92c09bb/propcache-0.4.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:082a643479f49a6778dcd68a80262fc324b14fd8e9b1a5380331fe41adde1738", size = 192819, upload-time = "2025-10-04T21:56:45.702Z" }, + { url = "https://files.pythonhosted.org/packages/0b/74/3de3ef483e8615aaaf62026fcdcb20cbfc4535ea14871b12f72d52c1d6dc/propcache-0.4.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:26692850120241a99bb4a4eec675cd7b4fdc431144f0d15ef69f7f8599f6165f", size = 202492, upload-time = "2025-10-04T21:56:47.388Z" }, + { url = "https://files.pythonhosted.org/packages/46/86/a130dd85199d651a6986ba6bf1ce297b7bbcafc01c8e139e6ba2b8218a20/propcache-0.4.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:33ad7d37b9a386f97582f5d042cc7b8d4b3591bb384cf50866b749a17e4dba90", size = 204106, upload-time = "2025-10-04T21:56:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f7/44eab58659d71d21995146c94139e63882bac280065b3a9ed10376897bcc/propcache-0.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e7fd82d4a5b7583588f103b0771e43948532f1292105f13ee6f3b300933c4ca", size = 198043, upload-time = "2025-10-04T21:56:50.561Z" }, + { url = "https://files.pythonhosted.org/packages/96/14/df37be1bf1423d2dda201a4cdb1c5cb44048d34e31a97df227cc25b0a55c/propcache-0.4.0-cp314-cp314-win32.whl", hash = "sha256:213eb0d3bc695a70cffffe11a1c2e1c2698d89ffd8dba35a49bc44a035d45c93", size = 38036, upload-time = "2025-10-04T21:56:51.868Z" }, + { url = "https://files.pythonhosted.org/packages/99/96/9cea65d6c50224737e80c57a3f3db4ca81bc7b1b52bc73346df8c50db400/propcache-0.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:087e2d3d7613e1b59b2ffca0daabd500c1a032d189c65625ee05ea114afcad0b", size = 41156, upload-time = "2025-10-04T21:56:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/52/4d/91523dcbe23cc127b097623a6ba177da51fca6b7c979082aa49745b527b7/propcache-0.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:94b0f7407d18001dbdcbb239512e753b1b36725a6e08a4983be1c948f5435f79", size = 37976, upload-time = "2025-10-04T21:56:54.351Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f7/7118a944cb6cdb548c9333cf311bda120f9793ecca54b2ca4a3f7e58723e/propcache-0.4.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b730048ae8b875e2c0af1a09ca31b303fc7b5ed27652beec03fa22b29545aec9", size = 81270, upload-time = "2025-10-04T21:56:55.516Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f9/04a8bc9977ea201783f3ccb04106f44697f635f70439a208852d4d08554d/propcache-0.4.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f495007ada16a4e16312b502636fafff42a9003adf1d4fb7541e0a0870bc056f", size = 46224, upload-time = "2025-10-04T21:56:56.695Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3d/808b074034156f130a0047304d811a5a5df3bb0976c9adfb9383718fd888/propcache-0.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:659a0ea6d9017558ed7af00fb4028186f64d0ba9adfc70a4d2c85fcd3d026321", size = 48246, upload-time = "2025-10-04T21:56:57.926Z" }, + { url = "https://files.pythonhosted.org/packages/66/eb/e311f3a59ddc93078cb079b12699af9fd844142c4b4d382b386ee071d921/propcache-0.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d74aa60b1ec076d4d5dcde27c9a535fc0ebb12613f599681c438ca3daa68acac", size = 275562, upload-time = "2025-10-04T21:56:59.221Z" }, + { url = "https://files.pythonhosted.org/packages/f4/05/a146094d6a00bb2f2036dd2a2f4c2b2733ff9574b59ce53bd8513edfca5d/propcache-0.4.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34000e31795bdcda9826e0e70e783847a42e3dcd0d6416c5d3cb717905ebaec0", size = 273627, upload-time = "2025-10-04T21:57:00.582Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/a6d138f6e3d5f6c9b34dbd336b964a1293f2f1a79cafbe70ae3403d7cc46/propcache-0.4.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bcb5bfac5b9635e6fc520c8af6efc7a0a56f12a1fe9e9d3eb4328537e316dd6a", size = 279778, upload-time = "2025-10-04T21:57:01.944Z" }, + { url = "https://files.pythonhosted.org/packages/ac/09/19594a20da0519bfa00deef8cf35dda6c9a5b51bba947f366e85ea59b3de/propcache-0.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ea11fceb31fa95b0fa2007037f19e922e2caceb7dc6c6cac4cb56e2d291f1a2", size = 262833, upload-time = "2025-10-04T21:57:03.326Z" }, + { url = "https://files.pythonhosted.org/packages/b5/92/60d2ddc7662f7b2720d3b628ad8ce888015f4ab5c335b7b1b50183194e68/propcache-0.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cd8684f628fe285ea5c86f88e1c30716239dc9d6ac55e7851a4b7f555b628da3", size = 260456, upload-time = "2025-10-04T21:57:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e2/4c2e25c77cf43add2e05a86c4fcf51107edc4d92318e5c593bbdc2515d57/propcache-0.4.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:790286d3d542c0ef9f6d0280d1049378e5e776dcba780d169298f664c39394db", size = 247284, upload-time = "2025-10-04T21:57:06.566Z" }, + { url = "https://files.pythonhosted.org/packages/dc/3e/c273ab8edc80683ec8b15b486e95c03096ef875d99e4b0ab0a36c1e42c94/propcache-0.4.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:009093c9b5dbae114a5958e6a649f8a5d94dd6866b0f82b60395eb92c58002d4", size = 262368, upload-time = "2025-10-04T21:57:08.231Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a9/3fa231f65a9f78614c5aafa9cee788d7f55c22187cc2f33e86c7c16d0262/propcache-0.4.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:728d98179e92d77096937fdfecd2c555a3d613abe56c9909165c24196a3b5012", size = 263010, upload-time = "2025-10-04T21:57:09.641Z" }, + { url = "https://files.pythonhosted.org/packages/38/a0/f4f5d368e60c9dc04d3158eaf1ca0ad899b40ac3d29c015bf62735225a6f/propcache-0.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9725d96a81e17e48a0fe82d0c3de2f5e623d7163fec70a6c7df90753edd1bec", size = 257298, upload-time = "2025-10-04T21:57:11.125Z" }, + { url = "https://files.pythonhosted.org/packages/c7/30/f78d6758dc36a98f1cddc39b3185cefde616cc58248715b7c65495491cb1/propcache-0.4.0-cp314-cp314t-win32.whl", hash = "sha256:0964c55c95625193defeb4fd85f8f28a9a754ed012cab71127d10e3dc66b1373", size = 42484, upload-time = "2025-10-04T21:57:12.652Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ad/de0640e9b56d2caa796c4266d7d1e6cc4544cc327c25b7ced5c59893b625/propcache-0.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:24403152e41abf09488d3ae9c0c3bf7ff93e2fb12b435390718f21810353db28", size = 46229, upload-time = "2025-10-04T21:57:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/da/bf/5aed62dddbf2bbe62a3564677436261909c9dd63a0fa1fb6cf0629daa13c/propcache-0.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0363a696a9f24b37a04ed5e34c2e07ccbe92798c998d37729551120a1bb744c4", size = 40329, upload-time = "2025-10-04T21:57:15.198Z" }, + { url = "https://files.pythonhosted.org/packages/c7/16/794c114f6041bbe2de23eb418ef58a0f45de27224d5540f5dbb266a73d72/propcache-0.4.0-py3-none-any.whl", hash = "sha256:015b2ca2f98ea9e08ac06eecc409d5d988f78c5fd5821b2ad42bc9afcd6b1557", size = 13183, upload-time = "2025-10-04T21:57:38.054Z" }, +] + +[[package]] +name = "protobuf" +version = "6.32.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" }, + { url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" }, + { url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" }, + { url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" }, + { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pyarrow" +version = "21.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/c2/ea068b8f00905c06329a3dfcd40d0fcc2b7d0f2e355bdb25b65e0a0e4cd4/pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc", size = 1133487, upload-time = "2025-07-18T00:57:31.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/ca/c7eaa8e62db8fb37ce942b1ea0c6d7abfe3786ca193957afa25e71b81b66/pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a", size = 31154306, upload-time = "2025-07-18T00:56:04.42Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e8/e87d9e3b2489302b3a1aea709aaca4b781c5252fcb812a17ab6275a9a484/pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe", size = 32680622, upload-time = "2025-07-18T00:56:07.505Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/79095d73a742aa0aba370c7942b1b655f598069489ab387fe47261a849e1/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd", size = 41104094, upload-time = "2025-07-18T00:56:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/89/4b/7782438b551dbb0468892a276b8c789b8bbdb25ea5c5eb27faadd753e037/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61", size = 42825576, upload-time = "2025-07-18T00:56:15.569Z" }, + { url = "https://files.pythonhosted.org/packages/b3/62/0f29de6e0a1e33518dec92c65be0351d32d7ca351e51ec5f4f837a9aab91/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d", size = 43368342, upload-time = "2025-07-18T00:56:19.531Z" }, + { url = "https://files.pythonhosted.org/packages/90/c7/0fa1f3f29cf75f339768cc698c8ad4ddd2481c1742e9741459911c9ac477/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99", size = 45131218, upload-time = "2025-07-18T00:56:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/01/63/581f2076465e67b23bc5a37d4a2abff8362d389d29d8105832e82c9c811c/pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636", size = 26087551, upload-time = "2025-07-18T00:56:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ab/357d0d9648bb8241ee7348e564f2479d206ebe6e1c47ac5027c2e31ecd39/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da", size = 31290064, upload-time = "2025-07-18T00:56:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8a/5685d62a990e4cac2043fc76b4661bf38d06efed55cf45a334b455bd2759/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7", size = 32727837, upload-time = "2025-07-18T00:56:33.935Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/c0828ee09525c2bafefd3e736a248ebe764d07d0fd762d4f0929dbc516c9/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6", size = 41014158, upload-time = "2025-07-18T00:56:37.528Z" }, + { url = "https://files.pythonhosted.org/packages/6e/26/a2865c420c50b7a3748320b614f3484bfcde8347b2639b2b903b21ce6a72/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8", size = 42667885, upload-time = "2025-07-18T00:56:41.483Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f9/4ee798dc902533159250fb4321267730bc0a107d8c6889e07c3add4fe3a5/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503", size = 43276625, upload-time = "2025-07-18T00:56:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/5a/da/e02544d6997037a4b0d22d8e5f66bc9315c3671371a8b18c79ade1cefe14/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79", size = 44951890, upload-time = "2025-07-18T00:56:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4e/519c1bc1876625fe6b71e9a28287c43ec2f20f73c658b9ae1d485c0c206e/pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10", size = 26371006, upload-time = "2025-07-18T00:56:56.379Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "2.11.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/54/ecab642b3bed45f7d5f59b38443dcb36ef50f85af192e6ece103dbfe9587/pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423", size = 788494, upload-time = "2025-10-04T10:40:41.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/1f/73c53fcbfb0b5a78f91176df41945ca466e71e9d9d836e5c522abda39ee7/pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a", size = 444823, upload-time = "2025-10-04T10:40:39.055Z" }, +] + +[package.optional-dependencies] +email = [ + { name = "email-validator" }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, + { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, + { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, + { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, + { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, + { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, + { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/85/1ea668bbab3c50071ca613c6ab30047fb36ab0da1b92fa8f17bbc38fd36c/pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee", size = 172583, upload-time = "2025-06-24T13:26:46.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/f0/427018098906416f580e3cf1366d3b1abfb408a0652e9f31600c24a1903c/pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796", size = 45235, upload-time = "2025-06-24T13:26:45.485Z" }, +] + +[[package]] +name = "pydot" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyparsing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/dd/e0e6a4fb84c22050f6a9701ad9fd6a67ef82faa7ba97b97eb6fdc6b49b34/pydot-3.0.4.tar.gz", hash = "sha256:3ce88b2558f3808b0376f22bfa6c263909e1c3981e2a7b629b65b451eee4a25d", size = 168167, upload-time = "2025-01-05T16:18:45.763Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/5f/1ebfd430df05c4f9e438dd3313c4456eab937d976f6ab8ce81a98f9fb381/pydot-3.0.4-py3-none-any.whl", hash = "sha256:bfa9c3fc0c44ba1d132adce131802d7df00429d1a79cc0346b0a5cd374dbe9c6", size = 35776, upload-time = "2025-01-05T16:18:42.836Z" }, +] + +[[package]] +name = "pydub" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" }, +] + +[[package]] +name = "pyee" +version = "13.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pylibsrtp" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/c8/a59e61f5dd655f5f21033bd643dd31fe980a537ed6f373cdfb49d3a3bd32/pylibsrtp-0.12.0.tar.gz", hash = "sha256:f5c3c0fb6954e7bb74dc7e6398352740ca67327e6759a199fe852dbc7b84b8ac", size = 10878, upload-time = "2025-04-06T12:35:51.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f0/b818395c4cae2d5cc5a0c78fc47d694eae78e6a0d678baeb52a381a26327/pylibsrtp-0.12.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5adde3cf9a5feef561d0eb7ed99dedb30b9bf1ce9a0c1770b2bf19fd0b98bc9a", size = 1727918, upload-time = "2025-04-06T12:35:36.456Z" }, + { url = "https://files.pythonhosted.org/packages/05/1a/ee553abe4431b7bd9bab18f078c0ad2298b94ea55e664da6ecb8700b1052/pylibsrtp-0.12.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:d2c81d152606721331ece87c80ed17159ba6da55c7c61a6b750cff67ab7f63a5", size = 2057900, upload-time = "2025-04-06T12:35:38.253Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a2/2dd0188be58d3cba48c5eb4b3c787e5743c111cd0c9289de4b6f2798382a/pylibsrtp-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:242fa3d44219846bf1734d5df595563a2c8fbb0fb00ccc79ab0f569fc0af2c1b", size = 2567047, upload-time = "2025-04-06T12:35:39.797Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3a/4bdab9fc1d78f2efa02c8a8f3e9c187bfa278e89481b5123f07c8dd69310/pylibsrtp-0.12.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74aaf8fac1b119a3c762f54751c3d20e77227b84c26d85aae57c2c43129b49c", size = 2168775, upload-time = "2025-04-06T12:35:41.422Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fc/0b1e1bfed420d79427d50aff84c370dcd78d81af9500c1e86fbcc5bf95e1/pylibsrtp-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e3e223102989b71f07e1deeb804170ed53fb4e1b283762eb031bd45bb425d4", size = 2225033, upload-time = "2025-04-06T12:35:43.03Z" }, + { url = "https://files.pythonhosted.org/packages/39/7b/e1021d27900315c2c077ec7d45f50274cedbdde067ff679d44df06f01a8a/pylibsrtp-0.12.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:36d07de64dbc82dbbb99fd77f36c8e23d6730bdbcccf09701945690a9a9a422a", size = 2606093, upload-time = "2025-04-06T12:35:44.587Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c2/0fae6687a06fcde210a778148ec808af49e431c36fe9908503a695c35479/pylibsrtp-0.12.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:ef03b4578577690f716fd023daed8914eee6de9a764fa128eda19a0e645cc032", size = 2193213, upload-time = "2025-04-06T12:35:46.167Z" }, + { url = "https://files.pythonhosted.org/packages/67/c2/2ed7a4a5c38b999fd34298f76b93d29f5ba8c06f85cfad3efd9468343715/pylibsrtp-0.12.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0a8421e9fe4d20ce48d439430e55149f12b1bca1b0436741972c362c49948c0a", size = 2256774, upload-time = "2025-04-06T12:35:47.704Z" }, + { url = "https://files.pythonhosted.org/packages/48/d7/f13fedce3b21d24f6f154d1dee7287464a34728dcb3b0c50f687dbad5765/pylibsrtp-0.12.0-cp39-abi3-win32.whl", hash = "sha256:cbc9bfbfb2597e993a1aa16b832ba16a9dd4647f70815421bb78484f8b50b924", size = 1156186, upload-time = "2025-04-06T12:35:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/9b/26/3a20b638a3a3995368f856eeb10701dd6c0e9ace9fb6665eeb1b95ccce19/pylibsrtp-0.12.0-cp39-abi3-win_amd64.whl", hash = "sha256:061ef1dbb5f08079ac6d7515b7e67ca48a3163e16e5b820beea6b01cb31d7e54", size = 1485072, upload-time = "2025-04-06T12:35:50.312Z" }, +] + +[[package]] +name = "pymoo" +version = "0.6.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alive-progress" }, + { name = "autograd" }, + { name = "cma" }, + { name = "deprecated" }, + { name = "dill" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/ed/ec5a76bb1556b774a67806c08234dab0e603509846b6b94934da59e5f4bd/pymoo-0.6.1.5.tar.gz", hash = "sha256:9ce71eaceb2f5cccf8c5af53102cf6d96fa911452addaf48fb971a60621f8364", size = 258027, upload-time = "2025-05-26T21:59:31.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/5e/260d77d5d44ee276fca63c902a38dbfa5315b13db4a856f4b4ede5769754/pymoo-0.6.1.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9ddaeb66ce18d473cdfdfd70c7e63e1cd7cddf47879e79bca1f8eab379a74413", size = 1552512, upload-time = "2025-05-26T21:59:02.748Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3f/38538bb89e92eb10357bc2aa8dcefdfa25ec01b57a2b4cd419e704de3139/pymoo-0.6.1.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:875d06c0f0617ea73eaedb810cc25d55b40b7ddf77db23f59bca51a18eab5079", size = 937888, upload-time = "2025-05-26T21:59:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/4d/75/7a7e1ddea474ef3e0d1845c20e7792743a8b01850e2956a6f776dbf87f46/pymoo-0.6.1.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da0d2afe9fa6a94fbec3fe970fa9426309b668eefb1eb796f44bfa186cf2c5ad", size = 4331420, upload-time = "2025-05-26T21:59:08.601Z" }, + { url = "https://files.pythonhosted.org/packages/85/6a/85e26ad9b046e89a4a77bee7f0ed3b3d77ecb2e743727d09187473346719/pymoo-0.6.1.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0926f8ba84fc1e104b30ccdcf0dd5ed886be209f7de6d729fe115cdd3fdec084", size = 5775567, upload-time = "2025-05-26T21:59:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/e2/1f/479758d597229563bf9d2003911bdb0829f031da4d1577bf76ff61e5b704/pymoo-0.6.1.5-cp313-cp313-win_amd64.whl", hash = "sha256:36543ab8690c9afb4a07c795f58018223394b86c5ba0ce6044f7f28c193dfacc", size = 2008812, upload-time = "2025-05-26T21:59:15.135Z" }, +] + +[[package]] +name = "pyopenssl" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329", size = 184073, upload-time = "2025-09-17T00:32:21.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", size = 57268, upload-time = "2025-09-17T00:32:19.474Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.2.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, +] + +[[package]] +name = "pyperclip" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "python-box" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/85/b02b80d74bdb95bfe491d49ad1627e9833c73d331edbe6eed0bdfe170361/python-box-6.1.0.tar.gz", hash = "sha256:6e7c243b356cb36e2c0f0e5ed7850969fede6aa812a7f501de7768996c7744d7", size = 41443, upload-time = "2022-10-29T22:30:45.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/c6/6d1e368710cb6c458ed692d179d7e101ebce80a3e640b2e74cc7ae886d6f/python_box-6.1.0-py3-none-any.whl", hash = "sha256:bdec0a5f5a17b01fc538d292602a077aa8c641fb121e1900dff0591791af80e8", size = 27277, upload-time = "2022-10-29T22:30:43.645Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-json-logger" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "pytorch-lightning" +version = "2.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec", extra = ["http"] }, + { name = "lightning-utilities" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "torch" }, + { name = "torchmetrics" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/78/bce84aab9a5b3b2e9d087d4f1a6be9b481adbfaac4903bc9daaaf09d49a3/pytorch_lightning-2.5.5.tar.gz", hash = "sha256:d6fc8173d1d6e49abfd16855ea05d2eb2415e68593f33d43e59028ecb4e64087", size = 643703, upload-time = "2025-09-05T16:01:18.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/f6/99a5c66478f469598dee25b0e29b302b5bddd4e03ed0da79608ac964056e/pytorch_lightning-2.5.5-py3-none-any.whl", hash = "sha256:0b533991df2353c0c6ea9ca10a7d0728b73631fd61f5a15511b19bee2aef8af0", size = 832431, upload-time = "2025-09-05T16:01:16.234Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pywinpty" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669, upload-time = "2025-10-03T21:16:29.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51", size = 2050057, upload-time = "2025-10-03T21:19:26.732Z" }, + { url = "https://files.pythonhosted.org/packages/cb/44/cbae12ecf6f4fa4129c36871fd09c6bef4f98d5f625ecefb5e2449765508/pywinpty-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:663383ecfab7fc382cc97ea5c4f7f0bb32c2f889259855df6ea34e5df42d305b", size = 2049874, upload-time = "2025-10-03T21:18:53.923Z" }, + { url = "https://files.pythonhosted.org/packages/ca/15/f12c6055e2d7a617d4d5820e8ac4ceaff849da4cb124640ef5116a230771/pywinpty-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:28297cecc37bee9f24d8889e47231972d6e9e84f7b668909de54f36ca785029a", size = 2050386, upload-time = "2025-10-03T21:18:50.477Z" }, + { url = "https://files.pythonhosted.org/packages/de/24/c6907c5bb06043df98ad6a0a0ff5db2e0affcecbc3b15c42404393a3f72a/pywinpty-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:34b55ae9a1b671fe3eae071d86618110538e8eaad18fcb1531c0830b91a82767", size = 2049834, upload-time = "2025-10-03T21:19:25.688Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + +[[package]] +name = "regex" +version = "2025.9.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/d3/eaa0d28aba6ad1827ad1e716d9a93e1ba963ada61887498297d3da715133/regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4", size = 400917, upload-time = "2025-09-19T00:38:35.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/c7/5c48206a60ce33711cf7dcaeaed10dd737733a3569dc7e1dce324dd48f30/regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2", size = 485955, upload-time = "2025-09-19T00:36:26.822Z" }, + { url = "https://files.pythonhosted.org/packages/e9/be/74fc6bb19a3c491ec1ace943e622b5a8539068771e8705e469b2da2306a7/regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb", size = 289583, upload-time = "2025-09-19T00:36:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/25/c4/9ceaa433cb5dc515765560f22a19578b95b92ff12526e5a259321c4fc1a0/regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af", size = 287000, upload-time = "2025-09-19T00:36:30.161Z" }, + { url = "https://files.pythonhosted.org/packages/7d/e6/68bc9393cb4dc68018456568c048ac035854b042bc7c33cb9b99b0680afa/regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29", size = 797535, upload-time = "2025-09-19T00:36:31.876Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1c/ebae9032d34b78ecfe9bd4b5e6575b55351dc8513485bb92326613732b8c/regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f", size = 862603, upload-time = "2025-09-19T00:36:33.344Z" }, + { url = "https://files.pythonhosted.org/packages/3b/74/12332c54b3882557a4bcd2b99f8be581f5c6a43cf1660a85b460dd8ff468/regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68", size = 910829, upload-time = "2025-09-19T00:36:34.826Z" }, + { url = "https://files.pythonhosted.org/packages/86/70/ba42d5ed606ee275f2465bfc0e2208755b06cdabd0f4c7c4b614d51b57ab/regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783", size = 802059, upload-time = "2025-09-19T00:36:36.664Z" }, + { url = "https://files.pythonhosted.org/packages/da/c5/fcb017e56396a7f2f8357412638d7e2963440b131a3ca549be25774b3641/regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac", size = 786781, upload-time = "2025-09-19T00:36:38.168Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ee/21c4278b973f630adfb3bcb23d09d83625f3ab1ca6e40ebdffe69901c7a1/regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e", size = 856578, upload-time = "2025-09-19T00:36:40.129Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/de51550dc7274324435c8f1539373ac63019b0525ad720132866fff4a16a/regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23", size = 849119, upload-time = "2025-09-19T00:36:41.651Z" }, + { url = "https://files.pythonhosted.org/packages/60/52/383d3044fc5154d9ffe4321696ee5b2ee4833a28c29b137c22c33f41885b/regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f", size = 788219, upload-time = "2025-09-19T00:36:43.575Z" }, + { url = "https://files.pythonhosted.org/packages/20/bd/2614fc302671b7359972ea212f0e3a92df4414aaeacab054a8ce80a86073/regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d", size = 264517, upload-time = "2025-09-19T00:36:45.503Z" }, + { url = "https://files.pythonhosted.org/packages/07/0f/ab5c1581e6563a7bffdc1974fb2d25f05689b88e2d416525271f232b1946/regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d", size = 275481, upload-time = "2025-09-19T00:36:46.965Z" }, + { url = "https://files.pythonhosted.org/packages/49/22/ee47672bc7958f8c5667a587c2600a4fba8b6bab6e86bd6d3e2b5f7cac42/regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb", size = 268598, upload-time = "2025-09-19T00:36:48.314Z" }, + { url = "https://files.pythonhosted.org/packages/e8/83/6887e16a187c6226cb85d8301e47d3b73ecc4505a3a13d8da2096b44fd76/regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2", size = 489765, upload-time = "2025-09-19T00:36:49.996Z" }, + { url = "https://files.pythonhosted.org/packages/51/c5/e2f7325301ea2916ff301c8d963ba66b1b2c1b06694191df80a9c4fea5d0/regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3", size = 291228, upload-time = "2025-09-19T00:36:51.654Z" }, + { url = "https://files.pythonhosted.org/packages/91/60/7d229d2bc6961289e864a3a3cfebf7d0d250e2e65323a8952cbb7e22d824/regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12", size = 289270, upload-time = "2025-09-19T00:36:53.118Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d7/b4f06868ee2958ff6430df89857fbf3d43014bbf35538b6ec96c2704e15d/regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0", size = 806326, upload-time = "2025-09-19T00:36:54.631Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e4/bca99034a8f1b9b62ccf337402a8e5b959dd5ba0e5e5b2ead70273df3277/regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6", size = 871556, upload-time = "2025-09-19T00:36:56.208Z" }, + { url = "https://files.pythonhosted.org/packages/6d/df/e06ffaf078a162f6dd6b101a5ea9b44696dca860a48136b3ae4a9caf25e2/regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef", size = 913817, upload-time = "2025-09-19T00:36:57.807Z" }, + { url = "https://files.pythonhosted.org/packages/9e/05/25b05480b63292fd8e84800b1648e160ca778127b8d2367a0a258fa2e225/regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a", size = 811055, upload-time = "2025-09-19T00:36:59.762Z" }, + { url = "https://files.pythonhosted.org/packages/70/97/7bc7574655eb651ba3a916ed4b1be6798ae97af30104f655d8efd0cab24b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d", size = 794534, upload-time = "2025-09-19T00:37:01.405Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c2/d5da49166a52dda879855ecdba0117f073583db2b39bb47ce9a3378a8e9e/regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368", size = 866684, upload-time = "2025-09-19T00:37:03.441Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2d/0a5c4e6ec417de56b89ff4418ecc72f7e3feca806824c75ad0bbdae0516b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90", size = 853282, upload-time = "2025-09-19T00:37:04.985Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8e/d656af63e31a86572ec829665d6fa06eae7e144771e0330650a8bb865635/regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7", size = 797830, upload-time = "2025-09-19T00:37:06.697Z" }, + { url = "https://files.pythonhosted.org/packages/db/ce/06edc89df8f7b83ffd321b6071be4c54dc7332c0f77860edc40ce57d757b/regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e", size = 267281, upload-time = "2025-09-19T00:37:08.568Z" }, + { url = "https://files.pythonhosted.org/packages/83/9a/2b5d9c8b307a451fd17068719d971d3634ca29864b89ed5c18e499446d4a/regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730", size = 278724, upload-time = "2025-09-19T00:37:10.023Z" }, + { url = "https://files.pythonhosted.org/packages/3d/70/177d31e8089a278a764f8ec9a3faac8d14a312d622a47385d4b43905806f/regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a", size = 269771, upload-time = "2025-09-19T00:37:13.041Z" }, + { url = "https://files.pythonhosted.org/packages/44/b7/3b4663aa3b4af16819f2ab6a78c4111c7e9b066725d8107753c2257448a5/regex-2025.9.18-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c6db75b51acf277997f3adcd0ad89045d856190d13359f15ab5dda21581d9129", size = 486130, upload-time = "2025-09-19T00:37:14.527Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/4533f5d7ac9c6a02a4725fe8883de2aebc713e67e842c04cf02626afb747/regex-2025.9.18-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8f9698b6f6895d6db810e0bda5364f9ceb9e5b11328700a90cae573574f61eea", size = 289539, upload-time = "2025-09-19T00:37:16.356Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8d/5ab6797c2750985f79e9995fad3254caa4520846580f266ae3b56d1cae58/regex-2025.9.18-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29cd86aa7cb13a37d0f0d7c21d8d949fe402ffa0ea697e635afedd97ab4b69f1", size = 287233, upload-time = "2025-09-19T00:37:18.025Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/95afcb02ba8d3a64e6ffeb801718ce73471ad6440c55d993f65a4a5e7a92/regex-2025.9.18-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c9f285a071ee55cd9583ba24dde006e53e17780bb309baa8e4289cd472bcc47", size = 797876, upload-time = "2025-09-19T00:37:19.609Z" }, + { url = "https://files.pythonhosted.org/packages/c8/fb/720b1f49cec1f3b5a9fea5b34cd22b88b5ebccc8c1b5de9cc6f65eed165a/regex-2025.9.18-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5adf266f730431e3be9021d3e5b8d5ee65e563fec2883ea8093944d21863b379", size = 863385, upload-time = "2025-09-19T00:37:21.65Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ca/e0d07ecf701e1616f015a720dc13b84c582024cbfbb3fc5394ae204adbd7/regex-2025.9.18-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1137cabc0f38807de79e28d3f6e3e3f2cc8cfb26bead754d02e6d1de5f679203", size = 910220, upload-time = "2025-09-19T00:37:23.723Z" }, + { url = "https://files.pythonhosted.org/packages/b6/45/bba86413b910b708eca705a5af62163d5d396d5f647ed9485580c7025209/regex-2025.9.18-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7cc9e5525cada99699ca9223cce2d52e88c52a3d2a0e842bd53de5497c604164", size = 801827, upload-time = "2025-09-19T00:37:25.684Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a6/740fbd9fcac31a1305a8eed30b44bf0f7f1e042342be0a4722c0365ecfca/regex-2025.9.18-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb9246568f72dce29bcd433517c2be22c7791784b223a810225af3b50d1aafb", size = 786843, upload-time = "2025-09-19T00:37:27.62Z" }, + { url = "https://files.pythonhosted.org/packages/80/a7/0579e8560682645906da640c9055506465d809cb0f5415d9976f417209a6/regex-2025.9.18-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6a52219a93dd3d92c675383efff6ae18c982e2d7651c792b1e6d121055808743", size = 857430, upload-time = "2025-09-19T00:37:29.362Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9b/4dc96b6c17b38900cc9fee254fc9271d0dde044e82c78c0811b58754fde5/regex-2025.9.18-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:ae9b3840c5bd456780e3ddf2f737ab55a79b790f6409182012718a35c6d43282", size = 848612, upload-time = "2025-09-19T00:37:31.42Z" }, + { url = "https://files.pythonhosted.org/packages/b3/6a/6f659f99bebb1775e5ac81a3fb837b85897c1a4ef5acffd0ff8ffe7e67fb/regex-2025.9.18-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d488c236ac497c46a5ac2005a952c1a0e22a07be9f10c3e735bc7d1209a34773", size = 787967, upload-time = "2025-09-19T00:37:34.019Z" }, + { url = "https://files.pythonhosted.org/packages/61/35/9e35665f097c07cf384a6b90a1ac11b0b1693084a0b7a675b06f760496c6/regex-2025.9.18-cp314-cp314-win32.whl", hash = "sha256:0c3506682ea19beefe627a38872d8da65cc01ffa25ed3f2e422dffa1474f0788", size = 269847, upload-time = "2025-09-19T00:37:35.759Z" }, + { url = "https://files.pythonhosted.org/packages/af/64/27594dbe0f1590b82de2821ebfe9a359b44dcb9b65524876cd12fabc447b/regex-2025.9.18-cp314-cp314-win_amd64.whl", hash = "sha256:57929d0f92bebb2d1a83af372cd0ffba2263f13f376e19b1e4fa32aec4efddc3", size = 278755, upload-time = "2025-09-19T00:37:37.367Z" }, + { url = "https://files.pythonhosted.org/packages/30/a3/0cd8d0d342886bd7d7f252d701b20ae1a3c72dc7f34ef4b2d17790280a09/regex-2025.9.18-cp314-cp314-win_arm64.whl", hash = "sha256:6a4b44df31d34fa51aa5c995d3aa3c999cec4d69b9bd414a8be51984d859f06d", size = 271873, upload-time = "2025-09-19T00:37:39.125Z" }, + { url = "https://files.pythonhosted.org/packages/99/cb/8a1ab05ecf404e18b54348e293d9b7a60ec2bd7aa59e637020c5eea852e8/regex-2025.9.18-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b176326bcd544b5e9b17d6943f807697c0cb7351f6cfb45bf5637c95ff7e6306", size = 489773, upload-time = "2025-09-19T00:37:40.968Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/6543c9b7f7e734d2404fa2863d0d710c907bef99d4598760ed4563d634c3/regex-2025.9.18-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0ffd9e230b826b15b369391bec167baed57c7ce39efc35835448618860995946", size = 291221, upload-time = "2025-09-19T00:37:42.901Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/e9fdee6ad6bf708d98c5d17fded423dcb0661795a49cba1b4ffb8358377a/regex-2025.9.18-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec46332c41add73f2b57e2f5b642f991f6b15e50e9f86285e08ffe3a512ac39f", size = 289268, upload-time = "2025-09-19T00:37:44.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/a6/bc3e8a918abe4741dadeaeb6c508e3a4ea847ff36030d820d89858f96a6c/regex-2025.9.18-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b80fa342ed1ea095168a3f116637bd1030d39c9ff38dc04e54ef7c521e01fc95", size = 806659, upload-time = "2025-09-19T00:37:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/2b/71/ea62dbeb55d9e6905c7b5a49f75615ea1373afcad95830047e4e310db979/regex-2025.9.18-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4d97071c0ba40f0cf2a93ed76e660654c399a0a04ab7d85472239460f3da84b", size = 871701, upload-time = "2025-09-19T00:37:48.882Z" }, + { url = "https://files.pythonhosted.org/packages/6a/90/fbe9dedb7dad24a3a4399c0bae64bfa932ec8922a0a9acf7bc88db30b161/regex-2025.9.18-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0ac936537ad87cef9e0e66c5144484206c1354224ee811ab1519a32373e411f3", size = 913742, upload-time = "2025-09-19T00:37:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1c/47e4a8c0e73d41eb9eb9fdeba3b1b810110a5139a2526e82fd29c2d9f867/regex-2025.9.18-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dec57f96d4def58c422d212d414efe28218d58537b5445cf0c33afb1b4768571", size = 811117, upload-time = "2025-09-19T00:37:52.686Z" }, + { url = "https://files.pythonhosted.org/packages/2a/da/435f29fddfd015111523671e36d30af3342e8136a889159b05c1d9110480/regex-2025.9.18-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48317233294648bf7cd068857f248e3a57222259a5304d32c7552e2284a1b2ad", size = 794647, upload-time = "2025-09-19T00:37:54.626Z" }, + { url = "https://files.pythonhosted.org/packages/23/66/df5e6dcca25c8bc57ce404eebc7342310a0d218db739d7882c9a2b5974a3/regex-2025.9.18-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:274687e62ea3cf54846a9b25fc48a04459de50af30a7bd0b61a9e38015983494", size = 866747, upload-time = "2025-09-19T00:37:56.367Z" }, + { url = "https://files.pythonhosted.org/packages/82/42/94392b39b531f2e469b2daa40acf454863733b674481fda17462a5ffadac/regex-2025.9.18-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a78722c86a3e7e6aadf9579e3b0ad78d955f2d1f1a8ca4f67d7ca258e8719d4b", size = 853434, upload-time = "2025-09-19T00:37:58.39Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f8/dcc64c7f7bbe58842a8f89622b50c58c3598fbbf4aad0a488d6df2c699f1/regex-2025.9.18-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:06104cd203cdef3ade989a1c45b6215bf42f8b9dd705ecc220c173233f7cba41", size = 798024, upload-time = "2025-09-19T00:38:00.397Z" }, + { url = "https://files.pythonhosted.org/packages/20/8d/edf1c5d5aa98f99a692313db813ec487732946784f8f93145e0153d910e5/regex-2025.9.18-cp314-cp314t-win32.whl", hash = "sha256:2e1eddc06eeaffd249c0adb6fafc19e2118e6308c60df9db27919e96b5656096", size = 273029, upload-time = "2025-09-19T00:38:02.383Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/02d4e4f88466f17b145f7ea2b2c11af3a942db6222429c2c146accf16054/regex-2025.9.18-cp314-cp314t-win_amd64.whl", hash = "sha256:8620d247fb8c0683ade51217b459cb4a1081c0405a3072235ba43a40d355c09a", size = 282680, upload-time = "2025-09-19T00:38:04.102Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a3/c64894858aaaa454caa7cc47e2f225b04d3ed08ad649eacf58d45817fad2/regex-2025.9.18-cp314-cp314t-win_arm64.whl", hash = "sha256:b7531a8ef61de2c647cdf68b3229b071e46ec326b3138b2180acb4275f470b01", size = 273034, upload-time = "2025-09-19T00:38:05.807Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, +] + +[[package]] +name = "rfc3987-syntax" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lark" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" }, +] + +[[package]] +name = "rich" +version = "14.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, +] + +[[package]] +name = "rich-argparse" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/a6/34460d81e5534f6d2fc8e8d91ff99a5835fdca53578eac89e4f37b3a7c6d/rich_argparse-1.7.1.tar.gz", hash = "sha256:d7a493cde94043e41ea68fb43a74405fa178de981bf7b800f7a3bd02ac5c27be", size = 38094, upload-time = "2025-05-25T20:20:35.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/f6/5fc0574af5379606ffd57a4b68ed88f9b415eb222047fe023aefcc00a648/rich_argparse-1.7.1-py3-none-any.whl", hash = "sha256:a8650b42e4a4ff72127837632fba6b7da40784842f08d7395eb67a9cbd7b4bf9", size = 25357, upload-time = "2025-05-25T20:20:33.793Z" }, +] + +[[package]] +name = "rich-rst" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/69/5514c3a87b5f10f09a34bb011bc0927bc12c596c8dae5915604e71abc386/rich_rst-1.3.1.tar.gz", hash = "sha256:fad46e3ba42785ea8c1785e2ceaa56e0ffa32dbe5410dec432f37e4107c4f383", size = 13839, upload-time = "2024-04-30T04:40:38.125Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/bc/cc4e3dbc5e7992398dcb7a8eda0cbcf4fb792a0cdb93f857b478bf3cf884/rich_rst-1.3.1-py3-none-any.whl", hash = "sha256:498a74e3896507ab04492d326e794c3ef76e7cda078703aa592d1853d91098c1", size = 11621, upload-time = "2024-04-30T04:40:32.619Z" }, +] + +[[package]] +name = "rich-toolkit" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/33/1a18839aaa8feef7983590c05c22c9c09d245ada6017d118325bbfcc7651/rich_toolkit-0.15.1.tar.gz", hash = "sha256:6f9630eb29f3843d19d48c3bd5706a086d36d62016687f9d0efa027ddc2dd08a", size = 115322, upload-time = "2025-09-04T09:28:11.789Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/49/42821d55ead7b5a87c8d121edf323cb393d8579f63e933002ade900b784f/rich_toolkit-0.15.1-py3-none-any.whl", hash = "sha256:36a0b1d9a135d26776e4b78f1d5c2655da6e0ef432380b5c6b523c8d8ab97478", size = 29412, upload-time = "2025-09-04T09:28:10.587Z" }, +] + +[[package]] +name = "rignore" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/46/e5ef3423a3746f91d3a3d9a68c499fde983be7dbab7d874efa8d3bb139ba/rignore-0.7.0.tar.gz", hash = "sha256:cfe6a2cbec855b440d7550d53e670246fce43ca5847e46557b6d4577c9cdb540", size = 12796, upload-time = "2025-10-02T13:26:22.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/85/cd1441043c5ed13e671153af260c5f328042ebfb87aa28849367602206f2/rignore-0.7.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:190e469db68112c4027a7a126facfd80ce353374ff208c585ca7dacc75de0472", size = 880474, upload-time = "2025-10-02T13:25:08.111Z" }, + { url = "https://files.pythonhosted.org/packages/f4/07/d5b9593cb05593718508308543a8fbee75998a7489cf4f4b489d2632bd4a/rignore-0.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0a43f6fabf46ed8e96fbf2861187362e513960c2a8200c35242981bd36ef8b96", size = 811882, upload-time = "2025-10-02T13:24:56.599Z" }, + { url = "https://files.pythonhosted.org/packages/aa/67/b82b2704660c280061d8bc90bc91092622309f78e20c9e3321f45f88cd4e/rignore-0.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b89a59e5291805eca3c3317a55fcd2a579e9ee1184511660078a398182463deb", size = 892043, upload-time = "2025-10-02T13:23:22.326Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7e/e91a1899a06882cd8a7acc3025c51b9f830971b193bd6b72e34254ed7733/rignore-0.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a155f36be847c05c800e0218e9ac04946ba44bf077e1f11dc024ca9e1f7a727", size = 865404, upload-time = "2025-10-02T13:23:40.085Z" }, + { url = "https://files.pythonhosted.org/packages/91/2c/68487538a2d2d7e0e1ca1051d143af690211314e22cbed58a245e816ebaf/rignore-0.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dba075135ac3cda5f3236b4f03f82bbcd97454a908631ad3da93aae1e7390b17", size = 1167661, upload-time = "2025-10-02T13:23:57.578Z" }, + { url = "https://files.pythonhosted.org/packages/b4/39/8498ac13fb710a1920526480f9476aaeaaaa20c522a027d07513929ba9d9/rignore-0.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8525b8c31f36dc9fbcb474ef58d654f6404b19b6110b7f5df332e58e657a4aa8", size = 936272, upload-time = "2025-10-02T13:24:13.414Z" }, + { url = "https://files.pythonhosted.org/packages/55/1a/38b92fde209931611dcff0db59bd5656a325ba58d368d4e50f1e711fdd16/rignore-0.7.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0428b64d8b02ad83fc0a2505ded0e9064cac97df7aa1dffc9c7558b56429912", size = 950552, upload-time = "2025-10-02T13:24:43.263Z" }, + { url = "https://files.pythonhosted.org/packages/e3/01/f59f38ae1b879309b0151b1ed0dd82880e1d3759f91bfdaa570730672308/rignore-0.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ab1db960a64835ec3ed541951821bfc38f30dfbd6ebd990f7d039d0c54ff957", size = 974407, upload-time = "2025-10-02T13:24:30.618Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/de92fdc09dc1a622abb6d1b2678e940d24de2a07c60d193126eb52a7e8ea/rignore-0.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3749711b1e50fb5b28b55784e159a3b8209ecc72d01cc1511c05bc3a23b4a063", size = 1072865, upload-time = "2025-10-02T13:25:20.451Z" }, + { url = "https://files.pythonhosted.org/packages/65/bb/75fbef03cf56b0918880cb3b922da83d6546309566be60f6c6b451f7221b/rignore-0.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:57240739c786f897f89e29c05e529291ee1b477df9f6b29b774403a23a169fe2", size = 1129007, upload-time = "2025-10-02T13:25:36.837Z" }, + { url = "https://files.pythonhosted.org/packages/ec/24/4d591d45a8994fb4afaefa22e356d69948726c9ccba0cfd76c82509aedc2/rignore-0.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6b70581286acd5f96ce11efd209bfe9261108586e1a948cc558fc3f58ba5bf5f", size = 1106827, upload-time = "2025-10-02T13:25:52.964Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b3/b614d54fa1f1c7621aeb20b2841cd980288ad9d7d61407fc4595d5c5f132/rignore-0.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33fb6e4cba1b798f1328e889b4bf2341894d82e3be42bb3513b4e0fe38788538", size = 1115328, upload-time = "2025-10-02T13:26:10.947Z" }, + { url = "https://files.pythonhosted.org/packages/83/22/ea0b3e30e230b2d2222e1ee18e20316c8297088f4cc6a6ea2ee6cb34f595/rignore-0.7.0-cp313-cp313-win32.whl", hash = "sha256:119f0497fb4776cddc663ee8f35085ce00758bd423221ba1e8222a816e10cf5e", size = 636896, upload-time = "2025-10-02T13:26:40.3Z" }, + { url = "https://files.pythonhosted.org/packages/79/16/f55b3db13f6fff408fde348d2a726d3b4ba06ed55dce8ff119e374ce3005/rignore-0.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:fb06e11dda689be138909f53639f0baa8d7c6be4d76ca9ec316382ccf3517469", size = 716519, upload-time = "2025-10-02T13:26:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/69/db/8c20a7b59abb21d3d20d387656b6759cd5890fa68185064fe8899f942a4b/rignore-0.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f2255821ab4bc34fa129a94535f5d0d88b164940b25d0a3b26ebd41d99f1a9f", size = 890684, upload-time = "2025-10-02T13:23:23.761Z" }, + { url = "https://files.pythonhosted.org/packages/45/a0/ae5ca63aed23f64dcd740f55ee6432037af5c09d25efaf79dc052a4a51ff/rignore-0.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b57efcbbc1510f8ce831a5e19fb1fe9dd329bb246c4e4f8a09bf1c06687b0331", size = 865174, upload-time = "2025-10-02T13:23:41.948Z" }, + { url = "https://files.pythonhosted.org/packages/ae/27/5aff661e792efbffda689f0d3fa91ea36f2e0d4bcca3b02f70ae95ea96da/rignore-0.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ead4bc2baceeccdfeb82cb70ba8f70fdb6dc1e58976f805f9d0d19b9ee915f0", size = 1165293, upload-time = "2025-10-02T13:23:59.238Z" }, + { url = "https://files.pythonhosted.org/packages/cb/df/13de7ce5ba2a58c724ef202310408729941c262179389df5e90cb9a41381/rignore-0.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4f0a8996437a22df0faf2844d65ec91d41176b9d4e7357abee42baa39dc996ae", size = 936093, upload-time = "2025-10-02T13:24:15.057Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/4ea42bc454db8499906c8d075a7a0053b7fd381b85f3bcc857e68a8b8b23/rignore-0.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cb17ef4a413444fccbd57e1b4a3870f1320951b81f1b7007af9c70e1a5bc2897", size = 1071518, upload-time = "2025-10-02T13:25:22.076Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a7/7400a4343d1b5a1345a98846c6fd7768ff13890d207fce79d690c7fd7798/rignore-0.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:b12b316adf6cf64f9d22bd690b2aa019a37335a1f632a0da7fb15a423cb64080", size = 1128403, upload-time = "2025-10-02T13:25:38.394Z" }, + { url = "https://files.pythonhosted.org/packages/45/8b/ce8ff27336a86bad47bbf011f8f7fb0b82b559ee4a0d6a4815ee3555ef56/rignore-0.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:dba8181d999387c17dd6cce5fd7f0009376ca8623d2d86842d034b18d83dc768", size = 1105552, upload-time = "2025-10-02T13:25:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e2/7925b564d853c7057f150a7f2f384400422ed30f7b7baf2fde5849562381/rignore-0.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:04a3d4513cdd184f4f849ae8d6407a169cca543a2c4dd69bfc42e67cb0155504", size = 1114826, upload-time = "2025-10-02T13:26:12.56Z" }, + { url = "https://files.pythonhosted.org/packages/c4/34/c42ccdd81143d38d99e45b965e4040a1ef6c07a365ad205dd94b6d16c794/rignore-0.7.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:a296bc26b713aacd0f31702e7d89426ba6240abdbf01b2b18daeeaeaa782f475", size = 879718, upload-time = "2025-10-02T13:25:09.62Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ba/f522adf949d2b581a0a1e488a79577631ed6661fdc12e80d4182ed655036/rignore-0.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7f71807ed0bc1542860a8fa1615a0d93f3d5a22dde1066e9f50d7270bc60686", size = 810391, upload-time = "2025-10-02T13:24:58.144Z" }, + { url = "https://files.pythonhosted.org/packages/f2/82/935bffa4ad7d9560541daaca7ba0e4ee9b0b9a6370ab9518cf9c991087bb/rignore-0.7.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7e6ff54399ddb650f4e4dc74b325766e7607967a49b868326e9687fc3642620", size = 950261, upload-time = "2025-10-02T13:24:45.121Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0e/22abda23cc6d20901262fcfea50c25ed66ca6e1a5dc610d338df4ca10407/rignore-0.7.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09dfad3ca450b3967533c6b1a2c7c0228c63c518f619ff342df5f9c3ed978b66", size = 974258, upload-time = "2025-10-02T13:24:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/ed/8d/0ba2c712723fdda62125087d00dcdad93102876d4e3fa5adbb99f0b859c3/rignore-0.7.0-cp314-cp314-win32.whl", hash = "sha256:2850718cfb1caece6b7ac19a524c7905a8d0c6627b0d0f4e81798e20b6c75078", size = 637403, upload-time = "2025-10-02T13:26:41.814Z" }, + { url = "https://files.pythonhosted.org/packages/1c/63/0d7df1237c6353d1a85d8a0bc1797ac766c68e8bc6fbca241db74124eb61/rignore-0.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2401637dc8ab074f5e642295f8225d2572db395ae504ffc272a8d21e9fe77b2c", size = 717404, upload-time = "2025-10-02T13:26:29.936Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" }, + { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" }, + { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" }, + { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" }, + { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" }, + { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" }, + { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" }, + { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" }, + { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" }, + { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" }, + { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" }, + { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" }, + { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" }, + { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" }, + { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" }, + { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" }, + { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" }, + { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" }, + { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" }, + { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" }, + { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" }, + { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" }, + { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" }, + { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" }, + { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" }, + { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" }, + { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" }, + { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "ruff" +version = "0.11.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/da/9c6f995903b4d9474b39da91d2d626659af3ff1eeb43e9ae7c119349dba6/ruff-0.11.13.tar.gz", hash = "sha256:26fa247dc68d1d4e72c179e08889a25ac0c7ba4d78aecfc835d49cbfd60bf514", size = 4282054, upload-time = "2025-06-05T21:00:15.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/ce/a11d381192966e0b4290842cc8d4fac7dc9214ddf627c11c1afff87da29b/ruff-0.11.13-py3-none-linux_armv6l.whl", hash = "sha256:4bdfbf1240533f40042ec00c9e09a3aade6f8c10b6414cf11b519488d2635d46", size = 10292516, upload-time = "2025-06-05T20:59:32.944Z" }, + { url = "https://files.pythonhosted.org/packages/78/db/87c3b59b0d4e753e40b6a3b4a2642dfd1dcaefbff121ddc64d6c8b47ba00/ruff-0.11.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aef9c9ed1b5ca28bb15c7eac83b8670cf3b20b478195bd49c8d756ba0a36cf48", size = 11106083, upload-time = "2025-06-05T20:59:37.03Z" }, + { url = "https://files.pythonhosted.org/packages/77/79/d8cec175856ff810a19825d09ce700265f905c643c69f45d2b737e4a470a/ruff-0.11.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:53b15a9dfdce029c842e9a5aebc3855e9ab7771395979ff85b7c1dedb53ddc2b", size = 10436024, upload-time = "2025-06-05T20:59:39.741Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5b/f6d94f2980fa1ee854b41568368a2e1252681b9238ab2895e133d303538f/ruff-0.11.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab153241400789138d13f362c43f7edecc0edfffce2afa6a68434000ecd8f69a", size = 10646324, upload-time = "2025-06-05T20:59:42.185Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9c/b4c2acf24ea4426016d511dfdc787f4ce1ceb835f3c5fbdbcb32b1c63bda/ruff-0.11.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c51f93029d54a910d3d24f7dd0bb909e31b6cd989a5e4ac513f4eb41629f0dc", size = 10174416, upload-time = "2025-06-05T20:59:44.319Z" }, + { url = "https://files.pythonhosted.org/packages/f3/10/e2e62f77c65ede8cd032c2ca39c41f48feabedb6e282bfd6073d81bb671d/ruff-0.11.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1808b3ed53e1a777c2ef733aca9051dc9bf7c99b26ece15cb59a0320fbdbd629", size = 11724197, upload-time = "2025-06-05T20:59:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f0/466fe8469b85c561e081d798c45f8a1d21e0b4a5ef795a1d7f1a9a9ec182/ruff-0.11.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d28ce58b5ecf0f43c1b71edffabe6ed7f245d5336b17805803312ec9bc665933", size = 12511615, upload-time = "2025-06-05T20:59:49.534Z" }, + { url = "https://files.pythonhosted.org/packages/17/0e/cefe778b46dbd0cbcb03a839946c8f80a06f7968eb298aa4d1a4293f3448/ruff-0.11.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55e4bc3a77842da33c16d55b32c6cac1ec5fb0fbec9c8c513bdce76c4f922165", size = 12117080, upload-time = "2025-06-05T20:59:51.654Z" }, + { url = "https://files.pythonhosted.org/packages/5d/2c/caaeda564cbe103bed145ea557cb86795b18651b0f6b3ff6a10e84e5a33f/ruff-0.11.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:633bf2c6f35678c56ec73189ba6fa19ff1c5e4807a78bf60ef487b9dd272cc71", size = 11326315, upload-time = "2025-06-05T20:59:54.469Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/782e7d681d660eda8c536962920c41309e6dd4ebcea9a2714ed5127d44bd/ruff-0.11.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ffbc82d70424b275b089166310448051afdc6e914fdab90e08df66c43bb5ca9", size = 11555640, upload-time = "2025-06-05T20:59:56.986Z" }, + { url = "https://files.pythonhosted.org/packages/5d/d4/3d580c616316c7f07fb3c99dbecfe01fbaea7b6fd9a82b801e72e5de742a/ruff-0.11.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a9ddd3ec62a9a89578c85842b836e4ac832d4a2e0bfaad3b02243f930ceafcc", size = 10507364, upload-time = "2025-06-05T20:59:59.154Z" }, + { url = "https://files.pythonhosted.org/packages/5a/dc/195e6f17d7b3ea6b12dc4f3e9de575db7983db187c378d44606e5d503319/ruff-0.11.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d237a496e0778d719efb05058c64d28b757c77824e04ffe8796c7436e26712b7", size = 10141462, upload-time = "2025-06-05T21:00:01.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8e/39a094af6967faa57ecdeacb91bedfb232474ff8c3d20f16a5514e6b3534/ruff-0.11.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:26816a218ca6ef02142343fd24c70f7cd8c5aa6c203bca284407adf675984432", size = 11121028, upload-time = "2025-06-05T21:00:04.06Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c0/b0b508193b0e8a1654ec683ebab18d309861f8bd64e3a2f9648b80d392cb/ruff-0.11.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:51c3f95abd9331dc5b87c47ac7f376db5616041173826dfd556cfe3d4977f492", size = 11602992, upload-time = "2025-06-05T21:00:06.249Z" }, + { url = "https://files.pythonhosted.org/packages/7c/91/263e33ab93ab09ca06ce4f8f8547a858cc198072f873ebc9be7466790bae/ruff-0.11.13-py3-none-win32.whl", hash = "sha256:96c27935418e4e8e77a26bb05962817f28b8ef3843a6c6cc49d8783b5507f250", size = 10474944, upload-time = "2025-06-05T21:00:08.459Z" }, + { url = "https://files.pythonhosted.org/packages/46/f4/7c27734ac2073aae8efb0119cae6931b6fb48017adf048fdf85c19337afc/ruff-0.11.13-py3-none-win_amd64.whl", hash = "sha256:29c3189895a8a6a657b7af4e97d330c8a3afd2c9c8f46c81e2fc5a31866517e3", size = 11548669, upload-time = "2025-06-05T21:00:11.147Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bf/b273dd11673fed8a6bd46032c0ea2a04b2ac9bfa9c628756a5856ba113b0/ruff-0.11.13-py3-none-win_arm64.whl", hash = "sha256:b4385285e9179d608ff1d2fb9922062663c658605819a6876d8beef0c30b7f3b", size = 10683928, upload-time = "2025-06-05T21:00:13.758Z" }, +] + +[[package]] +name = "safehttpx" +version = "0.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/4c/19db75e6405692b2a96af8f06d1258f8aa7290bdc35ac966f03e207f6d7f/safehttpx-0.1.6.tar.gz", hash = "sha256:b356bfc82cee3a24c395b94a2dbeabbed60aff1aa5fa3b5fe97c4f2456ebce42", size = 9987, upload-time = "2024-12-02T18:44:10.226Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/c0/1108ad9f01567f66b3154063605b350b69c3c9366732e09e45f9fd0d1deb/safehttpx-0.1.6-py3-none-any.whl", hash = "sha256:407cff0b410b071623087c63dd2080c3b44dc076888d8c5823c00d1e58cb381c", size = 8692, upload-time = "2024-12-02T18:44:08.555Z" }, +] + +[[package]] +name = "safetensors" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/cc/738f3011628920e027a11754d9cae9abec1aed00f7ae860abbf843755233/safetensors-0.6.2.tar.gz", hash = "sha256:43ff2aa0e6fa2dc3ea5524ac7ad93a9839256b8703761e76e2d0b2a3fa4f15d9", size = 197968, upload-time = "2025-08-08T13:13:58.654Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/b1/3f5fd73c039fc87dba3ff8b5d528bfc5a32b597fea8e7a6a4800343a17c7/safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9c85ede8ec58f120bad982ec47746981e210492a6db876882aa021446af8ffba", size = 454797, upload-time = "2025-08-08T13:13:52.066Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c9/bb114c158540ee17907ec470d01980957fdaf87b4aa07914c24eba87b9c6/safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6675cf4b39c98dbd7d940598028f3742e0375a6b4d4277e76beb0c35f4b843b", size = 432206, upload-time = "2025-08-08T13:13:50.931Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8e/f70c34e47df3110e8e0bb268d90db8d4be8958a54ab0336c9be4fe86dac8/safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d2d2b3ce1e2509c68932ca03ab8f20570920cd9754b05063d4368ee52833ecd", size = 473261, upload-time = "2025-08-08T13:13:41.259Z" }, + { url = "https://files.pythonhosted.org/packages/2a/f5/be9c6a7c7ef773e1996dc214e73485286df1836dbd063e8085ee1976f9cb/safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:93de35a18f46b0f5a6a1f9e26d91b442094f2df02e9fd7acf224cfec4238821a", size = 485117, upload-time = "2025-08-08T13:13:43.506Z" }, + { url = "https://files.pythonhosted.org/packages/c9/55/23f2d0a2c96ed8665bf17a30ab4ce5270413f4d74b6d87dd663258b9af31/safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89a89b505f335640f9120fac65ddeb83e40f1fd081cb8ed88b505bdccec8d0a1", size = 616154, upload-time = "2025-08-08T13:13:45.096Z" }, + { url = "https://files.pythonhosted.org/packages/98/c6/affb0bd9ce02aa46e7acddbe087912a04d953d7a4d74b708c91b5806ef3f/safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4d0d0b937e04bdf2ae6f70cd3ad51328635fe0e6214aa1fc811f3b576b3bda", size = 520713, upload-time = "2025-08-08T13:13:46.25Z" }, + { url = "https://files.pythonhosted.org/packages/fe/5d/5a514d7b88e310c8b146e2404e0dc161282e78634d9358975fd56dfd14be/safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8045db2c872db8f4cbe3faa0495932d89c38c899c603f21e9b6486951a5ecb8f", size = 485835, upload-time = "2025-08-08T13:13:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7b/4fc3b2ba62c352b2071bea9cfbad330fadda70579f617506ae1a2f129cab/safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81e67e8bab9878bb568cffbc5f5e655adb38d2418351dc0859ccac158f753e19", size = 521503, upload-time = "2025-08-08T13:13:47.651Z" }, + { url = "https://files.pythonhosted.org/packages/5a/50/0057e11fe1f3cead9254315a6c106a16dd4b1a19cd247f7cc6414f6b7866/safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0e4d029ab0a0e0e4fdf142b194514695b1d7d3735503ba700cf36d0fc7136ce", size = 652256, upload-time = "2025-08-08T13:13:53.167Z" }, + { url = "https://files.pythonhosted.org/packages/e9/29/473f789e4ac242593ac1656fbece6e1ecd860bb289e635e963667807afe3/safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fa48268185c52bfe8771e46325a1e21d317207bcabcb72e65c6e28e9ffeb29c7", size = 747281, upload-time = "2025-08-08T13:13:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/68/52/f7324aad7f2df99e05525c84d352dc217e0fa637a4f603e9f2eedfbe2c67/safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:d83c20c12c2d2f465997c51b7ecb00e407e5f94d7dec3ea0cc11d86f60d3fde5", size = 692286, upload-time = "2025-08-08T13:13:55.884Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/cad1d9762868c7c5dc70c8620074df28ebb1a8e4c17d4c0cb031889c457e/safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d944cea65fad0ead848b6ec2c37cc0b197194bec228f8020054742190e9312ac", size = 655957, upload-time = "2025-08-08T13:13:57.029Z" }, + { url = "https://files.pythonhosted.org/packages/59/a7/e2158e17bbe57d104f0abbd95dff60dda916cf277c9f9663b4bf9bad8b6e/safetensors-0.6.2-cp38-abi3-win32.whl", hash = "sha256:cab75ca7c064d3911411461151cb69380c9225798a20e712b102edda2542ddb1", size = 308926, upload-time = "2025-08-08T13:14:01.095Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c3/c0be1135726618dc1e28d181b8c442403d8dbb9e273fd791de2d4384bcdd/safetensors-0.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c", size = 320192, upload-time = "2025-08-08T13:13:59.467Z" }, +] + +[[package]] +name = "scikit-image" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "imageio" }, + { name = "lazy-loader" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "scipy" }, + { name = "tifffile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" }, + { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382, upload-time = "2025-09-09T08:20:54.731Z" }, + { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042, upload-time = "2025-09-09T08:20:57.313Z" }, + { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180, upload-time = "2025-09-09T08:20:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660, upload-time = "2025-09-09T08:21:01.71Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057, upload-time = "2025-09-09T08:21:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731, upload-time = "2025-09-09T08:21:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852, upload-time = "2025-09-09T08:21:08.628Z" }, + { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094, upload-time = "2025-09-09T08:21:11.486Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436, upload-time = "2025-09-09T08:21:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749, upload-time = "2025-09-09T08:21:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/dee5acf66837852e8e68df6d8d3a6cb22d3df997b733b032f513d95205b7/scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33", size = 9208906, upload-time = "2025-09-09T08:21:18.557Z" }, + { url = "https://files.pythonhosted.org/packages/3c/30/9029e54e17b87cb7d50d51a5926429c683d5b4c1732f0507a6c3bed9bf65/scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615", size = 8627836, upload-time = "2025-09-09T08:21:20.695Z" }, + { url = "https://files.pythonhosted.org/packages/60/18/4a52c635c71b536879f4b971c2cedf32c35ee78f48367885ed8025d1f7ee/scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106", size = 9426236, upload-time = "2025-09-09T08:21:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/99/7e/290362f6ab582128c53445458a5befd471ed1ea37953d5bcf80604619250/scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61", size = 9312593, upload-time = "2025-09-09T08:21:24.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/24f541b6d62b1794939ae6422f8023703bbf6900378b2b34e0b4384dfefd/scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8", size = 8820007, upload-time = "2025-09-09T08:21:26.713Z" }, +] + +[[package]] +name = "scipy" +version = "1.16.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b", size = 30580599, upload-time = "2025-09-11T17:48:08.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70", size = 36604856, upload-time = "2025-09-11T17:41:47.695Z" }, + { url = "https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9", size = 28864626, upload-time = "2025-09-11T17:41:52.642Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fc/ea36098df653cca26062a627c1a94b0de659e97127c8491e18713ca0e3b9/scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5", size = 20855689, upload-time = "2025-09-11T17:41:57.886Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6f/d0b53be55727f3e6d7c72687ec18ea6d0047cf95f1f77488b99a2bafaee1/scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925", size = 23512151, upload-time = "2025-09-11T17:42:02.303Z" }, + { url = "https://files.pythonhosted.org/packages/11/85/bf7dab56e5c4b1d3d8eef92ca8ede788418ad38a7dc3ff50262f00808760/scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9", size = 33329824, upload-time = "2025-09-11T17:42:07.549Z" }, + { url = "https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7", size = 35681881, upload-time = "2025-09-11T17:42:13.255Z" }, + { url = "https://files.pythonhosted.org/packages/c1/5f/331148ea5780b4fcc7007a4a6a6ee0a0c1507a796365cc642d4d226e1c3a/scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb", size = 36006219, upload-time = "2025-09-11T17:42:18.765Z" }, + { url = "https://files.pythonhosted.org/packages/46/3a/e991aa9d2aec723b4a8dcfbfc8365edec5d5e5f9f133888067f1cbb7dfc1/scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e", size = 38682147, upload-time = "2025-09-11T17:42:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c", size = 38520766, upload-time = "2025-09-11T17:43:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/85d3e867b6822d331e26c862a91375bb7746a0b458db5effa093d34cdb89/scipy-1.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104", size = 25451169, upload-time = "2025-09-11T17:43:30.198Z" }, + { url = "https://files.pythonhosted.org/packages/09/d9/60679189bcebda55992d1a45498de6d080dcaf21ce0c8f24f888117e0c2d/scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1", size = 37012682, upload-time = "2025-09-11T17:42:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/83/be/a99d13ee4d3b7887a96f8c71361b9659ba4ef34da0338f14891e102a127f/scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a", size = 29389926, upload-time = "2025-09-11T17:42:35.845Z" }, + { url = "https://files.pythonhosted.org/packages/bf/0a/130164a4881cec6ca8c00faf3b57926f28ed429cd6001a673f83c7c2a579/scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f", size = 21381152, upload-time = "2025-09-11T17:42:40.07Z" }, + { url = "https://files.pythonhosted.org/packages/47/a6/503ffb0310ae77fba874e10cddfc4a1280bdcca1d13c3751b8c3c2996cf8/scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4", size = 23914410, upload-time = "2025-09-11T17:42:44.313Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c7/1147774bcea50d00c02600aadaa919facbd8537997a62496270133536ed6/scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21", size = 33481880, upload-time = "2025-09-11T17:42:49.325Z" }, + { url = "https://files.pythonhosted.org/packages/6a/74/99d5415e4c3e46b2586f30cdbecb95e101c7192628a484a40dd0d163811a/scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7", size = 35791425, upload-time = "2025-09-11T17:42:54.711Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ee/a6559de7c1cc710e938c0355d9d4fbcd732dac4d0d131959d1f3b63eb29c/scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8", size = 36178622, upload-time = "2025-09-11T17:43:00.375Z" }, + { url = "https://files.pythonhosted.org/packages/4e/7b/f127a5795d5ba8ece4e0dce7d4a9fb7cb9e4f4757137757d7a69ab7d4f1a/scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472", size = 38783985, upload-time = "2025-09-11T17:43:06.661Z" }, + { url = "https://files.pythonhosted.org/packages/3e/9f/bc81c1d1e033951eb5912cd3750cc005943afa3e65a725d2443a3b3c4347/scipy-1.16.2-cp313-cp313t-win_amd64.whl", hash = "sha256:116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351", size = 38631367, upload-time = "2025-09-11T17:43:14.44Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5e/2cc7555fd81d01814271412a1d59a289d25f8b63208a0a16c21069d55d3e/scipy-1.16.2-cp313-cp313t-win_arm64.whl", hash = "sha256:98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d", size = 25787992, upload-time = "2025-09-11T17:43:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ac/ad8951250516db71619f0bd3b2eb2448db04b720a003dd98619b78b692c0/scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77", size = 36595109, upload-time = "2025-09-11T17:43:35.713Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f6/5779049ed119c5b503b0f3dc6d6f3f68eefc3a9190d4ad4c276f854f051b/scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70", size = 28859110, upload-time = "2025-09-11T17:43:40.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/09/9986e410ae38bf0a0c737ff8189ac81a93b8e42349aac009891c054403d7/scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88", size = 20850110, upload-time = "2025-09-11T17:43:44.981Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ad/485cdef2d9215e2a7df6d61b81d2ac073dfacf6ae24b9ae87274c4e936ae/scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f", size = 23497014, upload-time = "2025-09-11T17:43:49.074Z" }, + { url = "https://files.pythonhosted.org/packages/a7/74/f6a852e5d581122b8f0f831f1d1e32fb8987776ed3658e95c377d308ed86/scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb", size = 33401155, upload-time = "2025-09-11T17:43:54.661Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f5/61d243bbc7c6e5e4e13dde9887e84a5cbe9e0f75fd09843044af1590844e/scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7", size = 35691174, upload-time = "2025-09-11T17:44:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/03/99/59933956331f8cc57e406cdb7a483906c74706b156998f322913e789c7e1/scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548", size = 36070752, upload-time = "2025-09-11T17:44:05.619Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7d/00f825cfb47ee19ef74ecf01244b43e95eae74e7e0ff796026ea7cd98456/scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936", size = 38701010, upload-time = "2025-09-11T17:44:11.322Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9f/b62587029980378304ba5a8563d376c96f40b1e133daacee76efdcae32de/scipy-1.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff", size = 39360061, upload-time = "2025-09-11T17:45:09.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/04/7a2f1609921352c7fbee0815811b5050582f67f19983096c4769867ca45f/scipy-1.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d", size = 26126914, upload-time = "2025-09-11T17:45:14.73Z" }, + { url = "https://files.pythonhosted.org/packages/51/b9/60929ce350c16b221928725d2d1d7f86cf96b8bc07415547057d1196dc92/scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8", size = 37013193, upload-time = "2025-09-11T17:44:16.757Z" }, + { url = "https://files.pythonhosted.org/packages/2a/41/ed80e67782d4bc5fc85a966bc356c601afddd175856ba7c7bb6d9490607e/scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4", size = 29390172, upload-time = "2025-09-11T17:44:21.783Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a3/2f673ace4090452696ccded5f5f8efffb353b8f3628f823a110e0170b605/scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831", size = 21381326, upload-time = "2025-09-11T17:44:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/42/bf/59df61c5d51395066c35836b78136accf506197617c8662e60ea209881e1/scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3", size = 23915036, upload-time = "2025-09-11T17:44:30.527Z" }, + { url = "https://files.pythonhosted.org/packages/91/c3/edc7b300dc16847ad3672f1a6f3f7c5d13522b21b84b81c265f4f2760d4a/scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac", size = 33484341, upload-time = "2025-09-11T17:44:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/26/c7/24d1524e72f06ff141e8d04b833c20db3021020563272ccb1b83860082a9/scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374", size = 35790840, upload-time = "2025-09-11T17:44:41.76Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b7/5aaad984eeedd56858dc33d75efa59e8ce798d918e1033ef62d2708f2c3d/scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6", size = 36174716, upload-time = "2025-09-11T17:44:47.316Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c2/e276a237acb09824822b0ada11b028ed4067fdc367a946730979feacb870/scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c", size = 38790088, upload-time = "2025-09-11T17:44:53.011Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b4/5c18a766e8353015439f3780f5fc473f36f9762edc1a2e45da3ff5a31b21/scipy-1.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9", size = 39457455, upload-time = "2025-09-11T17:44:58.899Z" }, + { url = "https://files.pythonhosted.org/packages/97/30/2f9a5243008f76dfc5dee9a53dfb939d9b31e16ce4bd4f2e628bfc5d89d2/scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779", size = 26448374, upload-time = "2025-09-11T17:45:03.45Z" }, +] + +[[package]] +name = "semantic-version" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" }, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394, upload-time = "2024-04-07T00:01:09.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072, upload-time = "2024-04-07T00:01:07.438Z" }, +] + +[[package]] +name = "sentencepiece" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/15/2e7a025fc62d764b151ae6d0f2a92f8081755ebe8d4a64099accc6f77ba6/sentencepiece-0.2.1.tar.gz", hash = "sha256:8138cec27c2f2282f4a34d9a016e3374cd40e5c6e9cb335063db66a0a3b71fad", size = 3228515, upload-time = "2025-08-12T07:00:51.718Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/4a/85fbe1706d4d04a7e826b53f327c4b80f849cf1c7b7c5e31a20a97d8f28b/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dcd8161eee7b41aae57ded06272905dbd680a0a04b91edd0f64790c796b2f706", size = 1943150, upload-time = "2025-08-12T06:59:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/c2/83/4cfb393e287509fc2155480b9d184706ef8d9fa8cbf5505d02a5792bf220/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c6c8f42949f419ff8c7e9960dbadcfbc982d7b5efc2f6748210d3dd53a7de062", size = 1325651, upload-time = "2025-08-12T06:59:55.073Z" }, + { url = "https://files.pythonhosted.org/packages/8d/de/5a007fb53b1ab0aafc69d11a5a3dd72a289d5a3e78dcf2c3a3d9b14ffe93/sentencepiece-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:097f3394e99456e9e4efba1737c3749d7e23563dd1588ce71a3d007f25475fff", size = 1253641, upload-time = "2025-08-12T06:59:56.562Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d2/f552be5928105588f4f4d66ee37dd4c61460d8097e62d0e2e0eec41bc61d/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7b670879c370d350557edabadbad1f6561a9e6968126e6debca4029e5547820", size = 1316271, upload-time = "2025-08-12T06:59:58.109Z" }, + { url = "https://files.pythonhosted.org/packages/96/df/0cfe748ace5485be740fed9476dee7877f109da32ed0d280312c94ec259f/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7f0fd2f2693309e6628aeeb2e2faf6edd221134dfccac3308ca0de01f8dab47", size = 1387882, upload-time = "2025-08-12T07:00:00.701Z" }, + { url = "https://files.pythonhosted.org/packages/ac/dd/f7774d42a881ced8e1739f393ab1e82ece39fc9abd4779e28050c2e975b5/sentencepiece-0.2.1-cp313-cp313-win32.whl", hash = "sha256:92b3816aa2339355fda2c8c4e021a5de92180b00aaccaf5e2808972e77a4b22f", size = 999541, upload-time = "2025-08-12T07:00:02.709Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e9/932b9eae6fd7019548321eee1ab8d5e3b3d1294df9d9a0c9ac517c7b636d/sentencepiece-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:10ed3dab2044c47f7a2e7b4969b0c430420cdd45735d78c8f853191fa0e3148b", size = 1054669, upload-time = "2025-08-12T07:00:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/c9/3a/76488a00ea7d6931689cda28726a1447d66bf1a4837943489314593d5596/sentencepiece-0.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac650534e2251083c5f75dde4ff28896ce7c8904133dc8fef42780f4d5588fcd", size = 1033922, upload-time = "2025-08-12T07:00:06.496Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b6/08fe2ce819e02ccb0296f4843e3f195764ce9829cbda61b7513f29b95718/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8dd4b477a7b069648d19363aad0cab9bad2f4e83b2d179be668efa672500dc94", size = 1946052, upload-time = "2025-08-12T07:00:08.136Z" }, + { url = "https://files.pythonhosted.org/packages/ab/d9/1ea0e740591ff4c6fc2b6eb1d7510d02f3fb885093f19b2f3abd1363b402/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c0f672da370cc490e4c59d89e12289778310a0e71d176c541e4834759e1ae07", size = 1327408, upload-time = "2025-08-12T07:00:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/99/7e/1fb26e8a21613f6200e1ab88824d5d203714162cf2883248b517deb500b7/sentencepiece-0.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad8493bea8432dae8d6830365352350f3b4144415a1d09c4c8cb8d30cf3b6c3c", size = 1254857, upload-time = "2025-08-12T07:00:11.021Z" }, + { url = "https://files.pythonhosted.org/packages/bc/85/c72fd1f3c7a6010544d6ae07f8ddb38b5e2a7e33bd4318f87266c0bbafbf/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b81a24733726e3678d2db63619acc5a8dccd074f7aa7a54ecd5ca33ca6d2d596", size = 1315722, upload-time = "2025-08-12T07:00:12.989Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e8/661e5bd82a8aa641fd6c1020bd0e890ef73230a2b7215ddf9c8cd8e941c2/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a81799d0a68d618e89063fb423c3001a034c893069135ffe51fee439ae474d6", size = 1387452, upload-time = "2025-08-12T07:00:15.088Z" }, + { url = "https://files.pythonhosted.org/packages/99/5e/ae66c361023a470afcbc1fbb8da722c72ea678a2fcd9a18f1a12598c7501/sentencepiece-0.2.1-cp313-cp313t-win32.whl", hash = "sha256:89a3ea015517c42c0341d0d962f3e6aaf2cf10d71b1932d475c44ba48d00aa2b", size = 1002501, upload-time = "2025-08-12T07:00:16.966Z" }, + { url = "https://files.pythonhosted.org/packages/c1/03/d332828c4ff764e16c1b56c2c8f9a33488bbe796b53fb6b9c4205ddbf167/sentencepiece-0.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:33f068c9382dc2e7c228eedfd8163b52baa86bb92f50d0488bf2b7da7032e484", size = 1057555, upload-time = "2025-08-12T07:00:18.573Z" }, + { url = "https://files.pythonhosted.org/packages/88/14/5aee0bf0864df9bd82bd59e7711362908e4935e3f9cdc1f57246b5d5c9b9/sentencepiece-0.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:b3616ad246f360e52c85781e47682d31abfb6554c779e42b65333d4b5f44ecc0", size = 1036042, upload-time = "2025-08-12T07:00:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/24/9c/89eb8b2052f720a612478baf11c8227dcf1dc28cd4ea4c0c19506b5af2a2/sentencepiece-0.2.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5d0350b686c320068702116276cfb26c066dc7e65cfef173980b11bb4d606719", size = 1943147, upload-time = "2025-08-12T07:00:21.809Z" }, + { url = "https://files.pythonhosted.org/packages/82/0b/a1432bc87f97c2ace36386ca23e8bd3b91fb40581b5e6148d24b24186419/sentencepiece-0.2.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c7f54a31cde6fa5cb030370566f68152a742f433f8d2be458463d06c208aef33", size = 1325624, upload-time = "2025-08-12T07:00:23.289Z" }, + { url = "https://files.pythonhosted.org/packages/ea/99/bbe054ebb5a5039457c590e0a4156ed073fb0fe9ce4f7523404dd5b37463/sentencepiece-0.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c83b85ab2d6576607f31df77ff86f28182be4a8de6d175d2c33ca609925f5da1", size = 1253670, upload-time = "2025-08-12T07:00:24.69Z" }, + { url = "https://files.pythonhosted.org/packages/19/ad/d5c7075f701bd97971d7c2ac2904f227566f51ef0838dfbdfdccb58cd212/sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1855f57db07b51fb51ed6c9c452f570624d2b169b36f0f79ef71a6e6c618cd8b", size = 1316247, upload-time = "2025-08-12T07:00:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/fb/03/35fbe5f3d9a7435eebd0b473e09584bd3cc354ce118b960445b060d33781/sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01e6912125cb45d3792f530a4d38f8e21bf884d6b4d4ade1b2de5cf7a8d2a52b", size = 1387894, upload-time = "2025-08-12T07:00:28.339Z" }, + { url = "https://files.pythonhosted.org/packages/dc/aa/956ef729aafb6c8f9c443104c9636489093bb5c61d6b90fc27aa1a865574/sentencepiece-0.2.1-cp314-cp314-win32.whl", hash = "sha256:c415c9de1447e0a74ae3fdb2e52f967cb544113a3a5ce3a194df185cbc1f962f", size = 1096698, upload-time = "2025-08-12T07:00:29.764Z" }, + { url = "https://files.pythonhosted.org/packages/b8/cb/fe400d8836952cc535c81a0ce47dc6875160e5fedb71d2d9ff0e9894c2a6/sentencepiece-0.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:881b2e44b14fc19feade3cbed314be37de639fc415375cefaa5bc81a4be137fd", size = 1155115, upload-time = "2025-08-12T07:00:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/32/89/047921cf70f36c7b6b6390876b2399b3633ab73b8d0cb857e5a964238941/sentencepiece-0.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:2005242a16d2dc3ac5fe18aa7667549134d37854823df4c4db244752453b78a8", size = 1133890, upload-time = "2025-08-12T07:00:34.763Z" }, + { url = "https://files.pythonhosted.org/packages/a1/11/5b414b9fae6255b5fb1e22e2ed3dc3a72d3a694e5703910e640ac78346bb/sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a19adcec27c524cb7069a1c741060add95f942d1cbf7ad0d104dffa0a7d28a2b", size = 1946081, upload-time = "2025-08-12T07:00:36.97Z" }, + { url = "https://files.pythonhosted.org/packages/77/eb/7a5682bb25824db8545f8e5662e7f3e32d72a508fdce086029d89695106b/sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e37e4b4c4a11662b5db521def4e44d4d30ae69a1743241412a93ae40fdcab4bb", size = 1327406, upload-time = "2025-08-12T07:00:38.669Z" }, + { url = "https://files.pythonhosted.org/packages/03/b0/811dae8fb9f2784e138785d481469788f2e0d0c109c5737372454415f55f/sentencepiece-0.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:477c81505db072b3ab627e7eab972ea1025331bd3a92bacbf798df2b75ea86ec", size = 1254846, upload-time = "2025-08-12T07:00:40.611Z" }, + { url = "https://files.pythonhosted.org/packages/ef/23/195b2e7ec85ebb6a547969f60b723c7aca5a75800ece6cc3f41da872d14e/sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:010f025a544ef770bb395091d57cb94deb9652d8972e0d09f71d85d5a0816c8c", size = 1315721, upload-time = "2025-08-12T07:00:42.914Z" }, + { url = "https://files.pythonhosted.org/packages/7e/aa/553dbe4178b5f23eb28e59393dddd64186178b56b81d9b8d5c3ff1c28395/sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:733e59ff1794d26db706cd41fc2d7ca5f6c64a820709cb801dc0ea31780d64ab", size = 1387458, upload-time = "2025-08-12T07:00:44.56Z" }, + { url = "https://files.pythonhosted.org/packages/66/7c/08ff0012507297a4dd74a5420fdc0eb9e3e80f4e88cab1538d7f28db303d/sentencepiece-0.2.1-cp314-cp314t-win32.whl", hash = "sha256:d3233770f78e637dc8b1fda2cd7c3b99ec77e7505041934188a4e7fe751de3b0", size = 1099765, upload-time = "2025-08-12T07:00:46.058Z" }, + { url = "https://files.pythonhosted.org/packages/91/d5/2a69e1ce15881beb9ddfc7e3f998322f5cedcd5e4d244cb74dade9441663/sentencepiece-0.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e4366c97b68218fd30ea72d70c525e6e78a6c0a88650f57ac4c43c63b234a9d", size = 1157807, upload-time = "2025-08-12T07:00:47.673Z" }, + { url = "https://files.pythonhosted.org/packages/f3/16/54f611fcfc2d1c46cbe3ec4169780b2cfa7cf63708ef2b71611136db7513/sentencepiece-0.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:105e36e75cbac1292642045458e8da677b2342dcd33df503e640f0b457cb6751", size = 1136264, upload-time = "2025-08-12T07:00:49.485Z" }, +] + +[[package]] +name = "sentry-sdk" +version = "2.39.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/72/43294fa4bdd75c51610b5104a3ff834459ba653abb415150aa7826a249dd/sentry_sdk-2.39.0.tar.gz", hash = "sha256:8c185854d111f47f329ab6bc35993f28f7a6b7114db64aa426b326998cfa14e9", size = 348556, upload-time = "2025-09-25T09:15:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/44/4356cc64246ba7b2b920f7c97a85c3c52748e213e250b512ee8152eb559d/sentry_sdk-2.39.0-py2.py3-none-any.whl", hash = "sha256:ba655ca5e57b41569b18e2a5552cb3375209760a5d332cdd87c6c3f28f729602", size = 370851, upload-time = "2025-09-25T09:15:36.35Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "simplejson" +version = "3.20.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f4/a1ac5ed32f7ed9a088d62a59d410d4c204b3b3815722e2ccfb491fa8251b/simplejson-3.20.2.tar.gz", hash = "sha256:5fe7a6ce14d1c300d80d08695b7f7e633de6cd72c80644021874d985b3393649", size = 85784, upload-time = "2025-09-26T16:29:36.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/9e/f326d43f6bf47f4e7704a4426c36e044c6bedfd24e072fb8e27589a373a5/simplejson-3.20.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90d311ba8fcd733a3677e0be21804827226a57144130ba01c3c6a325e887dd86", size = 93530, upload-time = "2025-09-26T16:28:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/35/28/5a4b8f3483fbfb68f3f460bc002cef3a5735ef30950e7c4adce9c8da15c7/simplejson-3.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:feed6806f614bdf7f5cb6d0123cb0c1c5f40407ef103aa935cffaa694e2e0c74", size = 75846, upload-time = "2025-09-26T16:28:19.12Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4d/30dfef83b9ac48afae1cf1ab19c2867e27b8d22b5d9f8ca7ce5a0a157d8c/simplejson-3.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b1d8d7c3e1a205c49e1aee6ba907dcb8ccea83651e6c3e2cb2062f1e52b0726", size = 75661, upload-time = "2025-09-26T16:28:20.219Z" }, + { url = "https://files.pythonhosted.org/packages/09/1d/171009bd35c7099d72ef6afd4bb13527bab469965c968a17d69a203d62a6/simplejson-3.20.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:552f55745044a24c3cb7ec67e54234be56d5d6d0e054f2e4cf4fb3e297429be5", size = 150579, upload-time = "2025-09-26T16:28:21.337Z" }, + { url = "https://files.pythonhosted.org/packages/61/ae/229bbcf90a702adc6bfa476e9f0a37e21d8c58e1059043038797cbe75b8c/simplejson-3.20.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2da97ac65165d66b0570c9e545786f0ac7b5de5854d3711a16cacbcaa8c472d", size = 158797, upload-time = "2025-09-26T16:28:22.53Z" }, + { url = "https://files.pythonhosted.org/packages/90/c5/fefc0ac6b86b9108e302e0af1cf57518f46da0baedd60a12170791d56959/simplejson-3.20.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f59a12966daa356bf68927fca5a67bebac0033cd18b96de9c2d426cd11756cd0", size = 148851, upload-time = "2025-09-26T16:28:23.733Z" }, + { url = "https://files.pythonhosted.org/packages/43/f1/b392952200f3393bb06fbc4dd975fc63a6843261705839355560b7264eb2/simplejson-3.20.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133ae2098a8e162c71da97cdab1f383afdd91373b7ff5fe65169b04167da976b", size = 152598, upload-time = "2025-09-26T16:28:24.962Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b4/d6b7279e52a3e9c0fa8c032ce6164e593e8d9cf390698ee981ed0864291b/simplejson-3.20.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7977640af7b7d5e6a852d26622057d428706a550f7f5083e7c4dd010a84d941f", size = 150498, upload-time = "2025-09-26T16:28:26.114Z" }, + { url = "https://files.pythonhosted.org/packages/62/22/ec2490dd859224326d10c2fac1353e8ad5c84121be4837a6dd6638ba4345/simplejson-3.20.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b530ad6d55e71fa9e93e1109cf8182f427a6355848a4ffa09f69cc44e1512522", size = 152129, upload-time = "2025-09-26T16:28:27.552Z" }, + { url = "https://files.pythonhosted.org/packages/33/ce/b60214d013e93dd9e5a705dcb2b88b6c72bada442a97f79828332217f3eb/simplejson-3.20.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bd96a7d981bf64f0e42345584768da4435c05b24fd3c364663f5fbc8fabf82e3", size = 159359, upload-time = "2025-09-26T16:28:28.667Z" }, + { url = "https://files.pythonhosted.org/packages/99/21/603709455827cdf5b9d83abe726343f542491ca8dc6a2528eb08de0cf034/simplejson-3.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f28ee755fadb426ba2e464d6fcf25d3f152a05eb6b38e0b4f790352f5540c769", size = 154717, upload-time = "2025-09-26T16:28:30.288Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f9/dc7f7a4bac16cf7eb55a4df03ad93190e11826d2a8950052949d3dfc11e2/simplejson-3.20.2-cp313-cp313-win32.whl", hash = "sha256:472785b52e48e3eed9b78b95e26a256f59bb1ee38339be3075dad799e2e1e661", size = 74289, upload-time = "2025-09-26T16:28:31.809Z" }, + { url = "https://files.pythonhosted.org/packages/87/10/d42ad61230436735c68af1120622b28a782877146a83d714da7b6a2a1c4e/simplejson-3.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:a1a85013eb33e4820286139540accbe2c98d2da894b2dcefd280209db508e608", size = 75972, upload-time = "2025-09-26T16:28:32.883Z" }, + { url = "https://files.pythonhosted.org/packages/05/5b/83e1ff87eb60ca706972f7e02e15c0b33396e7bdbd080069a5d1b53cf0d8/simplejson-3.20.2-py3-none-any.whl", hash = "sha256:3b6bb7fb96efd673eac2e4235200bfffdc2353ad12c54117e1e4e2fc485ac017", size = 57309, upload-time = "2025-09-26T16:29:35.312Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/bc/d59b5d97d27229b0e009bd9098cd81af71c2fa5549c580a0a67b9bed0496/sqlalchemy-2.0.43.tar.gz", hash = "sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417", size = 9762949, upload-time = "2025-08-11T14:24:58.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/1c/a7260bd47a6fae7e03768bf66451437b36451143f36b285522b865987ced/sqlalchemy-2.0.43-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3", size = 2130598, upload-time = "2025-08-11T15:51:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/8e/84/8a337454e82388283830b3586ad7847aa9c76fdd4f1df09cdd1f94591873/sqlalchemy-2.0.43-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa", size = 2118415, upload-time = "2025-08-11T15:51:17.256Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ff/22ab2328148492c4d71899d62a0e65370ea66c877aea017a244a35733685/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9", size = 3248707, upload-time = "2025-08-11T15:52:38.444Z" }, + { url = "https://files.pythonhosted.org/packages/dc/29/11ae2c2b981de60187f7cbc84277d9d21f101093d1b2e945c63774477aba/sqlalchemy-2.0.43-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f", size = 3253602, upload-time = "2025-08-11T15:56:37.348Z" }, + { url = "https://files.pythonhosted.org/packages/b8/61/987b6c23b12c56d2be451bc70900f67dd7d989d52b1ee64f239cf19aec69/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738", size = 3183248, upload-time = "2025-08-11T15:52:39.865Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/29d216002d4593c2ce1c0ec2cec46dda77bfbcd221e24caa6e85eff53d89/sqlalchemy-2.0.43-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164", size = 3219363, upload-time = "2025-08-11T15:56:39.11Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e4/bd78b01919c524f190b4905d47e7630bf4130b9f48fd971ae1c6225b6f6a/sqlalchemy-2.0.43-cp313-cp313-win32.whl", hash = "sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d", size = 2096718, upload-time = "2025-08-11T15:55:05.349Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a5/ca2f07a2a201f9497de1928f787926613db6307992fe5cda97624eb07c2f/sqlalchemy-2.0.43-cp313-cp313-win_amd64.whl", hash = "sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197", size = 2123200, upload-time = "2025-08-11T15:55:07.932Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d9/13bdde6521f322861fab67473cec4b1cc8999f3871953531cf61945fad92/sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc", size = 1924759, upload-time = "2025-08-11T15:39:53.024Z" }, +] + +[package.optional-dependencies] +asyncio = [ + { name = "greenlet" }, +] + +[[package]] +name = "sqlparse" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999, upload-time = "2024-12-10T12:05:30.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/6f/22ed6e33f8a9e76ca0a412405f31abb844b779d52c5f96660766edcd737c/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a", size = 20985, upload-time = "2025-07-27T09:07:44.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/10/c78f463b4ef22eef8491f218f692be838282cd65480f6e423d7730dfd1fb/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a", size = 11297, upload-time = "2025-07-27T09:07:43.268Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "starlette" +version = "0.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tensorboard" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "grpcio" }, + { name = "markdown" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "protobuf" }, + { name = "setuptools" }, + { name = "tensorboard-data-server" }, + { name = "werkzeug" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/d9/a5db55f88f258ac669a92858b70a714bbbd5acd993820b41ec4a96a4d77f/tensorboard-2.20.0-py3-none-any.whl", hash = "sha256:9dc9f978cb84c0723acf9a345d96c184f0293d18f166bb8d59ee098e6cfaaba6", size = 5525680, upload-time = "2025-07-17T19:20:49.638Z" }, +] + +[[package]] +name = "tensorboard-data-server" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/13/e503968fefabd4c6b2650af21e110aa8466fe21432cd7c43a84577a89438/tensorboard_data_server-0.7.2-py3-none-any.whl", hash = "sha256:7e0610d205889588983836ec05dc098e80f97b7e7bbff7e994ebb78f578d0ddb", size = 2356, upload-time = "2023-10-23T21:23:32.16Z" }, + { url = "https://files.pythonhosted.org/packages/b7/85/dabeaf902892922777492e1d253bb7e1264cadce3cea932f7ff599e53fea/tensorboard_data_server-0.7.2-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:9fe5d24221b29625dbc7328b0436ca7fc1c23de4acf4d272f1180856e32f9f60", size = 4823598, upload-time = "2023-10-23T21:23:33.714Z" }, + { url = "https://files.pythonhosted.org/packages/73/c6/825dab04195756cf8ff2e12698f22513b3db2f64925bdd41671bfb33aaa5/tensorboard_data_server-0.7.2-py3-none-manylinux_2_31_x86_64.whl", hash = "sha256:ef687163c24185ae9754ed5650eb5bc4d84ff257aabdc33f0cc6f74d8ba54530", size = 6590363, upload-time = "2023-10-23T21:23:35.583Z" }, +] + +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "os_name != 'nt'" }, + { name = "pywinpty", marker = "os_name == 'nt' and sys_platform != 'linux'" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" }, +] + +[[package]] +name = "testfixtures" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/25/d7e9d05f87e2ab84657a0dfb1f24fc295d542ac2eb221531d976ea4aa1ff/testfixtures-8.3.0.tar.gz", hash = "sha256:d4c0b84af2f267610f908009b50d6f983a4e58ade22c67bab6787b5a402d59c0", size = 137420, upload-time = "2024-06-07T18:12:27.484Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/4e/699671ba484b94bda0959b281ff59b24f728263befd13e060fa038ce3bc8/testfixtures-8.3.0-py3-none-any.whl", hash = "sha256:3d1e0e0005c4d6ac2a2ab27916704c6471047f0d2f78f2e54adf20abdacc7b10", size = 105085, upload-time = "2024-06-07T18:12:23.298Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tifffile" +version = "2025.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/13/1dcd158fdcd99d697ba60af153db5034613011516fc0b37a4ca0b7e61b66/tifffile-2025.10.4.tar.gz", hash = "sha256:2e437c16ab211be5bcdc79f71b4907359115f1f83b5d919e7c297c29725d3e38", size = 369486, upload-time = "2025-10-06T00:14:00.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/ff/e2f8dae90fb642b4b6f24464a2f96a3dc3b69151c51f7db24433be0f3f56/tifffile-2025.10.4-py3-none-any.whl", hash = "sha256:7687d691e49026053181470cec70fa9250e3a586b2041041297e38b10bbd34e1", size = 230357, upload-time = "2025-10-06T00:13:58.806Z" }, +] + +[[package]] +name = "timm" +version = "1.0.20" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch" }, + { name = "torchvision" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/ba/6f5d96622a4a9fc315da53f58b3ca224c66015efe40aa191df0d523ede7c/timm-1.0.20.tar.gz", hash = "sha256:7468d32a410c359181c1ef961f49c7e213286e0c342bfb898b99534a4221fc54", size = 2360052, upload-time = "2025-09-21T17:26:35.492Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/74/5573615570bf010f788e977ac57c4b49db0aaf6d634134f6a9212d8dcdfd/timm-1.0.20-py3-none-any.whl", hash = "sha256:f6e62f780358476691996c47aa49de87b95cc507edf923c3042f74a07e45b7fe", size = 2504047, upload-time = "2025-09-21T17:26:33.487Z" }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/46/fb6854cec3278fbfa4a75b50232c77622bc517ac886156e6afbfa4d8fc6e/tokenizers-0.22.1.tar.gz", hash = "sha256:61de6522785310a309b3407bac22d99c4db5dba349935e99e4d15ea2226af2d9", size = 363123, upload-time = "2025-09-19T09:49:23.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/33/f4b2d94ada7ab297328fc671fed209368ddb82f965ec2224eb1892674c3a/tokenizers-0.22.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:59fdb013df17455e5f950b4b834a7b3ee2e0271e6378ccb33aa74d178b513c73", size = 3069318, upload-time = "2025-09-19T09:49:11.848Z" }, + { url = "https://files.pythonhosted.org/packages/1c/58/2aa8c874d02b974990e89ff95826a4852a8b2a273c7d1b4411cdd45a4565/tokenizers-0.22.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8d4e484f7b0827021ac5f9f71d4794aaef62b979ab7608593da22b1d2e3c4edc", size = 2926478, upload-time = "2025-09-19T09:49:09.759Z" }, + { url = "https://files.pythonhosted.org/packages/1e/3b/55e64befa1e7bfea963cf4b787b2cea1011362c4193f5477047532ce127e/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a", size = 3256994, upload-time = "2025-09-19T09:48:56.701Z" }, + { url = "https://files.pythonhosted.org/packages/71/0b/fbfecf42f67d9b7b80fde4aabb2b3110a97fac6585c9470b5bff103a80cb/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38201f15cdb1f8a6843e6563e6e79f4abd053394992b9bbdf5213ea3469b4ae7", size = 3153141, upload-time = "2025-09-19T09:48:59.749Z" }, + { url = "https://files.pythonhosted.org/packages/17/a9/b38f4e74e0817af8f8ef925507c63c6ae8171e3c4cb2d5d4624bf58fca69/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1cbe5454c9a15df1b3443c726063d930c16f047a3cc724b9e6e1a91140e5a21", size = 3508049, upload-time = "2025-09-19T09:49:05.868Z" }, + { url = "https://files.pythonhosted.org/packages/d2/48/dd2b3dac46bb9134a88e35d72e1aa4869579eacc1a27238f1577270773ff/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7d094ae6312d69cc2a872b54b91b309f4f6fbce871ef28eb27b52a98e4d0214", size = 3710730, upload-time = "2025-09-19T09:49:01.832Z" }, + { url = "https://files.pythonhosted.org/packages/93/0e/ccabc8d16ae4ba84a55d41345207c1e2ea88784651a5a487547d80851398/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afd7594a56656ace95cdd6df4cca2e4059d294c5cfb1679c57824b605556cb2f", size = 3412560, upload-time = "2025-09-19T09:49:03.867Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c6/dc3a0db5a6766416c32c034286d7c2d406da1f498e4de04ab1b8959edd00/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ef6063d7a84994129732b47e7915e8710f27f99f3a3260b8a38fc7ccd083f4", size = 3250221, upload-time = "2025-09-19T09:49:07.664Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a6/2c8486eef79671601ff57b093889a345dd3d576713ef047776015dc66de7/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba0a64f450b9ef412c98f6bcd2a50c6df6e2443b560024a09fa6a03189726879", size = 9345569, upload-time = "2025-09-19T09:49:14.214Z" }, + { url = "https://files.pythonhosted.org/packages/6b/16/32ce667f14c35537f5f605fe9bea3e415ea1b0a646389d2295ec348d5657/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:331d6d149fa9c7d632cde4490fb8bbb12337fa3a0232e77892be656464f4b446", size = 9271599, upload-time = "2025-09-19T09:49:16.639Z" }, + { url = "https://files.pythonhosted.org/packages/51/7c/a5f7898a3f6baa3fc2685c705e04c98c1094c523051c805cdd9306b8f87e/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:607989f2ea68a46cb1dfbaf3e3aabdf3f21d8748312dbeb6263d1b3b66c5010a", size = 9533862, upload-time = "2025-09-19T09:49:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/36/65/7e75caea90bc73c1dd8d40438adf1a7bc26af3b8d0a6705ea190462506e1/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a0f307d490295717726598ef6fa4f24af9d484809223bbc253b201c740a06390", size = 9681250, upload-time = "2025-09-19T09:49:21.501Z" }, + { url = "https://files.pythonhosted.org/packages/30/2c/959dddef581b46e6209da82df3b78471e96260e2bc463f89d23b1bf0e52a/tokenizers-0.22.1-cp39-abi3-win32.whl", hash = "sha256:b5120eed1442765cd90b903bb6cfef781fd8fe64e34ccaecbae4c619b7b12a82", size = 2472003, upload-time = "2025-09-19T09:49:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/e33a8c93907b631a99377ef4c5f817ab453d0b34f93529421f42ff559671/tokenizers-0.22.1-cp39-abi3-win_amd64.whl", hash = "sha256:65fd6e3fb11ca1e78a6a93602490f134d1fdeb13bcef99389d5102ea318ed138", size = 2674684, upload-time = "2025-09-19T09:49:24.953Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + +[[package]] +name = "torch" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/4e/469ced5a0603245d6a19a556e9053300033f9c5baccf43a3d25ba73e189e/torch-2.8.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2b2f96814e0345f5a5aed9bf9734efa913678ed19caf6dc2cddb7930672d6128", size = 101936856, upload-time = "2025-08-06T14:54:01.526Z" }, + { url = "https://files.pythonhosted.org/packages/16/82/3948e54c01b2109238357c6f86242e6ecbf0c63a1af46906772902f82057/torch-2.8.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:65616ca8ec6f43245e1f5f296603e33923f4c30f93d65e103d9e50c25b35150b", size = 887922844, upload-time = "2025-08-06T14:55:50.78Z" }, + { url = "https://files.pythonhosted.org/packages/e3/54/941ea0a860f2717d86a811adf0c2cd01b3983bdd460d0803053c4e0b8649/torch-2.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:659df54119ae03e83a800addc125856effda88b016dfc54d9f65215c3975be16", size = 241330968, upload-time = "2025-08-06T14:54:45.293Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/8b7b13bba430f5e21d77708b616f767683629fc4f8037564a177d20f90ed/torch-2.8.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:1a62a1ec4b0498930e2543535cf70b1bef8c777713de7ceb84cd79115f553767", size = 73915128, upload-time = "2025-08-06T14:54:34.769Z" }, + { url = "https://files.pythonhosted.org/packages/15/0e/8a800e093b7f7430dbaefa80075aee9158ec22e4c4fc3c1a66e4fb96cb4f/torch-2.8.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:83c13411a26fac3d101fe8035a6b0476ae606deb8688e904e796a3534c197def", size = 102020139, upload-time = "2025-08-06T14:54:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/4a/15/5e488ca0bc6162c86a33b58642bc577c84ded17c7b72d97e49b5833e2d73/torch-2.8.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:8f0a9d617a66509ded240add3754e462430a6c1fc5589f86c17b433dd808f97a", size = 887990692, upload-time = "2025-08-06T14:56:18.286Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a8/6a04e4b54472fc5dba7ca2341ab219e529f3c07b6941059fbf18dccac31f/torch-2.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a7242b86f42be98ac674b88a4988643b9bc6145437ec8f048fea23f72feb5eca", size = 241603453, upload-time = "2025-08-06T14:55:22.945Z" }, + { url = "https://files.pythonhosted.org/packages/04/6e/650bb7f28f771af0cb791b02348db8b7f5f64f40f6829ee82aa6ce99aabe/torch-2.8.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:7b677e17f5a3e69fdef7eb3b9da72622f8d322692930297e4ccb52fefc6c8211", size = 73632395, upload-time = "2025-08-06T14:55:28.645Z" }, +] + +[[package]] +name = "torchmetrics" +version = "1.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning-utilities" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/2e/48a887a59ecc4a10ce9e8b35b3e3c5cef29d902c4eac143378526e7485cb/torchmetrics-1.8.2.tar.gz", hash = "sha256:cf64a901036bf107f17a524009eea7781c9c5315d130713aeca5747a686fe7a5", size = 580679, upload-time = "2025-09-03T14:00:54.077Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/21/aa0f434434c48490f91b65962b1ce863fdcce63febc166ca9fe9d706c2b6/torchmetrics-1.8.2-py3-none-any.whl", hash = "sha256:08382fd96b923e39e904c4d570f3d49e2cc71ccabd2a94e0f895d1f0dac86242", size = 983161, upload-time = "2025-09-03T14:00:51.921Z" }, +] + +[[package]] +name = "torchvision" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/37/45a5b9407a7900f71d61b2b2f62db4b7c632debca397f205fdcacb502780/torchvision-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1c37e325e09a184b730c3ef51424f383ec5745378dc0eca244520aca29722600", size = 1856886, upload-time = "2025-08-06T14:58:05.491Z" }, + { url = "https://files.pythonhosted.org/packages/ac/da/a06c60fc84fc849377cf035d3b3e9a1c896d52dbad493b963c0f1cdd74d0/torchvision-0.23.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2f7fd6c15f3697e80627b77934f77705f3bc0e98278b989b2655de01f6903e1d", size = 2353112, upload-time = "2025-08-06T14:58:26.265Z" }, + { url = "https://files.pythonhosted.org/packages/a0/27/5ce65ba5c9d3b7d2ccdd79892ab86a2f87ac2ca6638f04bb0280321f1a9c/torchvision-0.23.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a76fafe113b2977be3a21bf78f115438c1f88631d7a87203acb3dd6ae55889e6", size = 8627658, upload-time = "2025-08-06T14:58:15.999Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e4/028a27b60aa578a2fa99d9d7334ff1871bb17008693ea055a2fdee96da0d/torchvision-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:07d069cb29691ff566e3b7f11f20d91044f079e1dbdc9d72e0655899a9b06938", size = 1600749, upload-time = "2025-08-06T14:58:10.719Z" }, + { url = "https://files.pythonhosted.org/packages/05/35/72f91ad9ac7c19a849dedf083d347dc1123f0adeb401f53974f84f1d04c8/torchvision-0.23.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2df618e1143805a7673aaf82cb5720dd9112d4e771983156aaf2ffff692eebf9", size = 2047192, upload-time = "2025-08-06T14:58:11.813Z" }, + { url = "https://files.pythonhosted.org/packages/1d/9d/406cea60a9eb9882145bcd62a184ee61e823e8e1d550cdc3c3ea866a9445/torchvision-0.23.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2a3299d2b1d5a7aed2d3b6ffb69c672ca8830671967eb1cee1497bacd82fe47b", size = 2359295, upload-time = "2025-08-06T14:58:17.469Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f4/34662f71a70fa1e59de99772142f22257ca750de05ccb400b8d2e3809c1d/torchvision-0.23.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:76bc4c0b63d5114aa81281390f8472a12a6a35ce9906e67ea6044e5af4cab60c", size = 8800474, upload-time = "2025-08-06T14:58:22.53Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f5/b5a2d841a8d228b5dbda6d524704408e19e7ca6b7bb0f24490e081da1fa1/torchvision-0.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b9e2dabf0da9c8aa9ea241afb63a8f3e98489e706b22ac3f30416a1be377153b", size = 1527667, upload-time = "2025-08-06T14:58:14.446Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" }, + { url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" }, + { url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" }, + { url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" }, + { url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "trackio" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gradio", extra = ["oauth"] }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "orjson" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "plotly" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/37/e80793c36f7144ebc9efbf66facc0ece0393f45c31ea847cab41aa717c15/trackio-0.6.0-py3-none-any.whl", hash = "sha256:b03c6397e41a2d01cffdfeea2ab27c76a6d687b937889deccf24187c8fabe7f0", size = 868362, upload-time = "2025-10-15T19:31:51.632Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "transformers" +version = "4.57.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/5c/a22c39dac2687f3fe2a6b97e2c1ae516e91cd4d3976a7a2b7c24ff2fae48/transformers-4.57.0.tar.gz", hash = "sha256:d045753f3d93f9216e693cdb168698dfd2e9d3aad1bb72579a5d60ebf1545a8b", size = 10142956, upload-time = "2025-10-03T17:03:47.177Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/2b/4d2708ac1ff5cd708b6548f4c5812d0ae40d1c28591c4c1c762b6dbdef2d/transformers-4.57.0-py3-none-any.whl", hash = "sha256:9d7c6d098c026e40d897e017ed1f481ab803cbac041021dbc6ae6100e4949b55", size = 11990588, upload-time = "2025-10-03T17:03:43.629Z" }, +] + +[[package]] +name = "triton" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools", marker = "sys_platform == 'linux'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/7b/0a685684ed5322d2af0bddefed7906674f67974aa88b0fae6e82e3b766f6/triton-3.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00be2964616f4c619193cb0d1b29a99bd4b001d7dc333816073f92cf2a8ccdeb", size = 155569223, upload-time = "2025-07-30T19:58:44.017Z" }, + { url = "https://files.pythonhosted.org/packages/20/63/8cb444ad5cdb25d999b7d647abac25af0ee37d292afc009940c05b82dda0/triton-3.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7936b18a3499ed62059414d7df563e6c163c5e16c3773678a3ee3d417865035d", size = 155659780, upload-time = "2025-07-30T19:58:51.171Z" }, +] + +[[package]] +name = "typer" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/ca/950278884e2ca20547ff3eb109478c6baf6b8cf219318e6bc4f666fad8e8/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca", size = 104755, upload-time = "2025-09-23T09:47:48.256Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20250822" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/0a/775f8551665992204c756be326f3575abba58c4a3a52eef9909ef4536428/types_python_dateutil-2.9.0.20250822.tar.gz", hash = "sha256:84c92c34bd8e68b117bff742bc00b692a1e8531262d4507b33afcc9f7716cd53", size = 16084, upload-time = "2025-08-22T03:02:00.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/d9/a29dfa84363e88b053bf85a8b7f212a04f0d7343a4d24933baa45c06e08b/types_python_dateutil-2.9.0.20250822-py3-none-any.whl", hash = "sha256:849d52b737e10a6dc6621d2bd7940ec7c65fcb69e6aa2882acf4e56b2b508ddc", size = 17892, upload-time = "2025-08-22T03:01:59.436Z" }, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20250915" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/69/3c51b36d04da19b92f9e815be12753125bd8bc247ba0470a982e6979e71c/types_pyyaml-6.0.12.20250915.tar.gz", hash = "sha256:0f8b54a528c303f0e6f7165687dd33fafa81c807fcac23f632b63aa624ced1d3", size = 17522, upload-time = "2025-09-15T03:01:00.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/e0/1eed384f02555dde685fff1a1ac805c1c7dcb6dd019c916fe659b1c1f9ec/types_pyyaml-6.0.12.20250915-py3-none-any.whl", hash = "sha256:e7d4d9e064e89a3b3cae120b4990cd370874d2bf12fa5f46c97018dd5d3c9ab6", size = 20338, upload-time = "2025-09-15T03:00:59.218Z" }, +] + +[[package]] +name = "types-requests" +version = "2.32.4.20250913" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, +] + +[[package]] +name = "typeshed-client" +version = "2.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-resources" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/3e/4074d3505b4700a6bf13cb1bb2d1848bb8c78e902e3f9fe5916274c5d284/typeshed_client-2.8.2.tar.gz", hash = "sha256:9d8e29fb74574d87bf9a719f77131dc40f2aeea20e97d25d4a3dc2cc30debd31", size = 501617, upload-time = "2025-07-16T01:49:49.299Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/db/e7474719e90062df673057e865f94f67da2d0b4f671d8051020c74962c77/typeshed_client-2.8.2-py3-none-any.whl", hash = "sha256:4cf886d976c777689cd31889f13abf5bfb7797c82519b07e5969e541380c75ee", size = 760467, upload-time = "2025-07-16T01:49:47.758Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/57/1616c8274c3442d802621abf5deb230771c7a0fec9414cb6763900eb3868/uvicorn-0.37.0.tar.gz", hash = "sha256:4115c8add6d3fd536c8ee77f0e14a7fd2ebba939fed9b02583a97f80648f9e13", size = 80367, upload-time = "2025-09-23T13:33:47.486Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/cd/584a2ceb5532af99dd09e50919e3615ba99aa127e9850eafe5f31ddfdb9a/uvicorn-0.37.0-py3-none-any.whl", hash = "sha256:913b2b88672343739927ce381ff9e2ad62541f9f8289664fa1d1d3803fa2ce6c", size = 67976, upload-time = "2025-09-23T13:33:45.842Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, + { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, + { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, + { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, + { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, +] + +[[package]] +name = "virtualenv" +version = "20.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/14/37fcdba2808a6c615681cd216fecae00413c9dab44fb2e57805ecf3eaee3/virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a", size = 6003808, upload-time = "2025-08-13T14:24:07.464Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/06/04c8e804f813cf972e3262f3f8584c232de64f0cde9f703b46cf53a45090/virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", size = 5983279, upload-time = "2025-08-13T14:24:05.111Z" }, +] + +[[package]] +name = "waitress" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/cb/04ddb054f45faa306a230769e868c28b8065ea196891f09004ebace5b184/waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f", size = 179901, upload-time = "2024-11-16T20:02:35.195Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/57/a27182528c90ef38d82b636a11f606b0cbb0e17588ed205435f8affe3368/waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e", size = 56232, upload-time = "2024-11-16T20:02:33.858Z" }, +] + +[[package]] +name = "wandb" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "gitpython" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sentry-sdk" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/6f/be255b13157cab6c6670594171f59f67bd8a89f20d1978dc4eb892e2de27/wandb-0.22.1.tar.gz", hash = "sha256:6a1d668ecd6bd6531a73f6f7cfec0a93a08ef578c16ccf7167168c52cbf8cb12", size = 40246806, upload-time = "2025-09-29T17:15:55.207Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/73/de1d62301ef5d084160221637f34a821b7ed90b0769698b7b420686608ab/wandb-0.22.1-py3-none-macosx_12_0_arm64.whl", hash = "sha256:d862d0d28919556a5c32977138449812a2d127853e20b0deb39a5ab17700230f", size = 18370574, upload-time = "2025-09-29T17:15:18.236Z" }, + { url = "https://files.pythonhosted.org/packages/42/0e/4f60d9c7f1fa9d249dcbe70c6bad1573a9cfc070d00c3d8dbd62f715938d/wandb-0.22.1-py3-none-macosx_12_0_x86_64.whl", hash = "sha256:ce57213de331717270020f7e07b098a0ea37646550b63758eabf8cb05eeb066f", size = 19392851, upload-time = "2025-09-29T17:15:22.093Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8b/757ede4a581eece5e72ade51fd4b43cfedbd3e39b85fe29d0198bc98131b/wandb-0.22.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5a0ac652c23bf88e12bf0c04e911ff4f95696ac60a3612d81e54f1f8d89f3c5", size = 18171463, upload-time = "2025-09-29T17:15:24.588Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e6/275d60292183d4de89fc9053887192f978fd8612e55c8f7719aa5c81bbd1/wandb-0.22.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dc14dd06938c282900dd9f72cbaaed45368b0c6b9bc2ffd1f45d07eeb13095b", size = 19585538, upload-time = "2025-09-29T17:15:28.432Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5c/4199abb92d06de6ebd63ee33551ba0de6d91a814ac42e372dec6d8009ea0/wandb-0.22.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1b2d2b9d8d1ea8aea3c2cbf9de7696105432886ba9845c50e7cc71613aa6c8ef", size = 18210525, upload-time = "2025-09-29T17:15:33.459Z" }, + { url = "https://files.pythonhosted.org/packages/0d/00/a7719c048115825861a31435fa911887c9949b20096dbc7307e11b3c981b/wandb-0.22.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:051906d7f22bdf8c07c8837ffc6d9ae357d61dcd74cfb7d29fd0243e03da8f4a", size = 19680055, upload-time = "2025-09-29T17:15:38.521Z" }, + { url = "https://files.pythonhosted.org/packages/6f/5d/e6270557a315211e880c1efa9c1cab577f945d81168bc1c1187fd483f1bb/wandb-0.22.1-py3-none-win32.whl", hash = "sha256:b2df59bd70771329f27171f55d25d5557731bb0674d60db4735c173a8fb8076d", size = 18769036, upload-time = "2025-09-29T17:15:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/92/fe/34cdfd491ea6c89495794f361102b727b922adcc4f3eedb47c8aa16984c3/wandb-0.22.1-py3-none-win_amd64.whl", hash = "sha256:c1b442e6de805d78743321200a27099517509f9e4aa2e6d330211a4809f932d7", size = 18769038, upload-time = "2025-09-29T17:15:46.977Z" }, + { url = "https://files.pythonhosted.org/packages/1e/9e/fe95f5d48ff10215b7d7e67dc998cba3f660027829fac2a67c79ce89e985/wandb-0.22.1-py3-none-win_arm64.whl", hash = "sha256:52758008c9ef9e7201113af08d6015322a699ebe3497a6e6fc885b39f5652b4d", size = 17077774, upload-time = "2025-09-29T17:15:50.588Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, + { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, + { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, + { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, + { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, + { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, + { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, + { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, + { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, + { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, + { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, + { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, + { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, + { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, + { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, + { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, + { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, + { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, + { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, + { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, + { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, + { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, + { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, + { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, + { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, + { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, + { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, + { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, + { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/30/6b0809f4510673dc723187aeaf24c7f5459922d01e2f794277a3dfb90345/wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605", size = 102293, upload-time = "2025-09-22T16:29:53.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, +] + +[[package]] +name = "webcolors" +version = "24.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064, upload-time = "2024-11-11T07:43:24.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934, upload-time = "2024-11-11T07:43:22.529Z" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/af/d4502dc713b4ccea7175d764718d5183caf8d0867a4f0190d5d4a45cea49/werkzeug-3.1.1.tar.gz", hash = "sha256:8cd39dfbdfc1e051965f156163e2974e52c210f130810e9ad36858f0fd3edad4", size = 806453, upload-time = "2024-11-01T16:40:45.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/ea/c67e1dee1ba208ed22c06d1d547ae5e293374bfc43e0eb0ef5e262b68561/werkzeug-3.1.1-py3-none-any.whl", hash = "sha256:a71124d1ef06008baafa3d266c02f56e1836a5984afd6dd6c9230669d60d9fb5", size = 224371, upload-time = "2024-11-01T16:40:43.994Z" }, +] + +[[package]] +name = "widgetsnbextension" +version = "4.0.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/53/2e0253c5efd69c9656b1843892052a31c36d37ad42812b5da45c62191f7e/widgetsnbextension-4.0.14.tar.gz", hash = "sha256:a3629b04e3edb893212df862038c7232f62973373869db5084aed739b437b5af", size = 1097428, upload-time = "2025-04-10T13:01:25.628Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/51/5447876806d1088a0f8f71e16542bf350918128d0a69437df26047c8e46f/widgetsnbextension-4.0.14-py3-none-any.whl", hash = "sha256:4875a9eaf72fbf5079dc372a51a9f268fc38d46f767cbf85c43a36da5cb9b575", size = 2196503, upload-time = "2025-04-10T13:01:23.086Z" }, +] + +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, +] + +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + +[[package]] +name = "wurlitzer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/33/90/623f99c55c7d0727a58eb2b7dfb65cb406c561a5c2e9a95b0d6a450c473d/wurlitzer-3.1.1.tar.gz", hash = "sha256:bfb9144ab9f02487d802b9ff89dbd3fa382d08f73e12db8adc4c2fb00cd39bd9", size = 11867, upload-time = "2024-06-12T10:27:30.089Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/24/93ce54550a9dd3fd996ed477f00221f215bf6da3580397fbc138d6036e2e/wurlitzer-3.1.1-py3-none-any.whl", hash = "sha256:0b2749c2cde3ef640bf314a9f94b24d929fe1ca476974719a6909dfc568c3aac", size = 8590, upload-time = "2024-06-12T10:27:28.787Z" }, +] + +[[package]] +name = "yarl" +version = "1.21.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/d1/a1ee68b513f31c6de9af56cdfafebb4939bf0d6528945a862e101699ae98/yarl-1.21.0.tar.gz", hash = "sha256:866c17223f7d734377a260a2800e14791cb5e55ec252de624e053a0b36b8568a", size = 187069, upload-time = "2025-10-05T17:22:46.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/b4/4fad5c66ad70f0e5d3b725c7ce72931d249891a4bec372c9181f9ba65f78/yarl-1.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bc8a06f7bc45219b2c191d68e779e6b3f62e32d09d2f8cf7b381ba1dcb7a68f9", size = 139931, upload-time = "2025-10-05T17:20:12.137Z" }, + { url = "https://files.pythonhosted.org/packages/b9/1a/684fcb0b57426b2f121d084a66cab6a3d8b60cf650d24bd0f18335111f11/yarl-1.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01ef0d7f1dd60d241529dc79a3fa647451056394f9a5ed05fbceeb5009de6122", size = 93384, upload-time = "2025-10-05T17:20:14.166Z" }, + { url = "https://files.pythonhosted.org/packages/ed/05/02f18b6b3ba344026d57796594a5630fc05816581c0d4aebfa00c26c6526/yarl-1.21.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37b5e7bba1f6df45058cff626c83a0e8a1259363095e768046a3da40b24e9c4f", size = 93779, upload-time = "2025-10-05T17:20:16.062Z" }, + { url = "https://files.pythonhosted.org/packages/f0/08/c9af7d6535959ade95fcb7692bedb8788b8f802bb52996476f7c93949c29/yarl-1.21.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:221aa7c16055e8b9f2eba718cbbf10f174e47f02e659156804d9679654c5cbb0", size = 373176, upload-time = "2025-10-05T17:20:18.171Z" }, + { url = "https://files.pythonhosted.org/packages/29/82/bc05acdd003e7676b0f668fd06c41091b3656a46747e3d5ef2db56b961fd/yarl-1.21.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:863d7401d3a109f75c7a5ca0e33e8fb7704a61007f4bda03e08e05f3bf1af40f", size = 342306, upload-time = "2025-10-05T17:20:20.435Z" }, + { url = "https://files.pythonhosted.org/packages/62/98/e2eafd1596fc48cdc1e3204a6d25d13d0b927339145c46e4d0a1e55d8e1f/yarl-1.21.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:692603a8f82e7baa86bb3921d5002b711788cec547b626030f1f6cf017290ab7", size = 386976, upload-time = "2025-10-05T17:20:22.669Z" }, + { url = "https://files.pythonhosted.org/packages/d2/24/65726cc4a131442b4af140a94b12429ab5a39832e7abd58de189ef77764a/yarl-1.21.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c48477c6ff32032624aa122323adc343055bb7e347e01146a86e652b06281731", size = 397645, upload-time = "2025-10-05T17:20:24.873Z" }, + { url = "https://files.pythonhosted.org/packages/99/b9/deded0027a1bb174aeeec914899773a2db1ef83088cb25c435ab9b57e9ac/yarl-1.21.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1107b93c32cf7d7e2ece9bbb1b1820ecb923cfea24c8aa599a309434ed37d707", size = 377016, upload-time = "2025-10-05T17:20:26.699Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c2/2bae5bd4e39c503738e8058659d68339f619d443129ea2d5375790a2b783/yarl-1.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0e485c4f9f5b5b9fc10b4bb0ba5baf145ed0a702756da126c9f62f8a89b391a8", size = 365316, upload-time = "2025-10-05T17:20:29.103Z" }, + { url = "https://files.pythonhosted.org/packages/14/d7/c20dc74713bccf5998babde260487d21b61497a9753200fdce887a715e24/yarl-1.21.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ec1f6129c1175d15da7b7c13ae5d4226acf6b5fe362c5b01ac9787fa88c64781", size = 361295, upload-time = "2025-10-05T17:20:31.296Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6f/fc3eee2f52f303f4b93b3d9b16842dd218bfb37b931f20c1e7b529f15395/yarl-1.21.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e0b01fa225ec12e54c73be383326ae2a4a59a4a465a0e6cac679f314ed85d1f", size = 382546, upload-time = "2025-10-05T17:20:33.436Z" }, + { url = "https://files.pythonhosted.org/packages/51/37/80baf3548b6e910ba278ba0255177d091f0af66afd738bbd88857b3ef552/yarl-1.21.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7d8917677a64304db00ec46629aff335c935c788a10a164b29464b7e2d707463", size = 385276, upload-time = "2025-10-05T17:20:35.77Z" }, + { url = "https://files.pythonhosted.org/packages/39/8a/d1302e6e4454eabf1aa4034b2907439a43f7b5d5159b8f0237f54e5e0c86/yarl-1.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b846a17f810708f1beff6ad088121fd35334729df3e520412163c74ef49433f7", size = 374395, upload-time = "2025-10-05T17:20:37.555Z" }, + { url = "https://files.pythonhosted.org/packages/58/8b/a6fa48483fc60233e7a4225b80a0610ebed8dfd41404f1e5a4e6694654bd/yarl-1.21.0-cp313-cp313-win32.whl", hash = "sha256:1743d35529a8b9b2b6a9e5f00076c2c146726453051621b739b081dda382ee70", size = 81513, upload-time = "2025-10-05T17:20:39.282Z" }, + { url = "https://files.pythonhosted.org/packages/1c/50/9e921fee3f29fe75be1c20d7344dd943bad642430adee4eabb230dfd7c55/yarl-1.21.0-cp313-cp313-win_amd64.whl", hash = "sha256:13c9b91e2e1224a8d33addc1bd58bb097396519c4c49524843947776b8dd45da", size = 86922, upload-time = "2025-10-05T17:20:41.371Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ff/d9af15a1e4c294c7a9b2a5063dbe866b6cda7236de609609b164a335e327/yarl-1.21.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c2c4da0802f6897f7fb766c4f0e7f55c96b103981265fcf12b648d088bee3744", size = 146171, upload-time = "2025-10-05T17:20:43.399Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f6/9c648fd2518821a0e8c80b9a96888e4d7ebe9e396d2aa4f5a804bd7e3903/yarl-1.21.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c115756cb1cad49862aa0c2687922ed10da6be7689cf35e3ab602c4a6da2d8fb", size = 95926, upload-time = "2025-10-05T17:20:45.274Z" }, + { url = "https://files.pythonhosted.org/packages/a2/72/528606b2d707e8d59ef905cc19a08c1265da4a535a99dbe813ccb56bed45/yarl-1.21.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfcca979b72f240bac7c73564026eae4c97639151a415e6ced6392d120022d2d", size = 97272, upload-time = "2025-10-05T17:20:46.947Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0f/441f882bda86de80cbd8c302b8f9bb1c449b0f4fc1ff7e9ea9e8161ed99e/yarl-1.21.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a999c5c50af0e564cab5bbbbbee97d494eb0e09f99481385108ddfd90049b3fe", size = 361934, upload-time = "2025-10-05T17:20:48.685Z" }, + { url = "https://files.pythonhosted.org/packages/38/b7/1af70aec3f4f0b60d3e94918adc1c38319120768e1b106b5c049bfc40838/yarl-1.21.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7d5d8eeb1051fac562d80aad7b6b496e2901f41fc2b0988c61016a1426996f66", size = 323909, upload-time = "2025-10-05T17:20:50.636Z" }, + { url = "https://files.pythonhosted.org/packages/65/89/b5d64607085bef4ef5319c1604e5e1f64604d7a4ed4efdfa12448dac5f37/yarl-1.21.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:61bf6233d04ccba7906f5261ff3628fa97a68fc526cda3d9dd092d2f49926933", size = 361785, upload-time = "2025-10-05T17:20:52.959Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/2544b2a6184b5e02736870c5919243da45cd105efd6285f2c7750cc0ea68/yarl-1.21.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:63157d66cf7682dec8b3117491cb87a5d8e1cd56df59156d5553ab9721895d19", size = 372589, upload-time = "2025-10-05T17:20:54.916Z" }, + { url = "https://files.pythonhosted.org/packages/ea/33/0cac77694b844e0e00aa2a5be679e47b62213d3ea2e6fe84396cb04183a1/yarl-1.21.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45aa7711e1933bac1679f9534f112767f1fe64c97a8576294b760015d0fb65e7", size = 341082, upload-time = "2025-10-05T17:20:56.759Z" }, + { url = "https://files.pythonhosted.org/packages/41/e5/8527ca2fee44a519f659cb1e71182da8f4739032f26acd3cf1567afed081/yarl-1.21.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:74b2e94d3e410ed49c7a4cb2c3a5089a6632f7ab68e49bb612b972577e26e771", size = 346739, upload-time = "2025-10-05T17:20:58.647Z" }, + { url = "https://files.pythonhosted.org/packages/c5/98/d31449d293c4a400c5eea2835d38f3b86ab1a7eae73750b5e011c4faf0eb/yarl-1.21.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:178860382595f3b1fab2596b19570adc495c6211eee8b10a4112ce96342f6515", size = 334808, upload-time = "2025-10-05T17:21:00.607Z" }, + { url = "https://files.pythonhosted.org/packages/39/5f/605873225112f3bfd7b924fc00f9ac8f2d4a6b9e0a9abbca90ef566ffd92/yarl-1.21.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f0a6cd797394761692cc6f33b10f2ea46789ac0b7fba82b6df737f51e1297122", size = 350964, upload-time = "2025-10-05T17:21:02.694Z" }, + { url = "https://files.pythonhosted.org/packages/65/17/f40496a4bd7fb2047caaa4c2f3c573cf4ad1d1ab02549584a7930bd0ecea/yarl-1.21.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:8910f022242c0a15f6d77d781c6ba16bb88d9fed3bff8964de652ee2580029ac", size = 356007, upload-time = "2025-10-05T17:21:04.493Z" }, + { url = "https://files.pythonhosted.org/packages/ce/56/8965a790ad8007c6fa59d7a769e18a6b4451944c38e953f8acd620c98747/yarl-1.21.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1b5d29c1a86cc63e55f69253b8c817091884c4e1b79ee762a8643de834e70a64", size = 342913, upload-time = "2025-10-05T17:21:06.673Z" }, + { url = "https://files.pythonhosted.org/packages/2c/25/3e45f26e9204e4ad89c91d89b1e946a12bc79b0e4f84e39916a28058463e/yarl-1.21.0-cp313-cp313t-win32.whl", hash = "sha256:99febd7a9efab236d798d72ca878ae0d92fffadcc2e472636d6e093ce2677980", size = 86902, upload-time = "2025-10-05T17:21:08.69Z" }, + { url = "https://files.pythonhosted.org/packages/48/0f/4496e5506abf690100fc5d37f31c3216e5c1c5fc2a228b08d39e42d174e5/yarl-1.21.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e00aaf1574075439ccb0b827ca822c5a97c0103351ead292c42a9f17bd2eae0a", size = 93676, upload-time = "2025-10-05T17:21:10.296Z" }, + { url = "https://files.pythonhosted.org/packages/cf/b9/01fc864ac6cc9bb1ae14ab852a7530d762254a27fe6c2c29e0c9c8dc6393/yarl-1.21.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9eaf0f28ed19919bdeb02cfa541daaee8a05c070227eaab8d9732f1eebfe2869", size = 140474, upload-time = "2025-10-05T17:21:11.976Z" }, + { url = "https://files.pythonhosted.org/packages/99/7a/142a173f148ea8a1b36ae498a961c0be26986a5fab86908210d0507e75a3/yarl-1.21.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:de9f7a51f828f73ea0ca2e856a7cac8766752f336241abdb6c5f45f402dd59ea", size = 93462, upload-time = "2025-10-05T17:21:13.628Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ba/71eeca357170115c28315ec1b1c015b44b10cadd801d28f5b25b754853f0/yarl-1.21.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b68c0c9deb2fcd183376600df99e88032a9c192d352b0f781e130b09220ef1cf", size = 94241, upload-time = "2025-10-05T17:21:15.552Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7d/cdf516659244105b6eb78ee316b182f47d92ebdc33ce2b9cfe42e12c3cc7/yarl-1.21.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ee84156656d4a09010c280f41011f0a317c62e745f7a2cfafabd8035823fe2d", size = 372050, upload-time = "2025-10-05T17:21:17.696Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a7/2b8401a64d91828f6e18bbdec8beb761a221d7795f94e7a1b3083af5d001/yarl-1.21.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:780313d2a1877adef0e3839ef9596ad53ab640715e7f453e7304c121cd7f262d", size = 338623, upload-time = "2025-10-05T17:21:19.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/c9/7ab9b63e3ca31a8b104d183774de3eccfe1da9889d5fbf11aa7d6c90f7d3/yarl-1.21.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f9dae6ef584d3241571674ed7bcd1a28b003a5f0c3a6ca561ab42e5ce0c482e3", size = 387105, upload-time = "2025-10-05T17:21:21.83Z" }, + { url = "https://files.pythonhosted.org/packages/e4/29/0ae170810edb493591b5eced0b0a214e62df81ff9767282fd386282a9e12/yarl-1.21.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a94664fe3c6dd44c36e875af0f338769dc9f80a1ccd58f53cf5f5b8341e8627", size = 394044, upload-time = "2025-10-05T17:21:23.775Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d2/c134f3acd2797dacd631851d7b868fc0c5e2d6b8ed8879bcf42696383504/yarl-1.21.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:056fc431f10ae35aa2375c9de2b68176b34f54fb7de8bc2e830564e2a3d29efa", size = 372610, upload-time = "2025-10-05T17:21:25.998Z" }, + { url = "https://files.pythonhosted.org/packages/46/21/949def9a5369ba8a653a30de07b01be5813db1fb0b5e0f1c34606a7f84cc/yarl-1.21.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4fcce63c1117ef0630a92a0bda3028a96dc17feed2c78c713de4c963d13d1881", size = 364605, upload-time = "2025-10-05T17:21:27.859Z" }, + { url = "https://files.pythonhosted.org/packages/df/2e/9b1971c584f5ba0fde7f40b74f8d1b54e95c46fa39765189a1d696beb9af/yarl-1.21.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1a0ba7cd4eabb7433e69737f33333d9e79d8ab6dbaa2f4d7313ad6611200cc65", size = 355323, upload-time = "2025-10-05T17:21:29.856Z" }, + { url = "https://files.pythonhosted.org/packages/d5/11/4271403204e6f0cb46f63de249d1f552d23e26ad04a16e7cab686ab46256/yarl-1.21.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3b539230fd64f283594a56633a9751d299cde5ab9c2791452ccb47a865842fa8", size = 381423, upload-time = "2025-10-05T17:21:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/72/92/7d00ecf66b56ae1509a1a82fdf671a0c60c50182092a5e08af638b896237/yarl-1.21.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:10580c7d9b50c883b93cc0ab5c91df5cc1e5b18713736471d622776b01c36810", size = 383767, upload-time = "2025-10-05T17:21:34.499Z" }, + { url = "https://files.pythonhosted.org/packages/d6/9e/c5ec1f51be336bdaac908219255318cb86074f1c403a72fd47ec0209b9b5/yarl-1.21.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c0123db2d86d169554d5fb19421e8e455efcfe2e8e254328b85c77e712ab506", size = 370765, upload-time = "2025-10-05T17:21:36.395Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4b/e3657b7069fb1e24f014e4351b311e522ae7a58afc76369e0f31cf65e9d8/yarl-1.21.0-cp314-cp314-win32.whl", hash = "sha256:5e7d24e9c3b638f046fcd9a5374818257a8c6d1c3fc7542887521b81a970fbc2", size = 82954, upload-time = "2025-10-05T17:21:38.414Z" }, + { url = "https://files.pythonhosted.org/packages/7e/2b/6447cbd3d43acc2ce2b6898fdaba7d517ee6269f5a278b5d09a1530cb645/yarl-1.21.0-cp314-cp314-win_amd64.whl", hash = "sha256:884d4f3509dfc810299d14faed24c0fbcac82ae2a9737b0cb1d8f7a5e8a291f8", size = 88253, upload-time = "2025-10-05T17:21:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/bd9d7894d4d14b6fc53e717536dd5e77e68fe278ce20ea6a04aa16dd413c/yarl-1.21.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3cbae81bff4014ca7745fa11f7015f784198fadba8935cf5a71e139b0b124ff0", size = 146189, upload-time = "2025-10-05T17:21:42.549Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f6/3054643d8187c0feb31db8da1abb73799a4d72f149bca3a928a171c6ecf3/yarl-1.21.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:de1ab4f48fbcb4c2e578951338cc1c8245e510be061d2773a2d47616fb0d6470", size = 95943, upload-time = "2025-10-05T17:21:44.224Z" }, + { url = "https://files.pythonhosted.org/packages/f5/19/fb9000892d04c500bad8f971cc2884cb986190ca606df9b4b41376d356af/yarl-1.21.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4b449296e2ba009481385349138130f209bb502c4f890b3298bf3ea13d43a6d5", size = 97264, upload-time = "2025-10-05T17:21:45.975Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/dbe3af9b3c55463413938933b349b7221a16f052fcc132890c634fbde116/yarl-1.21.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19df967a905f2f9a09733dfb397baa6807772502931000f881943d7cfc6e9f47", size = 361753, upload-time = "2025-10-05T17:21:48.059Z" }, + { url = "https://files.pythonhosted.org/packages/5f/7d/9bf7c744ec1fdb2d97ecdf70775d61e5825859cf0eb42b6f05c454e6aea4/yarl-1.21.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:26940710eece6b5b08a108e81d6325b47610990cd8bb28886e27d4a0d6d60930", size = 323188, upload-time = "2025-10-05T17:21:50.284Z" }, + { url = "https://files.pythonhosted.org/packages/54/e5/2edd706871c7bdfe199f9a8ceba742929e1608400b4adfde872e0ff5977e/yarl-1.21.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0aaa36261a1279b03fa0655a9bd879cc42e06406adaae0150fde25c778393fcb", size = 363145, upload-time = "2025-10-05T17:21:52.168Z" }, + { url = "https://files.pythonhosted.org/packages/53/fa/1403e1d8d7fb5a19456731d55ce36cb7eead99a1d6b16a916a71f5295b6e/yarl-1.21.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d070756da822a538231d519ce290a1423ab108d6174ad1497cd020bee503d818", size = 373527, upload-time = "2025-10-05T17:21:54.179Z" }, + { url = "https://files.pythonhosted.org/packages/02/bb/f00f4e6f441e66db127c8a61d0371cdb5fea690cdc9a13ee2a84912f04a0/yarl-1.21.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:212a5c72d551f94b7799b5de1cc55ddcf3c69ac462f7c0df1beee7e47edb9fef", size = 339781, upload-time = "2025-10-05T17:21:56.215Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/0a9558f924c98875f96bfbf7e75ccc7a53da2f3b6e39065f039521a808d7/yarl-1.21.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b2f8e0bbdf49530ed09b2bc988082cab6ce24f4c49a0efd2ff5d9477cb29084", size = 347513, upload-time = "2025-10-05T17:21:59.078Z" }, + { url = "https://files.pythonhosted.org/packages/af/83/957137aef698100645922f96fb78dd66ffbce4dcdd5e6c6e50eae5087a91/yarl-1.21.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:9618070bb76a064c13020323b7fc23c332930604dfbc96b77e7ad7baca960c12", size = 334470, upload-time = "2025-10-05T17:22:01.233Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9b/549c3e2cb0cb7dda9a59ad35c5a1e26e35942953a7debee8a983529c95e1/yarl-1.21.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:7a9d0efd6ff6f4f55ff7a37852e4fcdc24b1feb3b09e204df3dda990171fe725", size = 352339, upload-time = "2025-10-05T17:22:03.432Z" }, + { url = "https://files.pythonhosted.org/packages/24/85/8cff7f713821578b6a7989af8d7226fe6119cd3d1884f7b67716290f9233/yarl-1.21.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:f1b3930f0934057825227016a141ce16aad4b2a3805fb4e2de71064d042d72e9", size = 357107, upload-time = "2025-10-05T17:22:05.721Z" }, + { url = "https://files.pythonhosted.org/packages/fc/36/c124a3a2be46d051d693d5f0580be27b025f6bbf1d5dfeedcb933442dcd1/yarl-1.21.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fb09731156f54dfd8bb097ce80f9436c2a1a282061ba29e526c375c69086b764", size = 341432, upload-time = "2025-10-05T17:22:08.185Z" }, + { url = "https://files.pythonhosted.org/packages/74/d0/143a8b2bc5e19e4719a00fc453c0a2207ee8b3411e837a7a56d39b3cf60e/yarl-1.21.0-cp314-cp314t-win32.whl", hash = "sha256:2584651c047718ec4a863ee81a5432f6f68974e6f0c58975f0aab408ff839798", size = 89019, upload-time = "2025-10-05T17:22:10.623Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3d/2520bb07955ba583e0c500a1223d7139da80e523160c52bea0f23927f76b/yarl-1.21.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b0e38cf49c17e35831ec38029854b772717d6071f0419b74b80be57571a83d0a", size = 96254, upload-time = "2025-10-05T17:22:12.366Z" }, + { url = "https://files.pythonhosted.org/packages/08/be/3ebe06c6903bb0a0e63c1f445124c6367f4080ef347703fe6cd806672a28/yarl-1.21.0-py3-none-any.whl", hash = "sha256:c464852c531e44abc5ba05d0c0c97a8fa63719106b3dca46fedae14daedf46ae", size = 46777, upload-time = "2025-10-05T17:22:45.175Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] diff --git a/application/ui/.dockerignore b/application/ui/.dockerignore new file mode 100644 index 0000000000..df449a4c94 --- /dev/null +++ b/application/ui/.dockerignore @@ -0,0 +1,10 @@ +* +!src +!packages +!package.json +!package-lock.json +!eslint.config.js +!.prettierrc +!tsconfig.json +!rsbuild.config.ts +!nginx.conf diff --git a/application/ui/.editorconfig b/application/ui/.editorconfig new file mode 100644 index 0000000000..0f338fb924 --- /dev/null +++ b/application/ui/.editorconfig @@ -0,0 +1,30 @@ +# https://editorconfig.org +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.json] +indent_size = 4 + +[*.ts] +indent_size = 4 + +[*.tsx] +indent_size = 4 + +[*.md] +# double whitespace at end of line +# denotes a line break in Markdown +trim_trailing_whitespace = false + +[*.yml] +indent_size = 2 + +[Makefile] +indent_style = tab; diff --git a/application/ui/.gitignore b/application/ui/.gitignore new file mode 100644 index 0000000000..681b68a9b7 --- /dev/null +++ b/application/ui/.gitignore @@ -0,0 +1,28 @@ +# Local +.DS_Store +*.local +*.log* + +# Dist +node_modules +dist/ + +# Profile +.rspack-profile-*/ + +# IDE +.vscode/* +!.vscode/extensions.json +.idea + +# We clone our geti packages during the npm install phase +packages/ + +# Playwright +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ + +src/api/openapi-spec.d.ts +src/api/openapi-spec.json diff --git a/application/ui/.nvmrc b/application/ui/.nvmrc new file mode 100644 index 0000000000..dc864a052c --- /dev/null +++ b/application/ui/.nvmrc @@ -0,0 +1 @@ +v24.2.0 diff --git a/application/ui/.prettierignore b/application/ui/.prettierignore new file mode 100644 index 0000000000..5b10962b8c --- /dev/null +++ b/application/ui/.prettierignore @@ -0,0 +1,7 @@ +# Lock files +package-lock.json + +# Tauri +src-tauri/bin +src-tauri/target +src-tauri/gen diff --git a/application/ui/.prettierrc b/application/ui/.prettierrc new file mode 100644 index 0000000000..cbcc0e3f35 --- /dev/null +++ b/application/ui/.prettierrc @@ -0,0 +1,34 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "embeddedLanguageFormatting": "auto", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "bracketSameLine": false, + "jsxSingleQuote": true, + "printWidth": 120, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": true, + "singleQuote": true, + "tabWidth": 4, + "useTabs": false, + "endOfLine": "auto", + "trailingComma": "es5", + "plugins": ["@ianvs/prettier-plugin-sort-imports"], + "importOrder": [ + "", + "^react$", + "", + "BUILTIN_MODULES>", + "", + "", + "", + "^(?!.*[.]*css$)[./].*$", + "", + ".*css$" + ], + "importOrderTypeScriptVersion": "5.7.2", + "importOrderCaseSensitive": false +} diff --git a/application/ui/README.md b/application/ui/README.md new file mode 100644 index 0000000000..e6345012b0 --- /dev/null +++ b/application/ui/README.md @@ -0,0 +1,41 @@ +# Geti Inspect + +## Setup + +Install the dependencies: + +```bash +npm install +``` + +## Get started + +Start the dev server: + +```bash +npm run start +``` + +Build the app for production: + +```bash +npm run build +``` + +Preview the production build locally: + +```bash +npm run preview +``` + +## Testing + +Use the following commands to test your changes. + +```bash +npm run format +npm run lint +npm run type-check +npm run test:unit +npm run test:component +``` diff --git a/application/ui/eslint.config.js b/application/ui/eslint.config.js new file mode 100644 index 0000000000..a430f26071 --- /dev/null +++ b/application/ui/eslint.config.js @@ -0,0 +1,82 @@ +// Copyright (C) 2022-2025 Intel Corporation +// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE + +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { FlatCompat } from '@eslint/eslintrc'; +import js from '@eslint/js'; +import sharedEslintConfig from '@geti/config/lint'; + +const filename = fileURLToPath(import.meta.url); +const dirname = path.dirname(filename); +const compat = new FlatCompat({ + baseDirectory: dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +export default [ + { + ignores: [...sharedEslintConfig[0].ignores, 'src/api/openapi-spec.d.ts'], + }, + ...sharedEslintConfig, + { + rules: { + 'no-restricted-imports': [ + 'error', + { + patterns: [ + { + group: ['@react-spectrum'], + message: 'Use component from the @geti/ui folder instead.', + }, + { + group: ['@react-types/*'], + message: 'Use type from the @geti/ui folder instead.', + }, + { + group: ['@spectrum-icons'], + message: 'Use icons from the @geti/ui/icons folder instead.', + }, + ], + }, + ], + 'header/header': 'off', + }, + }, + { + settings: { + 'import/resolver': { + typescript: { + alwaysTryTypes: true, + project: ['./tsconfig.json', './tests/tsconfig.json'], + noWarnOnMultipleProjects: true, + }, + }, + }, + }, + ...compat.extends('plugin:playwright/playwright-test').map((config) => ({ + ...config, + files: ['tests/features/**/*.ts', 'tests/utils/**/*.ts', 'tests/fixtures/**/*.ts', 'tests/e2e/**/*.ts'], + })), + { + files: ['tests/features/**/*.ts', 'tests/utils/**/*.ts', 'tests/fixtures/**/*.ts', 'tests/e2e/**/*.ts'], + + rules: { + 'playwright/no-wait-for-selector': ['off'], + 'playwright/no-conditional-expect': ['off'], + 'playwright/no-standalone-expect': ['off'], + 'playwright/missing-playwright-await': ['warn'], + 'playwright/valid-expect': ['warn'], + 'playwright/no-useless-not': ['warn'], + 'playwright/no-page-pause': ['warn'], + 'playwright/prefer-to-have-length': ['warn'], + 'playwright/no-conditional-in-test': ['off'], + 'playwright/expect-expect': ['off'], + 'playwright/no-skipped-test': ['off'], + 'playwright/no-wait-for-timeout': ['off'], + 'playwright/no-nested-step': ['off'], + }, + }, +]; diff --git a/application/ui/mocks/mock-media-item.ts b/application/ui/mocks/mock-media-item.ts new file mode 100644 index 0000000000..483dbbfcbd --- /dev/null +++ b/application/ui/mocks/mock-media-item.ts @@ -0,0 +1,17 @@ +import { SchemaMediaList } from './../src/api/openapi-spec.d'; + +export const getMockedMediaItem = ( + data: Partial +): SchemaMediaList['media'][number] => { + return { + id: '1', + project_id: '123', + filename: 'test-image.jpg', + size: 1024, + is_anomalous: false, + width: 1920, + height: 1080, + created_at: '2024-01-01T00:00:00Z', + ...data, + }; +}; diff --git a/application/ui/mocks/mock-pipeline.ts b/application/ui/mocks/mock-pipeline.ts new file mode 100644 index 0000000000..d00f5f1287 --- /dev/null +++ b/application/ui/mocks/mock-pipeline.ts @@ -0,0 +1,36 @@ +import { SchemaPipeline } from './../src/api/openapi-spec.d'; + +export const getMockedPipeline = (customPipeline?: Partial): SchemaPipeline => { + return { + project_id: '123', + status: 'running' as const, + source: { + id: 'source-id', + name: 'source', + project_id: '123', + source_type: 'video_file' as const, + video_path: 'video.mp4', + }, + model: { + id: '1', + name: 'Object_Detection_TestModel', + format: 'onnx' as const, + project_id: '123', + threshold: 0.5, + is_ready: true, + train_job_id: 'train-job-1', + }, + sink: { + id: 'sink-id', + name: 'sink', + project_id: '123', + folder_path: 'data/sink', + output_formats: ['image_original', 'image_with_predictions', 'predictions'] as Array< + 'image_original' | 'image_with_predictions' | 'predictions' + >, + rate_limit: 0.2, + sink_type: 'folder' as const, + }, + ...customPipeline, + }; +}; diff --git a/application/ui/package-lock.json b/application/ui/package-lock.json new file mode 100644 index 0000000000..6607c2ce66 --- /dev/null +++ b/application/ui/package-lock.json @@ -0,0 +1,20786 @@ +{ + "name": "@geti/inspect-ui", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@geti/inspect-ui", + "version": "1.0.0", + "hasInstallScript": true, + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@geti/config": "*", + "@geti/ui": "*", + "@scalar/api-reference-react": "^0.7.49", + "@tanstack/react-query": "^5.80.10", + "clsx": "^2.1.1", + "comlink": "^4.4.2", + "lodash-es": "^4.17.21", + "motion": "^12.23.24", + "openapi-fetch": "^0.14.0", + "openapi-react-query": "^0.5.0", + "react": "^19.1.1", + "react-aria-components": "^1.10.1", + "react-dom": "^19.1.1", + "react-error-boundary": "^3.1.4", + "react-router": "^6.30.1", + "react-router-dom": "^6.30.1", + "recharts": "^2.15.3", + "spin-delay": "^2.0.1", + "static-path": "^0.0.4", + "uuid": "^11.1.0" + }, + "devDependencies": { + "@eslint/compat": "^1.2.8", + "@eslint/js": "^9.25.1", + "@ianvs/prettier-plugin-sort-imports": "^4.4.2", + "@msw/playwright": "^0.4.2", + "@mswjs/source": "^0.4.1", + "@playwright/test": "^1.54.1", + "@rsbuild/core": "^1.3.14", + "@rsbuild/plugin-babel": "^1.0.6", + "@rsbuild/plugin-react": "^1.3.0", + "@rsbuild/plugin-sass": "^1.3.1", + "@rsbuild/plugin-svgr": "^1.2.0", + "@tanstack/eslint-plugin-query": "^5.78.0", + "@testing-library/jest-dom": "^6.6.4", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/lodash-es": "^4.17.12", + "@types/node": "^24.0.15", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^8.31.1", + "@vitejs/plugin-react": "^4.7.0", + "babel-plugin-react-compiler": "^19.1.0-rc.3", + "babel-preset-react-app": "^10.1.0", + "concurrently": "^9.2.1", + "eslint": "^9.25.1", + "eslint-import-resolver-typescript": "^4.3.4", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-playwright": "^2.2.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-compiler": "^19.1.0-rc.2", + "eslint-plugin-react-hooks": "^6.0.0-rc.2", + "identity-obj-proxy": "^3.0.0", + "jsdom": "^26.1.0", + "msw": "^2.11.3", + "node-fetch": "^3.3.2", + "openapi-msw": "^1.3.0", + "openapi-typescript": "^7.8.0", + "prettier": "^3.5.3", + "typescript": "^5.8.3", + "typescript-eslint": "^8.31.1", + "vite": "^7.0.6", + "vite-plugin-svgr": "^4.3.0", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.2.4" + }, + "engines": { + "node": ">=v24.2.0", + "npm": ">=11.3.0" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.3.tgz", + "integrity": "sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@adobe/react-spectrum": { + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.43.0.tgz", + "integrity": "sha512-QeFxyUK60M26Nz2WhsNGZ5kRWT3tcrC/WWCASS8UFDI+Oy1P7+o35hNKBGJpeWFwBE/1+8B7G0rAk3+gapYAZg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/string": "^3.2.7", + "@react-aria/collections": "3.0.0-rc.4", + "@react-aria/i18n": "^3.12.11", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.0", + "@react-aria/visually-hidden": "^3.8.26", + "@react-spectrum/accordion": "^3.0.9", + "@react-spectrum/actionbar": "^3.6.10", + "@react-spectrum/actiongroup": "^3.11.0", + "@react-spectrum/avatar": "^3.0.24", + "@react-spectrum/badge": "^3.1.26", + "@react-spectrum/breadcrumbs": "^3.9.20", + "@react-spectrum/button": "^3.17.0", + "@react-spectrum/buttongroup": "^3.6.24", + "@react-spectrum/calendar": "^3.7.4", + "@react-spectrum/checkbox": "^3.10.0", + "@react-spectrum/color": "^3.1.0", + "@react-spectrum/combobox": "^3.16.0", + "@react-spectrum/contextualhelp": "^3.6.24", + "@react-spectrum/datepicker": "^3.14.4", + "@react-spectrum/dialog": "^3.9.0", + "@react-spectrum/divider": "^3.5.25", + "@react-spectrum/dnd": "^3.5.8", + "@react-spectrum/dropzone": "^3.0.14", + "@react-spectrum/filetrigger": "^3.0.14", + "@react-spectrum/form": "^3.7.17", + "@react-spectrum/icon": "^3.8.7", + "@react-spectrum/illustratedmessage": "^3.5.12", + "@react-spectrum/image": "^3.6.0", + "@react-spectrum/inlinealert": "^3.2.18", + "@react-spectrum/labeledvalue": "^3.2.5", + "@react-spectrum/layout": "^3.6.17", + "@react-spectrum/link": "^3.6.20", + "@react-spectrum/list": "^3.10.4", + "@react-spectrum/listbox": "^3.15.4", + "@react-spectrum/menu": "^3.22.4", + "@react-spectrum/meter": "^3.5.12", + "@react-spectrum/numberfield": "^3.10.0", + "@react-spectrum/overlays": "^5.8.0", + "@react-spectrum/picker": "^3.16.0", + "@react-spectrum/progress": "^3.7.18", + "@react-spectrum/provider": "^3.10.8", + "@react-spectrum/radio": "^3.7.19", + "@react-spectrum/searchfield": "^3.8.19", + "@react-spectrum/slider": "^3.8.0", + "@react-spectrum/statuslight": "^3.5.24", + "@react-spectrum/switch": "^3.6.4", + "@react-spectrum/table": "^3.17.4", + "@react-spectrum/tabs": "^3.8.23", + "@react-spectrum/tag": "^3.3.3", + "@react-spectrum/text": "^3.5.18", + "@react-spectrum/textfield": "^3.14.0", + "@react-spectrum/theme-dark": "^3.5.20", + "@react-spectrum/theme-default": "^3.5.20", + "@react-spectrum/theme-light": "^3.4.20", + "@react-spectrum/toast": "^3.1.0", + "@react-spectrum/tooltip": "^3.7.8", + "@react-spectrum/tree": "^3.1.4", + "@react-spectrum/view": "^3.6.21", + "@react-spectrum/well": "^3.4.25", + "@react-stately/collections": "^3.12.6", + "@react-stately/data": "^3.13.2", + "@react-types/shared": "^3.31.0", + "client-only": "^0.0.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@adobe/react-spectrum-ui": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@adobe/react-spectrum-ui/-/react-spectrum-ui-1.2.1.tgz", + "integrity": "sha512-wcrbEE2O/9WnEn6avBnaVRRx88S5PLFsPLr4wffzlbMfXeQsy+RMQwaJd3cbzrn18/j04Isit7f7Emfn0dhrJA==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@adobe/react-spectrum-workflow": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@adobe/react-spectrum-workflow/-/react-spectrum-workflow-2.3.5.tgz", + "integrity": "sha512-b53VIPwPWKb/T5gzE3qs+QlGP5gVrw/LnWV3xMksDU+CRl3rzOKUwxIGiZO8ICyYh1WiyqY4myGlPU/nAynBUg==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" + } + }, + "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/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "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/core/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/@babel/generator": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/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/@babel/helper-create-class-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/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/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/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/@babel/helper-define-polyfill-provider": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", + "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", + "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", + "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.28.0.tgz", + "integrity": "sha512-zOiZqvANjWDUaUS9xMxbMcK/Zccztbe/6ikvUXaG9nsPH3w6qh5UaPGAnirI/WhIbZ8m3OHU0ReyPrknG+ZKeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-decorators": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-numeric-separator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", + "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-methods": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", + "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz", + "integrity": "sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", + "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", + "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", + "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", + "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", + "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", + "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz", + "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", + "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-flow": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz", + "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.3", + "@babel/plugin-transform-parameters": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", + "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.27.1.tgz", + "integrity": "sha512-p9+Vl3yuHPmkirRrg021XiP+EETmPMQTLr6Ayjj85RLNEbb3Eya/4VI0vAdzQG9SEAl2Lnt7fy5lZyMzjYoZQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", + "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.4.tgz", + "integrity": "sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/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/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", + "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", + "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.27.1", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.27.1", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.27.1", + "@babel/plugin-transform-classes": "^7.27.1", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.27.1", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.27.2", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.1", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.27.1", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.40.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/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/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.5.2.tgz", + "integrity": "sha512-foZ7qr0IsUBjzWIq+SuBLfdQCpJ1j8cTuNNT4owngTHoN5KsJb8L9t65fzz7SCeSWzescoOil/0ldqiL041ABg==", + "dev": true, + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@bundled-es-modules/cookie": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.1.tgz", + "integrity": "sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cookie": "^0.7.2" + } + }, + "node_modules/@bundled-es-modules/statuses": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", + "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "statuses": "^2.0.1" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.7", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.7.tgz", + "integrity": "sha512-8EzdeIoWPJDsMBwz3zdzwXnUpCzMiCyz5/A3FIPpriaclFCGDkAzK13sMcnsu5rowqiyeQN2Vs2TsOcoDPZirQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", + "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.10", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.10.tgz", + "integrity": "sha512-h/SceTVsN5r+WE+TVP2g3KDvNoSzbSrtZXCKo4vkKdbfT5t4otuVgngGdFukOO/rwRD2++pCxoh6xD4TEVMkQA==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz", + "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", + "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", + "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", + "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.2.tgz", + "integrity": "sha512-bTWAJxL6EOFLPzTx+O5P5xAO3gTqpatQ2b/ARQ8itfU/v2LlpS3pH2fkL0A3E/Fx8Y2St2KES7ZEV0sHTsSW/A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", + "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", + "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", + "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/compat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.3.2.tgz", + "integrity": "sha512-jRNwzTbd6p2Rw4sZ1CgWRS8YMtqG15YyZf7zvb6gY2rB2u6n+2Z+ELW0GtL0fQgyl0pr4Y/BzBfng/BdsereRA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.40 || 9" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", + "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", + "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@faker-js/faker": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", + "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@floating-ui/vue": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.9.tgz", + "integrity": "sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4", + "@floating-ui/utils": "^0.2.10", + "vue-demi": ">=0.13.0" + } + }, + "node_modules/@floating-ui/vue/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@formatjs/ecma402-abstract": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz", + "integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==", + "license": "MIT", + "dependencies": { + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/intl-localematcher": "0.6.1", + "decimal.js": "^10.4.3", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", + "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz", + "integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "@formatjs/icu-skeleton-parser": "1.8.14", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz", + "integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "tslib": "^2.8.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz", + "integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@geti/config": { + "resolved": "packages/config", + "link": true + }, + "node_modules/@geti/ui": { + "resolved": "packages/ui", + "link": true + }, + "node_modules/@headlessui/tailwindcss": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@headlessui/tailwindcss/-/tailwindcss-0.2.2.tgz", + "integrity": "sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "tailwindcss": "^3.0 || ^4.0" + } + }, + "node_modules/@headlessui/vue": { + "version": "1.7.23", + "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz", + "integrity": "sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==", + "license": "MIT", + "dependencies": { + "@tanstack/vue-virtual": "^3.0.0-beta.60" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@hyperjump/browser": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@hyperjump/browser/-/browser-1.3.1.tgz", + "integrity": "sha512-Le5XZUjnVqVjkgLYv6yyWgALat/0HpB1XaCPuCZ+GCFki9NvXloSZITIJ0H+wRW7mb9At1SxvohKBbNQbrr/cw==", + "license": "MIT", + "dependencies": { + "@hyperjump/json-pointer": "^1.1.0", + "@hyperjump/uri": "^1.2.0", + "content-type": "^1.0.5", + "just-curry-it": "^5.3.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, + "node_modules/@hyperjump/json-pointer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@hyperjump/json-pointer/-/json-pointer-1.1.1.tgz", + "integrity": "sha512-M0T3s7TC2JepoWPMZQn1W6eYhFh06OXwpMqL+8c5wMVpvnCKNsPgpu9u7WyCI03xVQti8JAeAy4RzUa6SYlJLA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, + "node_modules/@hyperjump/json-schema": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/@hyperjump/json-schema/-/json-schema-1.16.2.tgz", + "integrity": "sha512-MJNvaEFc79+h5rvBPgAJK4OHEUr0RqsKcLC5rc3V9FEsJyQAjnP910deRFoZCE068kX/NrAPPhunMgUMwonPtg==", + "license": "MIT", + "dependencies": { + "@hyperjump/json-pointer": "^1.1.0", + "@hyperjump/pact": "^1.2.0", + "@hyperjump/uri": "^1.2.0", + "content-type": "^1.0.4", + "json-stringify-deterministic": "^1.0.12", + "just-curry-it": "^5.3.0", + "uuid": "^9.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + }, + "peerDependencies": { + "@hyperjump/browser": "^1.1.0" + } + }, + "node_modules/@hyperjump/json-schema/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@hyperjump/pact": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@hyperjump/pact/-/pact-1.4.0.tgz", + "integrity": "sha512-01Q7VY6BcAkp9W31Fv+ciiZycxZHGlR2N6ba9BifgyclHYHdbaZgITo0U6QMhYRlem4k8pf8J31/tApxvqAz8A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, + "node_modules/@hyperjump/uri": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@hyperjump/uri/-/uri-1.3.1.tgz", + "integrity": "sha512-2ecKymxf6prQMgrNpAvlx4RhsuM5+PFT6oh6uUTZdv5qmBv0RZvxv8LJ7oR30ZxGhdPdZAl4We/1NFc0nqHeAw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jdesrosiers" + } + }, + "node_modules/@ianvs/prettier-plugin-sort-imports": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ianvs/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.4.2.tgz", + "integrity": "sha512-KkVFy3TLh0OFzimbZglMmORi+vL/i2OFhEs5M07R9w0IwWAGpsNNyE4CY/2u0YoMF5bawKC2+8/fUH60nnNtjw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/generator": "^7.26.2", + "@babel/parser": "^7.26.2", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "semver": "^7.5.2" + }, + "peerDependencies": { + "@vue/compiler-sfc": "2.7.x || 3.x", + "prettier": "2 || 3 || ^4.0.0-0" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.14", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", + "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.1.15", + "@inquirer/type": "^3.0.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.1.15", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.15.tgz", + "integrity": "sha512-8xrp836RZvKkpNbVvgWUlxjT4CraKk2q+I3Ksy+seI2zkcE+y6wNs1BVhgcv8VyImFecUhdQrYLdW32pAjwBdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.13", + "@inquirer/type": "^3.0.8", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/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/@inquirer/core/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.13.tgz", + "integrity": "sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.8.tgz", + "integrity": "sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@internationalized/date": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.9.0.tgz", + "integrity": "sha512-yaN3brAnHRD+4KyyOsJyk49XUvj2wtbNACSqg0bz3u8t2VuzhC8Q5dfRnrSxjnnbDb+ienBnkn1TzQfE154vyg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/message": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.8.tgz", + "integrity": "sha512-Rwk3j/TlYZhn3HQ6PyXUV0XP9Uv42jqZGNegt0BXlxjE6G3+LwHjbQZAGHhCnCPdaA6Tvd3ma/7QzLlLkJxAWA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "intl-messageformat": "^10.1.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz", + "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/string": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.7.tgz", + "integrity": "sha512-D4OHBjrinH+PFZPvfCXvG28n2LSykWcJ7GIioQL+ok0LON15SdfoUssoHzzOUmVZLbRoREsQXVzA6r8JKsbP6A==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", + "license": "MIT" + }, + "node_modules/@lezer/css": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", + "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz", + "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.3.tgz", + "integrity": "sha512-jexmlKq5NpGiB7t+0QkyhSXRgaiab5YisHIQW9C7EcU19KSUsDguZe9WY+rmRDg34nXoNH2LQ4SxpC+aJUchSQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz", + "integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/@module-federation/error-codes": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.14.0.tgz", + "integrity": "sha512-GGk+EoeSACJikZZyShnLshtq9E2eCrDWbRiB4QAFXCX4oYmGgFfzXlx59vMNwqTKPJWxkEGnPYacJMcr2YYjag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@module-federation/runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.14.0.tgz", + "integrity": "sha512-kR3cyHw/Y64SEa7mh4CHXOEQYY32LKLK75kJOmBroLNLO7/W01hMNAvGBYTedS7hWpVuefPk1aFZioy3q2VLdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.14.0", + "@module-federation/runtime-core": "0.14.0", + "@module-federation/sdk": "0.14.0" + } + }, + "node_modules/@module-federation/runtime-core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-core/-/runtime-core-0.14.0.tgz", + "integrity": "sha512-fGE1Ro55zIFDp/CxQuRhKQ1pJvG7P0qvRm2N+4i8z++2bgDjcxnCKUqDJ8lLD+JfJQvUJf0tuSsJPgevzueD4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/error-codes": "0.14.0", + "@module-federation/sdk": "0.14.0" + } + }, + "node_modules/@module-federation/runtime-tools": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.14.0.tgz", + "integrity": "sha512-y/YN0c2DKsLETE+4EEbmYWjqF9G6ZwgZoDIPkaQ9p0pQu0V4YxzWfQagFFxR0RigYGuhJKmSU/rtNoHq+qF8jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.14.0", + "@module-federation/webpack-bundler-runtime": "0.14.0" + } + }, + "node_modules/@module-federation/sdk": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.14.0.tgz", + "integrity": "sha512-lg/OWRsh18hsyTCamOOhEX546vbDiA2O4OggTxxH2wTGr156N6DdELGQlYIKfRdU/0StgtQS81Goc0BgDZlx9A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@module-federation/webpack-bundler-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.14.0.tgz", + "integrity": "sha512-POWS6cKBicAAQ3DNY5X7XEUSfOfUsRaBNxbuwEfSGlrkTE9UcWheO06QP2ndHi8tHQuUKcIHi2navhPkJ+k5xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime": "0.14.0", + "@module-federation/sdk": "0.14.0" + } + }, + "node_modules/@msw/playwright": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@msw/playwright/-/playwright-0.4.2.tgz", + "integrity": "sha512-xJnHJBkxCF+wcusYAFTDTW1kiD+WVsuYMSxcqvaAbe/mhy73oxkcqUbsiqZsKPdNrXIQQtkCyX5vSP5ZaPUHdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mswjs/interceptors": "^0.39.2", + "outvariant": "^1.4.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "msw": "^2.10.3" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.39.3", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.39.3.tgz", + "integrity": "sha512-9bw/wBL7pblsnOCIqvn1788S9o4h+cC5HWXg0Xhh0dOzsZ53IyfmBM+FYqpDDPbm0xjCqEqvCITloF3Dm4TXRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@mswjs/source": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@mswjs/source/-/source-0.4.1.tgz", + "integrity": "sha512-sUaUz5OaMgsFm9KERnIP/bZY/6kLb1DJS9qPPw28oAndaESZCjr7Fmob7wYUluTokQDBTQeY2bXrUQ9FK/6fxQ==", + "dev": true, + "dependencies": { + "@stoplight/json": "^3.21.7", + "@types/har-format": "^1.2.16", + "@yellow-ticket/seed-json-schema": "^0.1.6", + "openapi-types": "^12.1.3", + "outvariant": "^1.4.3", + "yaml": "^2.8.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "msw": "^2.10.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", + "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@phosphor-icons/core": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@phosphor-icons/core/-/core-2.1.1.tgz", + "integrity": "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==", + "license": "MIT" + }, + "node_modules/@playwright/test": { + "version": "1.54.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.1.tgz", + "integrity": "sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.54.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-aria/actiongroup": { + "version": "3.7.20", + "resolved": "https://registry.npmjs.org/@react-aria/actiongroup/-/actiongroup-3.7.20.tgz", + "integrity": "sha512-a70hN9dntg/m5y7x6vtFKv9zjafpje2mI79aUf5+ujnlJbMOKy/x9ETIby2jOy2W22GJnddPispY9s7QK6VO9Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-stately/list": "^3.13.0", + "@react-types/actiongroup": "^3.4.20", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/autocomplete": { + "version": "3.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@react-aria/autocomplete/-/autocomplete-3.0.0-rc.1.tgz", + "integrity": "sha512-4/+XHkCq9nkC0TNfgPsbuMTu3iwM6Gz4j67rTQRMXrWwCTAqAHJJEmDz/YDt/04Rg+dkGPsauHHMqDxwxZV24Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/combobox": "^3.13.2", + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/listbox": "^3.14.8", + "@react-aria/searchfield": "^3.8.8", + "@react-aria/textfield": "^3.18.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/autocomplete": "3.0.0-beta.3", + "@react-stately/combobox": "^3.11.1", + "@react-types/autocomplete": "3.0.0-alpha.34", + "@react-types/button": "^3.14.0", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/breadcrumbs": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.28.tgz", + "integrity": "sha512-6S3QelpajodEzN7bm49XXW5gGoZksK++cl191W0sexq/E5hZHAEA9+CFC8pL3px13ji7qHGqKAxOP4IUVBdVpQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/link": "^3.8.5", + "@react-aria/utils": "^3.30.1", + "@react-types/breadcrumbs": "^3.7.16", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/button": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@react-aria/button/-/button-3.14.1.tgz", + "integrity": "sha512-Ug06unKEYVG3OF6zKmpVR7VfLzpj7eJVuFo3TCUxwFJG7DI28pZi2TaGWnhm7qjkxfl1oz0avQiHVfDC99gSuw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/toolbar": "3.0.0-beta.20", + "@react-aria/utils": "^3.30.1", + "@react-stately/toggle": "^3.9.1", + "@react-types/button": "^3.14.0", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/calendar": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.9.1.tgz", + "integrity": "sha512-dCJliRIi3x3VmAZkJDNTZddq0+QoUX9NS7GgdqPPYcJIMbVPbyLWL61//0SrcCr3MuSRCoI1eQZ8PkQe/2PJZQ==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.9.0", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/live-announcer": "^3.4.4", + "@react-aria/utils": "^3.30.1", + "@react-stately/calendar": "^3.8.4", + "@react-types/button": "^3.14.0", + "@react-types/calendar": "^3.7.4", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/checkbox": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.16.1.tgz", + "integrity": "sha512-YcG3QhuGIwqPHo4GVGVmwxPM5Ayq9CqYfZjla/KTfJILPquAJ12J7LSMpqS/Z5TlMNgIIqZ3ZdrYmjQlUY7eUg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/form": "^3.1.1", + "@react-aria/interactions": "^3.25.5", + "@react-aria/label": "^3.7.21", + "@react-aria/toggle": "^3.12.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/checkbox": "^3.7.1", + "@react-stately/form": "^3.2.1", + "@react-stately/toggle": "^3.9.1", + "@react-types/checkbox": "^3.10.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/collections": { + "version": "3.0.0-rc.4", + "resolved": "https://registry.npmjs.org/@react-aria/collections/-/collections-3.0.0-rc.4.tgz", + "integrity": "sha512-efcQW/Kly5ebS2kWrVRBD7yEl3b0FdQE/dDL/87skVMW0Vh6AtUgCShZfcOcGAIqvG7m6QItdUHwAilDA61riQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.4", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.0", + "@react-types/shared": "^3.31.0", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/color": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@react-aria/color/-/color-3.1.1.tgz", + "integrity": "sha512-4+woybtn4kh5ytggWQ06bqqWsoucOrxwNrwW1XP6EmvcjIcsfVW+VwFwM5ZYa2LGF+fHiW3dM4bjRqVa7i9PVg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/numberfield": "^3.12.1", + "@react-aria/slider": "^3.8.1", + "@react-aria/spinbutton": "^3.6.18", + "@react-aria/textfield": "^3.18.1", + "@react-aria/utils": "^3.30.1", + "@react-aria/visually-hidden": "^3.8.27", + "@react-stately/color": "^3.9.1", + "@react-stately/form": "^3.2.1", + "@react-types/color": "^3.1.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/combobox": { + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/@react-aria/combobox/-/combobox-3.13.2.tgz", + "integrity": "sha512-PNyqlaM19A+lKX9hwqkKTXvWDilCKaRH2RdrB/C5AfmGi3bh/IKsu66c8ohgadXB2AIdJB36EOOm3hNh8G9DqQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/listbox": "^3.14.8", + "@react-aria/live-announcer": "^3.4.4", + "@react-aria/menu": "^3.19.2", + "@react-aria/overlays": "^3.29.1", + "@react-aria/selection": "^3.25.1", + "@react-aria/textfield": "^3.18.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/collections": "^3.12.7", + "@react-stately/combobox": "^3.11.1", + "@react-stately/form": "^3.2.1", + "@react-types/button": "^3.14.0", + "@react-types/combobox": "^3.13.8", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/datepicker": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.15.1.tgz", + "integrity": "sha512-RfUOvsupON6E5ZELpBgb9qxsilkbqwzsZ78iqCDTVio+5kc5G9jVeHEIQOyHnavi/TmJoAnbmmVpEbE6M9lYJQ==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.9.0", + "@internationalized/number": "^3.6.5", + "@internationalized/string": "^3.2.7", + "@react-aria/focus": "^3.21.1", + "@react-aria/form": "^3.1.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/label": "^3.7.21", + "@react-aria/spinbutton": "^3.6.18", + "@react-aria/utils": "^3.30.1", + "@react-stately/datepicker": "^3.15.1", + "@react-stately/form": "^3.2.1", + "@react-types/button": "^3.14.0", + "@react-types/calendar": "^3.7.4", + "@react-types/datepicker": "^3.13.1", + "@react-types/dialog": "^3.5.21", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/dialog": { + "version": "3.5.30", + "resolved": "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.5.30.tgz", + "integrity": "sha512-fiodaeMSTiC4qKNwnCLbNykyvfcxuz/PiU/pBNhWYd4lUrX1TauBQb0++o5/K6OHt8iB+A7/LSHRbPtyOSWE9g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/overlays": "^3.29.1", + "@react-aria/utils": "^3.30.1", + "@react-types/dialog": "^3.5.21", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/disclosure": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@react-aria/disclosure/-/disclosure-3.0.8.tgz", + "integrity": "sha512-Q2v6czm3ViMTw7J+GCWdXw3rZ5Fgmy97gpSQjpEoxSyqA1UfpRRvNa+XYoXmbpaY1MGhtUX3m2GgZ4IuhhMHVQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.1", + "@react-stately/disclosure": "^3.0.7", + "@react-types/button": "^3.14.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/dnd": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@react-aria/dnd/-/dnd-3.11.2.tgz", + "integrity": "sha512-xaIUV0zPtUTLIBoE7qlGFPfRTfyDJT78fDzawYq6FwZcjgrl8X408UDCUaKk6xSJRh9UjNn78hil1WDYTLFNWA==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/string": "^3.2.7", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/live-announcer": "^3.4.4", + "@react-aria/overlays": "^3.29.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/collections": "^3.12.7", + "@react-stately/dnd": "^3.7.0", + "@react-types/button": "^3.14.0", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/focus": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.1.tgz", + "integrity": "sha512-hmH1IhHlcQ2lSIxmki1biWzMbGgnhdxJUM0MFfzc71Rv6YAzhlx4kX3GYn4VNcjCeb6cdPv4RZ5vunV4kgMZYQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/form": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.1.1.tgz", + "integrity": "sha512-PjZC25UgH5orit9p56Ymbbo288F3eaDd3JUvD8SG+xgx302HhlFAOYsQLLAb4k4H03bp0gWtlUEkfX6KYcE1Tw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-stately/form": "^3.2.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/grid": { + "version": "3.14.4", + "resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.14.4.tgz", + "integrity": "sha512-l1FLQNKnoHpY4UClUTPUV0AqJ5bfAULEE0ErY86KznWLd+Hqzo7mHLqqDV02CDa/8mIUcdoax/MrYYIbPDlOZA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/live-announcer": "^3.4.4", + "@react-aria/selection": "^3.25.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/collections": "^3.12.7", + "@react-stately/grid": "^3.11.5", + "@react-stately/selection": "^3.20.5", + "@react-types/checkbox": "^3.10.1", + "@react-types/grid": "^3.3.5", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/gridlist": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@react-aria/gridlist/-/gridlist-3.14.0.tgz", + "integrity": "sha512-8NWDaUbPe6ujI+kSvDqr2onPYWlBXiaLCQ6nfYOo+GFKxeVCsv4a2I5HAAoGf9THNQ5b8b8kJa+M0xyL1Z71XA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/grid": "^3.14.4", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/selection": "^3.25.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/list": "^3.13.0", + "@react-stately/tree": "^3.9.2", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/i18n": { + "version": "3.12.12", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.12.12.tgz", + "integrity": "sha512-JN6p+Xc6Pu/qddGRoeYY6ARsrk2Oz7UiQc9nLEPOt3Ch+blJZKWwDjcpo/p6/wVZdD/2BgXS7El6q6+eMg7ibw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.9.0", + "@internationalized/message": "^3.1.8", + "@internationalized/number": "^3.6.5", + "@internationalized/string": "^3.2.7", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.25.5", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.5.tgz", + "integrity": "sha512-EweYHOEvMwef/wsiEqV73KurX/OqnmbzKQa2fLxdULbec5+yDj6wVGaRHIzM4NiijIDe+bldEl5DG05CAKOAHA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.1", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/label": { + "version": "3.7.21", + "resolved": "https://registry.npmjs.org/@react-aria/label/-/label-3.7.21.tgz", + "integrity": "sha512-8G+059/GZahgQbrhMcCcVcrjm7W+pfzrypH/Qkjo7C1yqPGt6geeFwWeOIbiUZoI0HD9t9QvQPryd6m46UC7Tg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/landmark": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@react-aria/landmark/-/landmark-3.0.6.tgz", + "integrity": "sha512-dMPBqJWTDAr3Lj5hA+XYDH2PWqtFghYy+y7iq7K5sK/96cub8hZEUjhwn+HGgHsLerPp0dWt293nKupAJnf4Vw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/link": { + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.8.5.tgz", + "integrity": "sha512-klhV4roPp5MLRXJv1N+7SXOj82vx4gzVpuwQa3vouA+YI1my46oNzwgtkLGSTvE9OvDqYzPDj2YxFYhMywrkuw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-types/link": "^3.6.4", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/listbox": { + "version": "3.14.8", + "resolved": "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.14.8.tgz", + "integrity": "sha512-uRgbuD9afFv0PDhQ/VXCmAwlYctIyKRzxztkqp1p/1yz/tn/hs+bG9kew9AI02PtlRO1mSc+32O+mMDXDer8hA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/label": "^3.7.21", + "@react-aria/selection": "^3.25.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/collections": "^3.12.7", + "@react-stately/list": "^3.13.0", + "@react-types/listbox": "^3.7.3", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/live-announcer": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/@react-aria/live-announcer/-/live-announcer-3.4.4.tgz", + "integrity": "sha512-PTTBIjNRnrdJOIRTDGNifY2d//kA7GUAwRFJNOEwSNG4FW+Bq9awqLiflw0JkpyB0VNIwou6lqKPHZVLsGWOXA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/menu": { + "version": "3.19.2", + "resolved": "https://registry.npmjs.org/@react-aria/menu/-/menu-3.19.2.tgz", + "integrity": "sha512-WzDLW2MotL0L5/LEwc5oGgISf2ODuw4FnRpF0Zk+J4tKFfC88odvKz848ubBvThRXuXEvL0BHY+WqtM+j9fn3g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/overlays": "^3.29.1", + "@react-aria/selection": "^3.25.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/collections": "^3.12.7", + "@react-stately/menu": "^3.9.7", + "@react-stately/selection": "^3.20.5", + "@react-stately/tree": "^3.9.2", + "@react-types/button": "^3.14.0", + "@react-types/menu": "^3.10.4", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/meter": { + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@react-aria/meter/-/meter-3.4.26.tgz", + "integrity": "sha512-BI+Ri0dkhx9jjf6yPbOLl69M6808Fi08KNEmserMEapy++5usB/8krh9ARuR0GZYUPFOcny0Ml0or/HqamyFvw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/progress": "^3.4.26", + "@react-types/meter": "^3.4.12", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/numberfield": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@react-aria/numberfield/-/numberfield-3.12.1.tgz", + "integrity": "sha512-3KjxGgWiF4GRvIyqrE3nCndkkEJ68v86y0nx89TpAjdzg7gCgdXgU2Lr4BhC/xImrmlqCusw0IBUMhsEq9EQWA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/spinbutton": "^3.6.18", + "@react-aria/textfield": "^3.18.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/form": "^3.2.1", + "@react-stately/numberfield": "^3.10.1", + "@react-types/button": "^3.14.0", + "@react-types/numberfield": "^3.8.14", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/overlays": { + "version": "3.29.1", + "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.29.1.tgz", + "integrity": "sha512-Yz92XNPnbrTnxrvNrY/fXJ3iWaYNrj0q24ddvZNNKDcWak0S1/mQeUwNb+PwS2AryhFU5VQqKz5rNsM96TKmPQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.1", + "@react-aria/visually-hidden": "^3.8.27", + "@react-stately/overlays": "^3.6.19", + "@react-types/button": "^3.14.0", + "@react-types/overlays": "^3.9.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/progress": { + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@react-aria/progress/-/progress-3.4.26.tgz", + "integrity": "sha512-EJBzbE0IjXrJ19ofSyNKDnqC70flUM0Z+9heMRPLi6Uz01o6Uuz9tjyzmoPnd9Q1jnTT7dCl7ydhdYTGsWFcUg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/label": "^3.7.21", + "@react-aria/utils": "^3.30.1", + "@react-types/progress": "^3.5.15", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/radio": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@react-aria/radio/-/radio-3.12.1.tgz", + "integrity": "sha512-feZdMJyNp+UX03seIX0W6gdUk8xayTY+U0Ct61eci6YXzyyZoL2PVh49ojkbyZ2UZA/eXeygpdF5sgQrKILHCA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/form": "^3.1.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/label": "^3.7.21", + "@react-aria/utils": "^3.30.1", + "@react-stately/radio": "^3.11.1", + "@react-types/radio": "^3.9.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/searchfield": { + "version": "3.8.8", + "resolved": "https://registry.npmjs.org/@react-aria/searchfield/-/searchfield-3.8.8.tgz", + "integrity": "sha512-Yn6esCYEym3Cwrh/OZt6o/RFzsG2zyCAEZf7BhWk6NWUvP6aPwHgoSDVSjDN6YnnPn4yMqkqPnZulHV4+MvE/w==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/textfield": "^3.18.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/searchfield": "^3.5.15", + "@react-types/button": "^3.14.0", + "@react-types/searchfield": "^3.6.5", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/select": { + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/@react-aria/select/-/select-3.16.2.tgz", + "integrity": "sha512-MwsOJ6FfPxzrLP6spnYg2SUeGKNm4m5vyH6GebecLxTO1ee7/YyTNP1xkrQTqPMP9xx6uqhzFLFuCym2b6ripA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/form": "^3.1.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/label": "^3.7.21", + "@react-aria/listbox": "^3.14.8", + "@react-aria/menu": "^3.19.2", + "@react-aria/selection": "^3.25.1", + "@react-aria/utils": "^3.30.1", + "@react-aria/visually-hidden": "^3.8.27", + "@react-stately/select": "^3.7.1", + "@react-types/button": "^3.14.0", + "@react-types/select": "^3.10.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/selection": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.25.1.tgz", + "integrity": "sha512-HG+k3rDjuhnXPdVyv9CKiebee2XNkFYeYZBxEGlK3/pFVBzndnc8BXNVrXSgtCHLs2d090JBVKl1k912BPbj0Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-stately/selection": "^3.20.5", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/separator": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/@react-aria/separator/-/separator-3.4.12.tgz", + "integrity": "sha512-rvFCPdOPMQKY/Bpv2jNzXtetCuBLYSRCvpzam1LpMaEgwau5yECbId66+M2UX/cscPccKNU537SM6ei2j7RGog==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/slider": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-aria/slider/-/slider-3.8.1.tgz", + "integrity": "sha512-uPgwZQrcuqHaLU2prJtPEPIyN9ugZ7qGgi0SB2U8tvoODNVwuPvOaSsvR98Mn6jiAzMFNoWMydeIi+J1OjvWsQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/label": "^3.7.21", + "@react-aria/utils": "^3.30.1", + "@react-stately/slider": "^3.7.1", + "@react-types/shared": "^3.32.0", + "@react-types/slider": "^3.8.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/spinbutton": { + "version": "3.6.18", + "resolved": "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.18.tgz", + "integrity": "sha512-dnmh7sNsprhYTpqCJhcuc9QJ9C/IG/o9TkgW5a9qcd2vS+dzEgqAiJKIMbJFG9kiJymv2NwIPysF12IWix+J3A==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/live-announcer": "^3.4.4", + "@react-aria/utils": "^3.30.1", + "@react-types/button": "^3.14.0", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/switch": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.7.7.tgz", + "integrity": "sha512-auV3g1qh+d/AZk7Idw2BOcYeXfCD9iDaiGmlcLJb9Eaz4nkq8vOkQxIXQFrn9Xhb+PfQzmQYKkt5N6P2ZNsw/g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/toggle": "^3.12.1", + "@react-stately/toggle": "^3.9.1", + "@react-types/shared": "^3.32.0", + "@react-types/switch": "^3.5.14", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/table": { + "version": "3.17.7", + "resolved": "https://registry.npmjs.org/@react-aria/table/-/table-3.17.7.tgz", + "integrity": "sha512-FxXryGTxePgh8plIxlOMwXdleGWjK52vsmbRoqz66lTIHMUMLTmmm+Y0V3lBOIoaW1rxvKcolYgS79ROnbDYBw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/grid": "^3.14.4", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/live-announcer": "^3.4.4", + "@react-aria/utils": "^3.30.1", + "@react-aria/visually-hidden": "^3.8.27", + "@react-stately/collections": "^3.12.7", + "@react-stately/flags": "^3.1.2", + "@react-stately/table": "^3.15.0", + "@react-types/checkbox": "^3.10.1", + "@react-types/grid": "^3.3.5", + "@react-types/shared": "^3.32.0", + "@react-types/table": "^3.13.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/tabs": { + "version": "3.10.7", + "resolved": "https://registry.npmjs.org/@react-aria/tabs/-/tabs-3.10.7.tgz", + "integrity": "sha512-iA1M6H+N+9GggsEy/6MmxpMpeOocwYgFy2EoEl3it24RVccY6iZT4AweJq96s5IYga5PILpn7VVcpssvhkPgeA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/selection": "^3.25.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/tabs": "^3.8.5", + "@react-types/shared": "^3.32.0", + "@react-types/tabs": "^3.3.18", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/tag": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-aria/tag/-/tag-3.7.1.tgz", + "integrity": "sha512-VpF26ez+QmEzTK8E9tXZ4cofa1wocjnIo/Bd1LCXgLCytnHAkYGxeIRm5QbznJ0aF/9UgR1QtMqhyRrCZg9QqA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/gridlist": "^3.14.0", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/label": "^3.7.21", + "@react-aria/selection": "^3.25.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/list": "^3.13.0", + "@react-types/button": "^3.14.0", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/textfield": { + "version": "3.18.1", + "resolved": "https://registry.npmjs.org/@react-aria/textfield/-/textfield-3.18.1.tgz", + "integrity": "sha512-8yCoirnQzbbQgdk5J5bqimEu3GhHZ9FXeMHez1OF+H+lpTwyTYQ9XgioEN3HKnVUBNEufG4lYkQMxTKJdq1v9g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/form": "^3.1.1", + "@react-aria/interactions": "^3.25.5", + "@react-aria/label": "^3.7.21", + "@react-aria/utils": "^3.30.1", + "@react-stately/form": "^3.2.1", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.0", + "@react-types/textfield": "^3.12.5", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toast": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@react-aria/toast/-/toast-3.0.7.tgz", + "integrity": "sha512-nuxPQ7wcSTg9UNMhXl9Uwyc5you/D1RfwymI3VDa5OGTZdJOmV2j94nyjBfMO2168EYMZjw+wEovvOZphs2Pbw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/landmark": "^3.0.6", + "@react-aria/utils": "^3.30.1", + "@react-stately/toast": "^3.1.2", + "@react-types/button": "^3.14.0", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toggle": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.12.1.tgz", + "integrity": "sha512-XaFiRs1KEcIT6bTtVY/KTQxw4kinemj/UwXw2iJTu9XS43hhJ/9cvj8KzNGrKGqaxTpOYj62TnSHZbSiFViHDA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-stately/toggle": "^3.9.1", + "@react-types/checkbox": "^3.10.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/toolbar": { + "version": "3.0.0-beta.20", + "resolved": "https://registry.npmjs.org/@react-aria/toolbar/-/toolbar-3.0.0-beta.20.tgz", + "integrity": "sha512-Kxvqw+TpVOE/eSi8RAQ9xjBQ2uXe8KkRvlRNQWQsrzkZDkXhzqGfQuJnBmozFxqpzSLwaVqQajHFUSvPAScT8Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/tooltip": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@react-aria/tooltip/-/tooltip-3.8.7.tgz", + "integrity": "sha512-Aj7DPJYGZ9/+2ZfhkvbN7YMeA5qu4oy4LVQiMCpqNwcFzvhTAVhN7J7cS6KjA64fhd1shKm3BZ693Ez6lSpqwg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-stately/tooltip": "^3.5.7", + "@react-types/shared": "^3.32.0", + "@react-types/tooltip": "^3.4.20", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/tree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@react-aria/tree/-/tree-3.1.3.tgz", + "integrity": "sha512-CWjIvJS540Kzzxs1f4fF0ajPUfYoeptcA6MmXHBlCKE2euRSvKW6F1ZhvLVq81YsYWuAfBKnG2/JsTgBZnGPVQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/gridlist": "^3.14.0", + "@react-aria/i18n": "^3.12.12", + "@react-aria/selection": "^3.25.1", + "@react-aria/utils": "^3.30.1", + "@react-stately/tree": "^3.9.2", + "@react-types/button": "^3.14.0", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.30.1", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.30.1.tgz", + "integrity": "sha512-zETcbDd6Vf9GbLndO6RiWJadIZsBU2MMm23rBACXLmpRztkrIqPEb2RVdlLaq1+GklDx0Ii6PfveVjx+8S5U6A==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/virtualizer": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@react-aria/virtualizer/-/virtualizer-4.1.9.tgz", + "integrity": "sha512-LN5MfnM/fpZegzkqciipyAvPzbi4DNOGGCh98hVlpIT8IdTm0gNW1Ho2vza15EFcYgt9iinCZ9lhLT5HmE2ZtQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-stately/virtualizer": "^4.4.3", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/visually-hidden": { + "version": "3.8.27", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.27.tgz", + "integrity": "sha512-hD1DbL3WnjPnCdlQjwe19bQVRAGJyN0Aaup+s7NNtvZUn7AjoEH78jo8TE+L8yM7z/OZUQF26laCfYqeIwWn4g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/accordion": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@react-spectrum/accordion/-/accordion-3.0.11.tgz", + "integrity": "sha512-+MjhcaEu0uFOywaSsDngxJw3t1Z4uoapsBPqKBy66XcL859SDnYZ1dTb6R0JOvTwsx/0smToFPWP+mLb3HTo8g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-spectrum/utils": "^3.12.8", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0", + "react-aria-components": "^1.12.1" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/actionbar": { + "version": "3.6.12", + "resolved": "https://registry.npmjs.org/@react-spectrum/actionbar/-/actionbar-3.6.12.tgz", + "integrity": "sha512-nWdoMaHgqmEoAhZxaGaVnn/SoWRbrf3LLeFilupl7137znnTTk+x0PaVePNeyXSyXU5Jr5thoK/PVcwR0SZZCQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/live-announcer": "^3.4.4", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/actiongroup": "^3.11.2", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/overlays": "^5.8.2", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/collections": "^3.12.7", + "@react-types/actionbar": "^3.1.18", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/actiongroup": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@react-spectrum/actiongroup/-/actiongroup-3.11.2.tgz", + "integrity": "sha512-TgH0eLGcEvrEv7NL71gYUUru++NVcg6CjRtDh5nmOHrrAktLR0sBAoi1sZkMf7b4ad+wi4MZOWT8yfcpscRzXw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/actiongroup": "^3.7.20", + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/menu": "^3.22.6", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/tooltip": "^3.7.10", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/collections": "^3.12.7", + "@react-stately/list": "^3.13.0", + "@react-types/actiongroup": "^3.4.20", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@spectrum-icons/workflow": "^4.2.24", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.2.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/avatar": { + "version": "3.0.25", + "resolved": "https://registry.npmjs.org/@react-spectrum/avatar/-/avatar-3.0.25.tgz", + "integrity": "sha512-HB+yPU5UzP4P4sVofvSUP0/BjEaxdOncZ8cdjmRHDE7uPOH1qqf0O/XYCWByuCsjcqwW010aLTddHNU0Yjj5gQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/avatar": "^3.0.18", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.2.1", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/badge": { + "version": "3.1.28", + "resolved": "https://registry.npmjs.org/@react-spectrum/badge/-/badge-3.1.28.tgz", + "integrity": "sha512-D43ysp1ODLGPrnHVQewApw8OwxTpM8+Wl4sHqpx6POS8bj0tYggSgU+opnS2hKWJN9Asq+V2aX2AG3euprZahw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/utils": "^3.12.8", + "@react-types/badge": "^3.1.20", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/breadcrumbs": { + "version": "3.9.22", + "resolved": "https://registry.npmjs.org/@react-spectrum/breadcrumbs/-/breadcrumbs-3.9.22.tgz", + "integrity": "sha512-U2j7ZSD26PsYPFYVeKeEUDENGZBYO3+Cgh2L+0aV/gwa9Eo+rxbBmAtDGTt8zJOCU90Qxgv/up70j6KSNl/U2g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/breadcrumbs": "^3.5.28", + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/menu": "^3.22.6", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/collections": "^3.12.7", + "@react-types/breadcrumbs": "^3.7.16", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/button": { + "version": "3.17.2", + "resolved": "https://registry.npmjs.org/@react-spectrum/button/-/button-3.17.2.tgz", + "integrity": "sha512-ncBMSM1QuPzXapV/2P0jtCt2KuGjnTcmv+MqKvdDXjbBo7FR2UpMJsBlMrZZl3qTYMnBV8Lah3kXwy9o80OkTg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/button": "^3.14.1", + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/progress": "^3.7.19", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/toggle": "^3.9.1", + "@react-types/button": "^3.14.0", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/buttongroup": { + "version": "3.6.25", + "resolved": "https://registry.npmjs.org/@react-spectrum/buttongroup/-/buttongroup-3.6.25.tgz", + "integrity": "sha512-baqjq6JQc+nKVHUCplpML1PGmLqR5UObDMaLDDf6KS7f7uv0LSfj+6V/mQHBGyWlAY7dg/3H3U5954IlIIQl+g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/buttongroup": "^3.3.20", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/calendar": { + "version": "3.7.6", + "resolved": "https://registry.npmjs.org/@react-spectrum/calendar/-/calendar-3.7.6.tgz", + "integrity": "sha512-PR56wN2sSSFxZxfJYG8QTsXCTkMoDGeIugNibua7rjvlk5rCV6jP4+9qddS/bvtzGcxQWwSl0XhKqYbZv5JGhw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.9.0", + "@react-aria/calendar": "^3.9.1", + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-aria/visually-hidden": "^3.8.27", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/label": "^3.16.18", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/calendar": "^3.8.4", + "@react-types/button": "^3.14.0", + "@react-types/calendar": "^3.7.4", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/checkbox": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@react-spectrum/checkbox/-/checkbox-3.10.2.tgz", + "integrity": "sha512-zJz2tnI2ponYyUiZ04vJZDSRgIeDJ5wIeHWw1RW4jZxAogSGB8QHNHFi5HP3kvNu8fCtw7PzvDkts2DCQXrzzw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/checkbox": "^3.16.1", + "@react-aria/focus": "^3.21.1", + "@react-aria/interactions": "^3.25.5", + "@react-spectrum/form": "^3.7.18", + "@react-spectrum/label": "^3.16.18", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/checkbox": "^3.7.1", + "@react-stately/toggle": "^3.9.1", + "@react-types/checkbox": "^3.10.1", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0", + "react-aria-components": "^1.12.1" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-spectrum/color/-/color-3.1.2.tgz", + "integrity": "sha512-AVdG1ybiMCavgV9wBJ9yRLF9SXRGtCBbY7JsznLLT2x72+Zf2AUYHqmhEuX0dYB5cxV6OMkAhU9ay0YCNujUqw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/color": "^3.1.1", + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/dialog": "^3.9.2", + "@react-spectrum/form": "^3.7.18", + "@react-spectrum/label": "^3.16.18", + "@react-spectrum/overlays": "^5.8.2", + "@react-spectrum/picker": "^3.16.2", + "@react-spectrum/textfield": "^3.14.1", + "@react-spectrum/utils": "^3.12.8", + "@react-spectrum/view": "^3.6.22", + "@react-stately/color": "^3.9.1", + "@react-types/color": "^3.1.1", + "@react-types/shared": "^3.32.0", + "@react-types/textfield": "^3.12.5", + "@swc/helpers": "^0.5.0", + "react-aria-components": "^1.12.1" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/combobox": { + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/@react-spectrum/combobox/-/combobox-3.16.2.tgz", + "integrity": "sha512-/fe8E4e6NVtlOJBCi8rDgJYDYzEtqa/16q4vFRgxLomQUhi9bhBxBUtaF157wqZIwoeUSp7YwfnV7GOoZRT5ag==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/button": "^3.14.1", + "@react-aria/combobox": "^3.13.2", + "@react-aria/dialog": "^3.5.30", + "@react-aria/focus": "^3.21.1", + "@react-aria/form": "^3.1.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/label": "^3.7.21", + "@react-aria/overlays": "^3.29.1", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/form": "^3.7.18", + "@react-spectrum/label": "^3.16.18", + "@react-spectrum/listbox": "^3.15.6", + "@react-spectrum/overlays": "^5.8.2", + "@react-spectrum/progress": "^3.7.19", + "@react-spectrum/textfield": "^3.14.1", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/collections": "^3.12.7", + "@react-stately/combobox": "^3.11.1", + "@react-types/button": "^3.14.0", + "@react-types/combobox": "^3.13.8", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/contextualhelp": { + "version": "3.6.26", + "resolved": "https://registry.npmjs.org/@react-spectrum/contextualhelp/-/contextualhelp-3.6.26.tgz", + "integrity": "sha512-CMxod+S/pxsf/uFz6Hd/2tOc4IkTq8FZ+sGjcaK30rFb6H+tj3rukjBElmW494mXLbo4NBmk4AbaDx8MOyvqoA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/dialog": "^3.9.2", + "@react-spectrum/utils": "^3.12.8", + "@react-types/contextualhelp": "^3.2.21", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/workflow": "^4.2.24", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/datepicker": { + "version": "3.14.6", + "resolved": "https://registry.npmjs.org/@react-spectrum/datepicker/-/datepicker-3.14.6.tgz", + "integrity": "sha512-wnz6jye/moHsC3ZybclUSxb75wvmiQ4qJKs8+KMQV2gn6FnXLK3ktSwiBbwWO8vvqeu7Snl4scWxY9uDAfUoCg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.9.0", + "@react-aria/datepicker": "^3.15.1", + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/calendar": "^3.7.6", + "@react-spectrum/dialog": "^3.9.2", + "@react-spectrum/form": "^3.7.18", + "@react-spectrum/label": "^3.16.18", + "@react-spectrum/layout": "^3.6.18", + "@react-spectrum/utils": "^3.12.8", + "@react-spectrum/view": "^3.6.22", + "@react-stately/datepicker": "^3.15.1", + "@react-types/datepicker": "^3.13.1", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@spectrum-icons/workflow": "^4.2.24", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/dialog": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@react-spectrum/dialog/-/dialog-3.9.2.tgz", + "integrity": "sha512-72MK9xvoXdgXsWUtCYOvg2PYJFtVHGXqPw/a0cH2b9B1Mh81GQbhtdMt461NpwUgpahITbq/0scAWDAsMEdTCQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/dialog": "^3.5.30", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/overlays": "^3.29.1", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/buttongroup": "^3.6.25", + "@react-spectrum/divider": "^3.5.26", + "@react-spectrum/layout": "^3.6.18", + "@react-spectrum/overlays": "^5.8.2", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/utils": "^3.12.8", + "@react-spectrum/view": "^3.6.22", + "@react-stately/overlays": "^3.6.19", + "@react-types/button": "^3.14.0", + "@react-types/dialog": "^3.5.21", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/divider": { + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@react-spectrum/divider/-/divider-3.5.26.tgz", + "integrity": "sha512-kARPhf5/RFDf6E6zkxYfGsDZUQKuF2wV7KlrZLxtNif4coR/hzInh3/84pXVa5WpM3+4S/9WbCf+h+mXdEOSYw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/separator": "^3.4.12", + "@react-spectrum/utils": "^3.12.8", + "@react-types/divider": "^3.3.20", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/dnd": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/dnd/-/dnd-3.6.1.tgz", + "integrity": "sha512-Qfk1lhi9hKgBEw6le5MOCrYfsJaY+hvHdmE3k91S6RExiUpUkzRlgPIoYymyfTXhGMLmtiXq5/dT04qDrbX+zA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/dnd": "^3.11.2", + "@react-stately/dnd": "^3.7.0", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/dropzone": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/@react-spectrum/dropzone/-/dropzone-3.0.16.tgz", + "integrity": "sha512-GHI+bJRTojk1R0DxWkoXZVki1sqUrxLA7tw476+wqilxZs0Ogl7JQv4LOjGpg+hr0rrEZbCiKWjSfTdM1ncOvw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0", + "react-aria-components": "^1.12.1" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/filetrigger": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/@react-spectrum/filetrigger/-/filetrigger-3.0.16.tgz", + "integrity": "sha512-pKl2oQJjzJ9ClRkzNfPpMmcFMonLMRfGOhVI1RMo+NzA9MfqwpLTGxXFvm0jp6v4t7qEI0byeoMDE8ugfqv5wg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "react-aria-components": "^1.12.1" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/form": { + "version": "3.7.18", + "resolved": "https://registry.npmjs.org/@react-spectrum/form/-/form-3.7.18.tgz", + "integrity": "sha512-DxOOpHZ47SYhD3pBa7IvGQl/jsztvNUseG02YiOV8mH8/YVeSssP65sQ9qI0V6bL3blOYgx+NzAi2FGXDUjQEQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/form": "^3.2.1", + "@react-types/form": "^3.7.15", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/icon": { + "version": "3.8.8", + "resolved": "https://registry.npmjs.org/@react-spectrum/icon/-/icon-3.8.8.tgz", + "integrity": "sha512-zLvkBa8s0TFEk05kpwN17bITBn4feYfknfXsk/Jg5APr71mMm3k4Aeg2nLQnMksl2y78PQJYn81cJ8winr3GVw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/illustratedmessage": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@react-spectrum/illustratedmessage/-/illustratedmessage-3.5.13.tgz", + "integrity": "sha512-5lFIT/TsY459747qH+rx3wdiz0e3tOWGVBlcbnhvyPExIXYs5+HS9cCzl4pUMfiGJImp51oBlk8J30KxoqpQ4g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/layout": "^3.6.18", + "@react-spectrum/utils": "^3.12.8", + "@react-types/illustratedmessage": "^3.3.20", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/image": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/image/-/image-3.6.1.tgz", + "integrity": "sha512-ykd92BifEneJelQR+LMyo/18lBUcdKr3ZZRW+Xc0L9Q7J8x3tbZex3uivOuFaa+Rl2tyTSsC84OaCfwGQ0D32A==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/image": "^3.5.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/inlinealert": { + "version": "3.2.19", + "resolved": "https://registry.npmjs.org/@react-spectrum/inlinealert/-/inlinealert-3.2.19.tgz", + "integrity": "sha512-X/ZM79JpXrNEBUFbqm+t5fZHguSPNqyvGoX+bxRpD0xkxSuDMsHewF+CCjA0HYY6TElqO5yJE0G5FCoR11L9NQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/layout": "^3.6.18", + "@react-spectrum/utils": "^3.12.8", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/label": { + "version": "3.16.18", + "resolved": "https://registry.npmjs.org/@react-spectrum/label/-/label-3.16.18.tgz", + "integrity": "sha512-qLAWtm42u3Lh2jyz0JNFklOzt0P+xu34cSdbzrqA1jhaA6Q5QIy6MqXKawlIhIVLZBaHyC+CAksttPB7Qm8g2A==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/form": "^3.7.18", + "@react-spectrum/layout": "^3.6.18", + "@react-spectrum/utils": "^3.12.8", + "@react-types/label": "^3.9.14", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/labeledvalue": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@react-spectrum/labeledvalue/-/labeledvalue-3.2.6.tgz", + "integrity": "sha512-p+t7nC49RNEC9EH70yWqmNRtKstQGjU0FaZX/XTeZnWH9/SuyOH6bbB7m54emV54+EKA+cxN3ACdg2mA0FFu8Q==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.9.0", + "@react-aria/i18n": "^3.12.12", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/label": "^3.16.18", + "@react-spectrum/utils": "^3.12.8", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/layout": { + "version": "3.6.18", + "resolved": "https://registry.npmjs.org/@react-spectrum/layout/-/layout-3.6.18.tgz", + "integrity": "sha512-wLQY0AIpYNRxdCBfItXhw6aXto8h7f31uPr1sY33GqDRTGfAovhceE8hX5KfbUYBXvGB6JsYkmU0HgNrX3DoYw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/layout": "^3.3.26", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/link": { + "version": "3.6.21", + "resolved": "https://registry.npmjs.org/@react-spectrum/link/-/link-3.6.21.tgz", + "integrity": "sha512-H/pafrLNL18CFlnBnHz2vEDwE6cfhp6RsD3a7Uzkm8FcCZE4imSuuy6ZmUJtE62vEktRQaAcXVVW2caF0X5X0w==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/interactions": "^3.25.5", + "@react-aria/link": "^3.8.5", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/link": "^3.6.4", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/list": { + "version": "3.10.6", + "resolved": "https://registry.npmjs.org/@react-spectrum/list/-/list-3.10.6.tgz", + "integrity": "sha512-6eB/wdT/HjAz7s8HkY8ZE2hguYrWQX46kIaN2PsJKJK5kWAE0dXyiHHOeC1PJd5bp5AENvULMaW6La7ZVEFyJg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/button": "^3.14.1", + "@react-aria/focus": "^3.21.1", + "@react-aria/gridlist": "^3.14.0", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/selection": "^3.25.1", + "@react-aria/utils": "^3.30.1", + "@react-aria/virtualizer": "^4.1.9", + "@react-aria/visually-hidden": "^3.8.27", + "@react-spectrum/checkbox": "^3.10.2", + "@react-spectrum/dnd": "^3.6.1", + "@react-spectrum/layout": "^3.6.18", + "@react-spectrum/progress": "^3.7.19", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/collections": "^3.12.7", + "@react-stately/layout": "^4.5.0", + "@react-stately/list": "^3.13.0", + "@react-stately/virtualizer": "^4.4.3", + "@react-types/grid": "^3.3.5", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.2.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/listbox": { + "version": "3.15.6", + "resolved": "https://registry.npmjs.org/@react-spectrum/listbox/-/listbox-3.15.6.tgz", + "integrity": "sha512-VbdyX48RMcgGsGJIcfDs/g5H3ijfTWGX3u6TrNsBPH1DoHcZNu+sJCp90VbxcYEm5H8WGcgLLUFZu7soaq9T1Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/listbox": "^3.14.8", + "@react-aria/utils": "^3.30.1", + "@react-aria/virtualizer": "^4.1.9", + "@react-spectrum/layout": "^3.6.18", + "@react-spectrum/progress": "^3.7.19", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/collections": "^3.12.7", + "@react-stately/layout": "^4.5.0", + "@react-stately/list": "^3.13.0", + "@react-stately/virtualizer": "^4.4.3", + "@react-types/listbox": "^3.7.3", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.2.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/menu": { + "version": "3.22.6", + "resolved": "https://registry.npmjs.org/@react-spectrum/menu/-/menu-3.22.6.tgz", + "integrity": "sha512-QbKOnZ6tR+vKn4CyJEVMba5ojAlXwbVdtkdmyo92MPoYYCPv0Mt8wMjhP2wBpEVzuvbKviSs8Ols8XUZ322cKg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/menu": "^3.19.2", + "@react-aria/overlays": "^3.29.1", + "@react-aria/separator": "^3.4.12", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/layout": "^3.6.18", + "@react-spectrum/overlays": "^5.8.2", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/collections": "^3.12.7", + "@react-stately/menu": "^3.9.7", + "@react-stately/overlays": "^3.6.19", + "@react-stately/tree": "^3.9.2", + "@react-types/menu": "^3.10.4", + "@react-types/overlays": "^3.9.1", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@spectrum-icons/workflow": "^4.2.24", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/meter": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@react-spectrum/meter/-/meter-3.5.13.tgz", + "integrity": "sha512-1BQdkc3USZz69mMsaCjmk/J23Z5IylNzOnuMeRDUoYXXBSHgItxfzDKC43AKYUVQX7g/s07ga/SzKstEDHCL2g==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/meter": "^3.4.26", + "@react-spectrum/progress": "^3.7.19", + "@react-spectrum/utils": "^3.12.8", + "@react-types/meter": "^3.4.12", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/numberfield": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/numberfield/-/numberfield-3.10.1.tgz", + "integrity": "sha512-lafIkVBF6LnzcSl15sNcb1aU2pGVu+npm0WCwZ+zbG5PmSGqHrucagbv3ifpmOWfdpG3c1HaRRbqFxMeEOiPMg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/button": "^3.14.1", + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/numberfield": "^3.12.1", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/form": "^3.7.18", + "@react-spectrum/label": "^3.16.18", + "@react-spectrum/textfield": "^3.14.1", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/numberfield": "^3.10.1", + "@react-types/button": "^3.14.0", + "@react-types/numberfield": "^3.8.14", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@spectrum-icons/workflow": "^4.2.24", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/overlays": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/@react-spectrum/overlays/-/overlays-5.8.2.tgz", + "integrity": "sha512-hH2gvoGet0iSzy1sfcgXf6WipjjPZloEMy7dtbE4OFmzdtcPKgehMwLZNFZFWG1+eK8ZAALDQOxLiuIe4PgYOQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/overlays": "^3.29.1", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/overlays": "^3.6.19", + "@react-types/overlays": "^3.9.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/picker": { + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/@react-spectrum/picker/-/picker-3.16.2.tgz", + "integrity": "sha512-HclBB/cNeEdN4R5zUc3tg9ChlMQr734Pe+90DyDWjX1A6G5I6dowrKFHGW0xWO+sFfKuOqQagr8c6Yi5VvkeCA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/select": "^3.16.2", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/form": "^3.7.18", + "@react-spectrum/label": "^3.16.18", + "@react-spectrum/listbox": "^3.15.6", + "@react-spectrum/overlays": "^5.8.2", + "@react-spectrum/progress": "^3.7.19", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/collections": "^3.12.7", + "@react-stately/select": "^3.7.1", + "@react-types/select": "^3.10.1", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.1.4", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/progress": { + "version": "3.7.19", + "resolved": "https://registry.npmjs.org/@react-spectrum/progress/-/progress-3.7.19.tgz", + "integrity": "sha512-bCwFrbHrdfEMKS0IUkTLFnY09Io9UmuUIq6/mzuqAH791SalnlnsiWZmsTFK6uSuqvTSU9A2ltUq7/CYOY/YUg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/progress": "^3.4.26", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/progress": "^3.5.15", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/provider": { + "version": "3.10.10", + "resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.10.10.tgz", + "integrity": "sha512-wXlDbFvHgFrPvK6Zhx9IgJLANOXLAshUr+VMJHZ7Tfi9jeylcVirBJA/YyEJOfDgtL+SVEYBpoOecyN/SHLccg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/overlays": "^3.29.1", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/provider": "^3.8.12", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/radio": { + "version": "3.7.20", + "resolved": "https://registry.npmjs.org/@react-spectrum/radio/-/radio-3.7.20.tgz", + "integrity": "sha512-JmQsaiDDcKdj+UhK5Pk7kNMUMY6ufxf5woSekbVneIX4pquykF2Nq/I48ahQuny7AplehdSZbcWPoAYKpbmlig==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/interactions": "^3.25.5", + "@react-aria/radio": "^3.12.1", + "@react-spectrum/form": "^3.7.18", + "@react-spectrum/label": "^3.16.18", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/radio": "^3.11.1", + "@react-types/radio": "^3.9.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/searchfield": { + "version": "3.8.21", + "resolved": "https://registry.npmjs.org/@react-spectrum/searchfield/-/searchfield-3.8.21.tgz", + "integrity": "sha512-kby5+Pwtp2sMbmNMXxggGjBRif80GWMbqIG1ssYZaFGs/MKrNd2vBzrY4ZuSpH2w7ICK+fR+vT/KeMyIo6fO8Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/searchfield": "^3.8.8", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/form": "^3.7.18", + "@react-spectrum/textfield": "^3.14.1", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/searchfield": "^3.5.15", + "@react-types/searchfield": "^3.6.5", + "@react-types/textfield": "^3.12.5", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/slider": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/slider/-/slider-3.8.1.tgz", + "integrity": "sha512-amYY3kAtUFdycmexTcSyZtqNCnst033RLTWJT6ucW8rYZfi4QpQxjsckmzVWhKd3tmM9ltW+BCmDD3jdWwa+2A==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/slider": "^3.8.1", + "@react-aria/utils": "^3.30.1", + "@react-aria/visually-hidden": "^3.8.27", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/slider": "^3.7.1", + "@react-types/shared": "^3.32.0", + "@react-types/slider": "^3.8.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/statuslight": { + "version": "3.5.25", + "resolved": "https://registry.npmjs.org/@react-spectrum/statuslight/-/statuslight-3.5.25.tgz", + "integrity": "sha512-9zQ4VMXjCUVtYEtNE3x1wocepyAflzMsd7Ryqh5F6iEuJh8QV9x/Yv9gAoR3CNnznE3Asyk60Ztk7k20eLoIgQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/shared": "^3.32.0", + "@react-types/statuslight": "^3.3.20", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/switch": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@react-spectrum/switch/-/switch-3.6.5.tgz", + "integrity": "sha512-PgkqmRH4/pPY5CfJWghiVDffnd02PZkE5Z4G4eASMAIsSq4FL2j3npWjdga31BvN1TYMfgVu3/UP8iZblN+rVQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/interactions": "^3.25.5", + "@react-aria/switch": "^3.7.7", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/toggle": "^3.9.1", + "@react-types/shared": "^3.32.0", + "@react-types/switch": "^3.5.14", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/table": { + "version": "3.17.6", + "resolved": "https://registry.npmjs.org/@react-spectrum/table/-/table-3.17.6.tgz", + "integrity": "sha512-uFD6k7oL8SW+Pvg6inbazdHyS8j6DpEk+ZaITtIXoMpz5+15RIXjKj2FpFdAUklgIf2TqQo/xcjDYuhfyNrh7A==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/button": "^3.14.1", + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/overlays": "^3.29.1", + "@react-aria/selection": "^3.25.1", + "@react-aria/table": "^3.17.7", + "@react-aria/utils": "^3.30.1", + "@react-aria/virtualizer": "^4.1.9", + "@react-aria/visually-hidden": "^3.8.27", + "@react-spectrum/checkbox": "^3.10.2", + "@react-spectrum/dnd": "^3.6.1", + "@react-spectrum/layout": "^3.6.18", + "@react-spectrum/menu": "^3.22.6", + "@react-spectrum/progress": "^3.7.19", + "@react-spectrum/tooltip": "^3.7.10", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/flags": "^3.1.2", + "@react-stately/layout": "^4.5.0", + "@react-stately/table": "^3.15.0", + "@react-stately/virtualizer": "^4.4.3", + "@react-types/grid": "^3.3.5", + "@react-types/shared": "^3.32.0", + "@react-types/table": "^3.13.3", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/tabs": { + "version": "3.8.25", + "resolved": "https://registry.npmjs.org/@react-spectrum/tabs/-/tabs-3.8.25.tgz", + "integrity": "sha512-yzFdORFe1gYqeQ/4breNbBXYHmSe06V/cjxWL8Lcbc5/AitJOWwdJC4hJ2I9AF6i8+dES5NokXS1jxXnVV6Zzw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/tabs": "^3.10.7", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/picker": "^3.16.2", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/collections": "^3.12.7", + "@react-stately/list": "^3.13.0", + "@react-stately/tabs": "^3.8.5", + "@react-types/select": "^3.10.1", + "@react-types/shared": "^3.32.0", + "@react-types/tabs": "^3.3.18", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/tag": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@react-spectrum/tag/-/tag-3.3.5.tgz", + "integrity": "sha512-Ud9fMh5pFGRvQLllLruLaDVBEKgiYVDxIMYiNveNWQ7Q8RfCLHqSHn0FW/7UupGJaGzV0FAYtE1QTIMHcUzTCQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/selection": "^3.25.1", + "@react-aria/tag": "^3.7.1", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/form": "^3.7.18", + "@react-spectrum/label": "^3.16.18", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/collections": "^3.12.7", + "@react-stately/list": "^3.13.0", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/text": { + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@react-spectrum/text/-/text-3.5.20.tgz", + "integrity": "sha512-m6oleH1qyduYHOfeAQjIlnteMhRYYBzMU46beXUUfrNTCWUKLbxnt3OTsyAs03zvbx/+sImwE9gkujw4Z+iwZQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/shared": "^3.32.0", + "@react-types/text": "^3.3.20", + "@swc/helpers": "^0.5.0", + "react-aria-components": "^1.12.1" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/textfield": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@react-spectrum/textfield/-/textfield-3.14.1.tgz", + "integrity": "sha512-F8rEFv2c7/yvzj6c23lC8GM+HysQVFkXA+EpeQjOj+eHpXUewzrqc6qnbG3bURFywi4liCC6lzNCYo4W7IEokg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/textfield": "^3.18.1", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/form": "^3.7.18", + "@react-spectrum/label": "^3.16.18", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.0", + "@react-types/textfield": "^3.12.5", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/theme-dark": { + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@react-spectrum/theme-dark/-/theme-dark-3.5.20.tgz", + "integrity": "sha512-kMDfTSTYXsF33K4iiTylDRcQgR5uwsOowVOu+ODZ4edBYgK1JFEo6TkKspvEwCZSDBbYRX0Sw1GaxGYs83OjzA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/provider": "^3.8.11", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/theme-default": { + "version": "3.5.20", + "resolved": "https://registry.npmjs.org/@react-spectrum/theme-default/-/theme-default-3.5.20.tgz", + "integrity": "sha512-O19RMdTWJ9Y8JNtl6hOlGslD/o8AkNSO+O8hRbe7vnHW0m3rGyFh0MZIXfpKH3BNcc/8tF7jxh5GbjhjpcUZ8A==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/provider": "^3.8.11", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/theme-light": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@react-spectrum/theme-light/-/theme-light-3.4.20.tgz", + "integrity": "sha512-2Dtd8xRrNpShVJ3UKm86NuDNaFpcW805bZkAHrnR9LxKs75qFoiD0xgoulT/msk5RNkuwDOhBBYyt5vENR974A==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/provider": "^3.8.11", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/toast": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-spectrum/toast/-/toast-3.1.2.tgz", + "integrity": "sha512-PXfVxyaXv4EsvxhB/rhvVbPIey10mc0zJcu0jgUk2lmO7JOH9SyXj2eeWfKmIXJrcR+YHpIOAAr+rkfiDhVwFg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/overlays": "^3.29.1", + "@react-aria/toast": "^3.0.7", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/button": "^3.17.2", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/toast": "^3.1.2", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/tooltip": { + "version": "3.7.10", + "resolved": "https://registry.npmjs.org/@react-spectrum/tooltip/-/tooltip-3.7.10.tgz", + "integrity": "sha512-c9z1wzlUNN7ek/SpovKLY6+yX13eeyo84WLx311Hbz6h/jdh92KagXxJON4/fN71an8wWyKpayrd2ZfguRQiiw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/focus": "^3.21.1", + "@react-aria/overlays": "^3.29.1", + "@react-aria/tooltip": "^3.8.7", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/overlays": "^5.8.2", + "@react-spectrum/utils": "^3.12.8", + "@react-stately/tooltip": "^3.5.7", + "@react-types/overlays": "^3.9.1", + "@react-types/shared": "^3.32.0", + "@react-types/tooltip": "^3.4.20", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/tree": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@react-spectrum/tree/-/tree-3.1.6.tgz", + "integrity": "sha512-Mesxvj1Vp5CR1vw/4RCM1AXDGVruH22w2QsrYVKpc4tb7gRsa43gkIqhY70A/Q1Z3fzltfwMs5uVQlHH8NWkPA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/button": "^3.14.1", + "@react-aria/i18n": "^3.12.12", + "@react-aria/tree": "^3.1.3", + "@react-aria/utils": "^3.30.1", + "@react-spectrum/checkbox": "^3.10.2", + "@react-spectrum/text": "^3.5.20", + "@react-spectrum/utils": "^3.12.8", + "@react-types/shared": "^3.32.0", + "@spectrum-icons/ui": "^3.6.19", + "@swc/helpers": "^0.5.0", + "react-aria-components": "^1.12.1" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/utils": { + "version": "3.12.8", + "resolved": "https://registry.npmjs.org/@react-spectrum/utils/-/utils-3.12.8.tgz", + "integrity": "sha512-l9YR9S3im7Io3YcodWCce5P5u8PuEJGO+IkEKPJ0lua5fwzXg7H7rycJIe4Qmzd/7bHQjenaBuoosloXvGA/VA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/i18n": "^3.12.12", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/view": { + "version": "3.6.22", + "resolved": "https://registry.npmjs.org/@react-spectrum/view/-/view-3.6.22.tgz", + "integrity": "sha512-B5kyGSxpMi/xlOqt4P+cvOczx/1Et8tcSiqhpfVBVjhISTz8B8CemMETXM06QjSf/9ZG+ArTn6F6snXYj68d5Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/shared": "^3.32.0", + "@react-types/view": "^3.4.20", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-spectrum/well": { + "version": "3.4.26", + "resolved": "https://registry.npmjs.org/@react-spectrum/well/-/well-3.4.26.tgz", + "integrity": "sha512-hgfv7py7VLV1GGGT1Vuv+RmAPoHXCE0J0bIaLLJ0Ssnq0/k3uxuubs1fjKRc9fs8vmSb3oRUYOzh4Y5Q6GXpeg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-spectrum/utils": "^3.12.8", + "@react-types/shared": "^3.32.0", + "@react-types/well": "^3.3.20", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/autocomplete": { + "version": "3.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@react-stately/autocomplete/-/autocomplete-3.0.0-beta.3.tgz", + "integrity": "sha512-YfP/TrvkOCp6j7oqpZxJSvmSeXn+XtbKSOiBOuo+m2zCIhW2ncThmDB9uAUOkpmikDv/LkGKni40RQE8USdGdA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.8", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/calendar": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.8.4.tgz", + "integrity": "sha512-q9mq0ydOLS5vJoHLnYfSCS/vppfjbg0XHJlAoPR+w+WpYZF4wPP453SrlX9T1DbxCEYFTpcxcMk/O8SDW3miAw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.9.0", + "@react-stately/utils": "^3.10.8", + "@react-types/calendar": "^3.7.4", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/checkbox": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.7.1.tgz", + "integrity": "sha512-ezfKRJsDuRCLtNoNOi9JXCp6PjffZWLZ/vENW/gbRDL8i46RKC/HpfJrJhvTPmsLYazxPC99Me9iq3v0VoNCsw==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/form": "^3.2.1", + "@react-stately/utils": "^3.10.8", + "@react-types/checkbox": "^3.10.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/collections": { + "version": "3.12.7", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.12.7.tgz", + "integrity": "sha512-0kQc0mI986GOCQHvRy4L0JQiotIK/KmEhR9Mu/6V0GoSdqg5QeUe4kyoNWj3bl03uQXme80v0L2jLHt+fOHHjA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/color": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-stately/color/-/color-3.9.1.tgz", + "integrity": "sha512-fCj7fFamyuQbL++MOcf4W4d4aFWXYWJ2UI1dKhrXdqVz/ly9CBVjy/MHKQ6xZX2tEiuoPX5NexfxzKKiozE50Q==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/number": "^3.6.5", + "@internationalized/string": "^3.2.7", + "@react-stately/form": "^3.2.1", + "@react-stately/numberfield": "^3.10.1", + "@react-stately/slider": "^3.7.1", + "@react-stately/utils": "^3.10.8", + "@react-types/color": "^3.1.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/combobox": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@react-stately/combobox/-/combobox-3.11.1.tgz", + "integrity": "sha512-ZZh+SaAmddoY+MeJr470oDYA0nGaJm4xoHCBapaBA0JNakGC/wTzF/IRz3tKQT2VYK4rumr1BJLZQydGp7zzeg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.7", + "@react-stately/form": "^3.2.1", + "@react-stately/list": "^3.13.0", + "@react-stately/overlays": "^3.6.19", + "@react-stately/select": "^3.7.1", + "@react-stately/utils": "^3.10.8", + "@react-types/combobox": "^3.13.8", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/data": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@react-stately/data/-/data-3.14.0.tgz", + "integrity": "sha512-3GUsOXatYohBX2wTQHnJKVQlFfYXnt7IoDDuIaUeM8kXlF+dRSFAOAfPUSGAph6lJz2ht4dq1SEl6ZL/u+dRlQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/datepicker": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.15.1.tgz", + "integrity": "sha512-t64iYPms9y+MEQgOAu0XUHccbEXWVUWBHJWnYvAmILCHY8ZAOeSPAT1g4v9nzyiApcflSNXgpsvbs9BBEsrWww==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.9.0", + "@internationalized/string": "^3.2.7", + "@react-stately/form": "^3.2.1", + "@react-stately/overlays": "^3.6.19", + "@react-stately/utils": "^3.10.8", + "@react-types/datepicker": "^3.13.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/disclosure": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@react-stately/disclosure/-/disclosure-3.0.7.tgz", + "integrity": "sha512-ogM2y02uhpGfSOaBKIDz+hEha8qBH6WIRHRkoqdF4sEaR1kfq8LvBWdP1e/OcqHAhuRr28P2Rf0TDicnAnN7uA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/dnd": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@react-stately/dnd/-/dnd-3.7.0.tgz", + "integrity": "sha512-DddpCVkqt6vUPHLqe/2FHxW/gkR4tEt7W0MbFcCeCLbc9lmvzOClPwNpjmU/3UnU+vPQnwGGUeF3HvaxduUq2Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/selection": "^3.20.5", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/flags": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz", + "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-stately/form": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.2.1.tgz", + "integrity": "sha512-btgOPXkwvd6fdWKoepy5Ue43o2932OSkQxozsR7US1ffFLcQc3SNlADHaRChIXSG8ffPo9t0/Sl4eRzaKu3RgQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/grid": { + "version": "3.11.5", + "resolved": "https://registry.npmjs.org/@react-stately/grid/-/grid-3.11.5.tgz", + "integrity": "sha512-4cNjGYaNkcVS2wZoNHUrMRICBpkHStYw57EVemP7MjiWEVu53kzPgR1Iwmti2WFCpi1Lwu0qWNeCfzKpXW4BTg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.7", + "@react-stately/selection": "^3.20.5", + "@react-types/grid": "^3.3.5", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/layout": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@react-stately/layout/-/layout-4.5.0.tgz", + "integrity": "sha512-giN20XXxSjOG/pRSdzKkHhIFochl0Wer2aWCYceXRNSoP0dTPNU7bjn2p3n3atVRdC9iZpmwIiASO5qDf89sLQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.7", + "@react-stately/table": "^3.15.0", + "@react-stately/virtualizer": "^4.4.3", + "@react-types/grid": "^3.3.5", + "@react-types/shared": "^3.32.0", + "@react-types/table": "^3.13.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/list": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.13.0.tgz", + "integrity": "sha512-Panv8TmaY8lAl3R7CRhyUadhf2yid6VKsRDBCBB1FHQOOeL7lqIraz/oskvpabZincuaIUWqQhqYslC4a6dvuA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.7", + "@react-stately/selection": "^3.20.5", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/menu": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/@react-stately/menu/-/menu-3.9.7.tgz", + "integrity": "sha512-mfz1YoCgtje61AGxVdQaAFLlOXt9vV5dd1lQljYUPRafA/qu5Ursz4fNVlcavWW9GscebzFQErx+y0oSP7EUtQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/overlays": "^3.6.19", + "@react-types/menu": "^3.10.4", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/numberfield": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-stately/numberfield/-/numberfield-3.10.1.tgz", + "integrity": "sha512-lXABmcTneVvXYMGTgZvTCr4E+upOi7VRLL50ZzTMJqHwB/qlEQPAam3dmddQRwIsuCM3MEnL7bSZFFlSYAtkEw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/number": "^3.6.5", + "@react-stately/form": "^3.2.1", + "@react-stately/utils": "^3.10.8", + "@react-types/numberfield": "^3.8.14", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/overlays": { + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.19.tgz", + "integrity": "sha512-swZXfDvxTYd7tKEpijEHBFFaEmbbnCvEhGlmrAz4K72cuRR9O5u+lcla8y1veGBbBSzrIdKNdBoIIJ+qQH+1TQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.8", + "@react-types/overlays": "^3.9.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/radio": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@react-stately/radio/-/radio-3.11.1.tgz", + "integrity": "sha512-ld9KWztI64gssg7zSZi9li21sG85Exb+wFPXtCim1TtpnEpmRtB05pXDDS3xkkIU/qOL4eMEnnLO7xlNm0CRIA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/form": "^3.2.1", + "@react-stately/utils": "^3.10.8", + "@react-types/radio": "^3.9.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/searchfield": { + "version": "3.5.15", + "resolved": "https://registry.npmjs.org/@react-stately/searchfield/-/searchfield-3.5.15.tgz", + "integrity": "sha512-6LVVvm6Z60fetYLLa4B2Q/BIY+fSSknLTw8sjlV+iDEPAknj7MqWtoLz2gSQRTFKvyO7ZCjJoar8ZU/JEqcm+w==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.8", + "@react-types/searchfield": "^3.6.5", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/select": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-stately/select/-/select-3.7.1.tgz", + "integrity": "sha512-vZt4j9yVyOTWWJoP9plXmYaPZH2uMxbjcGMDbiShwsFiK8C2m9b3Cvy44TZehfzCWzpMVR/DYxEYuonEIGA82Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/form": "^3.2.1", + "@react-stately/list": "^3.13.0", + "@react-stately/overlays": "^3.6.19", + "@react-types/select": "^3.10.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/selection": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.20.5.tgz", + "integrity": "sha512-YezWUNEn2pz5mQlbhmngiX9HqQsruLSXlkrAzB1DD6aliGrUvPKufTTGCixOaB8KVeCamdiFAgx1WomNplzdQA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.7", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/slider": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-stately/slider/-/slider-3.7.1.tgz", + "integrity": "sha512-J+G18m1bZBCNQSXhxGd4GNGDUVonv4Sg7fZL+uLhXUy1x71xeJfFdKaviVvZcggtl0/q5InW41PXho7EouMDEg==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.0", + "@react-types/slider": "^3.8.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/table": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@react-stately/table/-/table-3.15.0.tgz", + "integrity": "sha512-KbvkrVF3sb25IPwyte9JcG5/4J7TgjHSsw7D61d/T/oUFMYPYVeolW9/2y+6u48WPkDJE8HJsurme+HbTN0FQA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.7", + "@react-stately/flags": "^3.1.2", + "@react-stately/grid": "^3.11.5", + "@react-stately/selection": "^3.20.5", + "@react-stately/utils": "^3.10.8", + "@react-types/grid": "^3.3.5", + "@react-types/shared": "^3.32.0", + "@react-types/table": "^3.13.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/tabs": { + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/@react-stately/tabs/-/tabs-3.8.5.tgz", + "integrity": "sha512-gdeI+NUH3hfqrxkJQSZkt+Zw4G2DrYJRloq/SGxu/9Bu5QD/U0psU2uqxQNtavW5qTChFK+D30rCPXpKlslWAA==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/list": "^3.13.0", + "@react-types/shared": "^3.32.0", + "@react-types/tabs": "^3.3.18", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/toast": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-stately/toast/-/toast-3.1.2.tgz", + "integrity": "sha512-HiInm7bck32khFBHZThTQaAF6e6/qm57F4mYRWdTq8IVeGDzpkbUYibnLxRhk0UZ5ybc6me+nqqPkG/lVmM42Q==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/toggle": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.9.1.tgz", + "integrity": "sha512-L6yUdE8xZfQhw4aEFZduF8u4v0VrpYrwWEA4Tu/4qwGIPukH0wd2W21Zpw+vAiLOaDKnxel1nXX68MWnm4QXpw==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/utils": "^3.10.8", + "@react-types/checkbox": "^3.10.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/tooltip": { + "version": "3.5.7", + "resolved": "https://registry.npmjs.org/@react-stately/tooltip/-/tooltip-3.5.7.tgz", + "integrity": "sha512-GYh764BcYZz+Lclyutyir5I3elNo+vVNYzeNOKmPGZCE3p5B+/8lgZAHKxnRc9qmBlxvofnhMcuQxAPlBhoEkw==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/overlays": "^3.6.19", + "@react-types/tooltip": "^3.4.20", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/tree": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@react-stately/tree/-/tree-3.9.2.tgz", + "integrity": "sha512-jsT1WZZhb7GRmg1iqoib9bULsilIK5KhbE8WrcfIml8NYr4usP4DJMcIYfRuiRtPLhKtUvHSoZ5CMbinPp8PUQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/collections": "^3.12.7", + "@react-stately/selection": "^3.20.5", + "@react-stately/utils": "^3.10.8", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz", + "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/virtualizer": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@react-stately/virtualizer/-/virtualizer-4.4.3.tgz", + "integrity": "sha512-kk6ZyMtOT51kZYGUjUhbgEdRBp/OR3WD+Vj9kFoCa1vbY+fGzbpcnjsvR2LDZuEq8W45ruOvdr1c7HRJG4gWxA==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/actionbar": { + "version": "3.1.18", + "resolved": "https://registry.npmjs.org/@react-types/actionbar/-/actionbar-3.1.18.tgz", + "integrity": "sha512-BA5zINFIOlBohTE374uBUryBq80JzH314UGVrhRdd6pzMV9s5tk8+/VPZ9a1wCItaWA865CJvZAFF1Hz3y8ifg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/actiongroup": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@react-types/actiongroup/-/actiongroup-3.4.20.tgz", + "integrity": "sha512-D6K4zUrTdharGlG24iGJfOl0/8AjLqZCBoOHD5nXkdm0obWva1jBwtCRee2WblCazVnaDedGYDPODAwyEatFDA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/autocomplete": { + "version": "3.0.0-alpha.34", + "resolved": "https://registry.npmjs.org/@react-types/autocomplete/-/autocomplete-3.0.0-alpha.34.tgz", + "integrity": "sha512-wswz7r0823EWfBZVMVicoDmFw0T6k7LqGlsLivq/2mq1dL62ywPFPtRUNU5nYqgslZYPUZMPyZgKdehKyuwE7Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/combobox": "^3.13.8", + "@react-types/searchfield": "^3.6.5", + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/avatar": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/@react-types/avatar/-/avatar-3.0.18.tgz", + "integrity": "sha512-tiKlK5u0SPIFd78hHmTF+IrK8ZkGiF0kvHdMfLQ0GrdpAb45wUEhbRcn/6Z0SYij1+JHt6rMT7oUH29BGOmfdw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/badge": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/@react-types/badge/-/badge-3.1.20.tgz", + "integrity": "sha512-gYZ7uzwvEJtViYzMFcKTzJ9lag7DKBvzZvN8aHOXMBFxWzpjvJ4FRzLgUDjioYf526+kAQi53nYRhnHrPRvbgA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/breadcrumbs": { + "version": "3.7.16", + "resolved": "https://registry.npmjs.org/@react-types/breadcrumbs/-/breadcrumbs-3.7.16.tgz", + "integrity": "sha512-4J+7b9y6z8QGZqvsBSWQfebx6aIbc+1unQqnZCAlJl9EGzlI6SGdXRsURGkOUGJCV2GqY8bSocc8AZbRXpQ0XQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/link": "^3.6.4", + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/button": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.14.0.tgz", + "integrity": "sha512-pXt1a+ElxiZyWpX0uznyjy5Z6EHhYxPcaXpccZXyn6coUo9jmCbgg14xR7Odo+JcbfaaISzZTDO7oGLVTcHnpA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/buttongroup": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/@react-types/buttongroup/-/buttongroup-3.3.20.tgz", + "integrity": "sha512-025RW2yOTk2q9ClMgoAwc+4BYZnp6OG5Okq9ialy3ZiRjPiRAE5PncoiAkYszungxESpbBaPaocI2CxbNYK8uQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/calendar": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.7.4.tgz", + "integrity": "sha512-MZDyXtvdHl8CKQGYBkjYwc4ABBq6Mb4Fu7k/4boQAmMQ5Rtz29ouBCJrAs0BpR14B8ZMGzoNIolxS5RLKBmFSA==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.9.0", + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/checkbox": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.10.1.tgz", + "integrity": "sha512-8ZqBoGBxtn6U/znpmyutGtBBaafUzcZnbuvYjwyRSONTrqQ0IhUq6jI/jbnE9r9SslIkbMB8IS1xRh2e63qmEQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/color": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@react-types/color/-/color-3.1.1.tgz", + "integrity": "sha512-zBF1Op4AO3mlygUq2gFhEoK3gZp2HgwCMUKkCzoDbrvcaahhVbDbfhRxgXKM/2dg7WkgsqhokdkjYV2mGQadRQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0", + "@react-types/slider": "^3.8.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/combobox": { + "version": "3.13.8", + "resolved": "https://registry.npmjs.org/@react-types/combobox/-/combobox-3.13.8.tgz", + "integrity": "sha512-HGC3X9hmDRsjSZcFiflvJ7vbIgQ2gX/ZDxo1HVtvQqUDbgQCVakCcCdrB44aYgHFnyDiO6hyp7Y7jXtDBaEIIA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/contextualhelp": { + "version": "3.2.21", + "resolved": "https://registry.npmjs.org/@react-types/contextualhelp/-/contextualhelp-3.2.21.tgz", + "integrity": "sha512-AOog6J2d3OhC+IL7P7Xq2rKYSxvy2cTenRp/8c594pbREwL5lNAqv34ORdCg1urYJdFvOkObS29ghol5InT32Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/overlays": "^3.9.1", + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/datepicker": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.13.1.tgz", + "integrity": "sha512-ub+g5pS3WOo5P/3FRNsQSwvlb9CuLl2m6v6KBkRXc5xqKhFd7UjvVpL6Oi/1zwwfow4itvD1t7l1XxgCo7wZ6Q==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.9.0", + "@react-types/calendar": "^3.7.4", + "@react-types/overlays": "^3.9.1", + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/dialog": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.21.tgz", + "integrity": "sha512-jF1gN4bvwYamsLjefaFDnaSKxTa3Wtvn5f7WLjNVZ8ICVoiMBMdUJXTlPQHAL4YWqtCj4hK/3uimR1E+Pwd7Xw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/overlays": "^3.9.1", + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/divider": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/@react-types/divider/-/divider-3.3.20.tgz", + "integrity": "sha512-4STS8c48/NCPNdtYnAqmmarfroWvJqDYqCetEP8BW0WVWefmQDFIvAaRZKE7aGYHQE9GorGpEeVlWle4tBy4sw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/form": { + "version": "3.7.15", + "resolved": "https://registry.npmjs.org/@react-types/form/-/form-3.7.15.tgz", + "integrity": "sha512-a7C1RXgMpHX9b1x/+h5YCOJL/2/Ojw9ErOJhLwUWzKUu5JWpQYf8JsXNsuMSndo4YBaiH/7bXFmg09cllHUmow==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/grid": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.3.5.tgz", + "integrity": "sha512-hG6J2KDfmOHitkWoCa/9DvY1nTO2wgMIApcFoqLv7AWJr9CzvVqo5tIhZZCXiT1AvU2kafJxu9e7sr5GxAT2YA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/illustratedmessage": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/@react-types/illustratedmessage/-/illustratedmessage-3.3.20.tgz", + "integrity": "sha512-Pe3wmA/YzN1hM+wlFOwlmL1B4KsEmMBHUIKGtzqC36PvPAgyS79XdbtyC9Uaebd8J9Si/iMhDkGy2J6R54uZWQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/image": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@react-types/image/-/image-3.5.1.tgz", + "integrity": "sha512-SpXzsTX3NpeG0REKhPYBe2Lce6YiOK1Q1sbwgyPsZ9FzgyDKUr6OGYMQDfUKLSZsrUY96CaxR/yFPdUZMXswzQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/label": { + "version": "3.9.14", + "resolved": "https://registry.npmjs.org/@react-types/label/-/label-3.9.14.tgz", + "integrity": "sha512-haXUFGcaPSZPYy492AXjnLoQsZkE+TtP69RSUa3ABReNuPxiMV2OYRzdMCUTjVaQnDE5rBXJeIs7WGEKGod4WQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/layout": { + "version": "3.3.26", + "resolved": "https://registry.npmjs.org/@react-types/layout/-/layout-3.3.26.tgz", + "integrity": "sha512-1eqAj+D5KEqcDjfJGcDF1ZyKj3rlOgLcOctfqwOjd57MKD6PycQlmFCxDpYcPdT3hwTISWbfTwQybkgLCDTJDw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/link": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/@react-types/link/-/link-3.6.4.tgz", + "integrity": "sha512-eLpIgOPf7GW4DpdMq8UqiRJkriend1kWglz5O9qU+/FM6COtvRnQkEeRhHICUaU2NZUvMRQ30KaGUo3eeZ6b+g==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/listbox": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.7.3.tgz", + "integrity": "sha512-ONgror9uyGmIer5XxpRRNcc8QFVWiOzINrMKyaS8G4l3aP52ZwYpRfwMAVtra8lkVNvXDmO7hthPZkB6RYdNOA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/menu": { + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/@react-types/menu/-/menu-3.10.4.tgz", + "integrity": "sha512-jCFVShLq3eASiuznenjoKBv3j0Jy2KQilAjBxdEp56WkZ5D338y/oY5zR6d25u9M0QslpI0DgwC8BwU7MCsPnw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/overlays": "^3.9.1", + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/meter": { + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/@react-types/meter/-/meter-3.4.12.tgz", + "integrity": "sha512-rx+yrwdesSabPworWRMpQnuT69gm8xt58cAfTDV9eSY1Jo+lO5OPp0OIyKb+U0q/whf60wnn2hsVnXm2fBXKhA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/progress": "^3.5.15" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/numberfield": { + "version": "3.8.14", + "resolved": "https://registry.npmjs.org/@react-types/numberfield/-/numberfield-3.8.14.tgz", + "integrity": "sha512-tlGEHJyeQSMlUoO4g9ekoELGJcqsjc/+/FAxo6YQMhQSkuIdkUKZg3UEBKzif4hLw787u80e1D0SxPUi3KO2oA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/overlays": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.9.1.tgz", + "integrity": "sha512-UCG3TOu8FLk4j0Pr1nlhv0opcwMoqbGEOUvsSr6ITN6Qs2y0j+KYSYQ7a4+04m3dN//8+9Wjkkid8k+V1dV2CA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/progress": { + "version": "3.5.15", + "resolved": "https://registry.npmjs.org/@react-types/progress/-/progress-3.5.15.tgz", + "integrity": "sha512-3SYvEyRt7vq7w0sc6wBYmkPqLMZbhH8FI3Lrnn9r3y8+69/efRjVmmJvwjm1z+c6rukszc2gCjUGTsMPQxVk2w==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/provider": { + "version": "3.8.12", + "resolved": "https://registry.npmjs.org/@react-types/provider/-/provider-3.8.12.tgz", + "integrity": "sha512-mMVz1LHsytBAKoba2tx2euRlj8Xp0hOyD8S1YwtQ4A4xGrWDYdtjM+y7Xiq8GijJt2BSwlpDstWEMZdU+RXKsA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/radio": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.9.1.tgz", + "integrity": "sha512-DUCN3msm8QZ0MJrP55FmqMONaadYq6JTxihYFGMLP+NoKRnkxvXqNZ2PlkAOLGy3y4RHOnOF8O1LuJqFCCuxDw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/searchfield": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@react-types/searchfield/-/searchfield-3.6.5.tgz", + "integrity": "sha512-5hI+Hb1U0bSxrJLvEwFEQfk7n3S+GO4c5W/0WZBG00YlYDY9asr1V0oU1WRmKPJJlRpyfG6PkMHDC3jhdj89ew==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0", + "@react-types/textfield": "^3.12.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/select": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.10.1.tgz", + "integrity": "sha512-teANUr1byOzGsS/r2j7PatV470JrOhKP8En9lscfnqW5CeUghr+0NxkALnPkiEhCObi/Vu8GIcPareD0HNhtFA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.0.tgz", + "integrity": "sha512-t+cligIJsZYFMSPFMvsJMjzlzde06tZMOIOFa1OV5Z0BcMowrb2g4mB57j/9nP28iJIRYn10xCniQts+qadrqQ==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/slider": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.8.1.tgz", + "integrity": "sha512-WxiQWj6iQr5Uft0/KcB9XSr361XnyTmL6eREZZacngA9CjPhRWYP3BRDPcCTuP7fj9Yi4QKMrryyjHqMHP8OKQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/statuslight": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/@react-types/statuslight/-/statuslight-3.3.20.tgz", + "integrity": "sha512-nz4+GP7MPQNkvDwH6RzWxH7p0cX2Td5IBE5IDghKFFMZqKxTVwynGUOfpG8ErnKJ87uKw08vPfSmVnJNbOR+lw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/switch": { + "version": "3.5.14", + "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.14.tgz", + "integrity": "sha512-M8kIv97i+ejCel4Ho+Y7tDbpOehymGwPA4ChxibeyD32+deyxu5B6BXxgKiL3l+oTLQ8ihLo3sRESdPFw8vpQg==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/table": { + "version": "3.13.3", + "resolved": "https://registry.npmjs.org/@react-types/table/-/table-3.13.3.tgz", + "integrity": "sha512-/kY/VlXN+8l9saySd6igcsDQ3x8pOVFJAWyMh6gOaOVN7HOJkTMIchmqS+ATa4nege8jZqcdzyGeAmv7mN655A==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/grid": "^3.3.5", + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/tabs": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.18.tgz", + "integrity": "sha512-yX/AVlGS7VXCuy2LSm8y8nxUrKVBgnLv+FrtkLqf6jUMtD4KP3k1c4+GPHeScR0HcYzCQF7gCF3Skba1RdYoug==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/text": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/@react-types/text/-/text-3.3.20.tgz", + "integrity": "sha512-++PODqN4tO1UOr0/6PI3iA/sEvqI+foI6dApN2RhPgkJT3bChty1eL/nO4KTtK0X/7Dwsm9KXgywXKsh7YbFbw==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/textfield": { + "version": "3.12.5", + "resolved": "https://registry.npmjs.org/@react-types/textfield/-/textfield-3.12.5.tgz", + "integrity": "sha512-VXez8KIcop87EgIy00r+tb30xokA309TfJ32Qv5qOYB5SMqoHnb6SYvWL8Ih2PDqCo5eBiiGesSaWYrHnRIL8Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/tooltip": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@react-types/tooltip/-/tooltip-3.4.20.tgz", + "integrity": "sha512-tF1yThwvgSgW8Gu/CLL0p92AUldHR6szlwhwW+ewT318sQlfabMGO4xlCNFdxJYtqTpEXk2rlaVrBuaC//du0w==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/overlays": "^3.9.1", + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/view": { + "version": "3.4.20", + "resolved": "https://registry.npmjs.org/@react-types/view/-/view-3.4.20.tgz", + "integrity": "sha512-mr0dr4nEgzcB7lKsk35+PrgDL4NvYCQo78uw4ytJ2S09kSBAHU7VSIzyYxY/uFqsmT/vHICQUsznN7ZSY8xrOA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/well": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/@react-types/well/-/well-3.3.20.tgz", + "integrity": "sha512-QOPG3ezu8h5OKcemEEw6Pemx5oFGZQFUM5p1Kr1zDf3WRG6k1LQC03b4B6EbhMR47QnKDKTPl0g6HrMt+LOFoA==", + "license": "Apache-2.0", + "dependencies": { + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@redocly/ajv": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz", + "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js-replace": "^1.0.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@redocly/ajv/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/config": { + "version": "0.22.2", + "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.2.tgz", + "integrity": "sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@redocly/openapi-core": { + "version": "1.34.3", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.3.tgz", + "integrity": "sha512-3arRdUp1fNx55itnjKiUhO6t4Mf91TsrTIYINDNLAZPS0TPd5YpiXRctwjel0qqWoOOhjA34cZ3m4dksLDFUYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/ajv": "^8.11.2", + "@redocly/config": "^0.22.0", + "colorette": "^1.2.0", + "https-proxy-agent": "^7.0.5", + "js-levenshtein": "^1.1.6", + "js-yaml": "^4.1.0", + "minimatch": "^5.0.1", + "pluralize": "^8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "engines": { + "node": ">=18.17.0", + "npm": ">=9.5.0" + } + }, + "node_modules/@redocly/openapi-core/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@replit/codemirror-css-color-picker": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@replit/codemirror-css-color-picker/-/codemirror-css-color-picker-6.3.0.tgz", + "integrity": "sha512-19biDANghUm7Fz7L1SNMIhK48tagaWuCOHj4oPPxc7hxPGkTVY2lU/jVZ8tsbTKQPVG7BO2CBDzs7CBwb20t4A==", + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "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/pluginutils/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rsbuild/core": { + "version": "1.3.22", + "resolved": "https://registry.npmjs.org/@rsbuild/core/-/core-1.3.22.tgz", + "integrity": "sha512-FGB7m8Tn/uiOhvqk0lw+NRMyD+VYJ+eBqVfpn0X11spkJDiPWn8UkMRvfzCX4XFcNZwRKYuuKJaZK1DNU8UG+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rspack/core": "1.3.12", + "@rspack/lite-tapable": "~1.0.1", + "@swc/helpers": "^0.5.17", + "core-js": "~3.42.0", + "jiti": "^2.4.2" + }, + "bin": { + "rsbuild": "bin/rsbuild.js" + }, + "engines": { + "node": ">=16.10.0" + } + }, + "node_modules/@rsbuild/plugin-babel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@rsbuild/plugin-babel/-/plugin-babel-1.0.6.tgz", + "integrity": "sha512-tWnqG938MedKJx7U4F1lHb156VDtNzj7mSsi2ZoxZVBnECQE01/V6QTN1XKw7nWunGyGoETb+nQBGc+fkVZjvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-proposal-decorators": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", + "@types/babel__core": "^7.20.5", + "deepmerge": "^4.3.1", + "reduce-configs": "^1.1.0", + "upath": "2.0.1" + }, + "peerDependencies": { + "@rsbuild/core": "1.x" + } + }, + "node_modules/@rsbuild/plugin-react": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rsbuild/plugin-react/-/plugin-react-1.3.2.tgz", + "integrity": "sha512-H4blXmgvVOrQlVy4ZfJ5IGfQIF5uKwtkGzwVnEsn1HN7DRRI9VlFrcuXj6+e3GigvYxg6TDHAAUJi6FoIGbnKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rspack/plugin-react-refresh": "~1.4.3", + "react-refresh": "^0.17.0" + }, + "peerDependencies": { + "@rsbuild/core": "1.x" + } + }, + "node_modules/@rsbuild/plugin-sass": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rsbuild/plugin-sass/-/plugin-sass-1.3.2.tgz", + "integrity": "sha512-askbmJllDZ7LYchT8AqdKt2zKNyBauq2KgA9peBExqjTIYGP+ZXA3UB4V8zGXoACqqAYl/jqf8LUjx6nRWHFSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "loader-utils": "^2.0.4", + "postcss": "^8.5.4", + "reduce-configs": "^1.1.0", + "sass-embedded": "1.89.0" + }, + "peerDependencies": { + "@rsbuild/core": "1.x" + } + }, + "node_modules/@rsbuild/plugin-svgr": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@rsbuild/plugin-svgr/-/plugin-svgr-1.2.0.tgz", + "integrity": "sha512-J0XEqp++cXnzVpVXsq92H6S6VXYMuGjlw07Juh73tMpxlJdPsv95ULUoiCHmuTwUeTZMRfVMamTxz03/owYYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rsbuild/plugin-react": "^1.1.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0", + "deepmerge": "^4.3.1", + "loader-utils": "^3.3.1" + }, + "peerDependencies": { + "@rsbuild/core": "1.x" + } + }, + "node_modules/@rsbuild/plugin-svgr/node_modules/loader-utils": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", + "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@rspack/binding": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.3.12.tgz", + "integrity": "sha512-4Ic8lV0+LCBfTlH5aIOujIRWZOtgmG223zC4L3o8WY/+ESAgpdnK6lSSMfcYgRanYLAy3HOmFIp20jwskMpbAg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "@rspack/binding-darwin-arm64": "1.3.12", + "@rspack/binding-darwin-x64": "1.3.12", + "@rspack/binding-linux-arm64-gnu": "1.3.12", + "@rspack/binding-linux-arm64-musl": "1.3.12", + "@rspack/binding-linux-x64-gnu": "1.3.12", + "@rspack/binding-linux-x64-musl": "1.3.12", + "@rspack/binding-win32-arm64-msvc": "1.3.12", + "@rspack/binding-win32-ia32-msvc": "1.3.12", + "@rspack/binding-win32-x64-msvc": "1.3.12" + } + }, + "node_modules/@rspack/binding-darwin-arm64": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.3.12.tgz", + "integrity": "sha512-8hKjVTBeWPqkMzFPNWIh72oU9O3vFy3e88wRjMPImDCXBiEYrKqGTTLd/J0SO+efdL3SBD1rX1IvdJpxCv6Yrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rspack/binding-darwin-x64": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.3.12.tgz", + "integrity": "sha512-Sj4m+mCUxL7oCpdu7OmWT7fpBM7hywk5CM9RDc3D7StaBZbvNtNftafCrTZzTYKuZrKmemTh5SFzT5Tz7tf6GA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rspack/binding-linux-arm64-gnu": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.12.tgz", + "integrity": "sha512-7MuOxf3/Mhv4mgFdLTvgnt/J+VouNR65DEhorth+RZm3LEWojgoFEphSAMAvpvAOpYSS68Sw4SqsOZi719ia2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-arm64-musl": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.3.12.tgz", + "integrity": "sha512-s6KKj20T9Z1bA8caIjU6EzJbwyDo1URNFgBAlafCT2UC6yX7flstDJJ38CxZacA9A2P24RuQK2/jPSZpWrTUFA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-x64-gnu": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.12.tgz", + "integrity": "sha512-0w/sRREYbRgHgWvs2uMEJSLfvzbZkPHUg6CMcYQGNVK6axYRot6jPyKetyFYA9pR5fB5rsXegpnFaZaVrRIK2g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-linux-x64-musl": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.3.12.tgz", + "integrity": "sha512-jEdxkPymkRxbijDRsBGdhopcbGXiXDg59lXqIRkVklqbDmZ/O6DHm7gImmlx5q9FoWbz0gqJuOKBz4JqWxjWVA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rspack/binding-win32-arm64-msvc": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.3.12.tgz", + "integrity": "sha512-ZRvUCb3TDLClAqcTsl/o9UdJf0B5CgzAxgdbnYJbldyuyMeTUB4jp20OfG55M3C2Nute2SNhu2bOOp9Se5Ongw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/binding-win32-ia32-msvc": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.3.12.tgz", + "integrity": "sha512-1TKPjuXStPJr14f3ZHuv40Xc/87jUXx10pzVtrPnw+f3hckECHrbYU/fvbVzZyuXbsXtkXpYca6ygCDRJAoNeQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/binding-win32-x64-msvc": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.3.12.tgz", + "integrity": "sha512-lCR0JfnYKpV+a6r2A2FdxyUKUS4tajePgpPJN5uXDgMGwrDtRqvx+d0BHhwjFudQVJq9VVbRaL89s2MQ6u+xYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rspack/core": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.3.12.tgz", + "integrity": "sha512-mAPmV4LPPRgxpouUrGmAE4kpF1NEWJGyM5coebsjK/zaCMSjw3mkdxiU2b5cO44oIi0Ifv5iGkvwbdrZOvMyFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@module-federation/runtime-tools": "0.14.0", + "@rspack/binding": "1.3.12", + "@rspack/lite-tapable": "1.0.1", + "caniuse-lite": "^1.0.30001718" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.1" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@rspack/lite-tapable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rspack/lite-tapable/-/lite-tapable-1.0.1.tgz", + "integrity": "sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@rspack/plugin-react-refresh": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@rspack/plugin-react-refresh/-/plugin-react-refresh-1.4.3.tgz", + "integrity": "sha512-wZx4vWgy5oMEvgyNGd/oUKcdnKaccYWHCRkOqTdAPJC3WcytxhTX+Kady8ERurSBiLyQpoMiU3Iyd+F1Y2Arbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-stack-parser": "^2.1.4", + "html-entities": "^2.6.0" + }, + "peerDependencies": { + "react-refresh": ">=0.10.0 <1.0.0", + "webpack-hot-middleware": "2.x" + }, + "peerDependenciesMeta": { + "webpack-hot-middleware": { + "optional": true + } + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@scalar/analytics-client": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@scalar/analytics-client/-/analytics-client-1.0.0.tgz", + "integrity": "sha512-pnkghhqfmSH7hhdlFpu2M3V/6EjP2R0XbKVbmP77JSli12DdInxR1c4Oiw9V5f/jgxQhVczQQTt74cFYbLzI/Q==", + "license": "MIT", + "dependencies": { + "zod": "3.24.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/analytics-client/node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@scalar/api-client": { + "version": "2.5.33", + "resolved": "https://registry.npmjs.org/@scalar/api-client/-/api-client-2.5.33.tgz", + "integrity": "sha512-GXJAdz9YtdftqkntVO9EXAr9+Z9Jkk1FE3QM3MDdgyMvpZzZgQyrXxbfH3cg/zvMvKJhSiuIDCPibX0y6CfoUA==", + "license": "MIT", + "dependencies": { + "@headlessui/tailwindcss": "^0.2.2", + "@headlessui/vue": "1.7.23", + "@scalar/analytics-client": "1.0.0", + "@scalar/components": "0.14.34", + "@scalar/draggable": "0.2.0", + "@scalar/helpers": "0.0.11", + "@scalar/icons": "0.4.7", + "@scalar/import": "0.4.24", + "@scalar/oas-utils": "0.4.29", + "@scalar/object-utils": "1.2.7", + "@scalar/openapi-parser": "0.20.6", + "@scalar/openapi-types": "0.3.7", + "@scalar/postman-to-openapi": "0.3.32", + "@scalar/snippetz": "0.4.9", + "@scalar/themes": "0.13.17", + "@scalar/types": "0.2.15", + "@scalar/use-codemirror": "0.12.35", + "@scalar/use-hooks": "0.2.5", + "@scalar/use-toasts": "0.8.0", + "@scalar/workspace-store": "0.15.6", + "@types/har-format": "^1.2.15", + "@vueuse/core": "13.9.0", + "@vueuse/integrations": "13.9.0", + "focus-trap": "^7", + "fuse.js": "^7.1.0", + "js-base64": "^3.7.8", + "microdiff": "^1.5.0", + "nanoid": "5.1.5", + "pretty-bytes": "^6.1.1", + "pretty-ms": "^8.0.0", + "shell-quote": "^1.8.1", + "type-fest": "4.41.0", + "vue": "^3.5.17", + "vue-router": "^4.3.0", + "whatwg-mimetype": "^4.0.0", + "yaml": "2.8.0", + "zod": "3.24.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/api-client/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/api-client/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@scalar/api-client/node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@scalar/api-reference": { + "version": "1.35.6", + "resolved": "https://registry.npmjs.org/@scalar/api-reference/-/api-reference-1.35.6.tgz", + "integrity": "sha512-jjCvY6+j2mcT2nCyQzUAuWGsBcpesdObJzbfa8VKNg0xG1m6ZemZK7uhMISdtsEt7HEMMrg4po6z9CXPDXmQjA==", + "license": "MIT", + "dependencies": { + "@floating-ui/vue": "1.1.9", + "@headlessui/vue": "1.7.23", + "@scalar/api-client": "2.5.33", + "@scalar/code-highlight": "0.1.9", + "@scalar/components": "0.14.34", + "@scalar/helpers": "0.0.11", + "@scalar/icons": "0.4.7", + "@scalar/json-magic": "0.4.3", + "@scalar/oas-utils": "0.4.29", + "@scalar/object-utils": "1.2.7", + "@scalar/openapi-parser": "0.20.6", + "@scalar/openapi-types": "0.3.7", + "@scalar/snippetz": "0.4.9", + "@scalar/themes": "0.13.17", + "@scalar/types": "0.2.15", + "@scalar/use-hooks": "0.2.5", + "@scalar/use-toasts": "0.8.0", + "@scalar/workspace-store": "0.15.6", + "@unhead/vue": "^1.11.20", + "@vueuse/core": "13.9.0", + "flatted": "^3.3.3", + "fuse.js": "^7.1.0", + "github-slugger": "^2.0.0", + "js-base64": "^3.7.8", + "microdiff": "^1.5.0", + "nanoid": "5.1.5", + "type-fest": "^4.41.0", + "vue": "^3.5.17", + "zod": "3.24.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/api-reference-react": { + "version": "0.7.49", + "resolved": "https://registry.npmjs.org/@scalar/api-reference-react/-/api-reference-react-0.7.49.tgz", + "integrity": "sha512-St4B96BoH0MQ3Lho7m81P7Z7Ic4SMPcP9d6D8bFdcyiqb0JIN/380AzWuVS7cBeVcuYLOlEBTmvsuIlffzPQTw==", + "license": "MIT", + "dependencies": { + "@scalar/api-reference": "1.35.6", + "@scalar/types": "0.2.15" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@scalar/api-reference/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/api-reference/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@scalar/api-reference/node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@scalar/code-highlight": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@scalar/code-highlight/-/code-highlight-0.1.9.tgz", + "integrity": "sha512-WUUVDd1Wk7QJVKWXl/Zdn/VINc2pc1NlWW8VJFYZRm3/hKJwBhi0on7+HjVQNKgUaRy7+zluru5Ckl1gcTHHEg==", + "license": "MIT", + "dependencies": { + "hast-util-to-text": "^4.0.2", + "highlight.js": "^11.9.0", + "highlightjs-curl": "^1.3.0", + "highlightjs-vue": "^1.0.0", + "lowlight": "^3.1.0", + "rehype-external-links": "^3.0.0", + "rehype-format": "^5.0.0", + "rehype-parse": "^9.0.0", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", + "rehype-stringify": "^10.0.0", + "remark-gfm": "^4.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/components": { + "version": "0.14.34", + "resolved": "https://registry.npmjs.org/@scalar/components/-/components-0.14.34.tgz", + "integrity": "sha512-yMUyNYfWIbaxhRkuMyRg++L7To6tCkF/Ls4SsP3ejC1TMJM1L8BcEWWkDnV8eZq9GFTwOQujZG78Dam2mk7IzQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "0.2.10", + "@floating-ui/vue": "1.1.9", + "@headlessui/vue": "1.7.23", + "@scalar/code-highlight": "0.1.9", + "@scalar/helpers": "0.0.11", + "@scalar/icons": "0.4.7", + "@scalar/oas-utils": "0.4.29", + "@scalar/themes": "0.13.17", + "@scalar/use-hooks": "0.2.5", + "@scalar/use-toasts": "0.8.0", + "@vueuse/core": "13.9.0", + "cva": "1.0.0-beta.2", + "nanoid": "5.1.5", + "pretty-bytes": "^6.1.1", + "radix-vue": "^1.9.3", + "vue": "^3.5.17", + "vue-component-type-helpers": "^3.0.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/components/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/draggable": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@scalar/draggable/-/draggable-0.2.0.tgz", + "integrity": "sha512-UetHRB5Bqo5egVYlS21roWBcICmyk8CKh2htsidO+bFGAjl2e7Te+rY0borhNrMclr0xezHlPuLpEs1dvgLS2g==", + "license": "MIT", + "dependencies": { + "vue": "^3.5.12" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/helpers": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.0.11.tgz", + "integrity": "sha512-EoAufzG0crQloYJxbCV8F+Y5vPyeeh1HMngGlXPT6oGSJPi6DiNA9wztqK3lvaBmSkJMh4VKIbejVqAXx1a0tg==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/icons": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@scalar/icons/-/icons-0.4.7.tgz", + "integrity": "sha512-0qXPGRdZ180TMfejWCPYy7ILszBrAraq4KBhPtcM12ghc5qkncFWWpTm5yXI/vrbm10t7wvtTK08CLZ36CnXlQ==", + "license": "MIT", + "dependencies": { + "@phosphor-icons/core": "^2.1.1", + "@types/node": "^22.9.0", + "chalk": "^5.4.1", + "vue": "^3.5.17" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/icons/node_modules/@types/node": { + "version": "22.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz", + "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@scalar/icons/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@scalar/icons/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/@scalar/import": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/@scalar/import/-/import-0.4.24.tgz", + "integrity": "sha512-SWGudO+ZPnjJC3jbTqxwo/h1r1jDfCFaAx+Icx+nyD8j0iIST2F3uNhgvobl2frQI4i3h29HAiZuxdZjIdOGAg==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.0.11", + "@scalar/openapi-parser": "0.20.6", + "yaml": "2.8.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/json-magic": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@scalar/json-magic/-/json-magic-0.4.3.tgz", + "integrity": "sha512-ve2prNKXWO+4O/fqzNhxxuXc0e3bgjVw4zmbYyPsc14gkHsbdDxvag/vKCVMmfKdBsiPVTW4ETiRu5saEHNBfA==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.0.11", + "vue": "^3.5.17", + "yaml": "2.8.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/oas-utils": { + "version": "0.4.29", + "resolved": "https://registry.npmjs.org/@scalar/oas-utils/-/oas-utils-0.4.29.tgz", + "integrity": "sha512-5vEnIhUL5ga3ibWeGowELb5NZNaeSpJTHQNdSWHAuGCcNE/jqLtke+0MB2iIXv5XLDN+an8ZTIXkN0GYPN0Cgw==", + "license": "MIT", + "dependencies": { + "@hyperjump/browser": "^1.1.0", + "@hyperjump/json-schema": "^1.9.6", + "@scalar/helpers": "0.0.11", + "@scalar/json-magic": "0.4.3", + "@scalar/object-utils": "1.2.7", + "@scalar/openapi-types": "0.3.7", + "@scalar/themes": "0.13.17", + "@scalar/types": "0.2.15", + "@scalar/workspace-store": "0.15.6", + "@types/har-format": "^1.2.15", + "flatted": "^3.3.3", + "js-base64": "^3.7.8", + "microdiff": "^1.5.0", + "nanoid": "5.1.5", + "type-fest": "4.41.0", + "yaml": "2.8.0", + "zod": "3.24.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/oas-utils/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/oas-utils/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@scalar/oas-utils/node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@scalar/object-utils": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@scalar/object-utils/-/object-utils-1.2.7.tgz", + "integrity": "sha512-zD+e/NLWXWSdD3DoFQi5IjSPiX4tYN5OavotujqRgjWI/J8Uee/ED/REm0+w16k4jEzyM8Fly/vH4Osl4SMJLQ==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.0.11", + "flatted": "^3.3.3", + "just-clone": "^6.2.0", + "ts-deepmerge": "^7.0.1", + "type-fest": "4.41.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/object-utils/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@scalar/openapi-parser": { + "version": "0.20.6", + "resolved": "https://registry.npmjs.org/@scalar/openapi-parser/-/openapi-parser-0.20.6.tgz", + "integrity": "sha512-TS2E9PVZRxls4gNwTAaEgyCJbW/5vm6+ntf8FwGe/osWYZTiKUZ5iEPW/NtrgfiNiylUPqlo0hGa/K//zeJeqw==", + "license": "MIT", + "dependencies": { + "@scalar/json-magic": "0.4.3", + "@scalar/openapi-types": "0.3.7", + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "ajv-formats": "^3.0.1", + "jsonpointer": "^5.0.1", + "leven": "^4.0.0", + "yaml": "2.8.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/openapi-parser/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@scalar/openapi-parser/node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@scalar/openapi-parser/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@scalar/openapi-types": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@scalar/openapi-types/-/openapi-types-0.3.7.tgz", + "integrity": "sha512-QHSvHBVDze3+dUwAhIGq6l1iOev4jdoqdBK7QpfeN1Q4h+6qpVEw3EEqBiH0AXUSh/iWwObBv4uMgfIx0aNZ5g==", + "license": "MIT", + "dependencies": { + "zod": "3.24.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/openapi-types/node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@scalar/postman-to-openapi": { + "version": "0.3.32", + "resolved": "https://registry.npmjs.org/@scalar/postman-to-openapi/-/postman-to-openapi-0.3.32.tgz", + "integrity": "sha512-ak9p9NQVF8IO2cVY2EVcuEVnAxUC+rfDM2GDPRnl7u395xe/HefUR7VktYcjr7jcCZsU/VpJsKnqREfRuG1lIQ==", + "license": "MIT", + "dependencies": { + "@scalar/helpers": "0.0.11", + "@scalar/oas-utils": "0.4.29", + "@scalar/openapi-types": "0.3.7" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/snippetz": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/@scalar/snippetz/-/snippetz-0.4.9.tgz", + "integrity": "sha512-l3dsAHZsUs7NaYwRC4Tuh+I9GFARgWESmY8CE9R6lY1oZQot6e16pXwR/xPdEU4MpHUXqHnRpO62ts3TYrTISA==", + "license": "MIT", + "dependencies": { + "@scalar/types": "0.2.15", + "stringify-object": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/themes": { + "version": "0.13.17", + "resolved": "https://registry.npmjs.org/@scalar/themes/-/themes-0.13.17.tgz", + "integrity": "sha512-6iIMuCvTf7k4N8h7mOke5cEHBWCK4W8JOCAEU636ZEREMg/q+27r+i0Z0nlqXshB5OEgPzOPoW7ls7dMfwEI6Q==", + "license": "MIT", + "dependencies": { + "@scalar/types": "0.2.15", + "nanoid": "5.1.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/themes/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/typebox": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@scalar/typebox/-/typebox-0.1.1.tgz", + "integrity": "sha512-Mhhubu4zj1PiXhtgvNbz34zniedtO6PYdD80haMkIjOJwV9aWejxXILr2elHGBMsLfdhH3s9qxux6TL6X8Q6/Q==", + "license": "MIT" + }, + "node_modules/@scalar/types": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.2.15.tgz", + "integrity": "sha512-x2aCNmkDqr3VXUHjw7wPXK9KZwHbGGMs4NuxJIzy9MbAxUS9li8HXGG0K82Q5fDm47SAM+68z0/tnWkJpu+kzg==", + "license": "MIT", + "dependencies": { + "@scalar/openapi-types": "0.3.7", + "nanoid": "5.1.5", + "zod": "3.24.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/types/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/types/node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@scalar/use-codemirror": { + "version": "0.12.35", + "resolved": "https://registry.npmjs.org/@scalar/use-codemirror/-/use-codemirror-0.12.35.tgz", + "integrity": "sha512-61lXivllaUB5H12bRdCrtUs8nV2nyg09pa+f2h/IANz3tbnVPU4EJrNT546M1p3bf0gaWZVBEq80Pa8ZAOImWw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.18.3", + "@codemirror/commands": "^6.7.1", + "@codemirror/lang-css": "^6.3.1", + "@codemirror/lang-html": "^6.4.8", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/lang-yaml": "^6.1.2", + "@codemirror/language": "^6.10.7", + "@codemirror/lint": "^6.8.4", + "@codemirror/state": "^6.5.0", + "@codemirror/view": "^6.35.3", + "@lezer/common": "^1.2.3", + "@lezer/highlight": "^1.2.1", + "@replit/codemirror-css-color-picker": "^6.3.0", + "@scalar/components": "0.14.34", + "codemirror": "^6.0.0", + "vue": "^3.5.17" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/use-hooks": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@scalar/use-hooks/-/use-hooks-0.2.5.tgz", + "integrity": "sha512-ML6o5gBNh5ZGObxmmHjCQA6mZhgi+4e8dBhYS1jcuj35tLmV+pMZe+izYJ58+k/szcyNz7aLixTWADBlgCEm0w==", + "license": "MIT", + "dependencies": { + "@scalar/use-toasts": "0.8.0", + "@vueuse/core": "13.9.0", + "cva": "1.0.0-beta.2", + "tailwind-merge": "^2.5.5", + "vue": "^3.5.17", + "zod": "3.24.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/use-hooks/node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@scalar/use-toasts": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@scalar/use-toasts/-/use-toasts-0.8.0.tgz", + "integrity": "sha512-u+o77cdTNZ5ePqHPu8ZcFw1BLlISv+cthN0bR1zJHXmqBjvanFTy2kL+Gmv3eW9HxZiHdqycKVETlYd0mWiqJQ==", + "license": "MIT", + "dependencies": { + "nanoid": "^5.1.5", + "vue": "^3.5.12", + "vue-sonner": "^1.0.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@scalar/use-toasts/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@scalar/workspace-store": { + "version": "0.15.6", + "resolved": "https://registry.npmjs.org/@scalar/workspace-store/-/workspace-store-0.15.6.tgz", + "integrity": "sha512-hJhrrOcDlZs7QfeitAu8krHIrizgnC9xoxHPCDu0LgjoJfGpi7TT92Hj+c1TlqMsLF/mXZ6uLAZ8NJZmiGFkPg==", + "license": "MIT", + "dependencies": { + "@scalar/code-highlight": "0.1.9", + "@scalar/helpers": "0.0.11", + "@scalar/json-magic": "0.4.3", + "@scalar/openapi-parser": "0.20.6", + "@scalar/snippetz": "0.4.9", + "@scalar/typebox": "0.1.1", + "@scalar/types": "0.2.15", + "github-slugger": "^2.0.0", + "type-fest": "4.41.0", + "vue": "^3.5.17", + "yaml": "2.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@scalar/workspace-store/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@spectrum-icons/illustrations": { + "version": "3.6.24", + "resolved": "https://registry.npmjs.org/@spectrum-icons/illustrations/-/illustrations-3.6.24.tgz", + "integrity": "sha512-owJAF3ZyDEEReznMvrqybIMvIZPyGzVdzTTktMZU0WbsUJ4zzFfOo2GwgELz2TwJvyePRSNmQ0X3ZN8hl5jR4Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/utils": "^3.30.0", + "@react-spectrum/icon": "^3.8.7", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@spectrum-icons/ui": { + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/@spectrum-icons/ui/-/ui-3.6.19.tgz", + "integrity": "sha512-QSqc1H6dRllOpukkX8ZY9c7v8j/gq9Ycb06pMZhD9v+AIuH1SUKkMiHrMweqnLXzKecbzs3ms5yAccVVAXaQbA==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum-ui": "1.2.1", + "@react-spectrum/icon": "^3.8.8", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@spectrum-icons/workflow": { + "version": "4.2.24", + "resolved": "https://registry.npmjs.org/@spectrum-icons/workflow/-/workflow-4.2.24.tgz", + "integrity": "sha512-2xvnPs8erBhz6zet9WS8pf2+qT5hzGlQlpAjyughohS+48rC4xu7ylJgjyJqdf53eDLHIdD+QDCkLigvWeGUTA==", + "license": "Apache-2.0", + "dependencies": { + "@adobe/react-spectrum-workflow": "2.3.5", + "@react-spectrum/icon": "^3.8.8", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "@react-spectrum/provider": "^3.0.0", + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@stoplight/json": { + "version": "3.21.7", + "resolved": "https://registry.npmjs.org/@stoplight/json/-/json-3.21.7.tgz", + "integrity": "sha512-xcJXgKFqv/uCEgtGlPxy3tPA+4I+ZI4vAuMJ885+ThkTHFVkC+0Fm58lA9NlsyjnkpxFh4YiQWpH+KefHdbA0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stoplight/ordered-object-literal": "^1.0.3", + "@stoplight/path": "^1.3.2", + "@stoplight/types": "^13.6.0", + "jsonc-parser": "~2.2.1", + "lodash": "^4.17.21", + "safe-stable-stringify": "^1.1" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/@stoplight/ordered-object-literal": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@stoplight/ordered-object-literal/-/ordered-object-literal-1.0.5.tgz", + "integrity": "sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/@stoplight/path": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@stoplight/path/-/path-1.3.2.tgz", + "integrity": "sha512-lyIc6JUlUA8Ve5ELywPC8I2Sdnh1zc1zmbYgVarhXIp9YeAB0ReeqmGEOWNtlHkbP2DAA1AL65Wfn2ncjK/jtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/@stoplight/types": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.20.0.tgz", + "integrity": "sha512-2FNTv05If7ib79VPDA/r9eUet76jewXFH2y2K5vuge6SXbRHtWBhcaRmu+6QpF4/WRNoJj5XYRSwLGXDxysBGA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + } + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tanstack/eslint-plugin-query": { + "version": "5.78.0", + "resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.78.0.tgz", + "integrity": "sha512-hYkhWr3UP0CkAsn/phBVR98UQawbw8CmTSgWtdgEBUjI60/GBaEIkpgi/Bp/2I8eIDK4+vdY7ac6jZx+GR+hEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.18.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.80.10", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.80.10.tgz", + "integrity": "sha512-mUNQOtzxkjL6jLbyChZoSBP6A5gQDVRUiPvW+/zw/9ftOAz+H754zCj3D8PwnzPKyHzGkQ9JbH48ukhym9LK1Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.80.10", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.80.10.tgz", + "integrity": "sha512-6zM098J8sLy9oU60XAdzUlAH4wVzoMVsWUWiiE/Iz4fd67PplxeyL4sw/MPcVJJVhbwGGXCsHn9GrQt2mlAzig==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.80.10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.12", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz", + "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.12", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.12.tgz", + "integrity": "sha512-vhF7kEU9EXWXh+HdAwKJ2m3xaOnTTmgcdXcF2pim8g4GvI7eRrk2YRuV5nUlZnd/NbCIX4/Ja2OZu5EjJL06Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.4.tgz", + "integrity": "sha512-xDXgLjVunjHqczScfkCJ9iyjdNOVHvvCdqHSSxwM9L0l/wHkTRum67SDc020uAlCoqktJplgO2AAQeLP1wgqDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", + "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.18", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.18.tgz", + "integrity": "sha512-KJ65INaxqxmU6EoCiJmRPZC9H9RVWCRd349tXM2M3O5NA7cY6YL7c0bHAHQ93NOfTObEQ004kd2QVHs/r0+m4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", + "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", + "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz", + "integrity": "sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/type-utils": "8.34.1", + "@typescript-eslint/utils": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.34.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.1.tgz", + "integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/typescript-estree": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", + "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.34.1", + "@typescript-eslint/types": "^8.34.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", + "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", + "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz", + "integrity": "sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.34.1", + "@typescript-eslint/utils": "8.34.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", + "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", + "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.34.1", + "@typescript-eslint/tsconfig-utils": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/visitor-keys": "8.34.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", + "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.34.1", + "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/typescript-estree": "8.34.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", + "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.34.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@unhead/dom": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.20.tgz", + "integrity": "sha512-jgfGYdOH+xHJF/j8gudjsYu3oIjFyXhCWcgKaw3vQnT616gSqyqnGQGOItL+BQtQZACKNISwIfx5PuOtztMKLA==", + "license": "MIT", + "dependencies": { + "@unhead/schema": "1.11.20", + "@unhead/shared": "1.11.20" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/schema": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.20.tgz", + "integrity": "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA==", + "license": "MIT", + "dependencies": { + "hookable": "^5.5.3", + "zhead": "^2.2.4" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/shared": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.20.tgz", + "integrity": "sha512-1MOrBkGgkUXS+sOKz/DBh4U20DNoITlJwpmvSInxEUNhghSNb56S0RnaHRq0iHkhrO/cDgz2zvfdlRpoPLGI3w==", + "license": "MIT", + "dependencies": { + "@unhead/schema": "1.11.20", + "packrup": "^0.1.2" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@unhead/vue": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.11.20.tgz", + "integrity": "sha512-sqQaLbwqY9TvLEGeq8Fd7+F2TIuV3nZ5ihVISHjWpAM3y7DwNWRU7NmT9+yYT+2/jw1Vjwdkv5/HvDnvCLrgmg==", + "license": "MIT", + "dependencies": { + "@unhead/schema": "1.11.20", + "@unhead/shared": "1.11.20", + "hookable": "^5.5.3", + "unhead": "1.11.20" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "vue": ">=2.7 || >=3" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.1.tgz", + "integrity": "sha512-dd7yIp1hfJFX9ZlVLQRrh/Re9WMUHHmF9hrKD1yIvxcyNr2BhQ3xc1upAVhy8NijadnCswAxWQu8MkkSMC1qXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.1.tgz", + "integrity": "sha512-EzUPcMFtDVlo5yrbzMqUsGq3HnLXw+3ZOhSd7CUaDmbTtnrzM+RO2ntw2dm2wjbbc5djWj3yX0wzbbg8pLhx8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.1.tgz", + "integrity": "sha512-nB+dna3q4kOleKFcSZJ/wDXIsAd1kpMO9XrVAt8tG3RDWJ6vi+Ic6bpz4cmg5tWNeCfHEY4KuqJCB+pKejPEmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.1.tgz", + "integrity": "sha512-aKWHCrOGaCGwZcekf3TnczQoBxk5w//W3RZ4EQyhux6rKDwBPgDU9Y2yGigCV1Z+8DWqZgVGQi+hdpnlSy3a1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.1.tgz", + "integrity": "sha512-4dIEMXrXt0UqDVgrsUd1I+NoIzVQWXy/CNhgpfS75rOOMK/4Abn0Mx2M2gWH4Mk9+ds/ASAiCmqoUFynmMY5hA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.1.tgz", + "integrity": "sha512-vtvS13IXPs1eE8DuS/soiosqMBeyh50YLRZ+p7EaIKAPPeevRnA9G/wu/KbVt01ZD5qiGjxS+CGIdVC7I6gTOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.1.tgz", + "integrity": "sha512-BfdnN6aZ7NcX8djW8SR6GOJc+K+sFhWRF4vJueVE0vbUu5N1bLnBpxJg1TGlhSyo+ImC4SR0jcNiKN0jdoxt+A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.1.tgz", + "integrity": "sha512-Jhge7lFtH0QqfRz2PyJjJXWENqywPteITd+nOS0L6AhbZli+UmEyGBd2Sstt1c+l9C+j/YvKTl9wJo9PPmsFNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.1.tgz", + "integrity": "sha512-ofdK/ow+ZSbSU0pRoB7uBaiRHeaAOYQFU5Spp87LdcPL/P1RhbCTMSIYVb61XWzsVEmYKjHFtoIE0wxP6AFvrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.1.tgz", + "integrity": "sha512-eC8SXVn8de67HacqU7PoGdHA+9tGbqfEdD05AEFRAB81ejeQtNi5Fx7lPcxpLH79DW0BnMAHau3hi4RVkHfSCw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.1.tgz", + "integrity": "sha512-fIkwvAAQ41kfoGWfzeJ33iLGShl0JEDZHrMnwTHMErUcPkaaZRJYjQjsFhMl315NEQ4mmTlC+2nfK/J2IszDOw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.1.tgz", + "integrity": "sha512-RAAszxImSOFLk44aLwnSqpcOdce8sBcxASledSzuFAd8Q5ZhhVck472SisspnzHdc7THCvGXiUeZ2hOC7NUoBQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.1.tgz", + "integrity": "sha512-QoP9vkY+THuQdZi05bA6s6XwFd6HIz3qlx82v9bTOgxeqin/3C12Ye7f7EOD00RQ36OtOPWnhEMMm84sv7d1XQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.1.tgz", + "integrity": "sha512-/p77cGN/h9zbsfCseAP5gY7tK+7+DdM8fkPfr9d1ye1fsF6bmtGbtZN6e/8j4jCZ9NEIBBkT0GhdgixSelTK9g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.1.tgz", + "integrity": "sha512-wInTqT3Bu9u50mDStEig1v8uxEL2Ht+K8pir/YhyyrM5ordJtxoqzsL1vR/CQzOJuDunUTrDkMM0apjW/d7/PA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.1.tgz", + "integrity": "sha512-eNwqO5kUa+1k7yFIircwwiniKWA0UFHo2Cfm8LYgkh9km7uMad+0x7X7oXbQonJXlqfitBTSjhA0un+DsHIrhw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.1.tgz", + "integrity": "sha512-Eaz1xMUnoa2mFqh20mPqSdbYl6crnk8HnIXDu6nsla9zpgZJZO8w3c1gvNN/4Eb0RXRq3K9OG6mu8vw14gIqiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.1.tgz", + "integrity": "sha512-H/+d+5BGlnEQif0gnwWmYbYv7HJj563PUKJfn8PlmzF8UmF+8KxdvXdwCsoOqh4HHnENnoLrav9NYBrv76x1wQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.1.tgz", + "integrity": "sha512-rS86wI4R6cknYM3is3grCb/laE8XBEbpWAMSIPjYfmYp75KL5dT87jXF2orDa4tQYg5aajP5G8Fgh34dRyR+Rw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.21.tgz", + "integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@vue/shared": "3.5.21", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/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/@vue/compiler-dom": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz", + "integrity": "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.21", + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz", + "integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@vue/compiler-core": "3.5.21", + "@vue/compiler-dom": "3.5.21", + "@vue/compiler-ssr": "3.5.21", + "@vue/shared": "3.5.21", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.18", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-sfc/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/@vue/compiler-ssr": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz", + "integrity": "sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.21", + "@vue/shared": "3.5.21" + } + }, + "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" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.21.tgz", + "integrity": "sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.21.tgz", + "integrity": "sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.21", + "@vue/shared": "3.5.21" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.21.tgz", + "integrity": "sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.21", + "@vue/runtime-core": "3.5.21", + "@vue/shared": "3.5.21", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.21.tgz", + "integrity": "sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.21", + "@vue/shared": "3.5.21" + }, + "peerDependencies": { + "vue": "3.5.21" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.21.tgz", + "integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.9.0.tgz", + "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "13.9.0", + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/integrations": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-13.9.0.tgz", + "integrity": "sha512-SDobKBbPIOe0cVL7QxMzGkuUGHvWTdihi9zOrrWaWUgFKe15cwEcwfWmgrcNzjT6kHnNmWuTajPHoIzUjYNYYQ==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "13.9.0", + "@vueuse/shared": "13.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7 || ^8", + "vue": "^3.5.0" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.9.0.tgz", + "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz", + "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@yellow-ticket/seed-json-schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@yellow-ticket/seed-json-schema/-/seed-json-schema-0.1.6.tgz", + "integrity": "sha512-RtI85ohEQpARt8qRLeqglOpPFu/00YGmw3Mi7iNphbjoDCwm4QrZWnVBy9uEn2veUC6fuw0GHjHAFzAfJxaEZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@faker-js/faker": "^8.4.1", + "@types/json-schema": "^7.0.15", + "outvariant": "^1.4.2", + "randexp": "^0.5.3" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/babel-plugin-macros/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", + "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.4", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/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/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", + "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.4" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-react-compiler": { + "version": "19.1.0-rc.3", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.1.0-rc.3.tgz", + "integrity": "sha512-mjRn69WuTz4adL0bXGx8Rsyk1086zFJeKmes6aK0xPuK3aaXmDJdLHqwKKMrpm6KAI1MCoUK72d2VeqQbu8YIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + } + }, + "node_modules/babel-plugin-transform-react-remove-prop-types": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", + "integrity": "sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-preset-react-app": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-react-app/-/babel-preset-react-app-10.1.0.tgz", + "integrity": "sha512-f9B1xMdnkCIqe+2dHrJsoQFRz7reChaAHE/65SdaykPklQqhme2WaC08oD3is77x9ff98/9EazAKFDZv5rFEQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.16.0", + "@babel/plugin-proposal-class-properties": "^7.16.0", + "@babel/plugin-proposal-decorators": "^7.16.4", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", + "@babel/plugin-proposal-numeric-separator": "^7.16.0", + "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@babel/plugin-proposal-private-methods": "^7.16.0", + "@babel/plugin-proposal-private-property-in-object": "^7.16.7", + "@babel/plugin-transform-flow-strip-types": "^7.16.0", + "@babel/plugin-transform-react-display-name": "^7.16.0", + "@babel/plugin-transform-runtime": "^7.16.4", + "@babel/preset-env": "^7.16.4", + "@babel/preset-react": "^7.16.0", + "@babel/preset-typescript": "^7.16.0", + "@babel/runtime": "^7.16.3", + "babel-plugin-macros": "^3.1.0", + "babel-plugin-transform-react-remove-prop-types": "^0.4.24" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "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.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", + "dev": true, + "license": "MIT/X11" + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001724", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001724.tgz", + "integrity": "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==", + "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/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.1.tgz", + "integrity": "sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/change-case": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", + "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorjs.io": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", + "integrity": "sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/comlink": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz", + "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", + "license": "Apache-2.0" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-js": { + "version": "3.42.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", + "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, + "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/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "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" + }, + "node_modules/cva": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/cva/-/cva-1.0.0-beta.2.tgz", + "integrity": "sha512-dqcOFe247I5pKxfuzqfq3seLL5iMYsTgo40Uw7+pKZAntPgFtR7Tmy59P5IVIq/XgB0NQWoIvYDt9TwHkuK8Cg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + }, + "peerDependencies": { + "typescript": ">= 4.5.5 < 6" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "license": "MIT" + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.171", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.171.tgz", + "integrity": "sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "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/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.1", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.29.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-import-context": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.8.tgz", + "integrity": "sha512-bq+F7nyc65sKpZGT09dY0S0QrOnQtuDVIfyTGQ8uuvtMIF7oHp6CEP3mouN0rrnYF3Jqo6Ke0BfU/5wASZue1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.1.1" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.3.tgz", + "integrity": "sha512-elVDn1eWKFrWlzxlWl9xMt8LltjKl161Ix50JFC50tHXI5/TRP32SNEqlJ/bo/HV+g7Rou/tlPQU2AcRtIhrOg==", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.1.1", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-header": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz", + "integrity": "sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=7.7.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/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/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-playwright": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-2.2.0.tgz", + "integrity": "sha512-qSQpAw7RcSzE3zPp8FMGkthaCWovHZ/BsXtpmnGax9vQLIovlh1bsZHEa2+j2lv9DWhnyeLM/qZmp7ffQZfQvg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "examples" + ], + "dependencies": { + "globals": "^13.23.0" + }, + "engines": { + "node": ">=16.6.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/eslint-plugin-playwright/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-playwright/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-compiler": { + "version": "19.1.0-rc.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.1.0-rc.2.tgz", + "integrity": "sha512-oKalwDGcD+RX9mf3NEO4zOoUMeLvjSvcbbEOpquzmzqEEM2MQdp7/FY/Hx9NzmUwFzH1W9SKTz5fihfMldpEYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "hermes-parser": "^0.25.1", + "zod": "^3.22.4", + "zod-validation-error": "^3.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.0.0 || >= 18.0.0" + }, + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "6.0.0-rc1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-6.0.0-rc1.tgz", + "integrity": "sha512-I4ntWyjqgGemGtOU85FUdVo00h0i0Y5xvQ7a8EVxyzjOZsxXaxvkKBcYoXbP97QDvDjMzY/nGIvfdB/WRLTGxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/plugin-transform-private-methods": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.22.4", + "zod-validation-error": "^3.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/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/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", + "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "license": "ISC" + }, + "node_modules/focus-trap": { + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.5.tgz", + "integrity": "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==", + "license": "MIT", + "dependencies": { + "tabbable": "^6.2.0" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/framer-motion": { + "version": "12.23.24", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz", + "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fuse.js": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz", + "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "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-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/graphql": { + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", + "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/harmony-reflect": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.2.tgz", + "integrity": "sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==", + "dev": true, + "license": "(Apache-2.0 OR MPL-1.1)" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-sanitize": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz", + "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "unist-util-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/highlightjs-curl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/highlightjs-curl/-/highlightjs-curl-1.3.0.tgz", + "integrity": "sha512-50UEfZq1KR0Lfk2Tr6xb/MUIZH3h10oNC0OTy9g7WELcs5Fgy/mKN1vEhuKTkKbdo8vr5F9GXstu2eLhApfQ3A==", + "license": "Apache-2.0" + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/identity-obj-proxy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz", + "integrity": "sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==", + "dev": true, + "license": "MIT", + "dependencies": { + "harmony-reflect": "^1.4.6" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", + "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/index-to-position": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", + "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/intl-messageformat": { + "version": "10.7.16", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz", + "integrity": "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==", + "license": "BSD-3-Clause", + "dependencies": { + "@formatjs/ecma402-abstract": "2.3.4", + "@formatjs/fast-memoize": "2.2.7", + "@formatjs/icu-messageformat-parser": "2.11.2", + "tslib": "^2.8.0" + } + }, + "node_modules/is-absolute-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", + "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "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/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "license": "BSD-3-Clause" + }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "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/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-deterministic": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/json-stringify-deterministic/-/json-stringify-deterministic-1.0.12.tgz", + "integrity": "sha512-q3PN0lbUdv0pmurkBNdJH3pfFvOTL/Zp0lquqpvcjfKzt6Y0j49EPHAmVHCAS4Ceq/Y+PejWTzyiVpoY71+D6g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "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/jsonc-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.2.1.tgz", + "integrity": "sha512-o6/yDBYccGvTz1+QFevz6l6OBZ2+fMVu2JZ9CIhzsYRX4mjaK5IyX9eldUdCmga16zlgQxyrj5pt9kzuj2C02w==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/just-clone": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-6.2.0.tgz", + "integrity": "sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==", + "license": "MIT" + }, + "node_modules/just-curry-it": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/just-curry-it/-/just-curry-it-5.3.0.tgz", + "integrity": "sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==", + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/leven": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-4.1.0.tgz", + "integrity": "sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/loupe": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.0.tgz", + "integrity": "sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.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.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/microdiff": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/microdiff/-/microdiff-1.5.0.tgz", + "integrity": "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==", + "license": "MIT" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/motion": { + "version": "12.23.24", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.23.24.tgz", + "integrity": "sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.23.24", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/msw": { + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.11.3.tgz", + "integrity": "sha512-878imp8jxIpfzuzxYfX0qqTq1IFQz/1/RBHs/PyirSjzi+xKM/RRfIpIqHSCWjH0GxidrjhgiiXC+DWXNDvT9w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@bundled-es-modules/cookie": "^2.0.1", + "@bundled-es-modules/statuses": "^1.0.1", + "@inquirer/confirm": "^5.0.0", + "@mswjs/interceptors": "^0.39.1", + "@open-draft/deferred-promise": "^2.2.0", + "@types/cookie": "^0.6.0", + "@types/statuses": "^2.0.4", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "rettime": "^0.7.0", + "strict-event-emitter": "^0.5.1", + "tough-cookie": "^6.0.0", + "type-fest": "^4.26.1", + "until-async": "^3.0.2", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/tldts": { + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.16.tgz", + "integrity": "sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.16" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/msw/node_modules/tldts-core": { + "version": "7.0.16", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.16.tgz", + "integrity": "sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==", + "dev": true, + "license": "MIT" + }, + "node_modules/msw/node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/msw/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "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/napi-postinstall": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", + "integrity": "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "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/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.21", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", + "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/openapi-fetch": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.14.0.tgz", + "integrity": "sha512-PshIdm1NgdLvb05zp8LqRQMNSKzIlPkyMxYFxwyHR+UlKD4t2nUjkDhNxeRbhRSEd3x5EUNh2w5sJYwkhOH4fg==", + "license": "MIT", + "dependencies": { + "openapi-typescript-helpers": "^0.0.15" + } + }, + "node_modules/openapi-msw": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/openapi-msw/-/openapi-msw-1.3.0.tgz", + "integrity": "sha512-qRe9zEHSYRi9s9EimTF/IxsG6yvEilkg+pn4Y0QldkuIx+13HcjoNACEumVdpc87nR5AtPErbzuuWFv+cw6fhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "msw": "^2.7.0" + } + }, + "node_modules/openapi-react-query": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/openapi-react-query/-/openapi-react-query-0.5.0.tgz", + "integrity": "sha512-VtyqiamsbWsdSWtXmj/fAR+m9nNxztsof6h8ZIsjRj8c8UR/x9AIwHwd60IqwgymmFwo7qfSJQ1ZzMJrtqjQVg==", + "license": "MIT", + "dependencies": { + "openapi-typescript-helpers": "^0.0.15" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.25.0", + "openapi-fetch": "^0.14.0" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/openapi-typescript": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.8.0.tgz", + "integrity": "sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@redocly/openapi-core": "^1.34.3", + "ansi-colors": "^4.1.3", + "change-case": "^5.4.4", + "parse-json": "^8.3.0", + "supports-color": "^10.0.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "openapi-typescript": "bin/cli.js" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/openapi-typescript-helpers": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.15.tgz", + "integrity": "sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==", + "license": "MIT" + }, + "node_modules/openapi-typescript/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-typescript/node_modules/supports-color": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.0.0.tgz", + "integrity": "sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/openapi-typescript/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/packrup": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/packrup/-/packrup-0.1.2.tgz", + "integrity": "sha512-ZcKU7zrr5GlonoS9cxxrb5HVswGnyj6jQvwFBa6p5VFw7G71VAHcUKL5wyZSU/ECtPM/9gacWxy2KFQKt1gMNA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-3.0.0.tgz", + "integrity": "sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "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/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "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": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.54.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.1.tgz", + "integrity": "sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.54.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.54.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.1.tgz", + "integrity": "sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-ms": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-8.0.0.tgz", + "integrity": "sha512-ASJqOugUF1bbzI35STMBUpZqdfYKlJugy6JBziGi2EE+AL5JPJGSzvpeVXojxrr0ViUYoToUjb5kjSEGf7Y83Q==", + "license": "MIT", + "dependencies": { + "parse-ms": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/radix-vue": { + "version": "1.9.17", + "resolved": "https://registry.npmjs.org/radix-vue/-/radix-vue-1.9.17.tgz", + "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.7", + "@floating-ui/vue": "^1.1.0", + "@internationalized/date": "^3.5.4", + "@internationalized/number": "^3.5.3", + "@tanstack/vue-virtual": "^3.8.1", + "@vueuse/core": "^10.11.0", + "@vueuse/shared": "^10.11.0", + "aria-hidden": "^1.2.4", + "defu": "^6.1.4", + "fast-deep-equal": "^3.1.3", + "nanoid": "^5.0.7" + }, + "peerDependencies": { + "vue": ">= 3.2.0" + } + }, + "node_modules/radix-vue/node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/radix-vue/node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/radix-vue/node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/radix-vue/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-aria": { + "version": "3.43.1", + "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.43.1.tgz", + "integrity": "sha512-/PmZGiw+Ya/YtzXmiLW4ALD4SMuDnbwhMaVh33VCduTl8vVujIUzUTIi5g4C+YHLDs/Z4edsN3aQsPMqFfg1xA==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/string": "^3.2.7", + "@react-aria/breadcrumbs": "^3.5.28", + "@react-aria/button": "^3.14.1", + "@react-aria/calendar": "^3.9.1", + "@react-aria/checkbox": "^3.16.1", + "@react-aria/color": "^3.1.1", + "@react-aria/combobox": "^3.13.2", + "@react-aria/datepicker": "^3.15.1", + "@react-aria/dialog": "^3.5.30", + "@react-aria/disclosure": "^3.0.8", + "@react-aria/dnd": "^3.11.2", + "@react-aria/focus": "^3.21.1", + "@react-aria/gridlist": "^3.14.0", + "@react-aria/i18n": "^3.12.12", + "@react-aria/interactions": "^3.25.5", + "@react-aria/label": "^3.7.21", + "@react-aria/landmark": "^3.0.6", + "@react-aria/link": "^3.8.5", + "@react-aria/listbox": "^3.14.8", + "@react-aria/menu": "^3.19.2", + "@react-aria/meter": "^3.4.26", + "@react-aria/numberfield": "^3.12.1", + "@react-aria/overlays": "^3.29.1", + "@react-aria/progress": "^3.4.26", + "@react-aria/radio": "^3.12.1", + "@react-aria/searchfield": "^3.8.8", + "@react-aria/select": "^3.16.2", + "@react-aria/selection": "^3.25.1", + "@react-aria/separator": "^3.4.12", + "@react-aria/slider": "^3.8.1", + "@react-aria/ssr": "^3.9.10", + "@react-aria/switch": "^3.7.7", + "@react-aria/table": "^3.17.7", + "@react-aria/tabs": "^3.10.7", + "@react-aria/tag": "^3.7.1", + "@react-aria/textfield": "^3.18.1", + "@react-aria/toast": "^3.0.7", + "@react-aria/tooltip": "^3.8.7", + "@react-aria/tree": "^3.1.3", + "@react-aria/utils": "^3.30.1", + "@react-aria/visually-hidden": "^3.8.27", + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-aria-components": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.12.1.tgz", + "integrity": "sha512-UTn2y2Pr1QuapXLRoGE/GWHrjcZZSuMf+zhbJjInOHgS+MC4hqXiINufvjQrdjQDzS1llc2aepP9op6+z6QSxA==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.9.0", + "@internationalized/string": "^3.2.7", + "@react-aria/autocomplete": "3.0.0-rc.1", + "@react-aria/collections": "3.0.0-rc.6", + "@react-aria/dnd": "^3.11.2", + "@react-aria/focus": "^3.21.1", + "@react-aria/interactions": "^3.25.5", + "@react-aria/live-announcer": "^3.4.4", + "@react-aria/overlays": "^3.29.1", + "@react-aria/ssr": "^3.9.10", + "@react-aria/textfield": "^3.18.1", + "@react-aria/toolbar": "3.0.0-beta.20", + "@react-aria/utils": "^3.30.1", + "@react-aria/virtualizer": "^4.1.9", + "@react-stately/autocomplete": "3.0.0-beta.3", + "@react-stately/layout": "^4.5.0", + "@react-stately/selection": "^3.20.5", + "@react-stately/table": "^3.15.0", + "@react-stately/utils": "^3.10.8", + "@react-stately/virtualizer": "^4.4.3", + "@react-types/form": "^3.7.15", + "@react-types/grid": "^3.3.5", + "@react-types/shared": "^3.32.0", + "@react-types/table": "^3.13.3", + "@swc/helpers": "^0.5.0", + "client-only": "^0.0.1", + "react-aria": "^3.43.1", + "react-stately": "^3.41.0", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-aria-components/node_modules/@react-aria/collections": { + "version": "3.0.0-rc.6", + "resolved": "https://registry.npmjs.org/@react-aria/collections/-/collections-3.0.0-rc.6.tgz", + "integrity": "sha512-N4AzRqzFJ4BztM1x56ot33smDUUsYQ6pzlbz6m4f8ARSqQYl0a2FsM13PYDtuNI5Dt9KtkL6rK/tLaZlTghLyg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.25.5", + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.30.1", + "@react-types/shared": "^3.32.0", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-colorful": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", + "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.1.tgz", + "integrity": "sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.1.tgz", + "integrity": "sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "react-router": "6.30.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "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" + } + }, + "node_modules/react-stately": { + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.41.0.tgz", + "integrity": "sha512-Fe8PaZPm9Ue9kDXVa8KaOz6gzbmZPuzftxeVQwKVX3u/kyFhbRkr/LeAFvgP7a+EeX+Bjmdht/9ixDsBXj4qbQ==", + "license": "Apache-2.0", + "dependencies": { + "@react-stately/calendar": "^3.8.4", + "@react-stately/checkbox": "^3.7.1", + "@react-stately/collections": "^3.12.7", + "@react-stately/color": "^3.9.1", + "@react-stately/combobox": "^3.11.1", + "@react-stately/data": "^3.14.0", + "@react-stately/datepicker": "^3.15.1", + "@react-stately/disclosure": "^3.0.7", + "@react-stately/dnd": "^3.7.0", + "@react-stately/form": "^3.2.1", + "@react-stately/list": "^3.13.0", + "@react-stately/menu": "^3.9.7", + "@react-stately/numberfield": "^3.10.1", + "@react-stately/overlays": "^3.6.19", + "@react-stately/radio": "^3.11.1", + "@react-stately/searchfield": "^3.5.15", + "@react-stately/select": "^3.7.1", + "@react-stately/selection": "^3.20.5", + "@react-stately/slider": "^3.7.1", + "@react-stately/table": "^3.15.0", + "@react-stately/tabs": "^3.8.5", + "@react-stately/toast": "^3.1.2", + "@react-stately/toggle": "^3.9.1", + "@react-stately/tooltip": "^3.5.7", + "@react-stately/tree": "^3.9.2", + "@react-types/shared": "^3.32.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/recharts": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.3.tgz", + "integrity": "sha512-EdOPzTwcFSuqtvkDoaM5ws/Km1+WTAO2eizL7rqiG0V2UVhTnz0m7J2i0CjVPUCdEkZImaWvXLbZDS2H5t6GFQ==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "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" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reduce-configs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reduce-configs/-/reduce-configs-1.1.0.tgz", + "integrity": "sha512-DQxy6liNadHfrLahZR7lMdc227NYVaQZhY5FMsxLEjX8X0SCuH+ESHSLCoz2yDZFq1/CLMDOAHdsEHwOEXKtvg==", + "dev": true, + "license": "MIT" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", + "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.0", + "regjsgen": "^0.8.0", + "regjsparser": "^0.12.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.0.2" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/rehype-external-links": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rehype-external-links/-/rehype-external-links-3.0.0.tgz", + "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-is-element": "^3.0.0", + "is-absolute-url": "^4.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-sanitize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz", + "integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-sanitize": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rettime": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.7.0.tgz", + "integrity": "sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-1.1.1.tgz", + "integrity": "sha512-ERq4hUjKDbJfE4+XtZLFPCDi8Vb1JqaxAPTxWFLBx8XcAlf9Bda/ZJdVezs/NAfsMQScyIlUMx+Yeu7P7rx5jw==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sass-embedded": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.89.0.tgz", + "integrity": "sha512-EDrK1el9zdgJFpocCGlxatDWaP18tJBWoM1hxzo2KJBvjdmBichXI6O6KlQrigvQPO3uJ8DfmFmAAx7s7CG6uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.0.0", + "buffer-builder": "^0.2.0", + "colorjs.io": "^0.5.0", + "immutable": "^5.0.2", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "sync-child-process": "^1.0.2", + "varint": "^6.0.0" + }, + "bin": { + "sass": "dist/bin/sass.js" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-android-arm": "1.89.0", + "sass-embedded-android-arm64": "1.89.0", + "sass-embedded-android-ia32": "1.89.0", + "sass-embedded-android-riscv64": "1.89.0", + "sass-embedded-android-x64": "1.89.0", + "sass-embedded-darwin-arm64": "1.89.0", + "sass-embedded-darwin-x64": "1.89.0", + "sass-embedded-linux-arm": "1.89.0", + "sass-embedded-linux-arm64": "1.89.0", + "sass-embedded-linux-ia32": "1.89.0", + "sass-embedded-linux-musl-arm": "1.89.0", + "sass-embedded-linux-musl-arm64": "1.89.0", + "sass-embedded-linux-musl-ia32": "1.89.0", + "sass-embedded-linux-musl-riscv64": "1.89.0", + "sass-embedded-linux-musl-x64": "1.89.0", + "sass-embedded-linux-riscv64": "1.89.0", + "sass-embedded-linux-x64": "1.89.0", + "sass-embedded-win32-arm64": "1.89.0", + "sass-embedded-win32-ia32": "1.89.0", + "sass-embedded-win32-x64": "1.89.0" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.89.0.tgz", + "integrity": "sha512-s6jxkEZQQrtyIGZX6Sbcu7tEixFG2VkqFgrX11flm/jZex7KaxnZtFace+wnYAgHqzzYpx0kNzJUpT+GXxm8CA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.89.0.tgz", + "integrity": "sha512-pr4R3p5R+Ul9ZA5nzYbBJQFJXW6dMGzgpNBhmaToYDgDhmNX5kg0mZAUlGLHvisLdTiR6oEfDDr9QI6tnD2nqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-ia32": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.89.0.tgz", + "integrity": "sha512-GoNnNGYmp1F0ZMHqQbAurlQsjBMZKtDd5H60Ruq86uQFdnuNqQ9wHKJsJABxMnjfAn60IjefytM5PYTMcAmbfA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-riscv64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.89.0.tgz", + "integrity": "sha512-di+i4KkKAWTNksaQYTqBEERv46qV/tvv14TPswEfak7vcTQ2pj2mvV4KGjLYfU2LqRkX/NTXix9KFthrzFN51Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.89.0.tgz", + "integrity": "sha512-1cRRDAnmAS1wLaxfFf6PCHu9sKW8FNxdM7ZkanwxO9mztrCu/uvfqTmaurY9+RaKvPus7sGYFp46/TNtl/wRjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.89.0.tgz", + "integrity": "sha512-EUNUzI0UkbQ6dASPyf09S3x7fNT54PjyD594ZGTY14Yh4qTuacIj27ckLmreAJNNu5QxlbhyYuOtz+XN5bMMxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.89.0.tgz", + "integrity": "sha512-23R8zSuB31Fq/MYpmQ38UR2C26BsYb66VVpJgWmWl/N+sgv/+l9ECuSPMbYNgM3vb9TP9wk9dgL6KkiCS5tAyg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.89.0.tgz", + "integrity": "sha512-KAzA1XD74d8/fiJXxVnLfFwfpmD2XqUJZz+DL6ZAPNLH1sb+yCP7brktaOyClDc/MBu61JERdHaJjIZhfX0Yqw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.89.0.tgz", + "integrity": "sha512-g9Lp57qyx51ttKj0AN/edV43Hu1fBObvD7LpYwVfs6u3I95r0Adi90KujzNrUqXxJVmsfUwseY8kA8zvcRjhYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-ia32": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.89.0.tgz", + "integrity": "sha512-5fxBeXyvBr3pb+vyrx9V6yd7QDRXkAPbwmFVVhjqshBABOXelLysEFea7xokh/tM8JAAQ4O8Ls3eW3Eojb477g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.89.0.tgz", + "integrity": "sha512-0Q1JeEU4/tzH7fwAwarfIh+Swn3aXG/jPhVsZpbR1c1VzkeaPngmXdmLJcVXsdb35tjk84DuYcFtJlE1HYGw4Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.89.0.tgz", + "integrity": "sha512-50oelrOtN64u15vJN9uJryIuT0+UPjyeoq0zdWbY8F7LM9294Wf+Idea+nqDUWDCj1MHndyPFmR1mjeuRouJhw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-ia32": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.89.0.tgz", + "integrity": "sha512-ILWqpTd+0RdsSw977iVAJf4CLetIbcQgLQf17ycS1N4StZKVRZs1bBfZhg/f/HU/4p5HondPAwepgJepZZdnFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-riscv64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.89.0.tgz", + "integrity": "sha512-n2V+Tdjj7SAuiuElJYhWiHjjB1YU0cuFvL1/m5K+ecdNStfHFWIzvBT6/vzQnBOWjI4eZECNVuQ8GwGWCufZew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.89.0.tgz", + "integrity": "sha512-KOHJdouBK3SLJKZLnFYzuxs3dn+6jaeO3p4p1JUYAcVfndcvh13Sg2sLGfOfpg7Og6ws2Nnqnx0CyL26jPJ7ag==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-riscv64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.89.0.tgz", + "integrity": "sha512-0A/UWeKX6MYhVLWLkdX3NPKHO+mvIwzaf6TxGCy3vS3TODWaeDUeBhHShAr7YlOKv5xRGxf7Gx7FXCPV0mUyMA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.89.0.tgz", + "integrity": "sha512-dRBoOFPDWctHPYK3hTk3YzyX/icVrXiw7oOjbtpaDr6JooqIWBe16FslkWyvQzdmfOFy80raKVjgoqT7DsznkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.89.0.tgz", + "integrity": "sha512-RnlVZ14hC/W7ubzvhqnbGfjU5PFNoFP/y5qycgCy+Mezb0IKbWvZ2Lyzux8TbL3OIjOikkNpfXoNQrX706WLAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-ia32": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.89.0.tgz", + "integrity": "sha512-eFe9VMNG+90nuoE3eXDy+38+uEHGf7xcqalq5+0PVZfR+H9RlaEbvIUNflZV94+LOH8Jb4lrfuekhHgWDJLfSg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.89.0", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.89.0.tgz", + "integrity": "sha512-AaGpr5R6MLCuSvkvDdRq49ebifwLcuGPk0/10hbYw9nh3jpy2/CylYubQpIpR4yPcuD1wFwFqufTXC3HJYGb0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "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/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spin-delay": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/spin-delay/-/spin-delay-2.0.1.tgz", + "integrity": "sha512-ilggKXKqAMwk21PSYvxuF/KCnrsGFDrnO6mXa629mj8fvfo+dOQfubDViqsRjRX5U1jd3Xb8FTsV+m4Tg7YeUg==", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0.1" + } + }, + "node_modules/stable-hash-x": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.1.1.tgz", + "integrity": "sha512-l0x1D6vhnsNUGPFVDx45eif0y6eedVC8nm5uACTrVFJFtl2mLRW17aWtVyxFCpn5t94VUPkjU8vSLwIuwwqtJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true, + "license": "MIT" + }, + "node_modules/static-path": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/static-path/-/static-path-0.0.4.tgz", + "integrity": "sha512-RAe93sPAKMDDuXuyU4UKJ+Sm84XJXk3JH21jlPu3bhs30o9Z3abRF6AE8qZCjCWPlJQrHIy4NfxO5CeYOThLFg==", + "license": "ISC" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", + "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-keys": "^1.0.0", + "is-obj": "^3.0.0", + "is-regexp": "^3.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/stringify-object?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/sync-child-process": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", + "integrity": "sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sync-message-port": "^1.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/sync-message-port": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sync-message-port/-/sync-message-port-1.1.3.tgz", + "integrity": "sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", + "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", + "license": "MIT", + "peer": true + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "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/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/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/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-deepmerge": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.3.tgz", + "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==", + "license": "ISC", + "engines": { + "node": ">=14.13.1" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "dev": true, + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.34.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.1.tgz", + "integrity": "sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.34.1", + "@typescript-eslint/parser": "8.34.1", + "@typescript-eslint/utils": "8.34.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unhead": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.11.20.tgz", + "integrity": "sha512-3AsNQC0pjwlLqEYHLjtichGWankK8yqmocReITecmpB1H0aOabeESueyy+8X1gyJx4ftZVwo9hqQ4O3fPWffCA==", + "license": "MIT", + "dependencies": { + "@unhead/dom": "1.11.20", + "@unhead/schema": "1.11.20", + "@unhead/shared": "1.11.20", + "hookable": "^5.5.3" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", + "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unrs-resolver": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.1.tgz", + "integrity": "sha512-4AZVxP05JGN6DwqIkSP4VKLOcwQa5l37SWHF/ahcuqBMbfxbpN1L1QKafEhWCziHhzKex9H/AR09H0OuVyU+9g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.2.2" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.9.1", + "@unrs/resolver-binding-android-arm64": "1.9.1", + "@unrs/resolver-binding-darwin-arm64": "1.9.1", + "@unrs/resolver-binding-darwin-x64": "1.9.1", + "@unrs/resolver-binding-freebsd-x64": "1.9.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.9.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.9.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.9.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.9.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.9.1", + "@unrs/resolver-binding-linux-x64-musl": "1.9.1", + "@unrs/resolver-binding-wasm32-wasi": "1.9.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.9.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.9.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.9.1" + } + }, + "node_modules/until-async": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", + "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/kettanaito" + } + }, + "node_modules/upath": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", + "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uri-js-replace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", + "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/usehooks-ts": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz", + "integrity": "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==", + "license": "MIT", + "dependencies": { + "lodash.debounce": "^4.0.8" + }, + "engines": { + "node": ">=16.15.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz", + "integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "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-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-plugin-svgr": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.3.0.tgz", + "integrity": "sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.3", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": ">=2.6.0" + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vue": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.21.tgz", + "integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.21", + "@vue/compiler-sfc": "3.5.21", + "@vue/runtime-dom": "3.5.21", + "@vue/server-renderer": "3.5.21", + "@vue/shared": "3.5.21" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.0.7.tgz", + "integrity": "sha512-TvyUcFXmjZcXUvU+r1MOyn4/vv4iF+tPwg5Ig33l/FJ3myZkxeQpzzQMLMFWcQAjr6Xs7BRwVy/TwbmNZUA/4w==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-sonner": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/vue-sonner/-/vue-sonner-1.3.2.tgz", + "integrity": "sha512-UbZ48E9VIya3ToiRHAZUbodKute/z/M1iT8/3fU8zEbwBRE11AKuHikssv18LMk2gTTr6eMQT4qf6JoLHWuj/A==", + "license": "MIT" + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "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/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zhead": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz", + "integrity": "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.5.3.tgz", + "integrity": "sha512-OT5Y8lbUadqVZCsnyFaTQ4/O2mys4tj7PqhdbBCp7McPwvIEKfPtdA6QfPeFQK2/Rz5LgwmAXRJTugBNBi0btw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/config": { + "name": "@geti/config", + "version": "1.0.0" + }, + "packages/ui": { + "name": "@geti/ui", + "version": "1.0.0", + "dependencies": { + "@adobe/react-spectrum": "^3.43.0", + "@spectrum-icons/illustrations": "^3.6.24", + "@spectrum-icons/workflow": "^4.2.23", + "lodash-es": "^4.17.21", + "react-aria-components": "^1.11.0", + "react-colorful": "^5.6.1", + "sonner": "^2.0.7", + "usehooks-ts": "^3.1.1" + }, + "devDependencies": { + "@geti/config": "*", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.6.4", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/lodash-es": "^4.17.12", + "eslint": "^9.25.1", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-playwright": "^2.2.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "typescript": "^5.8.3" + } + }, + "packages/ui/node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + } + } +} diff --git a/application/ui/package.json b/application/ui/package.json new file mode 100644 index 0000000000..18711472c7 --- /dev/null +++ b/application/ui/package.json @@ -0,0 +1,110 @@ +{ + "name": "@geti/inspect-ui", + "version": "1.0.0", + "private": true, + "type": "module", + "engines": { + "node": ">=v24.2.0", + "npm": ">=11.3.0" + }, + "scripts": { + "build": "rsbuild build", + "build:api:types": "npx openapi-typescript ./src/api/openapi-spec.json -o ./src/api/openapi-spec.d.ts --root-types && prettier --write src/api/openapi-spec.d.ts", + "build:api:download": "curl -o ./src/api/openapi-spec.json http://localhost:8000/api/openapi.json && prettier --write src/api/openapi-spec.json", + "build:api": "npm run build:api:download && npm run build:api:types", + "clone-geti-ui-packages": "npx degit git@github.com:open-edge-platform/geti.git/web_ui/packages/config packages/config --force && npx degit git@github.com:open-edge-platform/geti.git/web_ui/packages/ui packages/ui --force", + "cyclic-deps-check": "npx madge --circular src/**/*.ts*", + "dev": "concurrently -n Server,UI -c blue,yellow \"npm run server\" \"npm start\"", + "server": "cd ../backend && ./run.sh", + "start": "rsbuild dev", + "format": "prettier --write .", + "format:check": "prettier --check --cache \"./src/**/*.{js,ts,jsx,tsx,css,scss,sass}\"", + "lint": "eslint \"src/**/*.ts*\" --max-warnings 0", + "lint:fix": "eslint --fix \"./src/**/*.ts*\"", + "preinstall": "npm run clone-geti-ui-packages", + "preview": "rsbuild preview", + "test:unit": "vitest --config vitest.config.ts", + "test:unit:watch": "vitest --config vitest.config.ts --reporter=verbose --watch", + "test:component": "npx playwright test --config=playwright.config.ts", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "@geti/config": "*", + "@geti/ui": "*", + "@scalar/api-reference-react": "^0.7.49", + "@tanstack/react-query": "^5.80.10", + "clsx": "^2.1.1", + "comlink": "^4.4.2", + "lodash-es": "^4.17.21", + "motion": "^12.23.24", + "openapi-fetch": "^0.14.0", + "openapi-react-query": "^0.5.0", + "react": "^19.1.1", + "react-aria-components": "^1.10.1", + "react-dom": "^19.1.1", + "react-error-boundary": "^3.1.4", + "react-router": "^6.30.1", + "react-router-dom": "^6.30.1", + "recharts": "^2.15.3", + "spin-delay": "^2.0.1", + "static-path": "^0.0.4", + "uuid": "^11.1.0" + }, + "devDependencies": { + "@eslint/compat": "^1.2.8", + "@eslint/js": "^9.25.1", + "@ianvs/prettier-plugin-sort-imports": "^4.4.2", + "@msw/playwright": "^0.4.2", + "@mswjs/source": "^0.4.1", + "@playwright/test": "^1.54.1", + "@rsbuild/core": "^1.3.14", + "@rsbuild/plugin-babel": "^1.0.6", + "@rsbuild/plugin-react": "^1.3.0", + "@rsbuild/plugin-sass": "^1.3.1", + "@rsbuild/plugin-svgr": "^1.2.0", + "@tanstack/eslint-plugin-query": "^5.78.0", + "@testing-library/jest-dom": "^6.6.4", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/lodash-es": "^4.17.12", + "@types/node": "^24.0.15", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^8.31.1", + "@vitejs/plugin-react": "^4.7.0", + "babel-plugin-react-compiler": "^19.1.0-rc.3", + "babel-preset-react-app": "^10.1.0", + "concurrently": "^9.2.1", + "eslint": "^9.25.1", + "eslint-import-resolver-typescript": "^4.3.4", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-playwright": "^2.2.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-compiler": "^19.1.0-rc.2", + "eslint-plugin-react-hooks": "^6.0.0-rc.2", + "identity-obj-proxy": "^3.0.0", + "jsdom": "^26.1.0", + "msw": "^2.11.3", + "node-fetch": "^3.3.2", + "openapi-msw": "^1.3.0", + "openapi-typescript": "^7.8.0", + "prettier": "^3.5.3", + "typescript": "^5.8.3", + "typescript-eslint": "^8.31.1", + "vite": "^7.0.6", + "vite-plugin-svgr": "^4.3.0", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.2.4" + }, + "workspaces": [ + "packages/*" + ], + "msw": { + "workerDirectory": [ + "public" + ] + } +} diff --git a/application/ui/playwright.config.ts b/application/ui/playwright.config.ts new file mode 100644 index 0000000000..c418505ca2 --- /dev/null +++ b/application/ui/playwright.config.ts @@ -0,0 +1,49 @@ +import { defineConfig, devices } from '@playwright/test'; + +const CI = !!process.env.CI; + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [[CI ? 'github' : 'list'], ['html', { open: 'never' }]], + use: { + baseURL: 'http://localhost:3000', + trace: CI ? 'on-first-retry' : 'on', + video: CI ? 'on-first-retry' : 'on', + launchOptions: { + slowMo: 100, + headless: true, + devtools: false, + }, + timezoneId: 'UTC', + actionTimeout: CI ? 10000 : 5000, + navigationTimeout: CI ? 10000 : 5000, + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'Component tests', + use: { ...devices['Desktop Chrome'] }, + }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: CI ? 'npx serve -s dist -p 3000' : 'npm run start', + name: 'client', + url: 'http://localhost:3000', + reuseExistingServer: CI === false, + }, +}); diff --git a/application/ui/rsbuild.config.ts b/application/ui/rsbuild.config.ts new file mode 100644 index 0000000000..41b369b664 --- /dev/null +++ b/application/ui/rsbuild.config.ts @@ -0,0 +1,55 @@ +import { defineConfig, loadEnv } from '@rsbuild/core'; +import { pluginReact } from '@rsbuild/plugin-react'; +import { pluginSass } from '@rsbuild/plugin-sass'; +import { pluginSvgr } from '@rsbuild/plugin-svgr'; + +const { publicVars } = loadEnv({ prefixes: ['PUBLIC_'] }); + +export default defineConfig({ + plugins: [ + pluginReact(), + + pluginSass(), + + pluginSvgr({ + svgrOptions: { + exportType: 'named', + }, + }), + ], + + source: { + define: { + ...publicVars, + 'import.meta.env.PUBLIC_API_BASE_URL': + publicVars['import.meta.env.PUBLIC_API_BASE_URL'] ?? '"http://localhost:8000"', + 'process.env.PUBLIC_API_BASE_URL': + publicVars['process.env.PUBLIC_API_BASE_URL'] ?? '"http://localhost:8000"', + // Needed to prevent an issue with spectrum's picker + // eslint-disable-next-line max-len + // https://github.com/adobe/react-spectrum/blob/6173beb4dad153aef74fc81575fd97f8afcf6cb3/packages/%40react-spectrum/overlays/src/OpenTransition.tsx#L40 + 'process.env': {}, + }, + }, + html: { + title: 'Geti Inspect', + favicon: './src/assets/icons/build-icon.svg', + }, + tools: { + rspack: { + watchOptions: { + ignored: ['**/src-tauri/**'], + }, + }, + }, + server: { + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + ws: true, + //pathRewrite: { '^/api': '' }, // strip the /api prefix + }, + }, + }, +}); diff --git a/application/ui/src/api/client.ts b/application/ui/src/api/client.ts new file mode 100644 index 0000000000..8b5e27979b --- /dev/null +++ b/application/ui/src/api/client.ts @@ -0,0 +1,10 @@ +import createFetchClient from 'openapi-fetch'; +import createClient from 'openapi-react-query'; + +import type { paths } from './openapi-spec'; // generated by openapi-typescript + +export const API_BASE_URL = import.meta.env.PUBLIC_API_BASE_URL || ''; + +export const fetchClient = createFetchClient({ baseUrl: API_BASE_URL }); + +export const $api = createClient(fetchClient); diff --git a/application/ui/src/api/fetch-sse.ts b/application/ui/src/api/fetch-sse.ts new file mode 100644 index 0000000000..5b64d39eab --- /dev/null +++ b/application/ui/src/api/fetch-sse.ts @@ -0,0 +1,46 @@ +// Connect to an SSE endpoint and yield its messages +export function fetchSSE(url: string) { + return { + async *[Symbol.asyncIterator]() { + const eventSource = new EventSource(url); + + try { + let { promise, resolve, reject } = Promise.withResolvers(); + + eventSource.onmessage = (event) => { + if (event.data === 'DONE' || event.data.includes('COMPLETED')) { + eventSource.close(); + resolve('DONE'); + return; + } + resolve(event.data); + }; + + eventSource.onerror = (error) => { + eventSource.close(); + reject(new Error('EventSource failed: ' + error)); + }; + + // Keep yielding data as it comes in + while (true) { + const message = await promise; + + // If server sends 'DONE' message or similar, break the loop + if (message === 'DONE') { + break; + } + + try { + yield JSON.parse(message); + } catch { + console.error('Could not parse message:', message); + } + + ({ promise, resolve, reject } = Promise.withResolvers()); + } + } finally { + eventSource.close(); + } + }, + }; +} diff --git a/application/ui/src/api/utils.ts b/application/ui/src/api/utils.ts new file mode 100644 index 0000000000..7e427774a0 --- /dev/null +++ b/application/ui/src/api/utils.ts @@ -0,0 +1,28 @@ +import { fromOpenApi } from '@mswjs/source/open-api'; +import { createOpenApiHttp, OpenApiHttpHandlers } from 'openapi-msw'; + +import { paths } from './openapi-spec'; +import spec from './openapi-spec.json' with { type: 'json' }; + +const handlers = await fromOpenApi(JSON.stringify(spec).replace(/}:/g, '}//:')); + +const getOpenApiHttp = (): OpenApiHttpHandlers => { + const http = createOpenApiHttp({ + baseUrl: process.env.PUBLIC_API_BASE_URL ?? 'http://localhost:8000', + }); + + return { + ...http, + post: (path, ...other) => { + // @ts-expect-error MSW internal parsing function does not accept paths like + // `/api/models/{model_name}:activate` + // to get around this we escape the colon character with `\\` + // @see https://github.com/mswjs/msw/discussions/739 + return http.post(path.replace('}:', '}\\:'), ...other); + }, + }; +}; + +const http = getOpenApiHttp(); + +export { handlers, http }; diff --git a/application/ui/src/assets/background.png b/application/ui/src/assets/background.png new file mode 100644 index 0000000000..0fd713cf1d Binary files /dev/null and b/application/ui/src/assets/background.png differ diff --git a/application/ui/src/assets/graph.svg b/application/ui/src/assets/graph.svg new file mode 100644 index 0000000000..4556b903fb --- /dev/null +++ b/application/ui/src/assets/graph.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/ui/src/assets/icons/build-icon.svg b/application/ui/src/assets/icons/build-icon.svg new file mode 100644 index 0000000000..9d01dc6f22 --- /dev/null +++ b/application/ui/src/assets/icons/build-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/application/ui/src/assets/icons/camera-off.svg b/application/ui/src/assets/icons/camera-off.svg new file mode 100644 index 0000000000..5cae18dfd9 --- /dev/null +++ b/application/ui/src/assets/icons/camera-off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/application/ui/src/assets/icons/camera.svg b/application/ui/src/assets/icons/camera.svg new file mode 100644 index 0000000000..5fde282993 --- /dev/null +++ b/application/ui/src/assets/icons/camera.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/application/ui/src/assets/icons/dataset.svg b/application/ui/src/assets/icons/dataset.svg new file mode 100644 index 0000000000..ba8d8fe4b7 --- /dev/null +++ b/application/ui/src/assets/icons/dataset.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/application/ui/src/assets/icons/double-chevron-right-icon.svg b/application/ui/src/assets/icons/double-chevron-right-icon.svg new file mode 100644 index 0000000000..f95e858065 --- /dev/null +++ b/application/ui/src/assets/icons/double-chevron-right-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/application/ui/src/assets/icons/error-icon.svg b/application/ui/src/assets/icons/error-icon.svg new file mode 100644 index 0000000000..73b203e9c2 --- /dev/null +++ b/application/ui/src/assets/icons/error-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/application/ui/src/assets/icons/fire-works.svg b/application/ui/src/assets/icons/fire-works.svg new file mode 100644 index 0000000000..449f6a25a0 --- /dev/null +++ b/application/ui/src/assets/icons/fire-works.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/ui/src/assets/icons/folder-arrow-right.svg b/application/ui/src/assets/icons/folder-arrow-right.svg new file mode 100644 index 0000000000..a6b868456f --- /dev/null +++ b/application/ui/src/assets/icons/folder-arrow-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/ui/src/assets/icons/folder.svg b/application/ui/src/assets/icons/folder.svg new file mode 100644 index 0000000000..e89579ac68 --- /dev/null +++ b/application/ui/src/assets/icons/folder.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/ui/src/assets/icons/genicam.svg b/application/ui/src/assets/icons/genicam.svg new file mode 100644 index 0000000000..9dec1beb57 --- /dev/null +++ b/application/ui/src/assets/icons/genicam.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/application/ui/src/assets/icons/image.svg b/application/ui/src/assets/icons/image.svg new file mode 100644 index 0000000000..0356c0da2e --- /dev/null +++ b/application/ui/src/assets/icons/image.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/application/ui/src/assets/icons/images-folder.svg b/application/ui/src/assets/icons/images-folder.svg new file mode 100644 index 0000000000..9e9575e3f9 --- /dev/null +++ b/application/ui/src/assets/icons/images-folder.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/application/ui/src/assets/icons/index.ts b/application/ui/src/assets/icons/index.ts new file mode 100644 index 0000000000..f11e6490b8 --- /dev/null +++ b/application/ui/src/assets/icons/index.ts @@ -0,0 +1,25 @@ +export { ReactComponent as BuildIcon } from './build-icon.svg'; +export { ReactComponent as CameraOff } from './camera-off.svg'; +export { ReactComponent as Camera } from './camera.svg'; +export { ReactComponent as Dataset } from './dataset.svg'; +export { ReactComponent as DoubleChevronRightIcon } from './double-chevron-right-icon.svg'; +export { ReactComponent as ErrorIcon } from './error-icon.svg'; +export { ReactComponent as FolderArrowRight } from './folder-arrow-right.svg'; +export { ReactComponent as Genicam } from './genicam.svg'; +export { ReactComponent as ImagesFolder } from './images-folder.svg'; +export { ReactComponent as IpCamera } from './ip-camera.svg'; +export { ReactComponent as LiveFeedIcon } from './live-feed-icon.svg'; +export { ReactComponent as Models } from './models.svg'; +export { ReactComponent as Mqtt } from './mqtt.svg'; +export { ReactComponent as Ros } from './ros.svg'; +export { ReactComponent as Stats } from './stats.svg'; +export { ReactComponent as SuccessIcon } from './success-icon.svg'; +export { ReactComponent as ThreeDotsCircle } from './three-dots-circle.svg'; +export { ReactComponent as VideoFile } from './video-file.svg'; +export { ReactComponent as Webcam } from './webcam.svg'; +export { ReactComponent as Webhook } from './webhook.svg'; +export { ReactComponent as Image } from './image.svg'; +export { ReactComponent as Fireworks } from './fire-works.svg'; +export { ReactComponent as PipelineLink } from './pipeline-link.svg'; +export { ReactComponent as Folder } from './folder.svg'; +export { ReactComponent as LinkExpired } from './link-expired.svg'; diff --git a/application/ui/src/assets/icons/ip-camera.svg b/application/ui/src/assets/icons/ip-camera.svg new file mode 100644 index 0000000000..a5d70b9e60 --- /dev/null +++ b/application/ui/src/assets/icons/ip-camera.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/application/ui/src/assets/icons/link-expired.svg b/application/ui/src/assets/icons/link-expired.svg new file mode 100644 index 0000000000..b277f60db9 --- /dev/null +++ b/application/ui/src/assets/icons/link-expired.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/ui/src/assets/icons/live-feed-icon.svg b/application/ui/src/assets/icons/live-feed-icon.svg new file mode 100644 index 0000000000..b4e133c7c2 --- /dev/null +++ b/application/ui/src/assets/icons/live-feed-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/application/ui/src/assets/icons/models.svg b/application/ui/src/assets/icons/models.svg new file mode 100644 index 0000000000..14d24c2694 --- /dev/null +++ b/application/ui/src/assets/icons/models.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/ui/src/assets/icons/mqtt.svg b/application/ui/src/assets/icons/mqtt.svg new file mode 100644 index 0000000000..81a817bbf2 --- /dev/null +++ b/application/ui/src/assets/icons/mqtt.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/application/ui/src/assets/icons/pipeline-link.svg b/application/ui/src/assets/icons/pipeline-link.svg new file mode 100644 index 0000000000..a96cf458a9 --- /dev/null +++ b/application/ui/src/assets/icons/pipeline-link.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/application/ui/src/assets/icons/ros.svg b/application/ui/src/assets/icons/ros.svg new file mode 100644 index 0000000000..891abb328e --- /dev/null +++ b/application/ui/src/assets/icons/ros.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/application/ui/src/assets/icons/stats.svg b/application/ui/src/assets/icons/stats.svg new file mode 100644 index 0000000000..96d456ff61 --- /dev/null +++ b/application/ui/src/assets/icons/stats.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/application/ui/src/assets/icons/success-icon.svg b/application/ui/src/assets/icons/success-icon.svg new file mode 100644 index 0000000000..dd4e527f23 --- /dev/null +++ b/application/ui/src/assets/icons/success-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/application/ui/src/assets/icons/three-dots-circle.svg b/application/ui/src/assets/icons/three-dots-circle.svg new file mode 100644 index 0000000000..40dda9c37d --- /dev/null +++ b/application/ui/src/assets/icons/three-dots-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/ui/src/assets/icons/video-file.svg b/application/ui/src/assets/icons/video-file.svg new file mode 100644 index 0000000000..89bd0bc044 --- /dev/null +++ b/application/ui/src/assets/icons/video-file.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/application/ui/src/assets/icons/webcam.svg b/application/ui/src/assets/icons/webcam.svg new file mode 100644 index 0000000000..147d4f75e7 --- /dev/null +++ b/application/ui/src/assets/icons/webcam.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/application/ui/src/assets/icons/webhook.svg b/application/ui/src/assets/icons/webhook.svg new file mode 100644 index 0000000000..8d63fb3958 --- /dev/null +++ b/application/ui/src/assets/icons/webhook.svg @@ -0,0 +1,3 @@ + + + diff --git a/application/ui/src/assets/mocked-project-thumbnail.png b/application/ui/src/assets/mocked-project-thumbnail.png new file mode 100644 index 0000000000..96111a4705 Binary files /dev/null and b/application/ui/src/assets/mocked-project-thumbnail.png differ diff --git a/application/ui/src/components/disclosure-group/disclosure-group.component.tsx b/application/ui/src/components/disclosure-group/disclosure-group.component.tsx new file mode 100644 index 0000000000..fde916295a --- /dev/null +++ b/application/ui/src/components/disclosure-group/disclosure-group.component.tsx @@ -0,0 +1,67 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { ReactNode, useState } from 'react'; + +import { Disclosure, DisclosurePanel, DisclosureTitle, Flex, Text } from '@geti/ui'; +import { clsx } from 'clsx'; +import { isFunction } from 'lodash-es'; + +import styles from './disclosure-group.module.scss'; + +type DisclosureItem = { value: string; label: string; icon: ReactNode; content?: ReactNode }; + +type DisclosureItemProps = { + value: string | null; + onChange?: (value: string) => void; + item: DisclosureItem; +}; + +interface DisclosureGroupProps { + items: DisclosureItem[]; + defaultActiveInput: string | null; +} + +const DisclosureItem = ({ item, value, onChange }: DisclosureItemProps) => { + const isExpanded = item.value === value; + + const handleExpandedChange = () => { + isFunction(onChange) && onChange(item.value); + }; + + return ( + + + + + {item.icon} + {item.label} + + + + {isExpanded && item.content} + + ); +}; + +export const DisclosureGroup = ({ items, defaultActiveInput }: DisclosureGroupProps) => { + const [activeInput, setActiveInput] = useState(defaultActiveInput); + + const handleActiveInputChange = (value: string) => { + setActiveInput((prevValue) => (value !== prevValue ? value : null)); + }; + + return ( + + {items.map((item) => ( + + ))} + + ); +}; diff --git a/application/ui/src/components/disclosure-group/disclosure-group.module.scss b/application/ui/src/components/disclosure-group/disclosure-group.module.scss new file mode 100644 index 0000000000..2e3215a40e --- /dev/null +++ b/application/ui/src/components/disclosure-group/disclosure-group.module.scss @@ -0,0 +1,17 @@ +.disclosure { + background-color: var(--spectrum-global-color-gray-75); + border: var(--spectrum-alias-border-size-thin) solid var(--spectrum-global-color-gray-200) !important; + border-radius: var(--spectrum-alias-border-radius-regular); +} + +.selected { + border: var(--spectrum-alias-border-size-thick) solid var(--energy-blue) !important; +} + +.disclosureTitleContainer { + width: 100%; +} + +.disclosureTitle { + font-size: var(--spectrum-global-dimension-font-size-75); +} diff --git a/application/ui/src/components/error-page/error-page.tsx b/application/ui/src/components/error-page/error-page.tsx new file mode 100644 index 0000000000..24be6dda50 --- /dev/null +++ b/application/ui/src/components/error-page/error-page.tsx @@ -0,0 +1,68 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { Button, Heading, IllustratedMessage, View } from '@geti/ui'; +import { NotFound } from '@geti/ui/icons'; +import { isRouteErrorResponse, useRouteError } from 'react-router'; + +import { paths } from '../../routes/paths'; +import { redirectTo } from '../../routes/utils'; + +const useErrorMessage = () => { + const error = useRouteError(); + + if (isRouteErrorResponse(error)) { + if (error.status === 400) { + return 'The server cannot or will not process the current request.'; + } + + if (error.status === 403) { + return 'You do not have permission to access this page.'; + } + + if (error.status === 404) { + return "This page doesn't exist!"; + } + + if (error.status === 401) { + return "You aren't authorized to see this"; + } + + if (error.status === 500) { + return 'The server encountered an error and could not complete your request.'; + } + + if (error.status === 503) { + return 'Looks like our API is down'; + } + } + + if (error instanceof TypeError) { + return error.message; + } + + return 'An unknown error occurred'; +}; + +export const ErrorPage = () => { + const message = useErrorMessage(); + + return ( + + + + {message} + + + + + ); +}; diff --git a/application/ui/src/components/radio-disclosure-group/radio-disclosure-group.module.scss b/application/ui/src/components/radio-disclosure-group/radio-disclosure-group.module.scss new file mode 100644 index 0000000000..f3db77237d --- /dev/null +++ b/application/ui/src/components/radio-disclosure-group/radio-disclosure-group.module.scss @@ -0,0 +1,24 @@ +.disclosureTitle > button { + flex-direction: row-reverse; + justify-content: space-between; +} + +.disclosure { + border: var(--spectrum-global-dimension-size-40) solid var(--spectrum-global-color-gray-200); + border-radius: var(--spectrum-global-dimension-size-50); + + background-color: var(--spectrum-global-color-gray-75); +} + +.disclosure[data-expanded='true'] { + border-color: var(--energy-blue); +} + +.disclosurePanel { + padding: 0; +} + +.radio { + display: flex; + align-items: center; +} diff --git a/application/ui/src/components/radio-disclosure-group/radio-disclosure-group.tsx b/application/ui/src/components/radio-disclosure-group/radio-disclosure-group.tsx new file mode 100644 index 0000000000..924ee8a453 --- /dev/null +++ b/application/ui/src/components/radio-disclosure-group/radio-disclosure-group.tsx @@ -0,0 +1,59 @@ +import { ReactNode } from 'react'; + +import { Disclosure, DisclosurePanel, DisclosureTitle, Flex, Radio, RadioGroup, View } from '@geti/ui'; + +import classes from './radio-disclosure-group.module.scss'; + +export const RadioDisclosure = ({ + value, + setValue, + items, + ariaLabel, +}: { + value: ValueType; + setValue: (value: ValueType) => void; + items: Array<{ + value: ValueType; + label: ReactNode; + content: ReactNode; + }>; + ariaLabel?: string; +}) => { + return ( + { + setValue(newValue as ValueType); + }} + aria-label={ariaLabel} + value={value} + > + + {items.map((item) => { + return ( + expanded && setValue(item.value)} + isExpanded={item.value === value} + UNSAFE_className={classes.disclosure} + > + + + + + {item.label} + + + + + + {item.content} + + + ); + })} + + + ); +}; diff --git a/application/ui/src/components/required-text-field/required-text-field.component.tsx b/application/ui/src/components/required-text-field/required-text-field.component.tsx new file mode 100644 index 0000000000..2355bbcb69 --- /dev/null +++ b/application/ui/src/components/required-text-field/required-text-field.component.tsx @@ -0,0 +1,21 @@ +import { ComponentProps, useRef } from 'react'; + +import { TextField } from '@geti/ui'; +import { isEmpty } from 'lodash-es'; + +interface RequiredTextFieldProps extends Omit, 'onFocus' | 'isRequired' | 'validate'> { + errorMessage: string; +} + +export const RequiredTextField = ({ errorMessage, ...props }: RequiredTextFieldProps) => { + const isTouched = useRef(false); + + return ( + (isTouched.current = true)} + validate={(value) => (isEmpty(value.trim()) && isTouched.current ? errorMessage : '')} + /> + ); +}; diff --git a/application/ui/src/components/required-text-field/required-text-field.test.tsx b/application/ui/src/components/required-text-field/required-text-field.test.tsx new file mode 100644 index 0000000000..5a491141e4 --- /dev/null +++ b/application/ui/src/components/required-text-field/required-text-field.test.tsx @@ -0,0 +1,25 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { RequiredTextField } from './required-text-field.component'; + +describe('RequiredTextField', () => { + const errorMessage = 'This field is required'; + + it('does not display the error message before the input is interacted with', () => { + render(); + + expect(screen.queryByText(errorMessage)).not.toBeInTheDocument(); + }); + + it('shows error message when field has been touched and is empty', async () => { + render(); + + const input = screen.getByLabelText(/Required Text Field/i); + + await userEvent.type(input, 'test'); + await userEvent.clear(input); + + expect(await screen.findByText(errorMessage)).toBeVisible(); + }); +}); diff --git a/application/ui/src/components/status-tag/status-tag.component.tsx b/application/ui/src/components/status-tag/status-tag.component.tsx new file mode 100644 index 0000000000..ac4305855f --- /dev/null +++ b/application/ui/src/components/status-tag/status-tag.component.tsx @@ -0,0 +1,32 @@ +import { clsx } from 'clsx'; +import { Flex, Text } from 'packages/ui'; + +import classes from './status-tag.module.scss'; + +interface StatusTagProps { + isError?: boolean; + isConnected?: boolean; +} + +export const StatusTag = ({ isConnected = false, isError = false }: StatusTagProps) => { + if (isError) { + return ( + +
+ Error +
+ ); + } + return ( + +
+ {isConnected ? 'Connected' : 'Disconnected'} +
+ ); +}; diff --git a/application/ui/src/components/status-tag/status-tag.module.scss b/application/ui/src/components/status-tag/status-tag.module.scss new file mode 100644 index 0000000000..e6a41d3dfe --- /dev/null +++ b/application/ui/src/components/status-tag/status-tag.module.scss @@ -0,0 +1,20 @@ +.container { + padding: var(--spectrum-global-dimension-size-75) var(--spectrum-global-dimension-size-150); + background: var(--spectrum-global-color-gray-300); + border-radius: var(--spectrum-global-dimension-size-200); +} + +.status { + border-radius: 50%; + background: var(--electric-daisy); + width: var(--spectrum-global-dimension-size-130); + height: var(--spectrum-global-dimension-size-130); +} + +.connected { + background: var(--aqua); +} + +.disconnected { + background: var(--coral); +} diff --git a/application/ui/src/components/stream/web-rtc-connection-provider.test.tsx b/application/ui/src/components/stream/web-rtc-connection-provider.test.tsx new file mode 100644 index 0000000000..b78278e09b --- /dev/null +++ b/application/ui/src/components/stream/web-rtc-connection-provider.test.tsx @@ -0,0 +1,134 @@ +import { fireEvent, render } from '@testing-library/react'; +import { vi } from 'vitest'; + +import { Listener, WebRTCConnection, WebRTCConnectionStatus } from './web-rtc-connection'; +import { useWebRTCConnection, WebRTCConnectionProvider } from './web-rtc-connection-provider'; + +vi.mock('./web-rtc-connection.ts'); +class MockWebRTCConnection { + status: WebRTCConnectionStatus = 'idle'; + listeners: Listener[] = []; + + public getStatus() { + return this.status; + } + public getPeerConnection() { + return undefined; + } + public getId() { + return 'test'; + } + + public async start() { + this.status = 'connected'; + this.listeners.forEach((l) => l({ type: 'status_change', status: this.status })); + } + public async stop() { + this.status = 'idle'; + this.listeners.forEach((l) => l({ type: 'status_change', status: this.status })); + } + public subscribe(listener: Listener) { + this.listeners.push(listener); + return () => { + this.listeners = this.listeners.filter((currentListener) => currentListener !== listener); + }; + } +} + +describe('WebRTCConnectionProvider', () => { + beforeEach(() => { + // @ts-expect-error the mock implements all public methods + vi.mocked(WebRTCConnection).mockImplementation(() => { + return new MockWebRTCConnection(); + }); + }); + + const App = () => { + const { status, start, stop } = useWebRTCConnection(); + + return ( + <> + {status} + + + + ); + }; + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('provides initial status as idle', () => { + const { getByLabelText } = render( + + + + ); + + expect(getByLabelText('status')).toHaveTextContent('idle'); + }); + + it('updates status to connected after start', () => { + const { getByLabelText } = render( + + + + ); + + fireEvent.click(getByLabelText('start')); + + expect(getByLabelText('status')).toHaveTextContent('connected'); + }); + + it('updates status to idle after stop', () => { + const { getByLabelText } = render( + + + + ); + + fireEvent.click(getByLabelText('start')); + + expect(getByLabelText('status')).toHaveTextContent('connected'); + + fireEvent.click(getByLabelText('stop')); + + expect(getByLabelText('status')).toHaveTextContent('idle'); + }); + + it('cleans up on unmount', () => { + const stopSpy = vi.spyOn(MockWebRTCConnection.prototype, 'stop'); + + const { unmount } = render( + + + + ); + + unmount(); + + expect(stopSpy).toHaveBeenCalled(); + }); + + it('handles status sequence: start -> stop -> start', () => { + const { getByLabelText } = render( + + + + ); + + fireEvent.click(getByLabelText('start')); + expect(getByLabelText('status')).toHaveTextContent('connected'); + + fireEvent.click(getByLabelText('stop')); + expect(getByLabelText('status')).toHaveTextContent('idle'); + + fireEvent.click(getByLabelText('start')); + expect(getByLabelText('status')).toHaveTextContent('connected'); + }); +}); diff --git a/application/ui/src/components/stream/web-rtc-connection-provider.tsx b/application/ui/src/components/stream/web-rtc-connection-provider.tsx new file mode 100644 index 0000000000..fcf0f55d84 --- /dev/null +++ b/application/ui/src/components/stream/web-rtc-connection-provider.tsx @@ -0,0 +1,91 @@ +import { createContext, ReactNode, RefObject, useCallback, useContext, useEffect, useRef, useState } from 'react'; + +import { WebRTCConnection, WebRTCConnectionStatus } from './web-rtc-connection'; + +export type WebRTCConnectionState = null | { + status: WebRTCConnectionStatus; + start: () => Promise; + stop: () => Promise; + webRTCConnectionRef: RefObject; +}; + +export const WebRTCConnectionContext = createContext(null); + +const useWebRTCConnectionState = () => { + const webRTCConnectionRef = useRef(null); + const [status, setStatus] = useState('idle'); + + // Initialize WebRTCConnection on mount + useEffect(() => { + if (webRTCConnectionRef.current) { + return; + } + + const webRTCConnection = new WebRTCConnection(); + webRTCConnectionRef.current = webRTCConnection; + + const unsubscribe = webRTCConnection.subscribe((event) => { + if (event.type === 'status_change') { + setStatus(event.status); + } + + if (event.type === 'error') { + console.error('WebRTC Connection Error:', event.error); + // Optionally update status to 'failed' if not already + if (webRTCConnectionRef.current?.getStatus() !== 'failed') { + setStatus('failed'); + } + } + }); + + return () => { + unsubscribe(); + webRTCConnection.stop(); // Ensure connection is closed on unmount + webRTCConnectionRef.current = null; + }; + }, []); + + const start = useCallback(async () => { + if (!webRTCConnectionRef.current) { + return; + } + + try { + await webRTCConnectionRef.current.start(); + } catch (error) { + console.error('Failed to start WebRTC connection:', error); + setStatus('failed'); + } + }, []); + + const stop = useCallback(async () => { + if (!webRTCConnectionRef.current) { + return; + } + + await webRTCConnectionRef.current.stop(); + }, []); + + return { + start, + stop, + status, + webRTCConnectionRef, + }; +}; + +export const WebRTCConnectionProvider = ({ children }: { children: ReactNode }) => { + const value = useWebRTCConnectionState(); + + return {children}; +}; + +export const useWebRTCConnection = () => { + const context = useContext(WebRTCConnectionContext); + + if (context === null) { + throw new Error('useWebRTCConnection was used outside of WebRTCConnectionProvider'); + } + + return context; +}; diff --git a/application/ui/src/components/stream/web-rtc-connection.ts b/application/ui/src/components/stream/web-rtc-connection.ts new file mode 100644 index 0000000000..2585d58157 --- /dev/null +++ b/application/ui/src/components/stream/web-rtc-connection.ts @@ -0,0 +1,247 @@ +import { fetchClient } from '../../api/client'; + +export type WebRTCConnectionStatus = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'failed'; + +type WebRTCConnectionEvent = + | { + type: 'status_change'; + status: WebRTCConnectionStatus; + } + | { + type: 'error'; + error: Error; + }; + +export type Listener = (event: WebRTCConnectionEvent) => void; + +type SessionData = + | RTCSessionDescriptionInit + | { + status: 'failed'; + meta: { error: 'concurrency_limit_reached'; limit: number }; + }; + +const CONNECTION_TIMEOUT = 5000; +const CLOSE_CONNECTION_DELAY = 500; + +export class WebRTCConnection { + private peerConnection: RTCPeerConnection | undefined; + private webrtcId: string; + private status: WebRTCConnectionStatus = 'idle'; + private dataChannel: RTCDataChannel | undefined; + + private listeners: Array = []; + private timeoutId?: ReturnType; + + constructor() { + // TODO: replace with uuid + this.webrtcId = Math.random().toString(36).substring(7); + } + + public getStatus(): WebRTCConnectionStatus { + return this.status; + } + + public getPeerConnection(): RTCPeerConnection | undefined { + return this.peerConnection; + } + + public getId(): string { + return this.webrtcId; + } + + public async start(): Promise { + if (this.hasActiveConnection()) { + console.warn('WebRTC connection is already active or in progress.'); + return; + } + + this.updateStatus('connecting'); + this.peerConnection = new RTCPeerConnection(); + this.timeoutId = setTimeout(() => { + console.warn('Connection is taking longer than usual. Are you on a VPN?'); + }, CONNECTION_TIMEOUT); + + try { + this.setupPeerConnection(); + + await this.createAndSetOffer(); + await this.waitForIceGathering(); + + const data = await this.sendOffer(); + + if (!this.handleOfferResponse(data)) return; + + this.setupConnectionStateListener(); + } catch (err) { + clearTimeout(this.timeoutId); + console.error('Error setting up WebRTC:', err); + this.emit({ type: 'error', error: err as Error }); + this.updateStatus('failed'); + this.stop(); + } + + if (this.peerConnection) { + this.peerConnection.getTransceivers().forEach((t) => (t.direction = 'recvonly')); + } + } + + private setupPeerConnection() { + if (!this.peerConnection) return; + + this.peerConnection.addTransceiver('video', { direction: 'recvonly' }); + this.dataChannel = this.peerConnection.createDataChannel('text'); + this.dataChannel.onopen = () => { + this.dataChannel?.send('handshake'); + }; + } + + private async createAndSetOffer() { + if (!this.peerConnection) return; + + const offer = await this.peerConnection.createOffer(); + + await this.peerConnection.setLocalDescription(offer); + } + + private async waitForIceGathering(): Promise { + await new Promise((resolve) => { + if (!this.peerConnection || this.peerConnection.iceGatheringState === 'complete') { + resolve(); + return; + } + + const checkState = () => { + if (this.peerConnection && this.peerConnection.iceGatheringState === 'complete') { + this.peerConnection.removeEventListener('icegatheringstatechange', checkState); + resolve(); + } + }; + + this.peerConnection?.addEventListener('icegatheringstatechange', checkState); + }); + } + + private async sendOffer(): Promise { + if (!this.peerConnection) return; + + const { data } = await fetchClient.POST('/api/webrtc/offer', { + body: { + sdp: this.peerConnection.localDescription?.sdp ?? '', + type: this.peerConnection.localDescription?.type ?? '', + webrtc_id: this.webrtcId, + }, + }); + + return data as SessionData; + } + + private async handleOfferResponse(data: SessionData | undefined): Promise { + if (!data) return false; + + if ('status' in data && data.status === 'failed') { + const errorMessage = + data.meta.error === 'concurrency_limit_reached' + ? `Too many connections. Maximum limit is ${data.meta.limit}` + : data.meta.error; + + console.error(errorMessage); + + this.emit({ type: 'error', error: new Error(errorMessage) }); + + return false; + } + + if (this.peerConnection) { + await this.peerConnection.setRemoteDescription(data as RTCSessionDescriptionInit); + } + + return true; + } + + private setupConnectionStateListener() { + if (!this.peerConnection) return; + + this.peerConnection.addEventListener('connectionstatechange', () => { + if (!this.peerConnection) return; + + switch (this.peerConnection.connectionState) { + case 'connected': + this.updateStatus('connected'); + clearTimeout(this.timeoutId); + break; + case 'disconnected': + this.updateStatus('disconnected'); + break; + case 'failed': + this.updateStatus('failed'); + this.emit({ type: 'error', error: new Error('WebRTC connection failed.') }); + break; + case 'closed': + this.updateStatus('disconnected'); + break; + default: + this.updateStatus('connecting'); + break; + } + }); + } + + public async stop(): Promise { + if (!this.peerConnection) { + return; + } + + const transceivers = this.peerConnection.getTransceivers(); + + transceivers.forEach((transceiver) => { + if (transceiver.stop) { + transceiver.stop(); + } + }); + + const senders = this.peerConnection.getSenders(); + + senders.forEach((sender) => { + if (sender.track && sender.track.stop) sender.track.stop(); + }); + + // Give a brief moment for tracks to stop before closing the connection + await new Promise((resolve) => + setTimeout(() => { + if (this.peerConnection) { + this.peerConnection.close(); + this.peerConnection = undefined; + this.updateStatus('idle'); + } + + resolve(); + }, CLOSE_CONNECTION_DELAY) + ); + } + + public subscribe(listener: Listener): () => void { + this.listeners.push(listener); + + return () => this.unsubscribe(listener); + } + + private hasActiveConnection(): boolean { + return this.peerConnection !== undefined && this.status !== 'idle' && this.status !== 'disconnected'; + } + + private unsubscribe(listener: Listener) { + this.listeners = this.listeners.filter((currentListener) => currentListener !== listener); + } + + private emit(event: WebRTCConnectionEvent) { + this.listeners.forEach((listener) => listener(event)); + } + + private updateStatus(newStatus: WebRTCConnectionStatus) { + if (this.status !== newStatus) { + this.status = newStatus; + this.emit({ type: 'status_change', status: newStatus }); + } + } +} diff --git a/application/ui/src/components/zoom/use-container-size.ts b/application/ui/src/components/zoom/use-container-size.ts new file mode 100644 index 0000000000..783dd30ca5 --- /dev/null +++ b/application/ui/src/components/zoom/use-container-size.ts @@ -0,0 +1,27 @@ +import { RefObject, useState } from 'react'; + +import { useResizeObserver } from '@react-aria/utils'; + +export const useContainerSize = (ref: RefObject) => { + const [size, setSize] = useState({ width: 100, height: 100 }); + useResizeObserver({ + ref, + box: 'border-box', + onResize: () => { + if (!ref.current) { + return; + } + + if (size.width === ref.current.clientWidth && size.height === ref.current.clientHeight) { + return; + } + + setSize({ + width: ref.current.clientWidth, + height: ref.current.clientHeight, + }); + }, + }); + + return size; +}; diff --git a/application/ui/src/components/zoom/zoom-transform.tsx b/application/ui/src/components/zoom/zoom-transform.tsx new file mode 100755 index 0000000000..80807ff057 --- /dev/null +++ b/application/ui/src/components/zoom/zoom-transform.tsx @@ -0,0 +1,77 @@ +import { ReactNode, useEffect, useMemo, useRef } from 'react'; + +import { useContainerSize } from './use-container-size'; +import { useSetZoom, useZoom } from './zoom'; + +import classes from './zoom.module.scss'; + +type Size = { width: number; height: number }; + +const DEFAULT_SCREEN_ZOOM = 0.9; +const getCenterCoordinates = (container: Size, target: Size) => { + // Scale image so that it fits perfectly in the container + const scale = DEFAULT_SCREEN_ZOOM * Math.min(container.width / target.width, container.height / target.height); + + return { + scale, + // Center image + translate: { + x: container.width / 2 - target.width / 2, + y: container.height / 2 - target.height / 2, + }, + }; +}; + +const INITIAL_ZOOM = { scale: 1.0, translate: { x: 0, y: 0 } }; +const SyncZoom = ({ container, target }: { container: Size; target: Size }) => { + const setZoom = useSetZoom(); + + const targetZoom = useMemo(() => { + if (container.width === undefined || container.height === undefined) { + return INITIAL_ZOOM; + } + + return getCenterCoordinates({ width: container.width, height: container.height }, target); + }, [container, target]); + + useEffect(() => { + setZoom({ + scale: Number(targetZoom.scale.toFixed(3)), + translate: { + x: Number(targetZoom.translate.x.toFixed(3)), + y: Number(targetZoom.translate.y.toFixed(3)), + }, + }); + }, [targetZoom.scale, targetZoom.translate.x, targetZoom.translate.y, setZoom]); + + return null; +}; + +export const ZoomTransform = ({ children, target }: { children: ReactNode; target: Size }) => { + const zoom = useZoom(); + const ref = useRef(null); + const containerSize = useContainerSize(ref); + + return ( +
+
+ + {children} +
+
+ ); +}; diff --git a/application/ui/src/components/zoom/zoom.module.scss b/application/ui/src/components/zoom/zoom.module.scss new file mode 100644 index 0000000000..ba21b920d7 --- /dev/null +++ b/application/ui/src/components/zoom/zoom.module.scss @@ -0,0 +1,17 @@ +.wrapper { + overflow: hidden; + position: relative; + padding: 0; + margin: 0; + width: 100%; + height: 100%; + user-select: none; +} + +.wrapperInternal { + display: flex; + flex-wrap: wrap; + width: fit-content; + height: fit-content; + transform-origin: center; +} diff --git a/application/ui/src/components/zoom/zoom.test.tsx b/application/ui/src/components/zoom/zoom.test.tsx new file mode 100644 index 0000000000..ebf96a256a --- /dev/null +++ b/application/ui/src/components/zoom/zoom.test.tsx @@ -0,0 +1,32 @@ +import { render, screen } from '@testing-library/react'; +import { vi } from 'vitest'; + +import { useContainerSize } from './use-container-size'; +import { ZoomProvider } from './zoom'; +import { ZoomTransform } from './zoom-transform'; + +vi.mock('./use-container-size', () => ({ + useContainerSize: vi.fn(), +})); + +describe('Zoom', () => { + it('Scales and translates content so that it fits the screen', () => { + const screenSize = { width: 100, height: 500 }; + const contentSize = { width: 300, height: 200 }; + const expectedZoom = { translate: { x: -100, y: 150 }, scale: 0.3 }; + + vi.mocked(useContainerSize).mockImplementation(() => screenSize); + + render( + + Content + + ); + + const transform = screen.getByTestId('zoom-transform'); + + expect(transform).toHaveStyle({ + transform: `translate(${expectedZoom.translate.x}px, ${expectedZoom.translate.y}px) scale(${expectedZoom.scale})`, + }); + }); +}); diff --git a/application/ui/src/components/zoom/zoom.tsx b/application/ui/src/components/zoom/zoom.tsx new file mode 100755 index 0000000000..3f40c8ee06 --- /dev/null +++ b/application/ui/src/components/zoom/zoom.tsx @@ -0,0 +1,38 @@ +import { createContext, Dispatch, ReactNode, SetStateAction, useContext, useState } from 'react'; + +type ZoomState = { scale: number; translate: { x: number; y: number } }; +export const Zoom = createContext({ + scale: 1.0, + translate: { x: 0, y: 0 }, +}); +const SetZoom = createContext> | null>(null); + +export const useZoom = () => { + return useContext(Zoom); +}; + +export const useSetZoom = () => { + const context = useContext(SetZoom); + + if (!context) { + throw new Error(''); + } + + return context; +}; + +export const ZoomProvider = ({ children }: { children: ReactNode }) => { + // TODO: + // 1. Add scale restrictions - min max + // 2. Add translate restrictions - min max + const [zoom, setZoom] = useState({ + scale: 1.0, + translate: { x: 0, y: 0 }, + }); + + return ( + + {children} + + ); +}; diff --git a/application/ui/src/env.d.ts b/application/ui/src/env.d.ts new file mode 100644 index 0000000000..4dce89b93b --- /dev/null +++ b/application/ui/src/env.d.ts @@ -0,0 +1,7 @@ +/// + +// We need these two to be able to import/export svgs as ReactComponent, +// for instance: export { ReactComponent as Logo } from './logo.svg'; +declare module '*.svg' { + export const ReactComponent: React.FunctionComponent>; +} diff --git a/application/ui/src/features/inspect/dataset/dataset-item/dataset-item.component.tsx b/application/ui/src/features/inspect/dataset/dataset-item/dataset-item.component.tsx new file mode 100644 index 0000000000..0f7931a392 --- /dev/null +++ b/application/ui/src/features/inspect/dataset/dataset-item/dataset-item.component.tsx @@ -0,0 +1,66 @@ +import { Image } from '@geti-inspect/icons'; +import { Flex } from '@geti/ui'; +import { clsx } from 'clsx'; + +import { useInference } from '../../inference-provider.component'; +import { useSelectedMediaItem } from '../../selected-media-item-provider.component'; +import { type MediaItem } from '../types'; + +import styles from './dataset-item.module.scss'; + +const DatasetItemPlaceholder = () => { + return ( + + + + + + ); +}; + +interface DatasetItemProps { + mediaItem: MediaItem; +} + +const DatasetItem = ({ mediaItem }: DatasetItemProps) => { + const { selectedMediaItem, onSetSelectedMediaItem } = useSelectedMediaItem(); + const { onInference, selectedModelId } = useInference(); + + const isSelected = selectedMediaItem?.id === mediaItem.id; + + const mediaUrl = `/api/projects/${mediaItem.project_id}/images/${mediaItem.id}/thumbnail`; + + const handleClick = async () => { + const selection = mediaItem.id === selectedMediaItem?.id ? undefined : mediaItem; + + onSetSelectedMediaItem(selection); + selectedModelId !== undefined && (await onInference(mediaItem, selectedModelId)); + }; + + return ( +
+ {mediaItem.filename} +
+ ); +}; + +interface DatasetItemContainerProps { + mediaItem: MediaItem | undefined; +} + +export const DatasetItemContainer = ({ mediaItem }: DatasetItemContainerProps) => { + if (mediaItem === undefined) { + return ; + } + + return ; +}; diff --git a/application/ui/src/features/inspect/dataset/dataset-item/dataset-item.module.scss b/application/ui/src/features/inspect/dataset/dataset-item/dataset-item.module.scss new file mode 100644 index 0000000000..e103b6546e --- /dev/null +++ b/application/ui/src/features/inspect/dataset/dataset-item/dataset-item.module.scss @@ -0,0 +1,25 @@ +.datasetItem { + aspect-ratio: 4/3; + cursor: pointer; + + img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + } + + border: var(--spectrum-alias-border-size-thick) solid transparent; + + transition: border-color 0.3s ease-in-out; +} + +.datasetItemSelected { + border-color: var(--energy-blue); +} + +.datasetItemPlaceholder { + border: 1px dashed var(--spectrum-global-color-gray-700); + background-color: var(--spectrum-global-color-gray-200); +} diff --git a/application/ui/src/features/inspect/dataset/dataset-list.component.tsx b/application/ui/src/features/inspect/dataset/dataset-list.component.tsx new file mode 100644 index 0000000000..8e0e444644 --- /dev/null +++ b/application/ui/src/features/inspect/dataset/dataset-list.component.tsx @@ -0,0 +1,36 @@ +import { Flex, Grid, Heading, minmax, repeat } from '@geti/ui'; + +import { DatasetItemContainer } from './dataset-item/dataset-item.component'; +import { MediaItem } from './types'; +import { REQUIRED_NUMBER_OF_NORMAL_IMAGES_TO_TRIGGER_TRAINING } from './utils'; + +interface DatasetItemProps { + mediaItems: MediaItem[]; +} + +export const DatasetList = ({ mediaItems }: DatasetItemProps) => { + const mediaItemsToRender = [ + ...mediaItems, + ...Array.from({ + length: Math.max(0, REQUIRED_NUMBER_OF_NORMAL_IMAGES_TO_TRIGGER_TRAINING - mediaItems.length), + }).map(() => undefined), + ]; + + return ( + + Normal images + + + {mediaItemsToRender.map((mediaItem, index) => ( + + ))} + + + ); +}; diff --git a/application/ui/src/features/inspect/dataset/dataset-status-panel.component.tsx b/application/ui/src/features/inspect/dataset/dataset-status-panel.component.tsx new file mode 100644 index 0000000000..5fbef4c8b8 --- /dev/null +++ b/application/ui/src/features/inspect/dataset/dataset-status-panel.component.tsx @@ -0,0 +1,170 @@ +import { ComponentProps, Suspense, useEffect, useRef } from 'react'; + +import { $api } from '@geti-inspect/api'; +import { SchemaJob as Job, SchemaJob, SchemaJobStatus } from '@geti-inspect/api/spec'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { Content, Flex, Heading, InlineAlert, IntelBrandedLoading, ProgressBar, Text } from '@geti/ui'; +import { useQueryClient } from '@tanstack/react-query'; +import { isEqual } from 'lodash-es'; + +import { ShowJobLogs } from '../jobs/show-job-logs.component'; +import { REQUIRED_NUMBER_OF_NORMAL_IMAGES_TO_TRIGGER_TRAINING } from './utils'; + +interface NotEnoughNormalImagesToTrainProps { + mediaItemsCount: number; +} + +const NotEnoughNormalImagesToTrain = ({ mediaItemsCount }: NotEnoughNormalImagesToTrainProps) => { + const missingNormalImages = REQUIRED_NUMBER_OF_NORMAL_IMAGES_TO_TRIGGER_TRAINING - mediaItemsCount; + + return ( + + {missingNormalImages} images required + + Capture {missingNormalImages} images of normal cases. They help the model learn what is standard, so it + can better detect anomalies. + + + ); +}; + +interface TrainingInProgressProps { + job: Job; +} + +const statusToVariant: Record['variant']> = { + pending: 'info', + running: 'info', + completed: 'positive', + canceled: 'negative', + failed: 'negative', +}; + +function getHeading(job: SchemaJob) { + if (job.status === 'pending') { + return `Training will start soon - ${job.payload.model_name}`; + } + if (job.status === 'running') { + return `Training in progress - ${job.payload.model_name}`; + } + + if (job.status === 'failed') { + return `Training failed - ${job.payload.model_name}`; + } + + if (job.status === 'canceled') { + return `Training canceled - ${job.payload.model_name}`; + } + + if (job.status === 'completed') { + return `Training completed - ${job.payload.model_name}`; + } + return null; +} + +const TrainingInProgress = ({ job }: TrainingInProgressProps) => { + if (job === undefined) { + return null; + } + + const variant = statusToVariant[job.status]; + const heading = getHeading(job); + + return ( + + + + {heading} + {job.id && } + + + + + {job.message} + {job.status === 'pending' && } + + + + ); +}; + +const REFETCH_INTERVAL_WITH_TRAINING = 1_000; + +export const useProjectTrainingJobs = () => { + const { projectId } = useProjectIdentifier(); + + const { data } = $api.useQuery('get', '/api/jobs', undefined, { + refetchInterval: ({ state }) => { + const projectHasTrainingJob = state.data?.jobs.some( + ({ project_id, type, status }) => + projectId === project_id && type === 'training' && (status === 'running' || status === 'pending') + ); + + return projectHasTrainingJob ? REFETCH_INTERVAL_WITH_TRAINING : undefined; + }, + }); + + return { jobs: data?.jobs.filter((job) => job.project_id === projectId) }; +}; + +export const useRefreshModelsOnJobUpdates = (jobs: Job[] | undefined) => { + const queryClient = useQueryClient(); + const { projectId } = useProjectIdentifier(); + const prevJobsRef = useRef([]); + + useEffect(() => { + if (jobs === undefined) { + return; + } + + if (!isEqual(prevJobsRef.current, jobs)) { + const shouldRefetchModels = jobs.some((job, idx) => { + // NOTE: assuming index stays the same + return job.status === 'completed' && job.status !== prevJobsRef.current.at(idx)?.status; + }); + + if (shouldRefetchModels) { + queryClient.invalidateQueries({ + queryKey: [ + 'get', + '/api/projects/{project_id}/models', + { params: { path: { project_id: projectId } } }, + ], + }); + } + } + + prevJobsRef.current = jobs ?? []; + }, [jobs, queryClient, projectId]); +}; + +const TrainingInProgressList = () => { + const { jobs } = useProjectTrainingJobs(); + useRefreshModelsOnJobUpdates(jobs); + + if (jobs === undefined || jobs.length === 0) { + return null; + } + + return ( + + {jobs?.map((job) => )} + + ); +}; + +interface DatasetStatusPanelProps { + mediaItemsCount: number; +} + +export const DatasetStatusPanel = ({ mediaItemsCount }: DatasetStatusPanelProps) => { + if (mediaItemsCount < REQUIRED_NUMBER_OF_NORMAL_IMAGES_TO_TRIGGER_TRAINING) { + return ; + } + + return ( + }> + + + ); +}; diff --git a/application/ui/src/features/inspect/dataset/dataset.component.tsx b/application/ui/src/features/inspect/dataset/dataset.component.tsx new file mode 100644 index 0000000000..10a141a77d --- /dev/null +++ b/application/ui/src/features/inspect/dataset/dataset.component.tsx @@ -0,0 +1,121 @@ +import { Suspense } from 'react'; + +import { $api } from '@geti-inspect/api'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { Button, FileTrigger, Flex, Heading, Loading, toast, View } from '@geti/ui'; +import { useQueryClient } from '@tanstack/react-query'; + +import { TrainModelButton } from '../train-model/train-model-button.component'; +import { DatasetList } from './dataset-list.component'; +import { REQUIRED_NUMBER_OF_NORMAL_IMAGES_TO_TRIGGER_TRAINING } from './utils'; + +const useMediaItems = () => { + const { projectId } = useProjectIdentifier(); + + const { data } = $api.useSuspenseQuery('get', '/api/projects/{project_id}/images', { + params: { + path: { + project_id: projectId, + }, + }, + }); + + return { + mediaItems: data.media, + }; +}; + +const UploadImages = () => { + const { projectId } = useProjectIdentifier(); + const queryClient = useQueryClient(); + + const captureImageMutation = $api.useMutation('post', '/api/projects/{project_id}/capture'); + + const handleAddMediaItem = async (files: File[]) => { + const uploadPromises = files.map((file) => { + const formData = new FormData(); + formData.append('file', file); + + return captureImageMutation.mutateAsync({ + params: { path: { project_id: projectId } }, + // @ts-expect-error There is an incorrect type in OpenAPI + body: formData, + }); + }); + + const promises = await Promise.allSettled(uploadPromises); + + const succeeded = promises.filter((result) => result.status === 'fulfilled').length; + const failed = promises.filter((result) => result.status === 'rejected').length; + + const imagesOptions = $api.queryOptions('get', '/api/projects/{project_id}/images', { + params: { path: { project_id: projectId } }, + }); + await queryClient.invalidateQueries({ queryKey: imagesOptions.queryKey }); + const images = await queryClient.ensureQueryData(imagesOptions); + + if (images.media.length >= REQUIRED_NUMBER_OF_NORMAL_IMAGES_TO_TRIGGER_TRAINING) { + toast({ + title: 'Train', + type: 'info', + message: `You can start model training now with your collected dataset.`, + duration: Infinity, + actionButtons: [], + position: 'bottom-left', + }); + return; + } + + if (failed === 0) { + toast({ type: 'success', message: `Uploaded ${succeeded} item(s)` }); + } else if (succeeded === 0) { + toast({ type: 'error', message: `Failed to upload ${failed} item(s)` }); + } else { + toast({ + type: 'warning', + message: `Uploaded ${succeeded} item(s), ${failed} failed`, + }); + } + }; + + const captureImages = (files: FileList | null) => { + if (files === null) return; + + handleAddMediaItem(Array.from(files)); + }; + + return ( + + + + ); +}; + +const DatasetContent = () => { + const { mediaItems } = useMediaItems(); + + return ; +}; + +export const Dataset = () => { + return ( + + + + Dataset + + + + + + + }> + + + + + + + + ); +}; diff --git a/application/ui/src/features/inspect/dataset/types.ts b/application/ui/src/features/inspect/dataset/types.ts new file mode 100644 index 0000000000..9603520fd8 --- /dev/null +++ b/application/ui/src/features/inspect/dataset/types.ts @@ -0,0 +1,3 @@ +import { SchemaMediaList } from '@geti-inspect/api/spec'; + +export type MediaItem = SchemaMediaList['media'][number]; diff --git a/application/ui/src/features/inspect/dataset/utils.ts b/application/ui/src/features/inspect/dataset/utils.ts new file mode 100644 index 0000000000..3aee28c4bb --- /dev/null +++ b/application/ui/src/features/inspect/dataset/utils.ts @@ -0,0 +1 @@ +export const REQUIRED_NUMBER_OF_NORMAL_IMAGES_TO_TRIGGER_TRAINING = 20; diff --git a/application/ui/src/features/inspect/footer/footer.component.tsx b/application/ui/src/features/inspect/footer/footer.component.tsx new file mode 100644 index 0000000000..7de19e4025 --- /dev/null +++ b/application/ui/src/features/inspect/footer/footer.component.tsx @@ -0,0 +1,174 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { Suspense } from 'react'; + +import { $api } from '@geti-inspect/api'; +import { SchemaJob as Job, SchemaJob } from '@geti-inspect/api/spec'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { Flex, ProgressBar, Text, View } from '@geti/ui'; +import { CanceledIcon, WaitingIcon } from '@geti/ui/icons'; +import { queryOptions, experimental_streamedQuery as streamedQuery, useQuery } from '@tanstack/react-query'; +import { fetchSSE } from 'src/api/fetch-sse'; + +const IdleItem = () => { + return ( + + + + Idle + + + ); +}; + +const getStyleForMessage = (message: string) => { + if (message.toLowerCase().includes('valid')) { + return { + backgroundColor: 'var(--spectrum-global-color-yellow-600)', + color: '#000', + }; + } else if (message.toLowerCase().includes('test')) { + return { + backgroundColor: 'var(--spectrum-global-color-green-600)', + color: '#fff', + }; + } else if (message.toLowerCase().includes('train') || message.toLowerCase().includes('fit')) { + return { + backgroundColor: 'var(--spectrum-global-color-blue-600)', + color: '#fff', + }; + } + + return { + backgroundColor: 'var(--spectrum-global-color-blue-600)', + color: '#fff', + }; +}; + +const TrainingStatusItem = ({ trainingJob }: { trainingJob: SchemaJob }) => { + // Cancel training job + const cancelJobMutation = $api.useMutation('post', '/api/jobs/{job_id}:cancel'); + const handleCancel = async () => { + try { + if (trainingJob.id === undefined) { + throw Error('TODO: jobs should always have an ID'); + } + + console.info('Cancel training'); + await cancelJobMutation.mutateAsync({ + params: { + path: { + job_id: trainingJob.id, + }, + }, + }); + console.info('Job cancelled successfully'); + } catch (error) { + console.error('Failed to cancel job:', error); + } + }; + + const progressQuery = useQuery( + queryOptions({ + queryKey: ['get', '/api/jobs/{job_id}/progress', trainingJob.id], + queryFn: streamedQuery({ + queryFn: () => fetchSSE(`/api/jobs/${trainingJob.id}/progress`), + maxChunks: 1, + }), + staleTime: Infinity, + }) + ); + + // Get the job progress and message from the last SSE message, or fallback + const lastJobProgress = progressQuery.data?.at(-1); + const progress = lastJobProgress?.progress ?? trainingJob.progress; + const message = lastJobProgress?.message ?? trainingJob.message; + + const { backgroundColor, color } = getStyleForMessage(message); + + return ( +
+ + + + {message} + + + +
+ ); +}; + +const useCurrentJob = () => { + const { data: jobsData } = $api.useSuspenseQuery('get', '/api/jobs', undefined, { + refetchInterval: 5000, + }); + + const { projectId } = useProjectIdentifier(); + const runningJob = jobsData.jobs.find( + (job: Job) => job.project_id === projectId && (job.status === 'running' || job.status === 'pending') + ); + + return runningJob; +}; + +export const ProgressBarItem = () => { + const trainingJob = useCurrentJob(); + + if (trainingJob !== undefined) { + return ; + } + + return ; +}; + +export const Footer = () => { + return ( + + + + + + ); +}; diff --git a/application/ui/src/features/inspect/inference-provider.component.tsx b/application/ui/src/features/inspect/inference-provider.component.tsx new file mode 100644 index 0000000000..5881493a78 --- /dev/null +++ b/application/ui/src/features/inspect/inference-provider.component.tsx @@ -0,0 +1,120 @@ +import { createContext, ReactNode, use, useState } from 'react'; + +import { $api } from '@geti-inspect/api'; +import { components } from '@geti-inspect/api/spec'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { usePipeline } from 'src/hooks/use-pipeline.hook'; + +import { MediaItem } from './dataset/types'; +import { useSelectedMediaItem } from './selected-media-item-provider.component'; + +type InferenceResult = components['schemas']['PredictionResponse'] | undefined; + +interface InferenceContextProps { + onInference: (media: MediaItem, modelId: string) => Promise; + inferenceResult: InferenceResult; + isPending: boolean; + selectedModelId: string | undefined; + onSetSelectedModelId: (model: string | undefined) => void; + inferenceOpacity: number; + onInferenceOpacityChange: (opacity: number) => void; +} + +const InferenceContext = createContext(undefined); + +const downloadImageAsFile = async (media: MediaItem) => { + const response = await fetch(`/api/projects/${media.project_id}/images/${media.id}/full`); + + const blob = await response.blob(); + + return new File([blob], media.filename, { type: blob.type }); +}; + +const useInferenceMutation = () => { + const pipeline = usePipeline(); + const inferenceMutation = $api.useMutation('post', '/api/projects/{project_id}/models/{model_id}:predict'); + + const handleInference = async (mediaItem: MediaItem, modelId: string) => { + const file = await downloadImageAsFile(mediaItem); + + const formData = new FormData(); + formData.append('file', file); + + if (pipeline.data.inference_device) { + formData.append('device', pipeline.data.inference_device); + } + + inferenceMutation.mutate({ + // @ts-expect-error There is an incorrect type in OpenAPI + body: formData, + params: { + path: { + project_id: mediaItem.project_id, + model_id: modelId, + }, + }, + }); + }; + + return { + inferenceResult: inferenceMutation.data, + onInference: handleInference, + isPending: inferenceMutation.isPending, + }; +}; + +interface InferenceProviderProps { + children: ReactNode; +} + +export const InferenceProvider = ({ children }: InferenceProviderProps) => { + const { data: pipeline } = usePipeline(); + const { projectId } = useProjectIdentifier(); + const updatePipeline = $api.useMutation('patch', '/api/projects/{project_id}/pipeline', { + meta: { + invalidates: [ + ['get', '/api/projects/{project_id}/pipeline', { params: { path: { project_id: projectId } } }], + ], + }, + }); + + const { selectedMediaItem } = useSelectedMediaItem(); + const [inferenceOpacity, setInferenceOpacity] = useState(0.75); + const { inferenceResult, onInference, isPending } = useInferenceMutation(); + + const onSetSelectedModelId = (modelId: string | undefined) => { + updatePipeline.mutate({ + params: { path: { project_id: projectId } }, + body: { model_id: modelId }, + }); + + if (modelId && selectedMediaItem) { + onInference(selectedMediaItem, modelId); + } + }; + return ( + + {children} + + ); +}; + +export const useInference = () => { + const context = use(InferenceContext); + + if (context === undefined) { + throw new Error('useInference must be used within a InferenceProvider'); + } + + return context; +}; diff --git a/application/ui/src/features/inspect/jobs/show-job-logs.component.tsx b/application/ui/src/features/inspect/jobs/show-job-logs.component.tsx new file mode 100644 index 0000000000..b3ef175f2e --- /dev/null +++ b/application/ui/src/features/inspect/jobs/show-job-logs.component.tsx @@ -0,0 +1,80 @@ +import { Suspense } from 'react'; + +import { + ActionButton, + Button, + ButtonGroup, + Content, + Dialog, + DialogTrigger, + Divider, + Flex, + Heading, + Icon, + Loading, + Text, + View, +} from '@geti/ui'; +import { LogsIcon } from '@geti/ui/icons'; +import { queryOptions, experimental_streamedQuery as streamedQuery, useQuery } from '@tanstack/react-query'; +import { fetchSSE } from 'src/api/fetch-sse'; + +const JobLogsDialogContent = ({ jobId }: { jobId: string }) => { + const query = useQuery( + queryOptions({ + queryKey: ['get', '/api/jobs/{job_id}/logs', jobId], + queryFn: streamedQuery({ + queryFn: () => fetchSSE(`/api/jobs/${jobId}/logs`), + }), + staleTime: Infinity, + }) + ); + + return ( + + {query.data?.map((line, idx) => {line.text})} + + ); +}; + +const JobLogsDialog = ({ close, jobId }: { close: () => void; jobId: string }) => { + return ( + + Logs + + + + }> + + + + + + + + + ); +}; + +export const ShowJobLogs = ({ jobId }: { jobId: string }) => { + return ( + + + + + + + + {(close) => } + + + ); +}; diff --git a/application/ui/src/features/inspect/main-content/enable-project/confirmation-dialog.component.tsx b/application/ui/src/features/inspect/main-content/enable-project/confirmation-dialog.component.tsx new file mode 100644 index 0000000000..27d4b5692d --- /dev/null +++ b/application/ui/src/features/inspect/main-content/enable-project/confirmation-dialog.component.tsx @@ -0,0 +1,48 @@ +import { $api } from '@geti-inspect/api'; +import { useActivatePipeline, useDisablePipeline } from '@geti-inspect/hooks'; +import { AlertDialog } from '@geti/ui'; + +interface ConfirmationDialogProps { + activeProjectId: string; + currentProjectId: string; +} + +export const ConfirmationDialog = ({ activeProjectId, currentProjectId }: ConfirmationDialogProps) => { + const activePipeline = useActivatePipeline({}); + const disablePipeline = useDisablePipeline(activeProjectId); + + const activeProject = $api.useSuspenseQuery('get', '/api/projects/{project_id}', { + params: { path: { project_id: activeProjectId } }, + }); + + const currentProject = $api.useSuspenseQuery('get', '/api/projects/{project_id}', { + params: { path: { project_id: currentProjectId } }, + }); + + const isUpdating = activeProject.isLoading || currentProject.isLoading; + + const handleEnableProject = async () => { + await disablePipeline.mutateAsync({ + params: { path: { project_id: activeProjectId } }, + }); + + await activePipeline.mutateAsync({ + params: { path: { project_id: currentProjectId } }, + }); + }; + + return ( + + By activating this project, the current active project "{activeProject.data.name}" and its + respective inference workflow will be disabled. Changing your active project can influence any downstream + processes that might be connected to the output generated from the current active project. + + ); +}; diff --git a/application/ui/src/features/inspect/main-content/enable-project/enable-project.component.tsx b/application/ui/src/features/inspect/main-content/enable-project/enable-project.component.tsx new file mode 100644 index 0000000000..7c36c66b43 --- /dev/null +++ b/application/ui/src/features/inspect/main-content/enable-project/enable-project.component.tsx @@ -0,0 +1,52 @@ +import { Suspense, useEffect, useState } from 'react'; + +import { LinkExpired } from '@geti-inspect/icons'; +import { Button, DialogContainer, Flex, Loading, Text } from '@geti/ui'; + +import { useWebRTCConnection } from '../../../../components/stream/web-rtc-connection-provider'; +import { ConfirmationDialog } from './confirmation-dialog.component'; + +import classes from './enable-project.module.scss'; + +interface EnableProjectProps { + activeProjectId: string; + currentProjectId: string; +} + +const useStopCurrentWebRtcConnection = () => { + const { stop } = useWebRTCConnection(); + + useEffect(() => { + stop(); + }, [stop]); +}; + +export const EnableProject = ({ activeProjectId, currentProjectId }: EnableProjectProps) => { + const [isOpen, setIsOpen] = useState(false); + useStopCurrentWebRtcConnection(); + + return ( + + + + + + This project is set as inactive, therefore the pipeline configuration is disabled for this project. + You can still explore the dataset and models within this inactive project. + + + Would you like to activate this project? + + + + setIsOpen(false)}> + {isOpen && ( + }> + + + )} + + + + ); +}; diff --git a/application/ui/src/features/inspect/main-content/enable-project/enable-project.module.scss b/application/ui/src/features/inspect/main-content/enable-project/enable-project.module.scss new file mode 100644 index 0000000000..d8c393006d --- /dev/null +++ b/application/ui/src/features/inspect/main-content/enable-project/enable-project.module.scss @@ -0,0 +1,18 @@ +.container { + border: 1px dashed #b7b9be; + margin: var(--spectrum-global-dimension-size-500); + background: var(--background-inverse); + border-radius: var(--spectrum-global-dimension-size-200); +} + +.title { + font-size: var(--spectrum-global-dimension-size-200); + line-height: var(--spectrum-global-dimension-size-300); +} + +.description { + text-align: center; + color: var(--spectrum-global-color-gray-700); + font-size: var(--spectrum-global-dimension-size-150); + line-height: var(--spectrum-global-dimension-size-225); +} diff --git a/application/ui/src/features/inspect/main-content/inference-result/inference-result.component.tsx b/application/ui/src/features/inspect/main-content/inference-result/inference-result.component.tsx new file mode 100644 index 0000000000..d2115baa1b --- /dev/null +++ b/application/ui/src/features/inspect/main-content/inference-result/inference-result.component.tsx @@ -0,0 +1,80 @@ +import { Flex, Grid, Loading, Text } from '@geti/ui'; +import { clsx } from 'clsx'; +import { AnimatePresence, motion } from 'motion/react'; +import { useSpinDelay } from 'spin-delay'; + +import { MediaItem } from '../../dataset/types'; +import { useInference } from '../../inference-provider.component'; + +import styles from './inference-result.module.scss'; + +interface InferenceResultProps { + selectedMediaItem: MediaItem; +} + +interface LabelProps { + label: string; + score: number; +} + +const LabelScore = ({ label, score }: LabelProps) => { + const formatter = new Intl.NumberFormat('en-US', { + maximumFractionDigits: 0, + style: 'percent', + }); + + return ( + + {label} + {formatter.format(score)} + + ); +}; + +export const InferenceResult = ({ selectedMediaItem }: InferenceResultProps) => { + const { isPending, inferenceResult, inferenceOpacity } = useInference(); + const isLoadingInference = useSpinDelay(isPending, { delay: 300 }); + + return ( + + {selectedMediaItem.filename} + + {inferenceResult !== undefined && ( + <> + + + + )} + + {isLoadingInference && } + + ); +}; diff --git a/application/ui/src/features/inspect/main-content/inference-result/inference-result.module.scss b/application/ui/src/features/inspect/main-content/inference-result/inference-result.module.scss new file mode 100644 index 0000000000..ff77e8628f --- /dev/null +++ b/application/ui/src/features/inspect/main-content/inference-result/inference-result.module.scss @@ -0,0 +1,33 @@ +.canvasContainer { + overflow: hidden; + width: 100%; + height: 100%; + padding: var(--spectrum-global-dimension-size-1200); +} + +.img { + display: block; + width: 100%; + height: 100%; + object-fit: contain; +} + +.inferencedImage { + grid-area: inference-result; + justify-self: start; +} + +.label { + padding: var(--spectrum-global-dimension-size-65); + color: var(--spectrum-global-color-gray-50); + font-size: var(--spectrum-global-dimension-font-size-75); + font-weight: 500; +} + +.labelNormal { + background-color: var(--brand-moss); +} + +.labelAnomalous { + background-color: var(--brand-coral-cobalt); +} diff --git a/application/ui/src/features/inspect/main-content/main-content.component.tsx b/application/ui/src/features/inspect/main-content/main-content.component.tsx new file mode 100644 index 0000000000..d41ad19def --- /dev/null +++ b/application/ui/src/features/inspect/main-content/main-content.component.tsx @@ -0,0 +1,32 @@ +import { useActivePipeline, usePipeline, useProjectIdentifier } from '@geti-inspect/hooks'; +import isEmpty from 'lodash-es/isEmpty'; + +import { useSelectedMediaItem } from '../selected-media-item-provider.component'; +import { StreamContainer } from '../stream/stream-container'; +import { EnableProject } from './enable-project/enable-project.component'; +import { InferenceResult } from './inference-result/inference-result.component'; +import { SourceSinkMessage } from './source-sink-message/source-sink-message.component'; + +export const MainContent = () => { + const { data: pipeline } = usePipeline(); + const { projectId } = useProjectIdentifier(); + const { selectedMediaItem } = useSelectedMediaItem(); + const { data: activeProjectPipeline } = useActivePipeline(); + + const hasActiveProject = !isEmpty(activeProjectPipeline); + const isCurrentProjectActive = activeProjectPipeline?.project_id === projectId; + + if (isEmpty(selectedMediaItem) && isEmpty(pipeline.source?.id)) { + return ; + } + + if (isEmpty(selectedMediaItem) && hasActiveProject && !isCurrentProjectActive) { + return ; + } + + if (isEmpty(selectedMediaItem)) { + return ; + } + + return ; +}; diff --git a/application/ui/src/features/inspect/main-content/main-content.test.tsx b/application/ui/src/features/inspect/main-content/main-content.test.tsx new file mode 100644 index 0000000000..16e3a2cd03 --- /dev/null +++ b/application/ui/src/features/inspect/main-content/main-content.test.tsx @@ -0,0 +1,180 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen } from '@testing-library/react'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { SchemaPipeline } from 'src/api/openapi-spec'; +import { http } from 'src/api/utils'; +import { ZoomProvider } from 'src/components/zoom/zoom'; +import { server } from 'src/msw-node-setup'; + +import { getMockedMediaItem } from '../../../../mocks/mock-media-item'; +import { getMockedPipeline } from '../../../../mocks/mock-pipeline'; +import { useWebRTCConnection, WebRTCConnectionState } from '../../../components/stream/web-rtc-connection-provider'; +import { MediaItem } from '../dataset/types'; +import { useSelectedMediaItem } from '../selected-media-item-provider.component'; +import { MainContent } from './main-content.component'; +import { SOURCE_MESSAGE } from './source-sink-message/source-sink-message.component'; + +vi.mock('../../../components/stream/web-rtc-connection-provider', () => ({ + useWebRTCConnection: vi.fn(), +})); + +vi.mock('../selected-media-item-provider.component', () => ({ + useSelectedMediaItem: vi.fn(), +})); + +describe('MainContent', () => { + const mockMediaItem = getMockedMediaItem({}); + + const renderApp = ({ + webRtcConfig = {}, + pipelineConfig = {}, + selectedMediaItem, + activePipelineConfig = {}, + }: { + webRtcConfig?: Partial; + pipelineConfig?: Partial; + selectedMediaItem?: MediaItem; + activePipelineConfig?: Partial | null; + }) => { + vi.mocked(useWebRTCConnection).mockReturnValue({ + start: vi.fn(), + status: 'idle', + stop: vi.fn(), + webRTCConnectionRef: { current: null }, + ...webRtcConfig, + }); + + vi.mocked(useSelectedMediaItem).mockReturnValue({ selectedMediaItem, onSetSelectedMediaItem: vi.fn() }); + + server.use( + http.get('/api/projects/{project_id}/pipeline', ({ response }) => + response(200).json(getMockedPipeline(pipelineConfig)) + ), + http.get('/api/active-pipeline', ({ response }) => { + if (activePipelineConfig === null) { + return response(200).json(null); + } + return response(200).json(getMockedPipeline({ project_id: '123', ...activePipelineConfig })); + }) + ); + + return render( + + + + + } /> + + + + + ); + }; + + describe('SinkMessage', () => { + it('renders when no source is configured and no media item selected', async () => { + renderApp({ pipelineConfig: { source: undefined } }); + + expect(await screen.findByText(SOURCE_MESSAGE)).toBeVisible(); + }); + + it('does not render SinkMessage when no sink is configured', async () => { + renderApp({ pipelineConfig: { sink: undefined } }); + + expect(screen.queryByText(SOURCE_MESSAGE)).not.toBeInTheDocument(); + }); + + it('renders when both source and sink are missing', async () => { + renderApp({ pipelineConfig: { source: undefined, sink: undefined } }); + + expect(await screen.findByText(SOURCE_MESSAGE)).toBeVisible(); + }); + }); + + describe('EnableProject', () => { + it('renders when another project is active and no media item selected', async () => { + renderApp({ + pipelineConfig: { project_id: '123' }, + activePipelineConfig: { project_id: '456' }, + }); + + expect(await screen.findByRole('button', { name: /Activate project/i })).toBeVisible(); + }); + + it('does not render EnableProject when current project is active', async () => { + renderApp({ + pipelineConfig: { project_id: '123' }, + activePipelineConfig: { project_id: '123' }, + }); + + expect(screen.queryByRole('button', { name: /Activate project/i })).not.toBeInTheDocument(); + }); + + it('does not render EnableProject when no active pipeline', async () => { + renderApp({ + pipelineConfig: { project_id: '123' }, + activePipelineConfig: null, + }); + + expect(screen.queryByRole('button', { name: /Activate project/i })).not.toBeInTheDocument(); + }); + }); + + describe('StreamContainer', () => { + it('renders when no media item selected and source/sink are configured', async () => { + renderApp({ + pipelineConfig: { status: 'idle' }, + webRtcConfig: { status: 'idle' }, + }); + + expect(await screen.findByRole('button', { name: /Start stream/i })).toBeVisible(); + }); + + it('renders when no media item selected and no other project is active', async () => { + renderApp({ + pipelineConfig: { project_id: '123' }, + activePipelineConfig: null, + }); + + expect(await screen.findByRole('button', { name: /Start stream/i })).toBeVisible(); + }); + + it('renders when current project is active', async () => { + renderApp({ + webRtcConfig: { status: 'idle' }, + pipelineConfig: { project_id: '123', status: 'running' }, + activePipelineConfig: { project_id: '123' }, + }); + + expect(await screen.findByRole('button', { name: /Start stream/i })).toBeVisible(); + }); + }); + + describe('InferenceResult', () => { + it('renders when media item is selected', async () => { + renderApp({ selectedMediaItem: mockMediaItem }); + + expect(screen.queryByText(SOURCE_MESSAGE)).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: /Start stream/i })).not.toBeInTheDocument(); + }); + + it('renders InferenceResult even when source/sink missing if media item selected', async () => { + renderApp({ + pipelineConfig: { source: undefined, sink: undefined }, + selectedMediaItem: mockMediaItem, + }); + + expect(screen.queryByText(SOURCE_MESSAGE)).not.toBeInTheDocument(); + }); + + it('renders InferenceResult even when another project is active if media item selected', async () => { + renderApp({ + pipelineConfig: { project_id: '123' }, + activePipelineConfig: { project_id: '456' }, + selectedMediaItem: mockMediaItem, + }); + + expect(screen.queryByRole('button', { name: /Activate project/i })).not.toBeInTheDocument(); + }); + }); +}); diff --git a/application/ui/src/features/inspect/main-content/source-sink-message/source-sink-message.component.tsx b/application/ui/src/features/inspect/main-content/source-sink-message/source-sink-message.component.tsx new file mode 100644 index 0000000000..ca4ba67e68 --- /dev/null +++ b/application/ui/src/features/inspect/main-content/source-sink-message/source-sink-message.component.tsx @@ -0,0 +1,18 @@ +import { Grid, Heading } from '@geti/ui'; + +import styles from './source-sink-message.module.scss'; + +export const SOURCE_MESSAGE = 'No source configured. Please set it before starting the stream.'; + +export const SourceSinkMessage = () => { + return ( + + {SOURCE_MESSAGE} + + ); +}; diff --git a/application/ui/src/features/inspect/main-content/source-sink-message/source-sink-message.module.scss b/application/ui/src/features/inspect/main-content/source-sink-message/source-sink-message.module.scss new file mode 100644 index 0000000000..e41e84fe3f --- /dev/null +++ b/application/ui/src/features/inspect/main-content/source-sink-message/source-sink-message.module.scss @@ -0,0 +1,10 @@ +.canvasContainer { + overflow: hidden; + width: 100%; + height: 100%; + padding: var(--spectrum-global-dimension-size-1200); + + h3 { + text-align: center; + } +} diff --git a/application/ui/src/features/inspect/models/inference-devices.component.tsx b/application/ui/src/features/inspect/models/inference-devices.component.tsx new file mode 100644 index 0000000000..95bd374321 --- /dev/null +++ b/application/ui/src/features/inspect/models/inference-devices.component.tsx @@ -0,0 +1,52 @@ +import { useState } from 'react'; + +import { $api } from '@geti-inspect/api'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { Item, Key, Picker, toast } from '@geti/ui'; +import { usePipeline } from 'src/hooks/use-pipeline.hook'; + +export const InferenceDevices = () => { + const { data } = $api.useSuspenseQuery('get', '/api/inference-devices'); + const { data: pipeline } = usePipeline(); + const { projectId } = useProjectIdentifier(); + const [selectedKey, setSelectedKey] = useState(pipeline.inference_device?.toLowerCase() ?? null); + + const updatePipeline = $api.useMutation('patch', '/api/projects/{project_id}/pipeline', { + meta: { + invalidates: [ + ['get', '/api/projects/{project_id}/pipeline', { params: { path: { project_id: projectId } } }], + ], + }, + onError: (error) => { + if (error) { + toast({ type: 'error', message: String(error.detail) }); + } + }, + }); + + const options = data.devices.map((device) => ({ id: device, name: device })); + + const handleChange = (key: Key | null) => { + if (key === null) { + return; + } + + setSelectedKey(key); + updatePipeline.mutate({ + params: { path: { project_id: projectId } }, + body: { inference_device: key }, + }); + }; + + return ( + + {(item) => {item.name}} + + ); +}; diff --git a/application/ui/src/features/inspect/models/models-view.component.tsx b/application/ui/src/features/inspect/models/models-view.component.tsx new file mode 100644 index 0000000000..26728a26b1 --- /dev/null +++ b/application/ui/src/features/inspect/models/models-view.component.tsx @@ -0,0 +1,176 @@ +import { Badge } from '@adobe/react-spectrum'; +import { $api } from '@geti-inspect/api'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { + Cell, + Column, + Flex, + Heading, + IllustratedMessage, + Row, + TableBody, + TableHeader, + TableView, + Text, + View, +} from '@geti/ui'; +import { sortBy } from 'lodash-es'; +import { useDateFormatter } from 'react-aria'; +import { SchemaJob } from 'src/api/openapi-spec'; + +import { useProjectTrainingJobs, useRefreshModelsOnJobUpdates } from '../dataset/dataset-status-panel.component'; +import { useInference } from '../inference-provider.component'; +import { ShowJobLogs } from '../jobs/show-job-logs.component'; + +const useModels = () => { + const { projectId } = useProjectIdentifier(); + const modelsQuery = $api.useSuspenseQuery('get', '/api/projects/{project_id}/models', { + params: { path: { project_id: projectId } }, + }); + const models = modelsQuery.data.models; + + return models; +}; + +interface ModelData { + id: string; + name: string; + timestamp: string; + startTime: number; + durationInSeconds: number | null; + status: 'Training' | 'Completed' | 'Failed'; + architecture: string; + progress: number; + job: SchemaJob | undefined; +} + +export const ModelsView = () => { + const dateFormatter = useDateFormatter({ dateStyle: 'medium', timeStyle: 'short' }); + + const { jobs = [] } = useProjectTrainingJobs(); + useRefreshModelsOnJobUpdates(jobs); + + const models = useModels() + .map((model): ModelData | null => { + const job = jobs.find(({ id }) => id === model.train_job_id); + if (job === undefined) { + return null; + } + + let timestamp = ''; + let durationInSeconds = 0; + const start = job.start_time ? new Date(job.start_time) : new Date(); + if (job) { + const end = job.end_time ? new Date(job.end_time) : new Date(); + durationInSeconds = Math.floor((end.getTime() - start.getTime()) / 1000); + timestamp = dateFormatter.format(start); + } + + return { + id: model.id!, + name: model.name!, + status: 'Completed', + architecture: model.name!, + startTime: start.getTime(), + timestamp, + durationInSeconds, + progress: 1.0, + job, + }; + }) + .filter((model): model is ModelData => model !== null); + + const nonCompletedJobs = jobs + .filter((job) => job.status !== 'completed') + .map((job): ModelData => { + const name = String(job.payload['model_name']); + + const start = job.start_time ? new Date(job.start_time) : new Date(); + const timestamp = dateFormatter.format(start); + return { + id: job.id!, + name, + status: job.status === 'pending' ? 'Training' : job.status === 'running' ? 'Training' : 'Failed', + architecture: name, + timestamp, + startTime: start.getTime(), + progress: 1.0, + durationInSeconds: null, + job, + }; + }); + + const showModels = sortBy([...nonCompletedJobs, ...models], (model) => -model.startTime); + + const { selectedModelId, onSetSelectedModelId } = useInference(); + + return ( + + + {/* Models Table */} + { + if (typeof key === 'string') { + return; + } + + const selectedId = key.values().next().value; + const selectedModel = models.find((model) => model.id === selectedId); + + onSetSelectedModelId(selectedModel?.id); + }} + > + + MODEL NAME + + + + {showModels.map((model) => ( + + + + {model.name} + + {model.timestamp} + + + + + + + {model.job?.status === 'pending' && pending...} + {model.job?.status === 'running' && {model.job.progress}%...} + {model.job?.status === 'canceled' && ( + Cancelled + )} + {model.job?.status === 'failed' && Failed} + {selectedModelId === model.id && Active} + {model.job?.id && } + + + + + ))} + + + + {jobs.length === 0 && models.length === 0 && ( + + No models in training + Start a new training to see models here. + + )} + + + ); +}; diff --git a/application/ui/src/features/inspect/models/models.component.tsx b/application/ui/src/features/inspect/models/models.component.tsx new file mode 100644 index 0000000000..27fb62a4db --- /dev/null +++ b/application/ui/src/features/inspect/models/models.component.tsx @@ -0,0 +1,30 @@ +import { Suspense } from 'react'; + +import { Flex, Heading, Loading } from '@geti/ui'; + +import { TrainModelButton } from '../train-model/train-model-button.component'; +import { InferenceDevices } from './inference-devices.component'; +import { ModelsView } from './models-view.component'; + +export const Models = () => { + return ( + + + + Models + + + + + + }> + <> + + + + + + + + ); +}; diff --git a/application/ui/src/features/inspect/projects-management/project-list-item/project-list-item.component.tsx b/application/ui/src/features/inspect/projects-management/project-list-item/project-list-item.component.tsx new file mode 100644 index 0000000000..c0bf446b7d --- /dev/null +++ b/application/ui/src/features/inspect/projects-management/project-list-item/project-list-item.component.tsx @@ -0,0 +1,113 @@ +/** + * Copyright (C) 2025 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useEffect, useRef, useState } from 'react'; + +import { SchemaProjectList } from '@geti-inspect/api/spec'; +import { Flex, PhotoPlaceholder, Text, TextField, type TextFieldRef } from '@geti/ui'; +import { clsx } from 'clsx'; +import { useNavigate } from 'react-router'; + +import { useWebRTCConnection } from '../../../../components/stream/web-rtc-connection-provider'; +import { paths } from '../../../../routes/paths'; + +import styles from './project-list-item.module.scss'; + +export type Project = SchemaProjectList['projects'][number]; + +interface ProjectEditionProps { + onBlur: (newName: string) => void; + name: string; +} + +const ProjectEdition = ({ name, onBlur }: ProjectEditionProps) => { + const textFieldRef = useRef(null); + const [newName, setNewName] = useState(name); + + const handleBlur = () => { + onBlur(newName); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + onBlur(newName); + } + + if (e.key === 'Escape') { + e.preventDefault(); + setNewName(name); + onBlur(name); + } + }; + + useEffect(() => { + textFieldRef.current?.select(); + }, []); + + return ( + + ); +}; + +interface ProjectListItemProps { + project: Project; + isActive: boolean; + isInEditMode: boolean; + onBlur: (projectId: string, newName: string) => void; +} + +export const ProjectListItem = ({ project, isInEditMode, isActive, onBlur }: ProjectListItemProps) => { + const navigate = useNavigate(); + const { stop } = useWebRTCConnection(); + + const handleBlur = (newProjectId?: string) => (newName: string) => { + if (newProjectId === undefined) { + return; + } + + onBlur(newProjectId, newName); + }; + + const handleNavigateToProject = () => { + if (project.id === undefined || isActive) { + return; + } + + stop(); + navigate(`${paths.project({ projectId: project.id })}?mode=Dataset`); + }; + + return ( +
  • + + {isInEditMode ? ( + + ) : ( + + + {project.name} + + )} + +
  • + ); +}; diff --git a/application/ui/src/features/inspect/projects-management/project-list-item/project-list-item.module.scss b/application/ui/src/features/inspect/projects-management/project-list-item/project-list-item.module.scss new file mode 100644 index 0000000000..855bf10bd9 --- /dev/null +++ b/application/ui/src/features/inspect/projects-management/project-list-item/project-list-item.module.scss @@ -0,0 +1,16 @@ +.projectListItem { + padding: var(--spectrum-global-dimension-size-100); + + &:hover, + &:has(.actionMenu[aria-expanded='true']) { + background-color: var(--spectrum-global-color-gray-100); + } +} + +.actionMenu svg { + transform: rotate(90deg); +} + +.active { + background-color: var(--spectrum-global-color-gray-100); +} diff --git a/application/ui/src/features/inspect/projects-management/project-list-item/project-list-item.test.tsx b/application/ui/src/features/inspect/projects-management/project-list-item/project-list-item.test.tsx new file mode 100644 index 0000000000..87889009f7 --- /dev/null +++ b/application/ui/src/features/inspect/projects-management/project-list-item/project-list-item.test.tsx @@ -0,0 +1,47 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { useNavigate } from 'react-router'; +import { vi } from 'vitest'; + +import { useWebRTCConnection } from '../../../../components/stream/web-rtc-connection-provider'; +import { Project, ProjectListItem } from './project-list-item.component'; + +vi.mock('../../../../components/stream/web-rtc-connection-provider'); + +vi.mock('react-router', async () => { + const actual = await vi.importActual('react-router'); + return { + ...actual, + useNavigate: vi.fn(), + }; +}); + +const mockStop = vi.fn(); +const mockNavigate = vi.fn(); + +describe('ProjectListItem', () => { + const mockProject: Project = { + id: 'project-123', + name: 'Test Project', + }; + + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(useNavigate).mockReturnValue(mockNavigate); + vi.mocked(useWebRTCConnection).mockReturnValue({ + stop: mockStop, + start: vi.fn(), + status: 'idle', + webRTCConnectionRef: { current: null }, + }); + }); + + it('navigates to project when clicked', async () => { + render(); + + await userEvent.click(screen.getByRole('listitem')); + + expect(mockStop).toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalledWith('/projects/project-123?mode=Dataset'); + }); +}); diff --git a/application/ui/src/features/inspect/projects-management/projects-list-panel.component.tsx b/application/ui/src/features/inspect/projects-management/projects-list-panel.component.tsx new file mode 100644 index 0000000000..bf2175d304 --- /dev/null +++ b/application/ui/src/features/inspect/projects-management/projects-list-panel.component.tsx @@ -0,0 +1,134 @@ +/** + * Copyright (C) 2025 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useState } from 'react'; + +import { $api } from '@geti-inspect/api'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { + ActionButton, + ButtonGroup, + Content, + Dialog, + DialogTrigger, + Divider, + Flex, + Header, + Heading, + PhotoPlaceholder, + Text, + View, +} from '@geti/ui'; +import { AddCircle } from '@geti/ui/icons'; +import { v4 as uuid } from 'uuid'; + +import { ProjectsList } from './projects-list.component'; + +import styles from './projects-list.module.scss'; + +interface SelectedProjectProps { + name: string; + id: string | undefined; +} + +const SelectedProjectButton = ({ name, id }: SelectedProjectProps) => { + return ( + + {name} + + + + + ); +}; + +interface AddProjectProps { + onSetProjectInEdition: (projectId: string) => void; + projectsCount: number; +} + +const AddProjectButton = ({ onSetProjectInEdition, projectsCount }: AddProjectProps) => { + const addProjectMutation = $api.useMutation('post', '/api/projects', { + meta: { + invalidates: [['get', '/api/projects']], + }, + }); + + const addProject = () => { + const newProjectId = uuid(); + const newProjectName = `Project #${projectsCount + 1}`; + + addProjectMutation.mutate({ + body: { + id: newProjectId, + name: newProjectName, + }, + }); + + onSetProjectInEdition(newProjectId); + }; + + return ( + + + Add project + + ); +}; + +export const ProjectsListPanel = () => { + const { projectId } = useProjectIdentifier(); + const { data } = $api.useSuspenseQuery('get', '/api/projects'); + + const [projectInEdition, setProjectInEdition] = useState(null); + + const selectedProject = data.projects.find((project) => project.id === projectId); + const selectedProjectName = selectedProject?.name ?? ''; + + return ( + + + + +
    + + + + {selectedProjectName} + + +
    + + + + + + + + + +
    +
    + ); +}; diff --git a/application/ui/src/features/inspect/projects-management/projects-list.component.tsx b/application/ui/src/features/inspect/projects-management/projects-list.component.tsx new file mode 100644 index 0000000000..a45717bd28 --- /dev/null +++ b/application/ui/src/features/inspect/projects-management/projects-list.component.tsx @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2025 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { isEmpty } from 'lodash-es'; + +import { Project, ProjectListItem } from './project-list-item/project-list-item.component'; + +import styles from './projects-list.module.scss'; + +interface ProjectListProps { + projects: Project[]; + projectIdInEdition: string | null; + setProjectInEdition: (projectId: string | null) => void; +} + +export const ProjectsList = ({ projects, setProjectInEdition, projectIdInEdition }: ProjectListProps) => { + const { projectId: currentProjectId } = useProjectIdentifier(); + const isInEditionMode = (projectId?: string) => { + return projectIdInEdition === projectId; + }; + + const handleBlur = (projectId: string, newName: string) => { + setProjectInEdition(null); + + const projectToUpdate = projects.find((project) => project.id === projectId); + if (projectToUpdate?.name === newName || isEmpty(newName.trim())) { + return; + } + }; + + return ( +
      + {projects.map((project) => ( + + ))} +
    + ); +}; diff --git a/application/ui/src/features/inspect/projects-management/projects-list.module.scss b/application/ui/src/features/inspect/projects-management/projects-list.module.scss new file mode 100644 index 0000000000..2358cc2518 --- /dev/null +++ b/application/ui/src/features/inspect/projects-management/projects-list.module.scss @@ -0,0 +1,24 @@ +.panelButtons { + padding-block-start: 0; +} + +.addProjectButton { + color: var(--spectrum-global-color-gray-900); + + & span[class*='spectrum-ActionButton-label'] { + text-align: start; + margin-left: var(--spectrum-global-dimension-size-100) !important; + } +} + +.dialog { + & div[class*='spectrum-Dialog-grid'] { + --spectrum-dialog-padding-x: 0px; + --spectrum-dialog-padding-y: var(--spectrum-global-dimension-size-250); + } +} + +.projectList { + list-style: none; + padding-inline-start: 0; +} diff --git a/application/ui/src/features/inspect/selected-media-item-provider.component.tsx b/application/ui/src/features/inspect/selected-media-item-provider.component.tsx new file mode 100644 index 0000000000..c7df62d93f --- /dev/null +++ b/application/ui/src/features/inspect/selected-media-item-provider.component.tsx @@ -0,0 +1,34 @@ +import { createContext, ReactNode, use, useState } from 'react'; + +import { MediaItem } from './dataset/types'; + +interface SelectedMediaItemContextProps { + selectedMediaItem: MediaItem | undefined; + onSetSelectedMediaItem: (mediaItem: MediaItem | undefined) => void; +} + +const SelectedMediaItemContext = createContext(undefined); + +interface SelectedMediaItemProviderProps { + children: ReactNode; +} + +export const SelectedMediaItemProvider = ({ children }: SelectedMediaItemProviderProps) => { + const [selectedMediaItem, setSelectedMediaItem] = useState(undefined); + + return ( + + {children} + + ); +}; + +export const useSelectedMediaItem = () => { + const context = use(SelectedMediaItemContext); + + if (context === undefined) { + throw new Error('useSelectedMediaItem must be used within a SelectedMediaItemProvider'); + } + + return context; +}; diff --git a/application/ui/src/features/inspect/sidebar.component.tsx b/application/ui/src/features/inspect/sidebar.component.tsx new file mode 100644 index 0000000000..781f2d6ba1 --- /dev/null +++ b/application/ui/src/features/inspect/sidebar.component.tsx @@ -0,0 +1,80 @@ +/** + * Copyright (C) 2025 Intel Corporation + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Dataset as DatasetIcon, Models as ModelsIcon, Stats } from '@geti-inspect/icons'; +import { Flex, Grid, ToggleButton, View } from '@geti/ui'; +import { useSearchParams } from 'react-router-dom'; + +import { Dataset } from './dataset/dataset.component'; +import { Models } from './models/models.component'; + +import styles from './sidebar.module.scss'; + +const TABS = [ + { label: 'Dataset', icon: , content: }, + { label: 'Models', icon: , content: }, + { label: 'Stats', icon: , content: <>Stats }, +]; + +interface TabProps { + tabs: (typeof TABS)[number][]; +} + +const SidebarTabs = ({ tabs }: TabProps) => { + const [searchParams, setSearchParams] = useSearchParams(); + const selectTab = (tab: string | null) => { + if (tab === null) { + searchParams.delete('mode'); + } else { + searchParams.set('mode', tab); + } + setSearchParams(searchParams); + }; + const tab = searchParams.get('mode'); + + const gridTemplateColumns = tab !== null ? ['clamp(size-4600, 35vw, 40rem)', 'size-600'] : ['0px', 'size-600']; + + const content = tabs.find(({ label }) => label === tab)?.content; + + return ( + + + {content} + + + + {tabs.map(({ label, icon }) => ( + selectTab(label === tab ? null : label)} + UNSAFE_className={styles.toggleButton} + aria-label={`Toggle ${label} tab`} + > + {icon} + + ))} + + + + ); +}; + +export const Sidebar = () => { + return ; +}; diff --git a/application/ui/src/features/inspect/sidebar.module.scss b/application/ui/src/features/inspect/sidebar.module.scss new file mode 100644 index 0000000000..9b4bd5d3bb --- /dev/null +++ b/application/ui/src/features/inspect/sidebar.module.scss @@ -0,0 +1,50 @@ +.container { + height: 100%; + transition: grid-template-columns 0.3s ease-in-out; + border-top-width: var(--spectrum-alias-border-size-thin); + border-top-style: solid; + border-color: var(--spectrum-global-color-gray-50); +} + +.sidebarContent { + overflow-x: hidden; + overflow-y: auto; + will-change: opacity, filter; + opacity: 0; + filter: blur(6px); + transition: + opacity 260ms ease-out, + filter 300ms ease-out; + border-right-width: var(--spectrum-alias-border-size-thin); + border-right-style: solid; + border-color: var(--spectrum-global-color-gray-50); +} + +.container[data-expanded='true'] .sidebarContent { + opacity: 1; + filter: blur(0px); +} + +@media (prefers-reduced-motion: reduce) { + .sidebarContent { + filter: none !important; + opacity: 1; + } +} + +.toggleButton { + border: var(--spectrum-alias-border-size-thin) solid transparent !important; + + &[class*='is-hovered'] { + border: var(--spectrum-alias-border-size-thin) solid var(--spectrum-global-color-gray-300) !important; + background-color: var(--spectrum-global-color-gray-300) !important; + border-radius: var(--spectrum-alias-border-radius-regular) !important; + } + + &[class*='is-selected'], + &[class*='is-active'] { + border: var(--spectrum-alias-border-size-thin) solid var(--spectrum-global-color-gray-400) !important; + background-color: var(--spectrum-global-color-gray-400) !important; + border-radius: var(--spectrum-alias-border-radius-regular) !important; + } +} diff --git a/application/ui/src/features/inspect/stream/stream-container.module.scss b/application/ui/src/features/inspect/stream/stream-container.module.scss new file mode 100644 index 0000000000..32aa0b0ccf --- /dev/null +++ b/application/ui/src/features/inspect/stream/stream-container.module.scss @@ -0,0 +1,14 @@ +.canvasContainer { + overflow: hidden; + width: 100%; + height: 100%; +} + +.playButton { + display: flex; + gap: var(--spectrum-global-dimension-size-400); + align-items: center; + padding: var(--spectrum-global-dimension-size-1200); + position: relative; + border-radius: var(--spectrum-alias-border-radius-medium); +} diff --git a/application/ui/src/features/inspect/stream/stream-container.test.tsx b/application/ui/src/features/inspect/stream/stream-container.test.tsx new file mode 100644 index 0000000000..8961c89bf9 --- /dev/null +++ b/application/ui/src/features/inspect/stream/stream-container.test.tsx @@ -0,0 +1,115 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { HttpResponse } from 'msw'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { SchemaPipeline } from 'src/api/openapi-spec'; +import { http } from 'src/api/utils'; +import { ZoomProvider } from 'src/components/zoom/zoom'; +import { server } from 'src/msw-node-setup'; + +import { getMockedPipeline } from '../../../../mocks/mock-pipeline'; +import { useWebRTCConnection, WebRTCConnectionState } from '../../../components/stream/web-rtc-connection-provider'; +import { StreamContainer } from './stream-container'; + +vi.mock('../../../components/stream/web-rtc-connection-provider', () => ({ + useWebRTCConnection: vi.fn(), +})); + +describe('StreamContainer', () => { + const renderApp = ({ + webRtcConfig = {}, + pipelineConfig = {}, + }: { + webRtcConfig?: Partial; + pipelineConfig?: Partial; + }) => { + vi.mocked(useWebRTCConnection).mockReturnValue({ + start: vi.fn(), + status: 'idle', + stop: vi.fn(), + webRTCConnectionRef: { current: null }, + ...webRtcConfig, + }); + + server.use( + http.get('/api/projects/{project_id}/pipeline', ({ response }) => + response(200).json(getMockedPipeline(pipelineConfig)) + ) + ); + + render( + + + + + } /> + + + + + ); + }; + + describe('Start stream button', () => { + it('call pipeline enable', async () => { + const mockedStart = vi.fn(); + const pipelinePatchSpy = vi.fn(); + + server.use( + http.post('/api/projects/{project_id}/pipeline:run', () => { + pipelinePatchSpy(); + return HttpResponse.json({}, { status: 204 }); + }) + ); + + renderApp({ webRtcConfig: { status: 'idle', start: mockedStart }, pipelineConfig: { status: 'idle' } }); + + const button = await screen.findByRole('button', { name: /Start stream/i }); + await userEvent.click(button); + + expect(mockedStart).toHaveBeenCalled(); + expect(pipelinePatchSpy).toHaveBeenCalled(); + }); + + it('pipeline enable is enabled', async () => { + const mockedStart = vi.fn(); + const pipelinePatchSpy = vi.fn(); + + server.use( + http.post('/api/projects/{project_id}/pipeline:run', () => { + pipelinePatchSpy(); + return HttpResponse.json({}, { status: 204 }); + }) + ); + + renderApp({ webRtcConfig: { status: 'idle', start: mockedStart }, pipelineConfig: { status: 'running' } }); + + const button = await screen.findByRole('button', { name: /Start stream/i }); + await userEvent.click(button); + + expect(mockedStart).toHaveBeenCalled(); + expect(pipelinePatchSpy).not.toHaveBeenCalled(); + }); + }); + + it('render loading state', async () => { + renderApp({ webRtcConfig: { status: 'connecting' } }); + + expect(await screen.findByLabelText('Loading...')).toBeVisible(); + }); + + it('webRtc connected', async () => { + renderApp({ webRtcConfig: { status: 'connected' } }); + + expect(await screen.findByLabelText('stream player')).toBeVisible(); + }); + + it('autoplay webRtc if pipeline is enabled', async () => { + const mockedStart = vi.fn(); + renderApp({ webRtcConfig: { status: 'idle', start: mockedStart }, pipelineConfig: { status: 'running' } }); + + expect(await screen.findByRole('button', { name: /Start stream/i })).toBeVisible(); + expect(mockedStart).toHaveBeenCalled(); + }); +}); diff --git a/application/ui/src/features/inspect/stream/stream-container.tsx b/application/ui/src/features/inspect/stream/stream-container.tsx new file mode 100644 index 0000000000..db13c8aa92 --- /dev/null +++ b/application/ui/src/features/inspect/stream/stream-container.tsx @@ -0,0 +1,81 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { useEffect, useState } from 'react'; + +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { Button, Flex, Loading, toast, View } from '@geti/ui'; +import { Play } from '@geti/ui/icons'; +import { isEmpty } from 'lodash-es'; +import { useActivatePipeline, usePipeline } from 'src/hooks/use-pipeline.hook'; + +import { useWebRTCConnection } from '../../../components/stream/web-rtc-connection-provider'; +import { isStatusActive } from '../utils'; +import { Stream } from './stream'; + +import classes from './stream-container.module.scss'; + +export const StreamContainer = () => { + const { projectId } = useProjectIdentifier(); + const { data: pipeline } = usePipeline(); + const { start, status } = useWebRTCConnection(); + const activePipeline = useActivatePipeline({ onSuccess: start }); + const [size, setSize] = useState({ height: 608, width: 892 }); + + const hasNoSink = isEmpty(pipeline?.sink); + const hasNoSource = isEmpty(pipeline?.source); + const isPipelineActive = isStatusActive(pipeline.status); + + useEffect(() => { + if (status === 'failed') { + toast({ type: 'error', message: 'Failed to connect to the stream' }); + } + + if (isPipelineActive && status === 'idle') { + start(); + } + }, [isPipelineActive, status, start]); + + const handleStart = async () => { + if (isPipelineActive) { + start(); + } else { + activePipeline.mutate({ params: { path: { project_id: projectId } } }); + } + }; + + return ( + + {status === 'idle' && ( + + + + + + )} + + {(status === 'connecting' || activePipeline.isPending) && ( + + + + + + )} + + {status === 'connected' && } + + ); +}; diff --git a/application/ui/src/features/inspect/stream/stream.tsx b/application/ui/src/features/inspect/stream/stream.tsx new file mode 100644 index 0000000000..66842fdc84 --- /dev/null +++ b/application/ui/src/features/inspect/stream/stream.tsx @@ -0,0 +1,111 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { Dispatch, RefObject, SetStateAction, useCallback, useEffect, useRef } from 'react'; + +import { useWebRTCConnection } from '../../../components/stream/web-rtc-connection-provider'; +import { ZoomTransform } from '../../../components/zoom/zoom-transform'; + +interface StreamProps { + size: { width: number; height: number }; + setSize: Dispatch>; +} + +const useSetTargetSizeBasedOnVideo = ( + setSize: Dispatch>, + videoRef: RefObject +) => { + useEffect(() => { + const video = videoRef.current; + if (!video) return; + + const onLoaded = () => { + if (video.videoWidth && video.videoHeight) { + setSize({ width: video.videoWidth, height: video.videoHeight }); + } + }; + + const resizeObserver = new ResizeObserver(() => { + if (video.videoWidth && video.videoHeight) { + setSize({ width: video.videoWidth, height: video.videoHeight }); + } + }); + + video.addEventListener('loadedmetadata', onLoaded); + resizeObserver.observe(video); + + return () => { + video.removeEventListener('loadedmetadata', onLoaded); + resizeObserver.disconnect(); + }; + }, [setSize, videoRef]); +}; + +const useStreamToVideo = () => { + const videoRef = useRef(null); + + const { status, webRTCConnectionRef } = useWebRTCConnection(); + + const connect = useCallback(async () => { + const videoOutput = videoRef.current; + const webrtcConnection = webRTCConnectionRef.current; + const peerConnection = webrtcConnection?.getPeerConnection(); + + if (!peerConnection) { + return; + } + + const receivers = peerConnection.getReceivers() ?? []; + const stream = new MediaStream(receivers.map((receiver) => receiver.track).filter(Boolean)); + + if (videoOutput && videoOutput.srcObject !== stream) { + videoOutput.srcObject = stream; + } + }, [videoRef, webRTCConnectionRef]); + + useEffect(() => { + if (status === 'connected') { + connect(); + } + }, [status, connect]); + + useEffect(() => { + const webrtcConnection = webRTCConnectionRef.current; + const peerConnection = webrtcConnection?.getPeerConnection(); + + if (!peerConnection) { + return; + } + + peerConnection.addEventListener('track', connect); + + return () => { + peerConnection.removeEventListener('track', connect); + }; + }, [webRTCConnectionRef, connect]); + + return videoRef; +}; + +export const Stream = ({ size, setSize }: StreamProps) => { + const videoRef = useStreamToVideo(); + useSetTargetSizeBasedOnVideo(setSize, videoRef); + + return ( + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/inference-opacity.tsx b/application/ui/src/features/inspect/toolbar/inference-opacity.tsx new file mode 100644 index 0000000000..4d58e83189 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/inference-opacity.tsx @@ -0,0 +1,36 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { ActionButton, DialogTrigger, Flex, Slider, Text, View } from '@geti/ui'; +import { ChevronDownSmall } from '@geti/ui/icons'; + +import { useInference } from '../inference-provider.component'; + +export const InferenceOpacity = () => { + const { inferenceOpacity, onInferenceOpacityChange, inferenceResult } = useInference(); + + return ( + + Opacity: + + + + {Math.floor(inferenceOpacity * 100)}% + + + + + + + + + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/models-picker.component.tsx b/application/ui/src/features/inspect/toolbar/models-picker.component.tsx new file mode 100644 index 0000000000..82eefeac5d --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/models-picker.component.tsx @@ -0,0 +1,51 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { useEffect } from 'react'; + +import { $api } from '@geti-inspect/api'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { Item, Picker } from '@geti/ui'; + +import { useInference } from '../inference-provider.component'; + +const useTrainedModels = () => { + const { projectId } = useProjectIdentifier(); + const { data } = $api.useQuery('get', '/api/projects/{project_id}/models', { + params: { + path: { + project_id: projectId, + }, + }, + }); + + return data?.models.map((model) => ({ id: model.id, name: model.name })) || []; +}; + +export const ModelsPicker = () => { + const models = useTrainedModels(); + const { selectedModelId, onSetSelectedModelId } = useInference(); + + useEffect(() => { + if (selectedModelId !== undefined || models.length === 0) { + return; + } + + onSetSelectedModelId(models[0].id); + }, [selectedModelId, models, onSetSelectedModelId]); + + if (models === undefined || models.length === 0) { + return null; + } + + return ( + onSetSelectedModelId(String(key))} + > + {(item) => {item.name}} + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/pipeline-configuration.component.tsx b/application/ui/src/features/inspect/toolbar/pipeline-configuration.component.tsx new file mode 100644 index 0000000000..ec9817c84e --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/pipeline-configuration.component.tsx @@ -0,0 +1,69 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { CSSProperties, Suspense } from 'react'; + +import { PipelineLink } from '@geti-inspect/icons'; +import { + Button, + Content, + Dialog, + DialogTrigger, + dimensionValue, + Item, + Loading, + TabList, + TabPanels, + Tabs, + Text, + View, +} from '@geti/ui'; + +import { SinkActions } from './sinks/sink-actions.component'; +import { SourceActions } from './sources/source-actions.component'; + +const paddingStyle = { + '--spectrum-dialog-padding-x': dimensionValue('size-300'), + '--spectrum-dialog-padding-y': dimensionValue('size-300'), +} as CSSProperties; + +export const InputOutputSetup = () => { + return ( + + + + + + + + Input + + + Output + + + + + + }> + + + + + + + }> + + + + + + + + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/pipeline-switch/pipeline-switch.component.tsx b/application/ui/src/features/inspect/toolbar/pipeline-switch/pipeline-switch.component.tsx new file mode 100644 index 0000000000..72be0fbcbb --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/pipeline-switch/pipeline-switch.component.tsx @@ -0,0 +1,58 @@ +import { usePipeline, useProjectIdentifier, useRunPipeline, useStopPipeline } from '@geti-inspect/hooks'; +import { Flex, Switch } from '@geti/ui'; +import isEmpty from 'lodash-es/isEmpty'; +import { useWebRTCConnection } from 'src/components/stream/web-rtc-connection-provider'; + +import { useSelectedMediaItem } from '../../selected-media-item-provider.component'; +import { isStatusActive } from '../../utils'; +import { WebRTCConnectionStatus } from './web-rtc-connection-status.component'; + +import classes from './pipeline-switch.module.scss'; + +export const PipelineSwitch = () => { + const { projectId } = useProjectIdentifier(); + const stopPipeline = useStopPipeline(projectId); + const { status, start } = useWebRTCConnection(); + const { onSetSelectedMediaItem } = useSelectedMediaItem(); + const { data: pipeline, isLoading } = usePipeline(); + + const runPipeline = useRunPipeline({ + onSuccess: async () => { + await start(); + onSetSelectedMediaItem(undefined); + }, + }); + + const isSinkMissing = isEmpty(pipeline.sink?.id); + const isModelMissing = isEmpty(pipeline.model?.id); + const isPipelineActive = isStatusActive(pipeline.status); + const isWebRtcConnecting = status === 'connecting'; + const isProcessing = runPipeline.isPending || stopPipeline.isPending; + + const handleChange = (isSelected: boolean) => { + const handler = isSelected ? runPipeline.mutate : stopPipeline.mutate; + + handler({ params: { path: { project_id: projectId } } }); + }; + + return ( + + + Enabled + + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/pipeline-switch/pipeline-switch.module.scss b/application/ui/src/features/inspect/toolbar/pipeline-switch/pipeline-switch.module.scss new file mode 100644 index 0000000000..614ccf30a9 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/pipeline-switch/pipeline-switch.module.scss @@ -0,0 +1,3 @@ +.switch { + margin: 0px; +} diff --git a/application/ui/src/features/inspect/toolbar/pipeline-switch/pipeline-switch.test.tsx b/application/ui/src/features/inspect/toolbar/pipeline-switch/pipeline-switch.test.tsx new file mode 100644 index 0000000000..d4715cfb47 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/pipeline-switch/pipeline-switch.test.tsx @@ -0,0 +1,227 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { HttpResponse } from 'msw'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { SchemaPipeline } from 'src/api/openapi-spec'; +import { http } from 'src/api/utils'; +import { server } from 'src/msw-node-setup'; + +import { getMockedPipeline } from '../../../../../mocks/mock-pipeline'; +import { useWebRTCConnection, WebRTCConnectionState } from '../../../../components/stream/web-rtc-connection-provider'; +import { useSelectedMediaItem } from '../../selected-media-item-provider.component'; +import { PipelineSwitch } from './pipeline-switch.component'; + +vi.mock('../../../../components/stream/web-rtc-connection-provider', () => ({ + useWebRTCConnection: vi.fn(), +})); + +vi.mock('../../selected-media-item-provider.component', () => ({ + useSelectedMediaItem: vi.fn(), +})); + +describe('PipelineSwitch', () => { + const renderApp = ({ + webRtcConfig = {}, + pipelineConfig = {}, + onSetSelectedMediaItem = vi.fn(), + }: { + webRtcConfig?: Partial; + pipelineConfig?: Partial; + onSetSelectedMediaItem?: () => void; + } = {}) => { + vi.mocked(useWebRTCConnection).mockReturnValue({ + status: 'idle', + stop: vi.fn(), + start: vi.fn(), + webRTCConnectionRef: { current: null }, + ...webRtcConfig, + }); + + vi.mocked(useSelectedMediaItem).mockReturnValue({ + selectedMediaItem: undefined, + onSetSelectedMediaItem, + }); + + server.use( + http.get('/api/projects/{project_id}/pipeline', ({ response }) => + response(200).json(getMockedPipeline(pipelineConfig)) + ) + ); + + return render( + + + + } /> + + + + ); + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('Switch rendering', () => { + it('renders the switch with "Enabled" label', async () => { + renderApp(); + + expect(await screen.findByText('Enabled')).toBeVisible(); + }); + + it('switch is selected when pipeline status is "running"', async () => { + renderApp({ pipelineConfig: { status: 'running' } }); + + expect(await screen.findByRole('switch')).toBeChecked(); + }); + + it('switch is not selected when pipeline status is not "running"', async () => { + renderApp({ pipelineConfig: { status: 'idle' } }); + + expect(await screen.findByRole('switch')).not.toBeChecked(); + }); + }); + + describe('Switch disabled states', () => { + it('model is missing', async () => { + renderApp({ pipelineConfig: { model: undefined } }); + + expect(await screen.findByRole('switch')).toBeDisabled(); + }); + + it('model id is empty', async () => { + renderApp({ pipelineConfig: { model: { id: '' } as SchemaPipeline['model'] } }); + + expect(await screen.findByRole('switch')).toBeDisabled(); + }); + + it('pipeline status is not active', async () => { + renderApp({ pipelineConfig: { status: 'idle' } }); + + expect(await screen.findByRole('switch')).toBeDisabled(); + }); + + it('WebRTC is connecting', async () => { + renderApp({ webRtcConfig: { status: 'connecting' } }); + + expect(await screen.findByRole('switch')).toBeDisabled(); + }); + + it('source is missing', async () => { + renderApp({ pipelineConfig: { source: undefined } }); + + expect(await screen.findByRole('switch')).toBeEnabled(); + }); + + it('sink is missing', async () => { + renderApp({ pipelineConfig: { sink: undefined } }); + + expect(await screen.findByRole('switch')).toBeDisabled(); + }); + + it('both source and sink are missing', async () => { + renderApp({ pipelineConfig: { source: undefined, sink: undefined } }); + + expect(await screen.findByRole('switch')).toBeDisabled(); + }); + + it('is enabled when pipeline is active and all required components are present', async () => { + renderApp({ pipelineConfig: { status: 'active' } }); + + expect(await screen.findByRole('switch')).toBeEnabled(); + }); + + it('is enabled when pipeline status is "running"', async () => { + renderApp({ pipelineConfig: { status: 'running' } }); + + expect(await screen.findByRole('switch')).toBeEnabled(); + }); + }); + + describe('Switch interactions', () => { + it('calls runPipeline when switch is turned on', async () => { + const mockStart = vi.fn(); + const runPipelineSpy = vi.fn(); + const mockOnSetSelectedMediaItem = vi.fn(); + + server.use( + http.post('/api/projects/{project_id}/pipeline:run', () => { + runPipelineSpy(); + return HttpResponse.json({}, { status: 204 }); + }) + ); + + renderApp({ + pipelineConfig: { status: 'active' }, + webRtcConfig: { start: mockStart }, + onSetSelectedMediaItem: mockOnSetSelectedMediaItem, + }); + + expect(await screen.findByRole('switch')).not.toBeChecked(); + + await userEvent.click(await screen.findByRole('switch')); + + await waitFor(() => { + expect(runPipelineSpy).toHaveBeenCalled(); + expect(mockStart).toHaveBeenCalled(); + expect(mockOnSetSelectedMediaItem).toHaveBeenCalledWith(undefined); + }); + }); + + it('stops inference when switch is turned off', async () => { + const mockStart = vi.fn(); + const stopPipelineSpy = vi.fn(); + + server.use( + http.post('/api/projects/{project_id}/pipeline:stop', () => { + stopPipelineSpy(); + return HttpResponse.json({}, { status: 204 }); + }) + ); + + renderApp({ pipelineConfig: { status: 'running' }, webRtcConfig: { start: mockStart } }); + + expect(await screen.findByRole('switch')).toBeChecked(); + + await userEvent.click(await screen.findByRole('switch')); + + await waitFor(() => { + expect(stopPipelineSpy).toHaveBeenCalled(); + expect(mockStart).not.toHaveBeenCalled(); + }); + }); + + it('starts WebRTC connection after successful runPipeline', async () => { + const mockStart = vi.fn(); + + server.use( + http.post('/api/projects/{project_id}/pipeline:run', () => HttpResponse.json({}, { status: 204 })) + ); + + renderApp({ pipelineConfig: { status: 'active' }, webRtcConfig: { start: mockStart } }); + + await userEvent.click(await screen.findByRole('switch')); + + await waitFor(() => { + expect(mockStart).toHaveBeenCalled(); + }); + }); + + it('clears selected media item after successful runPipeline', async () => { + const mockOnSetSelectedMediaItem = vi.fn(); + server.use( + http.post('/api/projects/{project_id}/pipeline:run', () => HttpResponse.json({}, { status: 204 })) + ); + + renderApp({ pipelineConfig: { status: 'active' }, onSetSelectedMediaItem: mockOnSetSelectedMediaItem }); + + await userEvent.click(await screen.findByRole('switch')); + + await waitFor(() => { + expect(mockOnSetSelectedMediaItem).toHaveBeenCalledWith(undefined); + }); + }); + }); +}); diff --git a/application/ui/src/features/inspect/toolbar/pipeline-switch/web-rtc-connection-status.component.tsx b/application/ui/src/features/inspect/toolbar/pipeline-switch/web-rtc-connection-status.component.tsx new file mode 100644 index 0000000000..308aa41929 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/pipeline-switch/web-rtc-connection-status.component.tsx @@ -0,0 +1,61 @@ +import { Flex, PressableElement, StatusLight, Tooltip, TooltipTrigger } from '@geti/ui'; + +import { useWebRTCConnection } from '../../../../components/stream/web-rtc-connection-provider'; + +export const WebRTCConnectionStatus = () => { + const { status } = useWebRTCConnection(); + + switch (status) { + case 'idle': + return ( + + + Idle + + + ); + case 'connecting': + return ( + + + Connecting + + + ); + case 'disconnected': + return ( + + + Disconnected + + + ); + case 'failed': + return ( + + + Failed + + + ); + case 'connected': + return ( + + + + + Connected + + + WebRTC is ready to use + + + ); + } +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/add-sink/add-sink.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/add-sink/add-sink.component.tsx new file mode 100644 index 0000000000..3797e69ef1 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/add-sink/add-sink.component.tsx @@ -0,0 +1,38 @@ +import { ReactNode } from 'react'; + +import { Button, Form } from '@geti/ui'; + +import { useConnectSinkToPipeline } from '../../../../../hooks/use-pipeline.hook'; +import { useSinkAction } from '../hooks/use-sink-action.hook'; +import { SinkConfig } from '../utils'; + +interface AddSinkProps { + config: Awaited; + onSaved: () => void; + componentFields: (state: Awaited) => ReactNode; + bodyFormatter: (formData: FormData) => T; +} + +export const AddSink = ({ config, onSaved, bodyFormatter, componentFields }: AddSinkProps) => { + const connectToPipelineMutation = useConnectSinkToPipeline(); + + const [state, submitAction, isPending] = useSinkAction({ + config, + isNewSink: true, + onSaved: async (sourceId) => { + await connectToPipelineMutation(sourceId); + onSaved(); + }, + bodyFormatter, + }); + + return ( +
    + <>{componentFields(state)} + + +
    + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/edit-sink-form.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/edit-sink-form.component.tsx new file mode 100644 index 0000000000..c7ee51d08e --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/edit-sink-form.component.tsx @@ -0,0 +1,68 @@ +import { EditSink } from './edit-sink/edit-sink.component'; +import { LocalFolderFields } from './local-folder-fields/local-folder-fields.component'; +import { localFolderBodyFormatter } from './local-folder-fields/utils'; +import { MqttFields } from './mqtt-fields/mqtt-fields.component'; +import { mqttBodyFormatter } from './mqtt-fields/utils'; +import { RosFields } from './ros-fields/ros-fields.component'; +import { rosBodyFormatter } from './ros-fields/utils'; +import { SinkConfig } from './utils'; +import { webhookBodyFormatter } from './webhook-fields/utils'; +import { WebhookFields } from './webhook-fields/webhook-fields.component'; + +interface EditSinkFormProps { + config: SinkConfig; + onSaved: () => void; + onBackToList: () => void; +} + +export const EditSinkForm = ({ config, onSaved, onBackToList }: EditSinkFormProps) => { + if (config.sink_type === 'folder') { + return ( + } + bodyFormatter={localFolderBodyFormatter} + /> + ); + } + + if (config.sink_type === 'webhook') { + return ( + } + bodyFormatter={webhookBodyFormatter} + /> + ); + } + + if (config.sink_type === 'mqtt') { + return ( + } + bodyFormatter={mqttBodyFormatter} + /> + ); + } + + if (config.sink_type === 'ros') { + return ( + } + bodyFormatter={rosBodyFormatter} + /> + ); + } + + return <>; +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/edit-sink/edit-sink.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/edit-sink/edit-sink.component.tsx new file mode 100644 index 0000000000..2b51982b6c --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/edit-sink/edit-sink.component.tsx @@ -0,0 +1,77 @@ +import { ReactNode, useRef } from 'react'; + +import { ActionButton, Button, ButtonGroup, Divider, Flex, Form, Text, View } from '@geti/ui'; +import { Back } from '@geti/ui/icons'; + +import { useConnectSinkToPipeline } from '../../../../../hooks/use-pipeline.hook'; +import { useSinkAction } from '../hooks/use-sink-action.hook'; +import { SinkConfig } from '../utils'; + +import classes from './edit-sink.module.scss'; + +interface EditSinkProps { + config: Awaited; + onSaved: () => void; + onBackToList: () => void; + componentFields: (state: Awaited) => ReactNode; + bodyFormatter: (formData: FormData) => T; +} + +export const EditSink = ({ + config, + onSaved, + onBackToList, + bodyFormatter, + componentFields, +}: EditSinkProps) => { + const connectToPipeline = useRef(false); + const connectToPipelineMutation = useConnectSinkToPipeline(); + + const [state, submitAction, isPending] = useSinkAction({ + config, + isNewSink: false, + onSaved: async (sinkId) => { + connectToPipeline.current && (await connectToPipelineMutation(sinkId)); + connectToPipeline.current = false; + onSaved(); + }, + bodyFormatter, + }); + + return ( +
    + + + + + + Edit sink + + + + <>{componentFields(state)} + + + + + + + + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/edit-sink/edit-sink.module.scss b/application/ui/src/features/inspect/toolbar/sinks/edit-sink/edit-sink.module.scss new file mode 100644 index 0000000000..8aeb44db95 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/edit-sink/edit-sink.module.scss @@ -0,0 +1,6 @@ +.container { + background: #2e2f32; + border: 1px solid var(--background-inverse); + border-radius: var(--spectrum-global-dimension-size-100); + padding: var(--spectrum-global-dimension-size-200); +} diff --git a/application/ui/src/features/inspect/toolbar/sinks/hooks/use-sink-action.hook.tsx b/application/ui/src/features/inspect/toolbar/sinks/hooks/use-sink-action.hook.tsx new file mode 100644 index 0000000000..363f7fb863 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/hooks/use-sink-action.hook.tsx @@ -0,0 +1,49 @@ +import { useActionState } from 'react'; + +import { toast } from '@geti/ui'; +import { isFunction } from 'lodash'; + +import { SinkConfig } from '../utils'; +import { useSinkMutation } from './use-sink-mutation.hook'; + +interface useSinkActionProps { + config: Awaited; + isNewSink: boolean; + onSaved?: (sink_id: string) => void; + bodyFormatter: (formData: FormData) => T; +} + +export const useSinkAction = ({ + config, + isNewSink, + onSaved, + bodyFormatter, +}: useSinkActionProps) => { + const addOrUpdateSink = useSinkMutation(isNewSink); + + return useActionState(async (_prevState: T, formData: FormData) => { + const body = bodyFormatter(formData); + + try { + const sink_id = await addOrUpdateSink(body); + + toast({ + type: 'success', + message: `Sink configuration ${isNewSink ? 'created' : 'updated'} successfully.`, + }); + + isFunction(onSaved) && onSaved(sink_id); + + return { ...body, id: sink_id }; + } catch (error: unknown) { + const details = (error as { detail?: string })?.detail; + + toast({ + type: 'error', + message: `Failed to save sink configuration, ${details ?? 'please try again'}`, + }); + } + + return body; + }, config); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/hooks/use-sink-mutation.hook.tsx b/application/ui/src/features/inspect/toolbar/sinks/hooks/use-sink-mutation.hook.tsx new file mode 100644 index 0000000000..7c2063a4d1 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/hooks/use-sink-mutation.hook.tsx @@ -0,0 +1,41 @@ +import { $api } from '@geti-inspect/api'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { omit } from 'lodash-es'; + +import { SinkConfig } from '../utils'; + +export const useSinkMutation = (isNewSink: boolean) => { + const { projectId } = useProjectIdentifier(); + const addSink = $api.useMutation('post', '/api/projects/{project_id}/sinks', { + meta: { + invalidates: [['get', '/api/projects/{project_id}/sinks', { params: { path: { project_id: projectId } } }]], + }, + }); + const updateSink = $api.useMutation('patch', '/api/projects/{project_id}/sinks/{sink_id}', { + params: { path: { project_id: projectId } }, + meta: { + invalidates: [['get', '/api/projects/{project_id}/sinks', { params: { path: { project_id: projectId } } }]], + }, + }); + + return async (body: SinkConfig) => { + if (isNewSink) { + // Omit id and project_id when creating - they're auto-generated/injected from URL + const sinkPayload = omit(body, ['id', 'project_id']) as Parameters[0]['body']; + + const response = await addSink.mutateAsync({ + body: sinkPayload, + params: { path: { project_id: projectId } }, + }); + + return String(response.id); + } + + const response = await updateSink.mutateAsync({ + params: { path: { project_id: projectId, sink_id: String(body.id) } }, + body: omit(body, 'sink_type'), + }); + + return String(response.id); + }; +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/local-folder-fields/local-folder-fields.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/local-folder-fields/local-folder-fields.component.tsx new file mode 100644 index 0000000000..b0f6f2aa85 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/local-folder-fields/local-folder-fields.component.tsx @@ -0,0 +1,52 @@ +import { Flex, NumberField, TextField } from '@geti/ui'; + +import { ReactComponent as FolderIcon } from '../../../../../assets/icons/folder.svg'; +import { OutputFormats } from '../output-formats/output-formats.component'; +import { LocalFolderSinkConfig } from '../utils'; + +import classes from './local-folder-fields.module.scss'; + +interface LocalFolderFieldsProps { + defaultState: LocalFolderSinkConfig; +} + +export const LocalFolderFields = ({ defaultState }: LocalFolderFieldsProps) => { + return ( + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/local-folder-fields/local-folder-fields.module.scss b/application/ui/src/features/inspect/toolbar/sinks/local-folder-fields/local-folder-fields.module.scss new file mode 100644 index 0000000000..26d3fc962a --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/local-folder-fields/local-folder-fields.module.scss @@ -0,0 +1,15 @@ +.folderIcon { + width: var(--spectrum-global-dimension-size-400); + min-width: auto; + border-radius: 4px; + padding: 0px; + background-color: var(--spectrum-accent-background-color-default); + + svg { + stroke: var(--spectrum-global-color-gray-50); + } + + &:hover { + background-color: var(var(--spectrum-accent-background-color-hover)); + } +} diff --git a/application/ui/src/features/inspect/toolbar/sinks/local-folder-fields/utils.ts b/application/ui/src/features/inspect/toolbar/sinks/local-folder-fields/utils.ts new file mode 100644 index 0000000000..0d9b4b88d5 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/local-folder-fields/utils.ts @@ -0,0 +1,23 @@ +import { v4 as uuid } from 'uuid'; + +import { LocalFolderSinkConfig, SinkOutputFormats } from '../utils'; + +export const getLocalFolderInitialConfig = (project_id: string): LocalFolderSinkConfig => ({ + id: uuid(), + name: '', + project_id, + sink_type: 'folder', + rate_limit: 0, + folder_path: '', + output_formats: [], +}); + +export const localFolderBodyFormatter = (formData: FormData): LocalFolderSinkConfig => ({ + id: String(formData.get('id')), + name: String(formData.get('name')), + sink_type: 'folder', + rate_limit: formData.get('rate_limit') ? Number(formData.get('rate_limit')) : 0, + folder_path: String(formData.get('folder_path')), + project_id: String(formData.get('project_id')), + output_formats: formData.getAll('output_formats') as SinkOutputFormats, +}); diff --git a/application/ui/src/features/inspect/toolbar/sinks/mqtt-fields/mqtt-fields.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/mqtt-fields/mqtt-fields.component.tsx new file mode 100644 index 0000000000..bc3d2daad3 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/mqtt-fields/mqtt-fields.component.tsx @@ -0,0 +1,51 @@ +import { Flex, NumberField, Switch, TextField } from '@geti/ui'; + +import { OutputFormats } from '../output-formats/output-formats.component'; +import { MqttSinkConfig } from '../utils'; + +interface MqttFieldsProps { + defaultState: MqttSinkConfig; +} + +export const MqttFields = ({ defaultState }: MqttFieldsProps) => { + return ( + + + + + + + + + + + + + + + Auth Required + + + + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/mqtt-fields/utils.ts b/application/ui/src/features/inspect/toolbar/sinks/mqtt-fields/utils.ts new file mode 100644 index 0000000000..6629a1e560 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/mqtt-fields/utils.ts @@ -0,0 +1,29 @@ +import { v4 as uuid } from 'uuid'; + +import { MqttSinkConfig, SinkOutputFormats } from '../utils'; + +export const getMqttInitialConfig = (project_id: string): MqttSinkConfig => ({ + id: uuid(), + name: '', + project_id, + topic: '', + sink_type: 'mqtt', + rate_limit: 0, + broker_host: '', + broker_port: 0, + auth_required: false, + output_formats: [], +}); + +export const mqttBodyFormatter = (formData: FormData): MqttSinkConfig => ({ + id: String(formData.get('id')), + name: String(formData.get('name')), + topic: String(formData.get('topic')), + sink_type: 'mqtt', + rate_limit: formData.get('rate_limit') ? Number(formData.get('rate_limit')) : 0, + broker_host: String(formData.get('broker_host')), + broker_port: formData.get('broker_port') ? Number(formData.get('broker_port')) : 0, + auth_required: formData.get('auth_required') === 'on', + output_formats: formData.getAll('output_formats') as SinkOutputFormats, + project_id: String(formData.get('project_id')), +}); diff --git a/application/ui/src/features/inspect/toolbar/sinks/output-formats/output-formats.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/output-formats/output-formats.component.tsx new file mode 100644 index 0000000000..aff69dc24e --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/output-formats/output-formats.component.tsx @@ -0,0 +1,30 @@ +import { Checkbox, CheckboxGroup } from '@geti/ui'; + +import { OutputFormat, SinkOutputFormats } from '../utils'; + +import classes from './output-formats.module.scss'; + +type OutputFormatsProps = { + config?: SinkOutputFormats; +}; + +export const OutputFormats = ({ config = [] }: OutputFormatsProps) => { + return ( + + + Predictions + + + Image Original + + + Image with Predictions + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/output-formats/output-formats.module.scss b/application/ui/src/features/inspect/toolbar/sinks/output-formats/output-formats.module.scss new file mode 100644 index 0000000000..e94b7badb5 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/output-formats/output-formats.module.scss @@ -0,0 +1,7 @@ +.itemList { + [role='group'] { + display: flex; + flex-wrap: wrap; + flex-direction: row; + } +} diff --git a/application/ui/src/features/inspect/toolbar/sinks/ros-fields/ros-fields.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/ros-fields/ros-fields.component.tsx new file mode 100644 index 0000000000..8416e06360 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/ros-fields/ros-fields.component.tsx @@ -0,0 +1,32 @@ +import { Flex, NumberField, TextField } from '@geti/ui'; + +import { OutputFormats } from '../output-formats/output-formats.component'; +import { RosSinkConfig } from '../utils'; + +interface RosFieldsProps { + defaultState: RosSinkConfig; +} + +export const RosFields = ({ defaultState }: RosFieldsProps) => { + return ( + + + + + + + + + + + + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/ros-fields/utils.ts b/application/ui/src/features/inspect/toolbar/sinks/ros-fields/utils.ts new file mode 100644 index 0000000000..cde89d3c4e --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/ros-fields/utils.ts @@ -0,0 +1,23 @@ +import { v4 as uuid } from 'uuid'; + +import { RosSinkConfig, SinkOutputFormats } from '../utils'; + +export const getRosInitialConfig = (project_id: string): RosSinkConfig => ({ + id: uuid(), + name: '', + project_id, + topic: '', + rate_limit: 0, + output_formats: [], + sink_type: 'ros', +}); + +export const rosBodyFormatter = (formData: FormData): RosSinkConfig => ({ + id: String(formData.get('id')), + name: String(formData.get('name')), + topic: String(formData.get('topic')), + sink_type: 'ros', + rate_limit: formData.get('rate_limit') ? Number(formData.get('rate_limit')) : 0, + output_formats: formData.getAll('output_formats') as SinkOutputFormats, + project_id: String(formData.get('project_id')), +}); diff --git a/application/ui/src/features/inspect/toolbar/sinks/sink-actions.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/sink-actions.component.tsx new file mode 100644 index 0000000000..9ca9e10bbc --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/sink-actions.component.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react'; + +import { $api } from '@geti-inspect/api'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { ActionButton, Flex, Text } from '@geti/ui'; +import { Back } from '@geti/ui/icons'; +import { isEmpty } from 'lodash-es'; + +import { EditSinkForm } from './edit-sink-form.component'; +import { SinkList } from './sink-list/sink-list.component'; +import { SinkOptions } from './sink-options.component'; +import { SinkConfig } from './utils'; + +export const SinkActions = () => { + const { projectId } = useProjectIdentifier(); + const sinksQuery = $api.useSuspenseQuery('get', '/api/projects/{project_id}/sinks', { + params: { path: { project_id: projectId } }, + }); + + const sinks = sinksQuery.data ?? []; + + const [view, setView] = useState<'list' | 'options' | 'edit'>(isEmpty(sinks) ? 'options' : 'list'); + const [currentSink, setCurrentSink] = useState(null); + + const handleShowList = () => { + setView('list'); + }; + + const handleAddSinks = () => { + setView('options'); + }; + + const handleEditSink = (sink: SinkConfig) => { + setView('edit'); + setCurrentSink(sink); + }; + + if (view === 'edit' && !isEmpty(currentSink)) { + return ; + } + + if (view === 'list') { + return ; + } + + return ( + 0}> + + + + + + Add new sink + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/sink-list/settings-list/settings-list.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/sink-list/settings-list/settings-list.component.tsx new file mode 100644 index 0000000000..ac9cef3856 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/sink-list/settings-list/settings-list.component.tsx @@ -0,0 +1,91 @@ +import { removeUnderscore } from '../../../../utils'; +import { SinkConfig, SinkOutputFormats, WebhookSinkConfig } from '../../utils'; +import { getPairsFromObject } from '../../webhook-fields/utils'; + +import classes from './settings-list.module.scss'; + +interface SettingsListProps { + sink: SinkConfig; +} + +const OutputFormats = ({ outputFormats }: { outputFormats: SinkOutputFormats }) => { + return ( +
      + {outputFormats.map((item) => ( +
    • {removeUnderscore(item)}
    • + ))} +
    + ); +}; + +const WebhookHeaders = ({ sink }: { sink: WebhookSinkConfig }) => { + return ( +
      + {getPairsFromObject(sink.headers ?? {}).map((pair) => ( +
    • + {pair.key}: {pair.value} +
    • + ))} +
    + ); +}; + +export const SettingsList = ({ sink }: SettingsListProps) => { + if (sink.sink_type === 'folder') { + return ( +
      +
    • Folder path: {sink.folder_path}
    • +
    • Rate limit: {sink.rate_limit}
    • +
    • + Output formats: +
    • +
    + ); + } + + if (sink.sink_type === 'webhook') { + return ( +
      +
    • Rate limit: {sink.rate_limit}
    • +
    • HTTP method: {sink.http_method}
    • +
    • Timeout: {sink.timeout}
    • +
    • Webhook URL: {sink.webhook_url}
    • +
    • + Headers +
    • +
    • + Output formats: +
    • +
    + ); + } + + if (sink.sink_type === 'mqtt') { + return ( +
      +
    • Topic: {sink.topic}
    • +
    • Rate limit: {sink.rate_limit}
    • +
    • Auth required: {sink.auth_required ? 'Yes' : 'No'}
    • +
    • Broker host: {sink.broker_host}
    • +
    • Broker port: {sink.broker_port}
    • +
    • + Output formats: +
    • +
    + ); + } + + if (sink.sink_type === 'ros') { + return ( +
      +
    • Topic: {sink.topic}
    • +
    • Rate limit: {sink.rate_limit}
    • +
    • + Output formats: +
    • +
    + ); + } + + return <>; +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/sink-list/settings-list/settings-list.module.scss b/application/ui/src/features/inspect/toolbar/sinks/sink-list/settings-list/settings-list.module.scss new file mode 100644 index 0000000000..a53cfcf8df --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/sink-list/settings-list/settings-list.module.scss @@ -0,0 +1,9 @@ +.list { + margin-left: var(--spectrum-global-dimension-font-size-200); + list-style-type: disc; + + ul { + list-style-type: circle; + margin-inline-start: var(--spectrum-global-dimension-font-size-200); + } +} diff --git a/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-icon/sink-icon.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-icon/sink-icon.component.tsx new file mode 100644 index 0000000000..33032be6ea --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-icon/sink-icon.component.tsx @@ -0,0 +1,25 @@ +import { Folder, Mqtt, Ros, Webhook } from '@geti-inspect/icons'; + +interface SinkIconProps { + type: 'folder' | 'mqtt' | 'webhook' | 'ros' | 'disconnected'; +} + +export const SinkIcon = ({ type }: SinkIconProps) => { + if (type === 'folder') { + return ; + } + + if (type === 'mqtt') { + return ; + } + + if (type === 'ros') { + return ; + } + + if (type === 'webhook') { + return ; + } + + return <>; +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-list.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-list.component.tsx new file mode 100644 index 0000000000..06b1065881 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-list.component.tsx @@ -0,0 +1,84 @@ +import { Add as AddIcon } from '@geti/ui/icons'; +import { clsx } from 'clsx'; +import { isEqual } from 'lodash-es'; +import { Button, Flex, Text } from 'packages/ui'; + +import { StatusTag } from '../../../../../components/status-tag/status-tag.component'; +import { usePipeline } from '../../../../../hooks/use-pipeline.hook'; +import { removeUnderscore } from '../../../utils'; +import { SinkConfig } from '../utils'; +import { SettingsList } from './settings-list/settings-list.component'; +import { SinkIcon } from './sink-icon/sink-icon.component'; +import { SinkMenu } from './sink-menu/sink-menu.component'; + +import classes from './sink-list.module.scss'; + +type SinksListProps = { + sinks: SinkConfig[]; + onAddSink: () => void; + onEditSink: (config: SinkConfig) => void; +}; + +type SinksListItemProps = { + sink: SinkConfig; + isConnected: boolean; + onEditSink: (config: SinkConfig) => void; +}; + +const SinkListItem = ({ sink, isConnected, onEditSink }: SinksListItemProps) => { + return ( + + + + + + {sink.name} + + {removeUnderscore(sink.sink_type)} + + + + + + + + + onEditSink(sink)} + /> + + + ); +}; + +export const SinkList = ({ sinks, onAddSink, onEditSink }: SinksListProps) => { + const pipeline = usePipeline(); + const currentSinkId = pipeline.data.sink?.id; + + return ( + + + + {sinks.map((sink) => ( + + ))} + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-list.module.scss b/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-list.module.scss new file mode 100644 index 0000000000..3d2a6bffc7 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-list.module.scss @@ -0,0 +1,28 @@ +.addSink { + width: 100%; + cursor: pointer; + display: flex; + border-style: dashed; + gap: var(--spectrum-global-dimension-size-65); + padding: var(--spectrum-global-dimension-size-250); +} + +.card { + padding: var(--spectrum-global-dimension-size-250); + background: var(--spectrum-global-color-gray-200); + border-radius: var(--spectrum-global-dimension-size-65); +} + +.activeCard { + border-left: var(--spectrum-global-dimension-size-50) solid var(--aqua); +} + +.title { + font-weight: 500; + text-transform: capitalize; + font-size: var(--spectrum-global-dimension-font-size-200); +} + +.type { + text-transform: capitalize; +} diff --git a/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-menu/sink-menu.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-menu/sink-menu.component.tsx new file mode 100644 index 0000000000..312b3a5baf --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/sink-list/sink-menu/sink-menu.component.tsx @@ -0,0 +1,97 @@ +import { $api } from '@geti-inspect/api'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { MoreMenu } from '@geti/ui/icons'; +import { ActionButton, Item, Key, Menu, MenuTrigger, toast } from 'packages/ui'; + +export interface SinkMenuProps { + id: string; + name: string; + isConnected: boolean; + onEdit: () => void; +} + +export const SinkMenu = ({ id, name, isConnected, onEdit }: SinkMenuProps) => { + const { projectId } = useProjectIdentifier(); + const removeSink = $api.useMutation('delete', '/api/projects/{project_id}/sinks/{sink_id}', { + meta: { + invalidates: [['get', '/api/projects/{project_id}/sinks', { params: { path: { project_id: projectId } } }]], + }, + }); + + const updatePipeline = $api.useMutation('patch', '/api/projects/{project_id}/pipeline', { + meta: { + invalidates: [ + ['get', '/api/projects/{project_id}/pipeline', { params: { path: { project_id: projectId } } }], + ], + }, + }); + + const handleOnAction = (option: Key) => { + switch (option) { + case 'connect': + handleConnect(); + break; + case 'remove': + handleDelete(); + break; + default: + onEdit(); + break; + } + }; + + const handleConnect = async () => { + try { + await updatePipeline.mutateAsync({ + params: { path: { project_id: projectId } }, + body: { sink_id: id }, + }); + + toast({ + type: 'success', + message: `Successfully connected to "${name}"`, + }); + } catch (_error) { + toast({ + type: 'error', + message: `Failed to connect to "${name}".`, + }); + } + }; + + const handleDelete = async () => { + try { + if (isConnected) { + await updatePipeline.mutateAsync({ + params: { path: { project_id: projectId } }, + body: { sink_id: null }, + }); + } + + await removeSink.mutateAsync({ params: { path: { sink_id: id, project_id: projectId } } }); + + toast({ + type: 'success', + message: `${name} has been removed successfully!`, + }); + } catch (_error) { + toast({ + type: 'error', + message: `Failed to remove "${name}".`, + }); + } + }; + + return ( + + + + + + Connect + Edit + Remove + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/sink-options.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/sink-options.component.tsx new file mode 100644 index 0000000000..204b385f74 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/sink-options.component.tsx @@ -0,0 +1,92 @@ +import { ReactNode } from 'react'; + +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { Folder as FolderIcon, Mqtt as MqttIcon, Ros as RosIcon, Webhook as WebhookIcon } from '@geti-inspect/icons'; + +import { DisclosureGroup } from '../../../../components/disclosure-group/disclosure-group.component'; +import { AddSink } from './add-sink/add-sink.component'; +import { LocalFolderFields } from './local-folder-fields/local-folder-fields.component'; +import { getLocalFolderInitialConfig, localFolderBodyFormatter } from './local-folder-fields/utils'; +import { MqttFields } from './mqtt-fields/mqtt-fields.component'; +import { getMqttInitialConfig, mqttBodyFormatter } from './mqtt-fields/utils'; +import { RosFields } from './ros-fields/ros-fields.component'; +import { getRosInitialConfig, rosBodyFormatter } from './ros-fields/utils'; +import { LocalFolderSinkConfig, MqttSinkConfig, RosSinkConfig, WebhookSinkConfig } from './utils'; +import { getWebhookInitialConfig, webhookBodyFormatter } from './webhook-fields/utils'; +import { WebhookFields } from './webhook-fields/webhook-fields.component'; + +interface SinkOptionsProps { + onSaved: () => void; + hasHeader: boolean; + children: ReactNode; +} + +export const SinkOptions = ({ hasHeader, onSaved, children }: SinkOptionsProps) => { + const { projectId } = useProjectIdentifier(); + + return ( + <> + {hasHeader && children} + + ( + + )} + bodyFormatter={localFolderBodyFormatter} + /> + ), + icon: , + }, + { + label: 'Webhook', + value: 'webhook', + content: ( + } + bodyFormatter={webhookBodyFormatter} + /> + ), + icon: , + }, + { + label: 'MQTT', + value: 'mqtt', + content: ( + } + bodyFormatter={mqttBodyFormatter} + /> + ), + icon: , + }, + { + label: 'Ros', + value: 'ros', + content: ( + } + bodyFormatter={rosBodyFormatter} + /> + ), + icon: , + }, + ]} + /> + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/utils.test.ts b/application/ui/src/features/inspect/toolbar/sinks/utils.test.ts new file mode 100644 index 0000000000..847bb290a8 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/utils.test.ts @@ -0,0 +1,45 @@ +import { getObjectFromFormData } from './utils'; + +describe('getObjectFromFormData', () => { + it('return an object mapping keys to values', () => { + const keys = ['a', 'b', 'c']; + const values = ['1', '2', '3']; + expect(getObjectFromFormData(keys, values)).toEqual({ a: '1', b: '2', c: '3' }); + }); + + it('skip entries with empty keys', () => { + const keys = ['', 'b', '']; + const values = ['1', '2', '3']; + expect(getObjectFromFormData(keys, values)).toEqual({ b: '2' }); + }); + + it('skip entries with empty values', () => { + const keys = ['a', 'b', 'c']; + const values = ['', '2', '']; + expect(getObjectFromFormData(keys, values)).toEqual({ b: '2' }); + }); + + it('return an empty object if all keys are empty', () => { + const keys = ['', '', '']; + const values = ['1', '2', '3']; + expect(getObjectFromFormData(keys, values)).toEqual({}); + }); + + it('return an empty object if all values are empty', () => { + const keys = ['a', 'b', 'c']; + const values = ['', '', '']; + expect(getObjectFromFormData(keys, values)).toEqual({}); + }); + + it('handle different lengths of keys and values', () => { + const keys = ['a', 'b']; + const values = ['1', '2', '3']; + expect(getObjectFromFormData(keys, values)).toEqual({ a: '1', b: '2' }); + }); + + it('handle keys and values with whitespace', () => { + const keys = [' ', 'b', 'c']; + const values = ['1', ' ', '3']; + expect(getObjectFromFormData(keys, values)).toEqual({ c: '3' }); + }); +}); diff --git a/application/ui/src/features/inspect/toolbar/sinks/utils.ts b/application/ui/src/features/inspect/toolbar/sinks/utils.ts new file mode 100644 index 0000000000..b5c8353268 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/utils.ts @@ -0,0 +1,47 @@ +import { isEmpty } from 'lodash-es'; +import { components } from 'src/api/openapi-spec'; + +export type LocalFolderSinkConfig = components['schemas']['FolderSinkConfig']; +export type MqttSinkConfig = components['schemas']['MqttSinkConfig']; +export type WebhookSinkConfig = components['schemas']['WebhookSinkConfig']; +export type RosSinkConfig = components['schemas']['RosSinkConfig']; +export type DisconnectedSinkConfig = components['schemas']['DisconnectedSinkConfig']; + +export type SinkOutputFormats = LocalFolderSinkConfig['output_formats']; + +export type SinkConfig = + | LocalFolderSinkConfig + | MqttSinkConfig + | WebhookSinkConfig + | RosSinkConfig + | DisconnectedSinkConfig; + +export enum SinkType { + FOLDER = 'folder', + MQTT = 'mqtt', + ROS = 'ros', + WEBHOOK = 'webhook', +} + +export enum OutputFormat { + IMAGE_ORIGINAL = 'image_original', + IMAGE_WITH_PREDICTIONS = 'image_with_predictions', + PREDICTIONS = 'predictions', +} + +export enum WebhookHttpMethod { + PUT = 'PUT', + POST = 'POST', + PATCH = 'PATCH', +} + +const toStringAndTrim = (value: unknown) => String(value).trim(); + +export const getObjectFromFormData = (keys: FormDataEntryValue[], values: FormDataEntryValue[]) => { + const entries = keys.map((key, index) => [key, values[index]]); + const validEntries = entries.filter( + ([key, value]) => !isEmpty(toStringAndTrim(key)) && !isEmpty(toStringAndTrim(value)) + ); + + return Object.fromEntries(validEntries); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/webhook-fields/header-key-value-builder.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/webhook-fields/header-key-value-builder.component.tsx new file mode 100644 index 0000000000..0c69a7b920 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/webhook-fields/header-key-value-builder.component.tsx @@ -0,0 +1,94 @@ +import { Fragment, useState } from 'react'; + +import { ActionButton, Content, ContextualHelp, dimensionValue, Flex, Grid, Text } from '@geti/ui'; +import { Add, Delete } from '@geti/ui/icons'; +import { isEmpty } from 'lodash-es'; + +import { RequiredTextField } from '../../../../../components/required-text-field/required-text-field.component'; +import { Fields, getPairsFromObject, Pair } from './utils'; + +type HeaderKeyValueBuilderProps = { + title: string; + keysName: string; + valuesName: string; + config?: Record; +}; + +const updatePairAtIndex = (indexToUpdate: number, field: Fields, value: string) => (pair: Pair, index: number) => + index === indexToUpdate ? { ...pair, [field]: value } : pair; + +export const HeaderKeyValueBuilder = ({ title, keysName, valuesName, config = {} }: HeaderKeyValueBuilderProps) => { + const [pairs, setPairs] = useState(getPairsFromObject(config)); + + const addPair = () => { + setPairs([...pairs, { key: '', value: '' }]); + }; + + const updatePair = (indexToUpdate: number, field: Fields, value: string) => { + setPairs((prevValues) => prevValues.map(updatePairAtIndex(indexToUpdate, field, value))); + }; + + const removePair = (indexToRemove: number) => { + setPairs((prevValues) => prevValues.filter((_, index) => index !== indexToRemove)); + }; + + return ( + + + + + {title} + + + + + + Add as many key-value pairs as needed. Each pair will be included in the '{title} + ' object. + + + + + + + + + + + {pairs.map((pair, index) => ( + + updatePair(index, Fields.KEY, val)} + /> + updatePair(index, Fields.VALUE, val)} + /> + removePair(index)}> + + + + ))} + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sinks/webhook-fields/utils.ts b/application/ui/src/features/inspect/toolbar/sinks/webhook-fields/utils.ts new file mode 100644 index 0000000000..bea07cd1ca --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/webhook-fields/utils.ts @@ -0,0 +1,40 @@ +import { v4 as uuid } from 'uuid'; + +import { getObjectFromFormData, SinkOutputFormats, WebhookHttpMethod, WebhookSinkConfig } from '../utils'; + +export type Pair = Record; + +export enum Fields { + KEY = 'key', + VALUE = 'value', +} + +export const getPairsFromObject = (obj: Record): Pair[] => { + return Object.entries(obj).map(([key, value]) => ({ key, value })); +}; + +export const getWebhookInitialConfig = (project_id: string): WebhookSinkConfig => ({ + id: uuid(), + name: '', + timeout: 0, + project_id, + sink_type: 'webhook', + rate_limit: 0, + webhook_url: '', + http_method: WebhookHttpMethod.POST, + output_formats: [], + headers: {}, +}); + +export const webhookBodyFormatter = (formData: FormData): WebhookSinkConfig => ({ + id: String(formData.get('id')), + name: String(formData.get('name')), + headers: getObjectFromFormData(formData.getAll('headers-keys'), formData.getAll('headers-values')), + timeout: Number(formData.get('timeout')), + sink_type: 'webhook', + rate_limit: Number(formData.get('rate_limit')), + webhook_url: String(formData.get('webhook_url')), + http_method: formData.get('http_method') as WebhookHttpMethod, + output_formats: formData.getAll('output_formats') as SinkOutputFormats, + project_id: String(formData.get('project_id')), +}); diff --git a/application/ui/src/features/inspect/toolbar/sinks/webhook-fields/webhook-fields.component.tsx b/application/ui/src/features/inspect/toolbar/sinks/webhook-fields/webhook-fields.component.tsx new file mode 100644 index 0000000000..8f7323db7d --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sinks/webhook-fields/webhook-fields.component.tsx @@ -0,0 +1,49 @@ +import { Flex, Item, NumberField, Picker, TextField } from '@geti/ui'; + +import { OutputFormats } from '../output-formats/output-formats.component'; +import { WebhookHttpMethod, WebhookSinkConfig } from '../utils'; +import { HeaderKeyValueBuilder } from './header-key-value-builder.component'; + +interface WebhookFieldsProps { + defaultState: WebhookSinkConfig; +} + +export const WebhookFields = ({ defaultState }: WebhookFieldsProps) => { + return ( + + + + + + + + + + + {WebhookHttpMethod.POST} + {WebhookHttpMethod.PATCH} + {WebhookHttpMethod.PUT} + + + + + + + + + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/add-source/add-source.component.tsx b/application/ui/src/features/inspect/toolbar/sources/add-source/add-source.component.tsx new file mode 100644 index 0000000000..77483b5dd3 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/add-source/add-source.component.tsx @@ -0,0 +1,46 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { ReactNode } from 'react'; + +import { Button, Form } from '@geti/ui'; + +import { useConnectSourceToPipeline } from '../../../../../hooks/use-pipeline.hook'; +import { useSourceAction } from '../hooks/use-source-action.hook'; +import { SourceConfig } from '../util'; + +interface AddSourceProps { + config: Awaited; + onSaved: () => void; + componentFields: (state: Awaited) => ReactNode; + bodyFormatter: (formData: FormData) => T; +} + +export const AddSource = ({ + config, + onSaved, + bodyFormatter, + componentFields, +}: AddSourceProps) => { + const connectToPipelineMutation = useConnectSourceToPipeline(); + + const [state, submitAction, isPending] = useSourceAction({ + config, + isNewSource: true, + onSaved: async (sourceId) => { + await connectToPipelineMutation(sourceId); + onSaved(); + }, + bodyFormatter, + }); + + return ( +
    + <>{componentFields(state)} + + +
    + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/add-source/add-source.test.tsx b/application/ui/src/features/inspect/toolbar/sources/add-source/add-source.test.tsx new file mode 100644 index 0000000000..067e887b6d --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/add-source/add-source.test.tsx @@ -0,0 +1,66 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { TestProviders } from 'src/providers'; + +import { useConnectSourceToPipeline } from '../../../../../hooks/use-pipeline.hook'; +import { useSourceMutation } from '../hooks/use-source-mutation.hook'; +import { IpCameraFields } from '../ip-camera/ip-camera-fields.component'; +import { getIpCameraInitialConfig, ipCameraBodyFormatter } from '../ip-camera/utils'; +import { IPCameraSourceConfig } from '../util'; +import { AddSource } from './add-source.component'; + +vi.mock('../hooks/use-source-mutation.hook'); +vi.mock('../../../../../hooks/use-pipeline.hook'); + +describe('add-source', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + const newConfig = { + id: 'test-source-id', + name: 'Test Camera', + stream_url: 'rtsp://192.168.1.100:554/stream', + source_type: 'ip_camera', + auth_required: false, + project_id: '123', + }; + + it('calls connectToPipelineMutation after successful submit', async () => { + const mockOnSaved = vi.fn(); + const mockSourceMutation = vi.fn().mockResolvedValue(newConfig.id); + const mockConnectToPipeline = vi.fn().mockResolvedValue(undefined); + + vi.mocked(useConnectSourceToPipeline).mockReturnValue(mockConnectToPipeline); + vi.mocked(useSourceMutation).mockReturnValue(mockSourceMutation); + + render( + + } + bodyFormatter={ipCameraBodyFormatter} + /> + + ); + + const nameInput = screen.getByRole('textbox', { name: /Name/i }); + const streamUrlInput = screen.getByRole('textbox', { name: /Stream Url/i }); + + await userEvent.clear(nameInput); + await userEvent.type(nameInput, newConfig.name); + await userEvent.clear(streamUrlInput); + await userEvent.type(streamUrlInput, newConfig.stream_url); + await userEvent.click(screen.getByRole('button', { name: /Add & Connect/i })); + + await waitFor(() => { + expect(mockSourceMutation).toHaveBeenCalledWith(expect.objectContaining({ ...newConfig, id: '' })); + expect(mockConnectToPipeline).toHaveBeenCalledWith(newConfig.id); + expect(mockOnSaved).toHaveBeenCalled(); + }); + }); +}); diff --git a/application/ui/src/features/inspect/toolbar/sources/edit-source-form.component.tsx b/application/ui/src/features/inspect/toolbar/sources/edit-source-form.component.tsx new file mode 100644 index 0000000000..0dfcb1aa19 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/edit-source-form.component.tsx @@ -0,0 +1,64 @@ +import { EditSource } from './edit-source/edit-source.component'; +import { ImageFolderFields } from './image-folder/image-folder-fields.component'; +import { imageFolderBodyFormatter } from './image-folder/utils'; +import { IpCameraFields } from './ip-camera/ip-camera-fields.component'; +import { ipCameraBodyFormatter } from './ip-camera/utils'; +import { ImagesFolderSourceConfig, SourceConfig, VideoFileSourceConfig } from './util'; +import { videoFileBodyFormatter } from './video-file/utils'; +import { VideoFileFields } from './video-file/video-file-fields.component'; +import { webcamBodyFormatter } from './webcam/utils'; +import { WebcamFields } from './webcam/webcam-fields.component'; + +interface EditSourceFormProps { + config: SourceConfig; + onSaved: () => void; + onBackToList: () => void; +} + +export const EditSourceForm = ({ config, onSaved, onBackToList }: EditSourceFormProps) => { + if (config.source_type === 'webcam') { + return ( + } + bodyFormatter={webcamBodyFormatter} + /> + ); + } + + if (config.source_type === 'ip_camera') { + return ( + } + bodyFormatter={ipCameraBodyFormatter} + /> + ); + } + + if (config.source_type === 'video_file') { + return ( + } + bodyFormatter={videoFileBodyFormatter} + /> + ); + } + + return ( + } + bodyFormatter={imageFolderBodyFormatter} + /> + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/edit-source/edit-source.component.tsx b/application/ui/src/features/inspect/toolbar/sources/edit-source/edit-source.component.tsx new file mode 100644 index 0000000000..aa7c586a81 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/edit-source/edit-source.component.tsx @@ -0,0 +1,80 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { ReactNode, useRef } from 'react'; + +import { ActionButton, Button, ButtonGroup, Divider, Flex, Form, Text, View } from '@geti/ui'; +import { Back } from '@geti/ui/icons'; + +import { useConnectSourceToPipeline } from '../../../../../hooks/use-pipeline.hook'; +import { useSourceAction } from '../hooks/use-source-action.hook'; +import { SourceConfig } from '../util'; + +import classes from './edit-source.module.scss'; + +interface EditSourceProps { + config: Awaited; + onSaved: () => void; + onBackToList: () => void; + componentFields: (state: Awaited) => ReactNode; + bodyFormatter: (formData: FormData) => T; +} + +export const EditSource = ({ + config, + onSaved, + onBackToList, + bodyFormatter, + componentFields, +}: EditSourceProps) => { + const connectToPipeline = useRef(false); + const connectToPipelineMutation = useConnectSourceToPipeline(); + + const [state, submitAction, isPending] = useSourceAction({ + config, + isNewSource: false, + onSaved: async (sourceId) => { + connectToPipeline.current && (await connectToPipelineMutation(sourceId)); + connectToPipeline.current = false; + onSaved(); + }, + bodyFormatter, + }); + + return ( +
    + + + + + + Edit input source + + + + <>{componentFields(state)} + + + + + + + + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/edit-source/edit-source.module.scss b/application/ui/src/features/inspect/toolbar/sources/edit-source/edit-source.module.scss new file mode 100644 index 0000000000..8aeb44db95 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/edit-source/edit-source.module.scss @@ -0,0 +1,6 @@ +.container { + background: #2e2f32; + border: 1px solid var(--background-inverse); + border-radius: var(--spectrum-global-dimension-size-100); + padding: var(--spectrum-global-dimension-size-200); +} diff --git a/application/ui/src/features/inspect/toolbar/sources/edit-source/edit-source.test.tsx b/application/ui/src/features/inspect/toolbar/sources/edit-source/edit-source.test.tsx new file mode 100644 index 0000000000..7b08a2e308 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/edit-source/edit-source.test.tsx @@ -0,0 +1,97 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { TestProviders } from 'src/providers'; + +import { useConnectSourceToPipeline } from '../../../../../hooks/use-pipeline.hook'; +import { useSourceMutation } from '../hooks/use-source-mutation.hook'; +import { IpCameraFields } from '../ip-camera/ip-camera-fields.component'; +import { getIpCameraInitialConfig, ipCameraBodyFormatter } from '../ip-camera/utils'; +import { IPCameraSourceConfig } from '../util'; +import { EditSource } from './edit-source.component'; + +vi.mock('../hooks/use-source-mutation.hook'); +vi.mock('../../../../../hooks/use-pipeline.hook'); + +describe('EditIpCamera', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + const updatedConfig: IPCameraSourceConfig = { + id: 'existing-source-id', + name: 'Updated Camera', + project_id: '123', + stream_url: 'rtsp://192.168.1.201:554/stream', + source_type: 'ip_camera', + auth_required: true, + }; + + const renderApp = (mockOnSaved = vi.fn()) => { + render( + + } + bodyFormatter={ipCameraBodyFormatter} + /> + + ); + }; + + it('calls connectToPipelineMutation after successful "Save & Connect" submit', async () => { + const mockOnSaved = vi.fn(); + const mockSourceMutation = vi.fn().mockResolvedValue(updatedConfig.id); + const mockConnectToPipeline = vi.fn().mockResolvedValue(undefined); + + vi.mocked(useConnectSourceToPipeline).mockReturnValue(mockConnectToPipeline); + vi.mocked(useSourceMutation).mockReturnValue(mockSourceMutation); + + renderApp(mockOnSaved); + + const nameInput = screen.getByRole('textbox', { name: /Name/i }); + const streamUrlInput = screen.getByRole('textbox', { name: /Stream Url/i }); + + await userEvent.clear(nameInput); + await userEvent.type(nameInput, updatedConfig.name); + await userEvent.clear(streamUrlInput); + await userEvent.type(streamUrlInput, updatedConfig.stream_url); + await userEvent.click(screen.getByRole('button', { name: /Save & Connect/i })); + + await waitFor(() => { + expect(mockOnSaved).toHaveBeenCalled(); + expect(mockSourceMutation).toHaveBeenCalled(); + expect(mockConnectToPipeline).toHaveBeenCalledWith(updatedConfig.id); + }); + }); + + it('does not call connectToPipelineMutation after successful "Save" submit', async () => { + const mockOnSaved = vi.fn(); + const mockSourceMutation = vi.fn().mockResolvedValue(updatedConfig.id); + const mockConnectToPipeline = vi.fn().mockResolvedValue(undefined); + + vi.mocked(useConnectSourceToPipeline).mockReturnValue(mockConnectToPipeline); + vi.mocked(useSourceMutation).mockReturnValue(mockSourceMutation); + + renderApp(mockOnSaved); + + const nameInput = screen.getByRole('textbox', { name: /Name/i }); + const streamUrlInput = screen.getByRole('textbox', { name: /Stream Url/i }); + + await userEvent.clear(nameInput); + await userEvent.type(nameInput, updatedConfig.name); + await userEvent.clear(streamUrlInput); + await userEvent.type(streamUrlInput, updatedConfig.stream_url); + await userEvent.click(screen.getByRole('button', { name: /^Save$/i })); + + await waitFor(() => { + expect(mockOnSaved).toHaveBeenCalled(); + expect(mockSourceMutation).toHaveBeenCalled(); + expect(mockConnectToPipeline).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/application/ui/src/features/inspect/toolbar/sources/hooks/use-source-action.hook.tsx b/application/ui/src/features/inspect/toolbar/sources/hooks/use-source-action.hook.tsx new file mode 100644 index 0000000000..5202ddf4b3 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/hooks/use-source-action.hook.tsx @@ -0,0 +1,51 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { useActionState } from 'react'; + +import { toast } from '@geti/ui'; +import { isFunction } from 'lodash-es'; + +import { SourceConfig } from '../util'; +import { useSourceMutation } from './use-source-mutation.hook'; + +interface useSourceActionProps { + config: Awaited; + isNewSource: boolean; + onSaved?: (source_id: string) => void; + bodyFormatter: (formData: FormData) => T; +} + +export const useSourceAction = ({ + config, + isNewSource, + onSaved, + bodyFormatter, +}: useSourceActionProps) => { + const addOrUpdateSource = useSourceMutation(isNewSource); + + return useActionState(async (_prevState: T, formData: FormData) => { + const body = bodyFormatter(formData); + + try { + const source_id = await addOrUpdateSource(body); + + toast({ + type: 'success', + message: `Source configuration ${isNewSource ? 'created' : 'updated'} successfully.`, + }); + + isFunction(onSaved) && onSaved(source_id); + return { ...body, id: source_id }; + } catch (error: unknown) { + const details = (error as { detail?: string })?.detail; + + toast({ + type: 'error', + message: `Failed to save source configuration, ${details ?? 'please try again'}`, + }); + } + + return body; + }, config); +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/hooks/use-source-mutation.hook.tsx b/application/ui/src/features/inspect/toolbar/sources/hooks/use-source-mutation.hook.tsx new file mode 100644 index 0000000000..0cd40e2889 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/hooks/use-source-mutation.hook.tsx @@ -0,0 +1,49 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { $api } from '@geti-inspect/api'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { omit } from 'lodash-es'; + +import { SourceConfig } from '../util'; + +export const useSourceMutation = (isNewSource: boolean) => { + const { projectId } = useProjectIdentifier(); + const addSource = $api.useMutation('post', '/api/projects/{project_id}/sources', { + meta: { + invalidates: [ + ['get', '/api/projects/{project_id}/sources', { params: { path: { project_id: projectId } } }], + ], + }, + }); + const updateSource = $api.useMutation('patch', '/api/projects/{project_id}/sources/{source_id}', { + meta: { + invalidates: [ + ['get', '/api/projects/{project_id}/sources', { params: { path: { project_id: projectId } } }], + ], + }, + }); + + return async (body: SourceConfig) => { + if (isNewSource) { + // Omit id and project_id when creating - they're auto-generated/injected from URL + const sourcePayload = omit(body, ['id', 'project_id']) as Parameters< + typeof addSource.mutateAsync + >[0]['body']; + + const response = await addSource.mutateAsync({ + body: sourcePayload, + params: { path: { project_id: projectId } }, + }); + + return String(response.id); + } + + const response = await updateSource.mutateAsync({ + params: { path: { project_id: projectId, source_id: String(body.id) } }, + body: omit(body, 'source_type'), + }); + + return String(response.id); + }; +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/hooks/use-source-mutation.test.tsx b/application/ui/src/features/inspect/toolbar/sources/hooks/use-source-mutation.test.tsx new file mode 100644 index 0000000000..66caf3d0f5 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/hooks/use-source-mutation.test.tsx @@ -0,0 +1,62 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { act, renderHook } from '@testing-library/react'; +import { HttpResponse } from 'msw'; +import { http } from 'src/api/utils'; +import { server } from 'src/msw-node-setup'; +import { TestProviders } from 'src/providers'; + +import { WebcamSourceConfig } from '../util'; +import { useSourceMutation } from './use-source-mutation.hook'; + +const mockedSource: WebcamSourceConfig = { + id: 'original-id', + project_id: '123', + name: 'Mock Source', + source_type: 'webcam' as const, + device_id: 0, +}; + +vi.mock('@geti-inspect/hooks', () => ({ useProjectIdentifier: () => ({ projectId: 'project-id-123' }) })); + +describe('useSourceMutation', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('creates a new source and return its resource id', async () => { + const { result } = renderHook(() => useSourceMutation(true), { + wrapper: TestProviders, + }); + + const createdSource = { ...mockedSource, id: 'created-id' }; + server.use( + http.post('/api/projects/{project_id}/sources', async () => { + return HttpResponse.json(createdSource); + }), + http.patch('/api/projects/{project_id}/sources/{source_id}', () => HttpResponse.error()) + ); + + await act(async () => { + const response = await result.current(mockedSource); + expect(response).toBe(createdSource.id); + }); + }); + + it('update a source item and returns its resource id', async () => { + const { result } = renderHook(() => useSourceMutation(false), { + wrapper: TestProviders, + }); + + server.use( + http.post('/api/projects/{project_id}/sources', () => HttpResponse.error()), + http.patch('/api/projects/{project_id}/sources/{source_id}', () => HttpResponse.json(mockedSource)) + ); + + await act(async () => { + const response = await result.current(mockedSource); + expect(response).toBe(mockedSource.id); + }); + }); +}); diff --git a/application/ui/src/features/inspect/toolbar/sources/image-folder/image-folder-fields.component.tsx b/application/ui/src/features/inspect/toolbar/sources/image-folder/image-folder-fields.component.tsx new file mode 100644 index 0000000000..560897eb51 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/image-folder/image-folder-fields.component.tsx @@ -0,0 +1,51 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { Folder } from '@geti-inspect/icons'; +import { Flex, Switch, TextField } from '@geti/ui'; + +import { ImagesFolderSourceConfig } from '../util'; + +import classes from './image-folder-fields.module.scss'; + +type ImageFolderFieldsProps = { + defaultState: ImagesFolderSourceConfig; +}; + +export const ImageFolderFields = ({ defaultState }: ImageFolderFieldsProps) => { + return ( + + + + + + + + + + + + + + + Ignore existing images + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/image-folder/image-folder-fields.module.scss b/application/ui/src/features/inspect/toolbar/sources/image-folder/image-folder-fields.module.scss new file mode 100644 index 0000000000..3416b14778 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/image-folder/image-folder-fields.module.scss @@ -0,0 +1,15 @@ +.folderIcon { + width: var(--spectrum-global-dimension-size-400); + min-width: auto; + border-radius: 4px; + padding: 0px; + background-color: var(--spectrum-accent-background-color-default); + + svg { + stroke: var(--spectrum-global-color-gray-50); + } + + &:hover { + background-color: var(--spectrum-accent-background-color-hover); + } +} diff --git a/application/ui/src/features/inspect/toolbar/sources/image-folder/utils.ts b/application/ui/src/features/inspect/toolbar/sources/image-folder/utils.ts new file mode 100644 index 0000000000..2e963ee85b --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/image-folder/utils.ts @@ -0,0 +1,19 @@ +import { ImagesFolderSourceConfig } from '../util'; + +export const getImageFolderInitialConfig = (projectId: string): ImagesFolderSourceConfig => ({ + id: '', + name: '', + project_id: projectId, + source_type: 'images_folder', + images_folder_path: '', + ignore_existing_images: false, +}); + +export const imageFolderBodyFormatter = (formData: FormData): ImagesFolderSourceConfig => ({ + id: String(formData.get('id')), + name: String(formData.get('name')), + source_type: 'images_folder', + project_id: String(formData.get('project_id')), + images_folder_path: String(formData.get('images_folder_path')), + ignore_existing_images: formData.get('ignore_existing_images') === 'on', +}); diff --git a/application/ui/src/features/inspect/toolbar/sources/ip-camera/ip-camera-fields.component.tsx b/application/ui/src/features/inspect/toolbar/sources/ip-camera/ip-camera-fields.component.tsx new file mode 100644 index 0000000000..f4e6056847 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/ip-camera/ip-camera-fields.component.tsx @@ -0,0 +1,29 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { Flex, Switch, TextField } from '@geti/ui'; + +import { IPCameraSourceConfig } from '../util'; + +type IpCameraFieldsProps = { + defaultState: IPCameraSourceConfig; +}; + +export const IpCameraFields = ({ defaultState }: IpCameraFieldsProps) => { + return ( + + + + + + + Require Authentication + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/ip-camera/utils.ts b/application/ui/src/features/inspect/toolbar/sources/ip-camera/utils.ts new file mode 100644 index 0000000000..be2baf13ac --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/ip-camera/utils.ts @@ -0,0 +1,19 @@ +import { IPCameraSourceConfig } from '../util'; + +export const getIpCameraInitialConfig = (projectId: string): IPCameraSourceConfig => ({ + id: '', + name: '', + project_id: projectId, + source_type: 'ip_camera', + stream_url: '', + auth_required: false, +}); + +export const ipCameraBodyFormatter = (formData: FormData): IPCameraSourceConfig => ({ + id: String(formData.get('id')), + name: String(formData.get('name')), + source_type: 'ip_camera', + project_id: String(formData.get('project_id')), + stream_url: String(formData.get('stream_url')), + auth_required: String(formData.get('auth_required')) === 'on', +}); diff --git a/application/ui/src/features/inspect/toolbar/sources/source-actions.component.tsx b/application/ui/src/features/inspect/toolbar/sources/source-actions.component.tsx new file mode 100644 index 0000000000..6b7ba51b16 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/source-actions.component.tsx @@ -0,0 +1,59 @@ +// Copyright (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +import { useState } from 'react'; + +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { ActionButton, Flex, Text } from '@geti/ui'; +import { Back } from '@geti/ui/icons'; +import { isEmpty } from 'lodash-es'; + +import { $api } from '../../../../api/client'; +import { EditSourceForm } from './edit-source-form.component'; +import { SourcesList } from './source-list/source-list.component'; +import { SourceOptions } from './source-options.component'; +import { SourceConfig } from './util'; + +export const SourceActions = () => { + const { projectId } = useProjectIdentifier(); + const sourcesQuery = $api.useSuspenseQuery('get', '/api/projects/{project_id}/sources', { + params: { path: { project_id: projectId } }, + }); + + const sources = sourcesQuery.data ?? []; + const [view, setView] = useState<'list' | 'options' | 'edit'>(isEmpty(sources) ? 'options' : 'list'); + const [currentSource, setCurrentSource] = useState(null); + + const handleShowList = () => { + setView('list'); + }; + + const handleAddSource = () => { + setView('options'); + }; + + const handleEditSource = (source: SourceConfig) => { + setView('edit'); + setCurrentSource(source); + }; + + if (view === 'edit' && !isEmpty(currentSource)) { + return ; + } + + if (view === 'list') { + return ; + } + + return ( + 0}> + + + + + + Add new input source + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/source-list/settings-list/settings-list.component.tsx b/application/ui/src/features/inspect/toolbar/sources/source-list/settings-list/settings-list.component.tsx new file mode 100644 index 0000000000..8dfc1d8a30 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/source-list/settings-list/settings-list.component.tsx @@ -0,0 +1,45 @@ +import { SourceConfig } from '../../util'; + +import classes from './settings-list.module.scss'; + +interface SettingsListProps { + source: SourceConfig; +} + +export const SettingsList = ({ source }: SettingsListProps) => { + if (source.source_type === 'images_folder') { + return ( +
      +
    • Folder path: {source.images_folder_path}
    • +
    • Ignore existing images: {source.ignore_existing_images ? 'Yes' : 'No'}
    • +
    + ); + } + + if (source.source_type === 'ip_camera') { + return ( +
      +
    • Stream url: {source.stream_url}
    • +
    • Auth required: {source.auth_required ? 'Yes' : 'No'}
    • +
    + ); + } + + if (source.source_type === 'video_file') { + return ( +
      +
    • Video path: {source.video_path}
    • +
    + ); + } + + if (source.source_type === 'webcam') { + return ( +
      +
    • Device id: {source.device_id}
    • +
    + ); + } + + return <>; +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/source-list/settings-list/settings-list.module.scss b/application/ui/src/features/inspect/toolbar/sources/source-list/settings-list/settings-list.module.scss new file mode 100644 index 0000000000..db3eabfad5 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/source-list/settings-list/settings-list.module.scss @@ -0,0 +1,4 @@ +.list { + margin-left: var(--spectrum-global-dimension-font-size-200); + list-style-type: disc; +} diff --git a/application/ui/src/features/inspect/toolbar/sources/source-list/source-icon/source-icon.component.tsx b/application/ui/src/features/inspect/toolbar/sources/source-list/source-icon/source-icon.component.tsx new file mode 100644 index 0000000000..abbb3dd21b --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/source-list/source-icon/source-icon.component.tsx @@ -0,0 +1,21 @@ +import { ImagesFolder, IpCamera, VideoFile, Webcam } from '@geti-inspect/icons'; + +interface SourceIconProps { + type: string; +} + +export const SourceIcon = ({ type }: SourceIconProps) => { + if (type === 'webcam') { + return ; + } + + if (type === 'ip_camera') { + return ; + } + + if (type === 'video_file') { + return ; + } + + return ; +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/source-list/source-list.component.tsx b/application/ui/src/features/inspect/toolbar/sources/source-list/source-list.component.tsx new file mode 100644 index 0000000000..c6f818b797 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/source-list/source-list.component.tsx @@ -0,0 +1,84 @@ +import { Add as AddIcon } from '@geti/ui/icons'; +import { clsx } from 'clsx'; +import { isEqual } from 'lodash-es'; +import { Button, Flex, Text } from 'packages/ui'; + +import { StatusTag } from '../../../../../components/status-tag/status-tag.component'; +import { usePipeline } from '../../../../../hooks/use-pipeline.hook'; +import { removeUnderscore } from '../../../utils'; +import { SourceMenu } from '../source-menu/source-menu.component'; +import { SourceConfig } from '../util'; +import { SettingsList } from './settings-list/settings-list.component'; +import { SourceIcon } from './source-icon/source-icon.component'; + +import classes from './source-list.module.scss'; + +type SourcesListProps = { + sources: SourceConfig[]; + onAddSource: () => void; + onEditSource: (config: SourceConfig) => void; +}; + +type SourceListItemProps = { + source: SourceConfig; + isConnected: boolean; + onEditSource: (config: SourceConfig) => void; +}; + +const SourceListItem = ({ source, isConnected, onEditSource }: SourceListItemProps) => { + return ( + + + + + + {source.name} + + {removeUnderscore(source.source_type)} + + + + + + + + + onEditSource(source)} + /> + + + ); +}; + +export const SourcesList = ({ sources, onAddSource, onEditSource }: SourcesListProps) => { + const pipeline = usePipeline(); + const currentSourceId = pipeline.data.source?.id; + + return ( + + + + {sources.map((source) => ( + + ))} + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/source-list/source-list.module.scss b/application/ui/src/features/inspect/toolbar/sources/source-list/source-list.module.scss new file mode 100644 index 0000000000..df9e07479a --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/source-list/source-list.module.scss @@ -0,0 +1,28 @@ +.addSource { + width: 100%; + cursor: pointer; + display: flex; + border-style: dashed; + gap: var(--spectrum-global-dimension-size-65); + padding: var(--spectrum-global-dimension-size-250); +} + +.card { + padding: var(--spectrum-global-dimension-size-250); + background: var(--spectrum-global-color-gray-200); + border-radius: var(--spectrum-global-dimension-size-65); +} + +.activeCard { + border-left: var(--spectrum-global-dimension-size-50) solid var(--aqua); +} + +.title { + font-weight: 500; + text-transform: capitalize; + font-size: var(--spectrum-global-dimension-font-size-200); +} + +.type { + text-transform: capitalize; +} diff --git a/application/ui/src/features/inspect/toolbar/sources/source-menu/source-menu.component.tsx b/application/ui/src/features/inspect/toolbar/sources/source-menu/source-menu.component.tsx new file mode 100644 index 0000000000..ae846e763f --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/source-menu/source-menu.component.tsx @@ -0,0 +1,101 @@ +import { $api } from '@geti-inspect/api'; +import { useProjectIdentifier } from '@geti-inspect/hooks'; +import { MoreMenu } from '@geti/ui/icons'; +import { ActionButton, Item, Key, Menu, MenuTrigger, toast } from 'packages/ui'; + +export interface SourceMenuProps { + id: string; + name: string; + isConnected: boolean; + onEdit: () => void; +} + +export const SourceMenu = ({ id, name, isConnected, onEdit }: SourceMenuProps) => { + const { projectId } = useProjectIdentifier(); + + const handleOnAction = (option: Key) => { + switch (option) { + case 'connect': + handleConnect(); + break; + case 'remove': + handleDelete(); + break; + default: + onEdit(); + break; + } + }; + + const updatePipeline = $api.useMutation('patch', '/api/projects/{project_id}/pipeline', { + meta: { + invalidates: [ + ['get', '/api/projects/{project_id}/sources', { params: { path: { project_id: projectId } } }], + ['get', '/api/projects/{project_id}/pipeline', { params: { path: { project_id: projectId } } }], + ], + }, + }); + + const handleConnect = async () => { + try { + await updatePipeline.mutateAsync({ + params: { path: { project_id: projectId } }, + body: { source_id: id }, + }); + + toast({ + type: 'success', + message: `Successfully connected to "${name}"`, + }); + } catch (_error) { + toast({ + type: 'error', + message: `Failed to connect to "${name}".`, + }); + } + }; + + const removeSource = $api.useMutation('delete', '/api/projects/{project_id}/sources/{source_id}', { + meta: { + invalidates: [ + ['get', '/api/projects/{project_id}/sources', { params: { path: { project_id: projectId } } }], + ], + }, + }); + + const handleDelete = async () => { + try { + if (isConnected) { + await updatePipeline.mutateAsync({ + params: { path: { project_id: projectId } }, + body: { source_id: null }, + }); + } + + await removeSource.mutateAsync({ params: { path: { project_id: projectId, source_id: id } } }); + + toast({ + type: 'success', + message: `${name} has been removed successfully!`, + }); + } catch (_error) { + toast({ + type: 'error', + message: `Failed to remove "${name}".`, + }); + } + }; + + return ( + + + + + + Connect + Edit + Remove + + + ); +}; diff --git a/application/ui/src/features/inspect/toolbar/sources/source-menu/source-menu.test.tsx b/application/ui/src/features/inspect/toolbar/sources/source-menu/source-menu.test.tsx new file mode 100644 index 0000000000..ad316bb89e --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/source-menu/source-menu.test.tsx @@ -0,0 +1,126 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { HttpResponse } from 'msw'; +import { http } from 'src/api/utils'; +import { server } from 'src/msw-node-setup'; +import { TestProviders } from 'src/providers'; + +import { SourceMenu, SourceMenuProps } from './source-menu.component'; + +vi.mock('@geti-inspect/hooks', () => ({ useProjectIdentifier: () => ({ projectId: '123' }) })); + +describe('SourceMenu', () => { + const renderApp = ({ + id = 'id-test', + name = 'name test', + isConnected = false, + onEdit = vi.fn(), + }: Partial) => { + render( + + + + ); + }; + + it('edit', async () => { + const mockedOnEdit = vi.fn(); + + renderApp({ onEdit: mockedOnEdit }); + + await userEvent.click(screen.getByRole('button', { name: /source menu/i })); + await userEvent.click(screen.getByRole('menuitem', { name: /Edit/i })); + + expect(mockedOnEdit).toHaveBeenCalled(); + }); + + describe('remove', () => { + const name = 'test-name'; + const configRequests = (status = 200) => { + const pipelinePatchSpy = vi.fn(); + + server.use( + http.patch('/api/projects/{project_id}/pipeline', () => { + pipelinePatchSpy(); + return HttpResponse.json({}, { status }); + }), + http.delete('/api/projects/{project_id}/sources/{source_id}', () => + HttpResponse.json(null, { status: 204 }) + ) + ); + + return pipelinePatchSpy; + }; + + it('success', async () => { + const pipelinePatchSpy = configRequests(); + + renderApp({ name, isConnected: false }); + + await userEvent.click(screen.getByRole('button', { name: /source menu/i })); + await userEvent.click(screen.getByRole('menuitem', { name: /Remove/i })); + + await expect(await screen.findByLabelText('toast')).toHaveTextContent( + `${name} has been removed successfully!` + ); + expect(pipelinePatchSpy).not.toHaveBeenCalled(); + }); + + it('success with isConnected true - calls pipeline patch', async () => { + const pipelinePatchSpy = configRequests(); + + renderApp({ name, isConnected: true }); + + await userEvent.click(screen.getByRole('button', { name: /source menu/i })); + await userEvent.click(screen.getByRole('menuitem', { name: /Remove/i })); + + await expect(await screen.findByLabelText('toast')).toHaveTextContent( + `${name} has been removed successfully!` + ); + + expect(pipelinePatchSpy).toHaveBeenCalled(); + }); + + it('error', async () => { + configRequests(500); + + renderApp({ name, isConnected: true }); + + await userEvent.click(screen.getByRole('button', { name: /source menu/i })); + await userEvent.click(screen.getByRole('menuitem', { name: /Remove/i })); + + expect(await screen.findByLabelText('toast')).toHaveTextContent(`Failed to remove "${name}".`); + }); + }); + + describe('connect', () => { + const name = 'test-name'; + const configRequests = (status = 200) => { + server.use(http.patch('/api/projects/{project_id}/pipeline', () => HttpResponse.json({}, { status }))); + }; + + it('success', async () => { + configRequests(); + + renderApp({ name }); + + await userEvent.click(screen.getByRole('button', { name: /source menu/i })); + await userEvent.click(screen.getByRole('menuitem', { name: /Connect/i })); + + await expect(await screen.findByLabelText('toast')).toHaveTextContent( + `Successfully connected to "${name}"` + ); + }); + + it('error', async () => { + configRequests(500); + + renderApp({ name }); + + await userEvent.click(screen.getByRole('button', { name: /source menu/i })); + await userEvent.click(screen.getByRole('menuitem', { name: /Connect/i })); + + await expect(await screen.findByLabelText('toast')).toHaveTextContent(`Failed to connect to "${name}"`); + }); + }); +}); diff --git a/application/ui/src/features/inspect/toolbar/sources/source-options.component.tsx b/application/ui/src/features/inspect/toolbar/sources/source-options.component.tsx new file mode 100644 index 0000000000..db842a3972 --- /dev/null +++ b/application/ui/src/features/inspect/toolbar/sources/source-options.component.tsx @@ -0,0 +1,97 @@ +import { ReactNode } from 'react'; + +import { useProjectIdentifier } from '@geti-inspect/hooks'; + +import { ReactComponent as IpCameraIcon } from '../../../../assets/icons/ip-camera.svg'; +import { ReactComponent as Video } from '../../../../assets/icons/video-file.svg'; +import { ReactComponent as WebcamIcon } from '../../../../assets/icons/webcam.svg'; +import { DisclosureGroup } from '../../../../components/disclosure-group/disclosure-group.component'; +import { AddSource } from './add-source/add-source.component'; +import { IpCameraFields } from './ip-camera/ip-camera-fields.component'; +import { getIpCameraInitialConfig, ipCameraBodyFormatter } from './ip-camera/utils'; +import { IPCameraSourceConfig, VideoFileSourceConfig, WebcamSourceConfig } from './util'; +import { getVideoFileInitialConfig, videoFileBodyFormatter } from './video-file/utils'; +import { VideoFileFields } from './video-file/video-file-fields.component'; +import { getWebcamInitialConfig, webcamBodyFormatter } from './webcam/utils'; +import { WebcamFields } from './webcam/webcam-fields.component'; + +interface SourceOptionsProps { + onSaved: () => void; + hasHeader: boolean; + children: ReactNode; +} + +export const SourceOptions = ({ hasHeader, children, onSaved }: SourceOptionsProps) => { + const { projectId } = useProjectIdentifier(); + + return ( + <> + {hasHeader && children} + + , + content: ( + } + bodyFormatter={webcamBodyFormatter} + /> + ), + }, + { + label: 'IP Camera', + value: 'ip_camera', + icon: , + content: ( + ( + + )} + bodyFormatter={ipCameraBodyFormatter} + /> + ), + }, + { + label: 'Video file', + value: 'video_file', + icon: